Mat.inv()在opencv中产生全零

时间:2013-02-24 12:22:06

标签: c++ opencv

我有以下代码:

    cv::Mat temp0 = R.t();
    cv::Mat temp1 = R * temp0;
    cv::Mat temp2(960, 960, CV_32FC1);
    temp2 = temp1.inv();

    cv::Size s = temp1.size();
    std::cout<<s.height<<" "<<s.width<<std::endl;

    std::cout<<cv::format(temp1, "numpy" ) << std::endl;
    std::cout<<cv::format(temp2, "numpy" ) << std::endl;

Transpose工作正常,矩阵乘法也是如此。因此Mat temp1的大小为960x960。但是,当我执行temp2 =temp1.inv()时,我会在temp2中收到所有零。我的意思是零是所有960x960细胞。此外,R仅为CV_32FC1类型。所以它可能不是数据类型问题。我在这里无法理解这个问题。我用Google搜索了这么多。你能帮忙吗?

修改

我正在复制Mat::inv()函数的gdb输出下面。我很难搞清楚这一切,但如果有人对OpenCV更熟悉,也许会有所帮助:)

Breakpoint 1, CreateShares::ConstructShares (this=0x80556d0, channel=..., k=2, n=4) at CreateShares.cpp:165
165     temp2 = temp1.inv();
(gdb) step

cv::Mat::operator= (this=0xbffff294, e=...) at /usr/include/opencv2/core/mat.hpp:1373
1373        e.op->assign(e, *this);
(gdb) 
1374        return *this;
(gdb) step
1375    }    
(gdb) step
cv::MatExpr::~MatExpr (this=0xbfffef64, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:1167
1167    class CV_EXPORTS MatExpr
(gdb) step
cv::Mat::~Mat (this=0xbfffefdc, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:295
295     release();
(gdb) step
cv::Mat::release (this=0xbfffefdc) at /usr/include/opencv2/core/mat.hpp:381
381     if( refcount && CV_XADD(refcount, -1) == 1 )
(gdb) step
383     data = datastart = dataend = datalimit = 0;
(gdb) step
384     size.p[0] = 0;
(gdb) step
385     refcount = 0;
(gdb) step
386 }
(gdb) step
cv::Mat::~Mat (this=0xbfffefdc, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:296
296     if( step.p != step.buf )
(gdb) step
298 }
(gdb) step
cv::Mat::~Mat (this=0xbfffefa4, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:295
295     release();
(gdb) step
cv::Mat::release (this=0xbfffefa4) at /usr/include/opencv2/core/mat.hpp:381
381     if( refcount && CV_XADD(refcount, -1) == 1 )
(gdb) step
383     data = datastart = dataend = datalimit = 0;
(gdb) step
384     size.p[0] = 0;
(gdb) step
385     refcount = 0;
(gdb) step
386 }
(gdb) step
cv::Mat::~Mat (this=0xbfffefa4, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:296
296     if( step.p != step.buf )
(gdb) step
298 }
(gdb) step
cv::Mat::~Mat (this=0xbfffef6c, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:295
295     release();
(gdb) step
cv::Mat::release (this=0xbfffef6c) at /usr/include/opencv2/core/mat.hpp:381
381     if( refcount && CV_XADD(refcount, -1) == 1 )
(gdb) step
383     data = datastart = dataend = datalimit = 0;
(gdb) step
384     size.p[0] = 0;
(gdb) step
385     refcount = 0;
(gdb) step
386 }
(gdb) step
cv::Mat::~Mat (this=0xbfffef6c, __in_chrg=<optimized out>) at /usr/include/opencv2/core/mat.hpp:296
296     if( step.p != step.buf )
(gdb) step
298 }
(gdb) step
CreateShares::ConstructShares (this=0x80556d0, channel=..., k=2, n=4) at CreateShares.cpp:167
167     cv::Size s = temp1.size();
(gdb) step
cv::Mat::MSize::operator() (this=0xbffff284) at /usr/include/opencv2/core/mat.hpp:705
705     return Size(p[1], p[0]);
(gdb) step
cv::Size_<int>::Size_ (this=0xbffff2f8, _width=960, _height=960) at /usr/include/opencv2/core/operations.hpp:1624
1624        : width(_width), height(_height) {}
(gdb) step
cv::Mat::MSize::operator() (this=0xbffff284) at /usr/include/opencv2/core/mat.hpp:706
706 }
(gdb) step

3 个答案:

答案 0 :(得分:10)

最有可能的是,行列式为零。

来自维基百科:

  

不可逆的方阵称为单数或   退化。当且仅当它的行列式时,方阵是单数的   是0。

您可以像这样显示行列式......

std::cout<<"determinant(temp1)="<<cv::determinant(temp1)<<"\n";

