読者です 読者をやめる 読者になる 読者になる

デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

射影変換(ホモグラフィ)について理解してみる その7

2D 射影変換

画像補完の予定でしたが、その前段階でおかしな現象を見つけてしまいました。
射影変換(ホモグラフィ)について理解してみる その4」から射影変換のパラメータを逆行列変換して描画処理に渡していました。

ところが今頃気が付いたのですが、その方法では実際(下図の左側)より少し大きく表示(下図の右側)されてしまいます。
f:id:Yaju3D:20130916215921j:plain

射影変換の座標は下記の通りです。あえて台形を崩す値にしています。
射影変換前の4点座標([0, 0],[160, 0],[160,120],[0,120])
射影変換後の4点座標([20, 20],[140, 0],[160,120],[0,120])

上図の違いは描画する際の射影変換パラメータの求め方が違います。
上図の左側は、Yahoo 知恵袋の「射影変換式の逆式」の回答「関数に与える引数を逆にすれば良い」の通りに、射影変換前と射影変換後の座標を入れ替えてパラメータを求めています。

    //射影変換パラメータを取得(変換前originと変換後markersの座標入れ替え)
    var param = getParam(markers, origin);
    //描画処理
    draw(param);

上図の右側は、通常通りに射影変換のパラメータを求めた後、そのパラメータを逆行列変換してパラメータ求めています。

    //射影変換パラメータを取得
    var param = getParam(origin, markers);

    //描画処理用に射影変換パラメータの逆行列を取得
    var mx = new Matrix44();
    mx.set(param[0], param[1], param[2], 0, 
           param[3], param[4], param[5], 0, 
           param[6], param[7],        1, 0, 
                  0,        0,        0, 1);
    mx.Invert();
    var inv_param = new Array(8);
    inv_param[0] = mx.m11;
    inv_param[1] = mx.m12;
    inv_param[2] = mx.m13;
    inv_param[3] = mx.m21;
    inv_param[4] = mx.m22;
    inv_param[5] = mx.m23;
    inv_param[6] = mx.m31;
    inv_param[7] = mx.m32;

    //描画処理
    draw(inv_param);

取得方法の違いによるパラメータ結果

左側パラメータ         右側パラメータ  
[ 1.8457296624118131,     [ 1.6438178878456111,
  0.3615701173345402,       0.3220165116553853,
-43.58376060049736,       -38.81595693452913,
  0.29301768518656657,      0.2609633038252881,
  1.7975633714693318,       1.6009207129852896,
-41.24938428986118,       -36.73694928072075,
  0.0024418139543063294,    0.002174694198544031,
  0.0037818208849204325]    0.0033681124196855584]

右側の方が数値が小さい分、倍率が大きくなってしまうようです。

関数に与える引数を逆にする方式が簡単なんですが、パラメータを逆行列変換する方法で描画時のサイズ調整する案を試してみようと思います。


■追記
この記事を書いた後に解決案を模索していたところ、翌日に解決方法があっさり見つかりました。
倍率が大きくなってしまうことから、行列クラスの演算の精度が悪いからかもしれないと思い、Web上で逆行列(3次元)を求めるサイトにて、射影変換のパラメータを入力してみたところ、結果自体は右側パラメータと同等の数値となりましたので、演算の精度の問題では無いことは分かりました。
f:id:Yaju3D:20130918014209j:plain

ただ、1点気になるところがありまして、3行3列目の数値「0.89060597862」です。この数値は描画処理では使用していませんでした。
その部分は平面射影変換の公式に従い、あまり深く考えずに「1.0」をセットしていました。

//u = (x*a + y*b + c) / (x*g + y*h + 1)
//v = (x*d + y*e + f) / (x*g + y*h + 1)
var tmp = j * inv_param[6] + i * inv_param[7] + 1.0;
var tmpX = (j * inv_param[0] + i * inv_param[1] + inv_param[2]) / tmp;
var tmpY = (j * inv_param[3] + i * inv_param[4] + inv_param[5]) / tmp;

ところが射影変換の逆式を求めたわけですから、3行3列目の数値を使用する必要があることに気が付いたわけです。倍率も少しだけ大きいことから、1.0と0.89の違いなら小さくなることは間違いないと、早速プログラムを修正しました。

    //射影変換パラメータを取得
    var param = getParam(origin, markers);

    //描画処理用に射影変換パラメータの逆行列を取得
    var mx = new Matrix44();
    mx.set(param[0], param[1], param[2], 0, 
           param[3], param[4], param[5], 0, 
           param[6], param[7],        1, 0, 
                  0,        0,        0, 1);
    mx.Invert();
    var inv_param = new Array(9);
    inv_param[0] = mx.m11;
    inv_param[1] = mx.m12;
    inv_param[2] = mx.m13;
    inv_param[3] = mx.m21;
    inv_param[4] = mx.m22;
    inv_param[5] = mx.m23;
    inv_param[6] = mx.m31;
    inv_param[7] = mx.m32;
    inv_param[8] = mx.m33; //追加した部分

    //描画処理
    draw(inv_param);

draw関数の内部 1.0→inv_param[8]に修正

var tmp = j * inv_param[6] + i * inv_param[7] + inv_param[8];
var tmpX = (j * inv_param[0] + i * inv_param[1] + inv_param[2]) / tmp;
var tmpY = (j * inv_param[3] + i * inv_param[4] + inv_param[5]) / tmp;

結果は見事に赤枠に納まるようになりました。
f:id:Yaju3D:20130918020941j:plain

ということで、過去に掲載したソースコードも修正しておきました。