web-dev-qa-db-ja.com

効率的な4x4行列逆(アフィン変換)

4x4のアフィン行列変換の効率的な式を誰かが指摘してくれることを期待していました。現在、私のコードは補因子展開を使用しており、各補因子に一時配列を割り当てています。読みやすいですが、本来の速度よりも遅くなります。

これは宿題ではないので、4x4補因子展開を使用して手動で解決する方法を知っています。これは苦痛であり、私にとって興味深い問題ではありません。また、私はググっていくつかの公式サイトを思いつきました( http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm =)。しかし、これはおそらくいくつかの製品を事前に計算することによってさらに最適化できるでしょう。誰かがいつかこれについて「最良の」式を考え出したと思いますか?

30
Budric

行列がアフィンであるという事実を利用して、完全な逆行列よりも速度を上げることができるはずです。つまり、マトリックスが次のようになっている場合

A = [ M   b  ]
    [ 0   1  ]

ここで、Aは4x4、Mは3x3、bは3x1、一番下の行は(0,0,0,1)です。

inv(A) = [ inv(M)   -inv(M) * b ]
         [   0            1     ]

状況によっては、実際にinv(A)を形成するよりも、inv(A)* xの結果を計算する方が速い場合があります。その場合、物事は単純化されます

inv(A) * [x] = [ inv(M) * (x - b) ]
         [1] = [        1         ] 

ここで、xは3x1ベクトル(通常は3Dポイント)です。

最後に、Mが回転を表す場合(つまり、その列が正規直交である場合)、inv(M)= transpose(M)という事実を使用できます。次に、Aの逆数を計算することは、平行移動成分を減算し、3x3部分の転置を乗算することです。

マトリックスが正規直交であるかどうかは、問題の分析から知っておくべきことです。実行時にそれをチェックすることはかなり高価になるでしょう。ただし、前提条件が満たされていることを確認するために、デバッグビルドで実行することもできます。

すべてが明確であることを願っています...

45
celion

誰かが入力を保存したい場合に備えて、phkahlerによって上記に掲載されたリンクの9ページ(Laplace Expansion Theoremのより効率的なバージョン)に基づいて私が書いたAS3バージョンを次に示します。

public function invert() : Matrix4 {
    var m : Matrix4 = new Matrix4();

    var s0 : Number = i00 * i11 - i10 * i01;
    var s1 : Number = i00 * i12 - i10 * i02;
    var s2 : Number = i00 * i13 - i10 * i03;
    var s3 : Number = i01 * i12 - i11 * i02;
    var s4 : Number = i01 * i13 - i11 * i03;
    var s5 : Number = i02 * i13 - i12 * i03;

    var c5 : Number = i22 * i33 - i32 * i23;
    var c4 : Number = i21 * i33 - i31 * i23;
    var c3 : Number = i21 * i32 - i31 * i22;
    var c2 : Number = i20 * i33 - i30 * i23;
    var c1 : Number = i20 * i32 - i30 * i22;
    var c0 : Number = i20 * i31 - i30 * i21;

    // Should check for 0 determinant

    var invdet : Number = 1 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);

    m.i00 = (i11 * c5 - i12 * c4 + i13 * c3) * invdet;
    m.i01 = (-i01 * c5 + i02 * c4 - i03 * c3) * invdet;
    m.i02 = (i31 * s5 - i32 * s4 + i33 * s3) * invdet;
    m.i03 = (-i21 * s5 + i22 * s4 - i23 * s3) * invdet;

    m.i10 = (-i10 * c5 + i12 * c2 - i13 * c1) * invdet;
    m.i11 = (i00 * c5 - i02 * c2 + i03 * c1) * invdet;
    m.i12 = (-i30 * s5 + i32 * s2 - i33 * s1) * invdet;
    m.i13 = (i20 * s5 - i22 * s2 + i23 * s1) * invdet;

    m.i20 = (i10 * c4 - i11 * c2 + i13 * c0) * invdet;
    m.i21 = (-i00 * c4 + i01 * c2 - i03 * c0) * invdet;
    m.i22 = (i30 * s4 - i31 * s2 + i33 * s0) * invdet;
    m.i23 = (-i20 * s4 + i21 * s2 - i23 * s0) * invdet;

    m.i30 = (-i10 * c3 + i11 * c1 - i12 * c0) * invdet;
    m.i31 = (i00 * c3 - i01 * c1 + i02 * c0) * invdet;
    m.i32 = (-i30 * s3 + i31 * s1 - i32 * s0) * invdet;
    m.i33 = (i20 * s3 - i21 * s1 + i22 * s0) * invdet;

    return m;
}

これにより、さまざまな3D変換行列にこのメソッドから返された逆行列を掛けると、単位行列が正常に生成されました。私はあなたがこれをあなたが好きな言語に取得するために検索/置換できると確信しています。