the documentation for Mat::inv()开始,有三种方法可供选择:

  • DECOMP_LU(默认值)是LU分解。 矩阵必须是非单数的。
  • DECOMP_CHOLESKY只是对称正定义矩阵的Cholesky分解。这种类型比大型矩阵上的LU快两倍。
  • DECOMP_SVD是SVD分解。如果矩阵是单数或甚至是非正方形,则计算伪反转。

来自the documentation for invert(),可能是由Mat :: inv()内部使用的:

  

在DECOMP_LU方法的情况下,该函数返回src   行列式(src必须是正方形)。如果是0,则矩阵不是   倒置和dst用零填充。

这与您所看到的结果一致。


关于数学的注释

我不是数学家,但我觉得反转矩阵可能是一件混乱的事情 - 如果你的矩阵非常大,更是如此。实际上,原则上存在这些反转可能是正确的,但实际上不可能以任何精度计算。在使用您的代码进行一些实验时,我发现在很多情况下我会得到不完全为零的行列式,但非常接近于零 - 可能表示数值精度可能是限制因素。我尝试使用64位值而不是32来指定矩阵,并且得到了不同,但不一定是更好的答案。

根据计算temp1矩阵的方式,识别它总是对称可能很有用。 DECOMP_CHOLESKY方法专门用于处理对称positive definite矩阵,因此使用它可能会带来一些优势。

通过实验,我发现归一化(如@cedrou所示)使得反函数更有可能返回非零矩阵(DECOMP_LU但不归DECOMP_CHOLESKY)。但是,根据我对如何初始化R矩阵的猜测,得到的矩阵似乎永远不会满足逆的定义:A*inverse(A)=Identity。但是你并不一定关心这一点 - 这也许是SVD方法计算伪逆的原因。

最后,似乎为什么反转失败的这个更深层次的问题可能是数学问题而不是编程问题。基于此我在数学网站上进行了一些搜索,结果发现有人已经问过如何做这件事:https://math.stackexchange.com/questions/182662


有关调试的说明

根据您的调试跟踪,我倾向于认为您感兴趣的部分已编译为不可跟踪的库,并在您运行step时跳过。换句话说,在您的第一个step之后的那个神秘的空白行代表它实际运行inv()函数的部分。之后,它将结果分配给temp2并破坏临时对象。所以你的调试跟踪并没有告诉我们inv()内发生的事情。

165     temp2 = temp1.inv();
(gdb) step

cv::Mat::operator= (this=0xbffff294, e=...) at /usr/include/opencv2/core/mat.hpp:1373
1373        e.op->assign(e, *this);

我自己运行了一个调试器,并能够追踪内部调用invert(),并根据矩阵的内部分析(确定它不可逆)观察它决定失败 - 并且因此返回一个填充零的矩阵,与您报告的内容相匹配。

invert()函数在cxlapack.cpp中定义,如果您有兴趣查看源代码。

答案 1 :(得分:3)

对于随机矩阵R,产品R^T*R可能是单数。因此,任何类型的LU分解都会过早停止,导致零输出。

为了克服这个问题,可以将矩阵R^T*R+alpha*I反转。这里I是单位矩阵,alpha - 一些正数。如果alpha接近零并且R^T*R不是单数,则R^T*R+alpha*I的倒数接近R^T*R的倒数。有关详细信息,请参阅Tikhonov regularization

另一种情况是矩阵R^T*R不是单数但是病态。 Condition number对于大的非结构化矩阵可能是巨大的,导致矩阵反演的奇怪行为(LU分解仅适用于相对较小的条件数)。

关于规范化

归一化矩阵将改善反演行为,因为它减少了条件数。

答案 2 :(得分:2)

我得出的结论与@berak相同。我希望以下实验能为您提供帮助。

我尝试使用填充了一些随机值的矩阵(正态分布以0为中心,标准开发为sigma。当sigma增加时,最终矩阵的值会减小。< / p>

以下是我使用的代码:

cv::Mat R(960,960, CV_32FC1);

double sigma[] = { 1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 10000000.0 };
cv::Scalar mean[_countof(sigma)] = {0};
cv::Scalar stdv[_countof(sigma)] = {0};
for (int i = 0; i < _countof(sigma); i++)
{
    cv::randn(R, cv::Scalar::all(0.0), cv::Scalar::all(sigma[i]));
    cv::Mat temp2 = (R * R.t()).inv();

    cv::meanStdDev(temp2, mean[i], stdv[i]);
}

以下是增加sigma的输出矩阵的平均值和标准值:

sigma       mean        stddev
1.0         3.94e-004   1.32
10.0        1.25e-004   3.82e-002
100.0       3.32e-007   1.09e-004
1000.0      2.40e-009   2.23e-006
10000.0     9.82e-012   1.05e-008
100000.0    2.23e-013   1.73e-010
1000000.0   1.44e-015   2.88e-012
10000000.0  9.61e-017   2.77e-014

因此,一个解决方案是对输入矩阵进行标准化,使所有值都适合[0; 1]或[-1; 1]。