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

アラフィフプログラマーが数学と物理を基礎からやり直す。https://qiita.com/yaju

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

前回は、転送元座標と転送先座標を内部的に入れ替えて射影変換パラメータを求めて画像に穴が空かないように表示していました。
しかし、下記サイトのCSSのtransformで渡している8個のパラメータを求める際には、特に転送元座標と転送先座標を入れ替えたりしていません。
ホモグラフィ(CSS3 Transform 3D Test)- Shogo Computing Laboratory
これはCSSのtransform内部でパラメータを描画用に逆行列させているからと思われます。射影および回転にしても穴が空かないようにするには、転送先から転送元に逆行列させる必要があるからです。

なので、transform同様に渡されたパラメータの逆行列を求めて、描画処理に渡すようにしたいと思います。

射影変換で求めたパラメータを、3x3の正方行列に格納して逆行列を求めます。

//射影変換パラメータの逆行列を求める
var N = [];
N.push([ans.e(1), ans.e(2), ans.e(3)]);
N.push([ans.e(4), ans.e(5), ans.e(6)]);
N.push([ans.e(7), ans.e(8), 1]);
var ans_i = $M(N).inv();


そして、描画処理には逆行列したパラメータ(ans_i.e)を使用する。

var i, M = [], V = [];
var x, y, X, Y;
for(i=0;i<4;i++) {
	x = origin[i][0];
	y = origin[i][1];
	X = markers[i].x() - imgx;
	Y = markers[i].y() - imgy;
	M.push([x, y, 1, 0, 0, 0, -x*X, -y*X]);
	M.push([0, 0, 0, x, y, 1, -x*Y, -y*Y]);
	V.push(X);
	V.push(Y);
}

//射影変換を求める
var ans = $M(M).inv().x($V(V));
console.log($M(M).inspect());
console.log($V(V).inspect());
console.log(ans.inspect());

//射影変換パラメータの逆行列を求める
var N = [];
N.push([ans.e(1), ans.e(2), ans.e(3)]);
N.push([ans.e(4), ans.e(5), ans.e(6)]);
N.push([ans.e(7), ans.e(8), 1]);
var ans_i = $M(N).inv();

//描画
draw();

function draw(){
   var imgW = imgObj.width;
   var imgH = imgObj.height;
   var canvas = document.getElementById('ctx');
   var context = canvas.getContext("2d");
   var input = context.getImageData(0, 0, imgW, imgH);
   var output =context.createImageData(imgW, imgH);
   for(var i = 0; i < imgH; ++i){
       for(var j = 0; j < imgW; ++j){
           //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 * ans_i.e(3,1) + i * ans_i.e(3,2) + ans_i.e(3,3);
           var tmpX = (j * ans_i.e(1,1) + i * ans_i.e(1,2) + ans_i.e(1,3)) / tmp;
           var tmpY = (j * ans_i.e(2,1) + i * ans_i.e(2,2) + ans_i.e(2,3)) / tmp;

           var floorX = tmpX | 0;
           var floorY = tmpY | 0;

           if (floorX >= 0 && floorX < imgW && floorY >= 0 && floorY < imgH) {
               // 左上 + 右上 + 左下 + 右下
               //var pixelData = getPixel(input, j, i);   // ピクセル値を取得する
               var pixelData = getPixel(input, floorX, floorY);   // ピクセル値を取得する     
               var R = pixelData.R;
               var G = pixelData.G;
               var B = pixelData.B;
               //setPixel(output, floorX, floorY, R, G, B, 255);
               setPixel(output, j, i, R, G, B, 255);
           }
       }
   }

今まで代数ライブラリsylvesterを使用して行列計算を求めてきました。これは、8次元連立方程式を求める上では必要だったからですね。
しかし、汎用または自作の3Dライブラリでは、よくて4x4行列クラスまでが用意されているくらいです。これはクォータニオンを使う上で4x4行列が必要なので、ここまでは用意されているわけです。また4×4行列の逆行列の公式があるのも理由の一つでしょう。


次回は、代数ライブラリを使わずに8次元連立方程式を4x4行列で求める方法を説明します。