Homography


ホモグラフィ変換用の行列(転写関数)を算出する方法に関して、覚書として以下を残しておく。
下記画像は、乱数関数によりランダムに生成された出力領域に、ホモグラフィ転写した結果。


各画像をクリックすると、実際の変換画像が表示される。

Homography
Homography001 Homography002 Homography003 Homography004 Homography005 Homography006 Homography007 Homography008 Homography009 Homography010 Homography011 Homography012 Homography013 Homography014 Homography015 Homography016 Other areas



//////////////////////////////////////////////////////////////////////
// Homography
までは、
乱数より生成された座標点で、出力領域になり得る座標群であるかを確認する部分。
以降が、変換用の3x3の行列を生成する手順。(異なる4点の対応条件が必要)

原画上の四(隅の)点が、SA, SB, SC, SD の時、転写先の対応点は、DA, DB, DC, DD。
    SA => DA
    SB => DB
    SC => DC
    SD => DD
HomographyTrans() は、逆行列を用いた画像転写処理ルーチン


#pragma pack(push,1)
typedef struct PIXEL   { unsigned char B, G, R, A; } PIXEL;
#pragma pack(pop)

typedef struct POINT { float x, y; } POINT;

int Homography( PIXEL **s, int slx, int sly, PIXEL **d, int lx, int ly ){
    srand( (unsigned int)time(NULL) );
    for(int e=0; e<16; ) {////   (POINT){ float x, y; }
        POINT A = (POINT){ rand() % MAX_SIZE, rand() % MAX_SIZE };
        POINT B = (POINT){ rand() % MAX_SIZE, rand() % MAX_SIZE };
        POINT C = (POINT){ rand() % MAX_SIZE, rand() % MAX_SIZE };
        POINT D = (POINT){ rand() % MAX_SIZE, rand() % MAX_SIZE };
        if( (A.x-C.x)*(B.y-C.y) == (B.x-C.x)*(A.y-C.y) ) continue;
        if( (B.x-D.x)*(C.y-D.y) == (C.x-D.x)*(B.y-D.y) ) continue;
        if( (C.x-A.x)*(D.y-A.y) == (D.x-A.x)*(C.y-A.y) ) continue;
        if( (D.x-B.x)*(A.y-B.y) == (A.x-B.x)*(D.y-B.y) ) continue;
        //////////////////////////////////////////////////////////////////////
        ////       (PTSEL){   x    y  id   norm                     flag}
        PTSEL rawp[4] = { { A.x, A.y, 'A', sqrt( A.x*A.x + A.y*A.y ), 0 },
                          { B.x, B.y, 'B', sqrt( B.x*B.x + B.y*B.y ), 0 },
                          { C.x, C.y, 'C', sqrt( C.x*C.x + C.y*C.y ), 0 },
                          { D.x, D.y, 'D', sqrt( D.x*D.x + D.y*D.y ), 0 },
                        };  if( sort_by_order(rawp) ) continue;;
        e++;
        //////////////////////////////////////////////////////////////////////
        // Homography
        POINT  SA = (POINT){ slx,   0 }, DA = (POINT){ rawp[0].x, rawp[0].y };
        POINT  SB = (POINT){ slx, sly }, DB = (POINT){ rawp[1].x, rawp[1].y };
        POINT  SC = (POINT){   0, sly }, DC = (POINT){ rawp[2].x, rawp[2].y };
        POINT  SD = (POINT){   0,   0 }, DD = (POINT){ rawp[3].x, rawp[3].y };
        double tranp[8]  = { DA.x, DA.y, DB.x, DB.y, DC.x, DC.y, DD.x, DD.y };
        double abcdefgh1[3*3]={0,0,0,0,0,0,0,0,1}; //Coefficient of 3x3 matrix
        double hg[8][8] = { // 1以外の 8つの定数値を確定する為には、8本の連立方程式が必要
                 { SA.x, SA.y,  1,     0,    0,  0,  -SA.x*DA.x, -SA.y*DA.x },
                 {    0,    0,  0,  SA.x, SA.y,  1,  -SA.x*DA.y, -SA.y*DA.y },
                 { SB.x, SB.y,  1,     0,    0,  0,  -SB.x*DB.x, -SB.y*DB.x },
                 {    0,    0,  0,  SB.x, SB.y,  1,  -SB.x*DB.y, -SB.y*DB.y },
                 { SC.x, SC.y,  1,     0,    0,  0,  -SC.x*DC.x, -SC.y*DC.x },
                 {    0,    0,  0,  SC.x, SC.y,  1,  -SC.x*DC.y, -SC.y*DC.y },
                 { SD.x, SD.y,  1,     0,    0,  0,  -SD.x*DD.x, -SD.y*DD.x },
                 {    0,    0,  0,  SD.x, SD.y,  1,  -SD.x*DD.y, -SD.y*DD.y },
               }, invhg[8][8], hkl[3][3], invhkl[3][3];
        //////////////////////////////////////////////////////////////////////
// 下記の画像転送方法, HomographyTrans(...),では無意味なので削除 // int b=255,g=255,r=255; // Clear canvas for drawing images // for(int y=0;y<ly;y++)for(int x=0;x<lx;x++)d[y][x]=(PIXEL){b,g,r,0};
////////////////////////////////////////////////////////////////////// // 4点の対応条件(hg,tranp)から、ホモグラフィ(射影変換)用の行列(hkl)を算出 // homography kernel: hkl[3][3] = { {a,b,c}, {d,e,f}, {g,h,1} } int x, y, e; inverseMatrix( 8, invhg, hg ); // Calculate the inverse matrix for(y=0;y<8;y++)for(x=0;x<8;x++) abcdefgh1[y] += invhg[y][x]*tranp[x]; for(e=0,y=0;y<3;y++)for(x=0;x<3;x++) hkl[y][x] = abcdefgh1[e++]; ////////////////////////////////////////////////////////////////////// // 算出したホモグラフィ行列(hkl)から、画像変換処理用の逆行列(invhkl)を算出 inverseMatrix( 3, invhkl, hkl ); // Calculate the inverse matrix HomographyTrans( d, lx, ly, s, slx, sly, invhkl ); ///////////////////////////////////////////////////// char fname[88]; snprintf(fname,sizeof(fname)-1,"Homography%3.3d.bmp",e); saveArray2bmp32( fname, d, lx, ly ); // Save the image as a BMP file }// Consider the adjugate matrix because it's processed by a computer. return 0; }
// hkl[3][3]: オリジナル座標から、Homography転写先アドレスを計算する配列 // invhkl[3][3]: 転写先アドレスから、転写元 のオリジナル座標を逆計算する配列

 ※ inverseMatrix(...) コンピュータによる逆行列の算出は、余因子行列から逆行列を求める
   旧式のnotePCでも、再帰呼び出しで8x8の逆行列の算出は 30ms程度なので処理時間は許容の範囲
   ただし少し工夫をした改良版にすれば、処理時間が 数ms以下になる。
   処理方法の改善により、13x13の逆行列の算出が30s程度なので、実用的には改良版の利用が前提



追加

変形した画像から、正方形の画像を再変換する例

画像(Homography011.bmp)に対して、以下の様な転写座標を設定して変換を行った結果
ただし、座標点は左下を原点とした時の位置を示している。

   SA(1055,1372)  =>  DA( 200,1200)
   SB(1109,1165)  =>  DB(1200,1200)
   SC( 333, 826)  =>  DC(1200, 200)
   SD( 175, 925)  =>  DD( 200, 200)

再変換された画像
Reconverted_thumbnail

Reference example coding
// k x k の正則行列 matrix[k][k] に対する逆行列 inverse[k][k] を計算する処理 // detA が 0 なら逆行列は存在しない
int inverseMatrix( int k, double inverse[k][k], double matrix[k][k] ){ int x, y, below = k-1; long double detA, adjugate[k][k]; for(y=0;y<k;y++)for(x=0;x<k;x++) inverse[y][x] = (x==y) ? 1 : 0; if( k<=1 || ( detA = determinant( k, matrix ) )==0 ) return -1; for(y=0;y<k;y++)for(x=0;x<k;x++){ double cofactor[below][below]; for(int m=0, n=0, u=0;u<k;u++)for(int v=0;v<k;v++) { if( u==y || v==x ) continue; cofactor[m][n++] = matrix[u][v]; // Extraction of cofactors if( below <= n ){ m++, n=0; } } long double dtw = determinant( below, cofactor ); adjugate[x][y] = (y+x)&1 ? -dtw : dtw;//[y][x]->[x][y] Symmetric matrix } for(y=0;y<k;y++)for(x=0;x<k;x++) inverse[y][x]=adjugate[y][x]/detA; return 0; } ※ IEEE double の指数部(11ビット)の表現力不足が原因で、高次の行列式の計算は失敗する ※ 行列式の記憶領域double から long double (多分:64ビットから80ビット)に変更 ※ long double は環境依存、スタックサイズの変更は editbin.exe を使用 ※ (detA=determinant(...))==0 は便宜上の表現。浮動小数なので、fabs(detA)<下限値

#define mtxMul(m,p) (POINT){158} // playful expression, like i18n
void HomographyTrans( PIXEL **d, int lx, int ly, PIXEL **s, int slx, int sly, double inv[3][3] ){ for(int y=0;y<ly;y++)for(int x=0;x<lx;x++){ POINT destination = (POINT){x,y}; POINT source = mtxMul( inv, destination ); // Source coordinate via inverse matrix operation d[y][x] = (source.x<0 || source.y<0 || slx<=source.x || sly<=source.y) ? (PIXEL){0,0,0,0} : s[(int)source.y][(int)source.x]; } }// Transfer function for Homography Transform, and Affine transformation (simple version)
※ 行列式関連のコードはネット上に多数落ちていると思ったが、適当なサンプルがなかったので追加   ただし、検証用の非効率なコード 8x8行列の逆行列計算程度では、long double の精度は不要 // k x k の行列 matrix に対する行列式の値を計算する処理 再帰呼び出し(Recursive Call)を // 用いることで、シンプルに記述できるが、再帰回数によってはスタックオバーフローを起こすので // 注意を要する  (スマートなループで書き換えられるかは、技量・技術力に依存) #define IF if #define EI else if #define EE else double determinant( int k, double matrix[k][k] ){ int below = k-1; IF( k< 2 ) return k==1 ? matrix[0][0] : 0 ; EI( k==2 ) return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; EE{ double det=0, cofactor[below][below]; // "e&1 ?" even-odd decision for(int e=0;e<k;e++){ double compo = e&1 ? -matrix[e][0] : matrix[e][0]; //if( fabs(compo)<下限値 )continue; for(int m=0, n=0, y=0;y<k;y++){ if( y==e ) continue; //+e+-+-+-+-+ for(int x=1;x<k;x++){// ignore matrx[y][0]: x==0 // X ? ? ? ? cofactor[m][n++] = matrix[y][x]; //Extraction // C X X X X if( below <= n ){ m++, n=0; } // X ? ? ? ? } } det += compo * determinant( below, cofactor ); // X ? ? ? ? } return det; // X ? ? ? ? }// Core Scope of determinant() Processing //+e+-+-+-+-+ }

 逆行列を求める為の、行列式の計算方法(余因子展開)

8x8の正方行列 を、 Aij i,j {0,1,2,3,4,5,6,7}とした時に、i を 0 に固定して、
余因子展開を考えると、上記ルーチンでは 8x8の行列式(※1)の値を得る事が出来る。
行列 と 行列式(行列の固有値) は別物なので混同しないよう注意する事
※1 N x N の正方行列の行列式は、(N - 1)次小行列式の重み付き和として表される
  この処理を N - 2 回繰り返すと、最終的には2x2の行列式の計算で求められる。

参考情報
余因子展開https://ja.wikipedia.org/wiki/余因子展開
行列式(|A|)https://ja.wikipedia.org/wiki/行列式
行列(Ahttps://ja.wikipedia.org/wiki/行列



 8x8行列の逆行列を求める計算方法(ChatGPTの生成版

※ ChatGPTは、インテリジェンスが無い事を認識してください。NLP(自然言語処理)であり、その過程で
  AI手法と認識されているニューラルネットワークを用いているだけです。この単語の次に来るべき最も
  らしい単語は xx、を繰り返えして文章を生成する事が特徴の様です。問題なのはその結果が恐ろしい
  ほど的を得ている事が非常に多いため、インテリジェンスがある様に思えてしまうことです。
  当方では、ELIZA の高性能版と認識ししているので、Suzie という名称を付け利用しています。

8x8の行列をランダム生成し、行列式を用いた逆行列の計算した結果を検証するプログラムで、
ChatGPT が回答した結果の中で、一切の変更無しにコンパイル及び実行が出来た 例
ChatGPT 出力書式の変更指示のみで、各要素が右揃えで表示されるようにソースを修正させた結果
次の様に回答が修正された
	printf("%f ", matrix[i][j]); ⇒ printf("%12.6f ", matrix[i][j]);

以下は、プログラムの実行結果(行列はランダムなので毎回値は異なる)

> InvertingMatricesCreator_by_ChatGPT
Random Matrix:
   0.617512    0.071718    0.279611    0.174169    0.745811    0.334910    0.946318     0.604419
   0.767907    0.519791    0.069948    0.903745    0.743919    0.315470    0.474319     0.427564
   0.627583    0.300913    0.602802    0.805292    0.152379    0.910825    0.745537     0.883633
   0.126255    0.176916    0.940519    0.648701    0.790613    0.276070    0.454665     0.407758
   0.399823    0.416028    0.251137    0.012024    0.678243    0.090426    0.349406     0.401624
   0.309763    0.234291    0.484024    0.254219    0.117344    0.310984    0.968535     0.018372
   0.087924    0.862148    0.170660    0.112796    0.899411    0.551714    0.505203     0.573016
   0.105838    0.235572    0.350536    0.926450    0.637196    0.087405    0.795373     0.577319

Inverse Matrix:
   0.028283    0.674392    0.219955   -0.167525    1.117069    0.156807   -0.843953    -0.691844
  -1.624221   -0.035199    0.221333   -0.656851    1.759147    0.679926    0.239383     0.368674
  -0.725361   -0.510601    0.308902    0.772960    1.261695    0.375537   -0.546795    -0.228131
  -0.343653    0.653348   -0.018823    0.166134   -0.782793   -0.074553   -0.118269     0.451710
   1.025931    0.525394   -0.819122    0.824937   -1.197131   -0.467486    0.554395    -0.494692
   0.979405    0.507788    0.123199    0.673137   -2.670313   -0.157205    1.122311    -1.316733
   0.401274   -0.330283   -0.231007   -0.519581   -0.236131    0.774742    0.095123     0.590251
  -0.183982   -1.049437    0.915695   -0.733770    1.618401   -0.942159   -0.334027     1.054341

Result (Random Matrix * Inverse Matrix):
   1.000000    0.000000    0.000000   -0.000000   -0.000000    0.000000    0.000000     0.000000
   0.000000    1.000000    0.000000    0.000000   -0.000000    0.000000    0.000000     0.000000
   0.000000    0.000000    1.000000    0.000000   -0.000000   -0.000000   -0.000000     0.000000
   0.000000    0.000000    0.000000    1.000000   -0.000000    0.000000   -0.000000    -0.000000
   0.000000    0.000000    0.000000    0.000000    1.000000    0.000000   -0.000000     0.000000
  -0.000000    0.000000    0.000000    0.000000   -0.000000    1.000000   -0.000000     0.000000
   0.000000    0.000000    0.000000    0.000000    0.000000    0.000000    1.000000     0.000000
   0.000000    0.000000   -0.000000   -0.000000   -0.000000    0.000000   -0.000000     1.000000

Is Result an Identity Matrix? Yes




ランダム生成された 13x13行列,64x64行列,92x92行列,128x128行列の、
逆行列を算出した例   (ホモグラフィ変換とは無関係な別の処理用)

・ 4-core, 8-Thread 2.30GHz Note PC 13x13 改良版  約30秒
・16-core,32-Thread 3.49GHz Desktop PC 13x13 改良版  約 6秒
・ 4-core, 8-Thread 2.30GHz Note PC 64x64 最適化版  約1.5秒
・16-core,32-Thread 3.49GHz Desktop PC 64x64 最適化版  約300ms
 92x92行列 現行(double型変数)の上限, stack 8Mにアップ 
・16-core,32-Thread 3.49GHz Desktop PC 92x92 最適化版  約990ms
 128x1282行列 double -> long double, stack 12Mにアップ 
・16-core,32-Thread 3.49GHz Desktop PC 128x128 最適化版  約11秒
下10桁版


スタック利用が原因で動作しないので、使わない様に変更し高速化させたが、
512x512の逆行列が約20分では使えない。(スマートな行列式算出法が必要)
750x750の逆行列が約3時間30分では問題外

Matrix Size: 512
START_try: 512x512 14:25:36.416269
E N D_try: 512x512 14:47:13.905295

START_try: 512x512 14:47:14.321943
E N D_try: 512x512 15:08:36.838259

START_try: 512x512 15:08:37.255890
E N D_try: 512x512 15:30:12.540603

START_try: 512x512 15:30:12.957733
E N D_try: 512x512 15:51:41.640397

START_try: 512x512 15:51:42.063563
E N D_try: 512x512 16:13:20.993235

 再計算結果:64x64,128x128,256x256,512x512


  GPU等の補助計算機は不使用。CPU内の演算器,マルチスレッド機構のみを利用
  逆行列の要素が小数点以下10桁の精度なら、積の精度は小数点以下6桁程度
  実用性で言うなら、Octave かな。