7

私は次のコードを持っています:

    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;

転置は正しく機能し、行列の乗算も正しく機能します。したがって、Mat temp1のサイズは 960x960 です。しかし、私がそうするとtemp2 =temp1.inv()、私はすべてゼロを受け取りますtemp2。つまり、ゼロはすべて 960x960 セルです。また、RタイプCV_32FC1のみです。したがって、おそらくデータ型の問題ではありません。ここの問題が理解できません。私はとてもググった。助けてください。

編集

関数の gdb 出力の下にコピーしていMat::inv()ます。私はそれをすべて理解するのに苦労していますが、誰かが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
4

3 に答える 3

10

ほとんどの場合、決定要因はゼロです。

ウィキペディアから:

可逆でない正方行列は、特異行列または縮退行列と呼ばれます。正方行列は、その行列式が 0 の場合にのみ特異です。

行列式を次のように表示できます...

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

Mat::inv() のドキュメントから、次の3 つの方法から選択できます。

  • DECOMP_LU (デフォルト) は LU 分解です。行列は正則でなければなりません。
  • DECOMP_CHOLESKY は、正に定義された対称行列のみのコレスキー分解です。このタイプは、大きな行列では LU よりも約 2 倍高速です。
  • DECOMP_SVD は SVD 分解です。行列が特異な場合、または正方でない場合でも、疑似反転が計算されます。

おそらく Mat::inv() によって内部的に使用されるinvert() のドキュメントから:

DECOMP_LU メソッドの場合、関数は src 行列式を返します (src は正方でなければなりません)。0 の場合、行列は反転されず、dst はゼロで埋められます。

これは、表示されている結果と一致します。


数学についてのメモ

私は数学者ではありませんが、行列の逆変換は厄介な作業になる可能性があるという印象を受けます。行列が非常に大きい場合はなおさらです。実際、これらの逆数が原理的に存在することは事実かもしれませんが、正確に計算することは事実上不可能です。コードでいくつかの実験を実行したところ、多くの場合、正確にゼロではなく、ゼロに非常に近い行列式が得られることがわかりました。おそらく、数値精度が制限要因である可能性があることを示しています。32 の代わりに 64 ビット値を使用して行列を指定しようとしましたが、異なる結果が得られましたが、必ずしもより良い答えではありませんでした。

temp1行列を計算する方法に基づいて、常に対称になることを認識すると役立つ場合があります。このメソッドは、対称正定DECOMP_CHOLESKY行列で機能するように特別に設計されているため、それを使用するといくつかの利点が得られる場合があります。

実験的に、(@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()、マトリックスの内部分析に基づいて失敗することを決定するのを見ることができました(反転できないと判断しました)-したがって、ゼロで満たされたマトリックスを返します。あなたが報告したものと一致します。

ソースコードを確認したい場合に備えて、関数は cxlapack.cpp で定義invert()されています。

于 2013-03-03T09:27:09.097 に答える
3

ランダム行列の場合、RR^T*Rは特異な場合があります。そのため、あらゆる種類の LU 分解が途中で停止し、出力がゼロになります。

これを克服するために、行列を逆にすることができR^T*R+alpha*Iます。ここIに恒等行列がありますalpha- いくつかの正の数。alpha がゼロに近くR^T*R、特異でない場合、 の逆数は の逆数にR^T*R+alpha*I近くなりR^T*Rます。詳細については、Tikhonov 正則化を参照してください。

もう 1 つのケースは、行列R^T*Rが特異ではないが条件が悪い場合です。大規模な非構造化行列の条件数は非常に大きく、逆行列の奇妙な動作が発生する可能性があります (LU 分解は、比較的小さな条件数に対してのみ正しく機能します)。

正規化について

行列を正規化すると、条件数が減少するため、反転動作が改善されます。

于 2013-03-05T10:26:52.223 に答える
2

@berakと同じ結論に達しました。以下の実験がお役に立てば幸いです。

いくつかのランダムな値で満たされた行列を使用してコードを試しました (0 を中心とし、標準偏差が の正規分布sigmasigma増加するにつれて、最終的な行列の値は減少します。

使用したコードは次のとおりです。

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       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] に収まるように入力行列を正規化することです。

于 2013-02-28T15:06:25.277 に答える