20
Robin Hilliard

上記の pkhalerRobin Hilliard の優れた応答をフォローアップするために、C#メソッドに変換されたRobinのActionScript 3コードを示します。うまくいけば、これにより、他のC#開発者や、C/C++およびJava 4x4行列反転関数を必要とする開発者)のタイピングを節約できます。

public static double[,] GetInverse(double[,] a)
{
    var s0 = a[0, 0] * a[1, 1] - a[1, 0] * a[0, 1];
    var s1 = a[0, 0] * a[1, 2] - a[1, 0] * a[0, 2];
    var s2 = a[0, 0] * a[1, 3] - a[1, 0] * a[0, 3];
    var s3 = a[0, 1] * a[1, 2] - a[1, 1] * a[0, 2];
    var s4 = a[0, 1] * a[1, 3] - a[1, 1] * a[0, 3];
    var s5 = a[0, 2] * a[1, 3] - a[1, 2] * a[0, 3];

    var c5 = a[2, 2] * a[3, 3] - a[3, 2] * a[2, 3];
    var c4 = a[2, 1] * a[3, 3] - a[3, 1] * a[2, 3];
    var c3 = a[2, 1] * a[3, 2] - a[3, 1] * a[2, 2];
    var c2 = a[2, 0] * a[3, 3] - a[3, 0] * a[2, 3];
    var c1 = a[2, 0] * a[3, 2] - a[3, 0] * a[2, 2];
    var c0 = a[2, 0] * a[3, 1] - a[3, 0] * a[2, 1];

    // Should check for 0 determinant
    var invdet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0);

    var b = new double[4, 4];

    b[0, 0] = ( a[1, 1] * c5 - a[1, 2] * c4 + a[1, 3] * c3) * invdet;
    b[0, 1] = (-a[0, 1] * c5 + a[0, 2] * c4 - a[0, 3] * c3) * invdet;
    b[0, 2] = ( a[3, 1] * s5 - a[3, 2] * s4 + a[3, 3] * s3) * invdet;
    b[0, 3] = (-a[2, 1] * s5 + a[2, 2] * s4 - a[2, 3] * s3) * invdet;

    b[1, 0] = (-a[1, 0] * c5 + a[1, 2] * c2 - a[1, 3] * c1) * invdet;
    b[1, 1] = ( a[0, 0] * c5 - a[0, 2] * c2 + a[0, 3] * c1) * invdet;
    b[1, 2] = (-a[3, 0] * s5 + a[3, 2] * s2 - a[3, 3] * s1) * invdet;
    b[1, 3] = ( a[2, 0] * s5 - a[2, 2] * s2 + a[2, 3] * s1) * invdet;

    b[2, 0] = ( a[1, 0] * c4 - a[1, 1] * c2 + a[1, 3] * c0) * invdet;
    b[2, 1] = (-a[0, 0] * c4 + a[0, 1] * c2 - a[0, 3] * c0) * invdet;
    b[2, 2] = ( a[3, 0] * s4 - a[3, 1] * s2 + a[3, 3] * s0) * invdet;
    b[2, 3] = (-a[2, 0] * s4 + a[2, 1] * s2 - a[2, 3] * s0) * invdet;

    b[3, 0] = (-a[1, 0] * c3 + a[1, 1] * c1 - a[1, 2] * c0) * invdet;
    b[3, 1] = ( a[0, 0] * c3 - a[0, 1] * c1 + a[0, 2] * c0) * invdet;
    b[3, 2] = (-a[3, 0] * s3 + a[3, 1] * s1 - a[3, 2] * s0) * invdet;
    b[3, 3] = ( a[2, 0] * s3 - a[2, 1] * s1 + a[2, 2] * s0) * invdet;

    return b;
}
19

IIRCでは、束(12?)の2x2行列式を事前計算することにより、コードと時間を大幅に縮小できます。行列を垂直に半分に分割し、上半分と下半分の両方で2x2ごとに計算します。これらの小さな行列式の1つは、より大きな計算に必要なすべての項で使用され、それぞれが再利用されます。

また、別の行列式関数を使用しないでください。随伴関数に対して計算したサブ行列式を再利用して、行列式を取得してください。

ああ、見つけた this。

特定の種類の変換についても知っておくことができるいくつかの改善点があります。

4
phkahler

私は逆を計算する唯一の方法は方程式のn倍を解くことであると信じています:A x = y、ここでyは単位ベクトルにまたがります、すなわち、最初のものは(1,0,0,0)、2番目は(0 、1、0、0)など.

(逆数のシンボリック式が必要でない限り、補因子の使用(クラマーの法則)はお勧めできません。)

ほとんどの線形代数ライブラリでは、これらの線形システムを解くことができ、逆行列を計算することさえできます。 python(numpyを使用)の例:

from numpy.linalg import inv
inv(A) # here you go
1
Olivier Verdier