Mat.inv() yielding all zeroes in opencv

2020-06-17 04:45发布

问题:

I have the following code :

    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;

The Transpose works correctly, so does the matrix multiplication. Thus the Mat temp1 has a size of 960x960. However, when I do temp2 =temp1.inv(), I recieve all zeroes in temp2. I mean zeroes is all of the 960x960 cells. Also, R is of type CV_32FC1 only. So it is probably not a datatype issue. I cannot understand the issue here. I googled so much. Can you please help.

EDIT

I am copying below the gdb output for the Mat::inv() function. I am having a hard time figuring it all out, but if someone is more familiar with OpenCV, maybe it will be of help :)

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

回答1:

Most likely, the determinant is zero.

From Wikipedia:

A square matrix that is not invertible is called singular or degenerate. A square matrix is singular if and only if its determinant is 0.

You can display the determinant like so...

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

From the documentation for Mat::inv(), there are three methods to choose from:

  • DECOMP_LU (default) is the LU decomposition. The matrix must be non-singular.
  • DECOMP_CHOLESKY is the Cholesky decomposition for symmetrical positively defined matrices only. This type is about twice faster than LU on big matrices.
  • DECOMP_SVD is the SVD decomposition. If the matrix is singular or even non-square, the pseudo inversion is computed.

From the documentation for invert(), which is presumably used internally by Mat::inv():

In the case of DECOMP_LU method, the function returns the src determinant ( src must be square). If it is 0, the matrix is not inverted and dst is filled with zeros.

This agrees with the results that you are seeing.


notes about the math

I'm no mathematician, but I get the impression that inverting a matrix can be a messy business -- all the more so if your matrix is very large. In fact, it may be true that these inverses exist in principle, but are practically impossible to calculate with any accuracy. In running some experiments with your code, I found that in many cases I would get determinants that were not exactly zero, but were very close to zero -- perhaps indicating that numerical precision may be the limiting factor. I tried specifying the matrices using 64-bit values instead of 32, and got different, but not necessarily better answers.

It may be useful to recognize that, based on the way you are calculating the temp1 matrix, it will always be symmetric. The DECOMP_CHOLESKY method is specifically designed to work on symmetric positive definite matrices, so using that might provide some advantages.

Experimentally, I found that normalizing (as suggested by @cedrou) makes it more likely that the inverse function returns a non-zero matrix (with DECOMP_LU but not with DECOMP_CHOLESKY). However, based on my guesses of how you might be initializing the R matrix, the resulting matrices never seemed to satisfy the definition of an inverse: A*inverse(A)=Identity. But you don't necessarily care about that -- which is perhaps why the SVD method computes a pseudo inverse.

Finally, it seems that this deeper question of why inversion is failing might be a math question rather than a programming question. Based on that I did some searching on the math site, and it turns out that someone has already asked how to do this very thing: https://math.stackexchange.com/questions/182662


notes about debugging

Based on your debug trace, I am inclined to think that the part that you are interested in was compiled into a non-traceable library and skipped over when you ran step. In other words, that mysterious blank line after your first step represents the part where it actually ran the inv() function. After that it is assigning the result to temp2 and destructing temporary objects. So your debug trace doesn't tell us anything about what is happening inside of 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);

I ran a debugger on this myself and was able to trace through the inner call to invert() and watch it decide to fail based on an internal analysis of the matrix (determining that it was not invertible) -- and therefore return a matrix filled with zeros, matching what you have reported.

The invert() function is defined in cxlapack.cpp, in case you are interested in taking a look at the source code.



回答2:

For random matrix R product R^T*R may be singular. So any kind of LU-decomposition will stop prematurely, resulting in zero output.

To overcome this, one may invert matrix R^T*R+alpha*I. Here I is identity matrix, alpha - some positive number. If alpha is close zero and R^T*R is not singular, inverse of R^T*R+alpha*I is close to inverse of R^T*R. For details, see Tikhonov regularization

Another case is matrix R^T*R being not singular but ill-conditioned. Condition number for large unstructured matrix may be tremendous, resulting in weird behavior of matrix inversion (LU-decomposition works correctly only for relatively small condition numbers).

About normalizing

Normalizing matrix will improve inversion behavior because it decreases condition number.



回答3:

I've came to the same conclusion than @berak. I hope the following experiments will help you.

I tried your code with a matrix filled with some random values (normal distribution centered on 0 with a standard dev of sigma. As sigma is increasing, the values of the final matrix decrease.

Here is the code I used:

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]);
}

Here are the mean and standard dev of the output matrix for increasing sigmas:

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

So, a solution for you would be to normalize your input matrix so that all the values fit into [0;1] or [-1;1].



标签: c++ opencv