我有以下代码:
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
答案 0 :(得分:10)
最有可能的是,行列式为零。
来自维基百科:
不可逆的方阵称为单数或 退化。当且仅当它的行列式时,方阵是单数的 是0。
您可以像这样显示行列式......
std::cout<<"determinant(temp1)="<<cv::determinant(temp1)<<"\n";
从the documentation for Mat::inv()开始,有三种方法可供选择:
来自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]。