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

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

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

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

2D 射影変換

射影変換についてツールも作成して、まーそれなりに理解は出来てきたわけですが、初回の記事で取り上げたHomography HIDIHO!サイトの射影変換の下記ソースリスト(actionscript)の疑問が残っていたわけです。(cv.jsのgetPerspectiveTransformも同様)

static private function getSystem( P:Array ):Array 
{
	var system:Array = new Array( 8 );
	var sx:Number = (P[0].x-P[1].x)+(P[2].x-P[3].x);
	var sy:Number = (P[0].y-P[1].y)+(P[2].y-P[3].y);
	
	var dx1:Number = P[1].x-P[2].x;
	var dx2:Number = P[3].x-P[2].x;
	var dy1:Number = P[1].y-P[2].y;
	var dy2:Number = P[3].y-P[2].y;
 
	var z:Number = (dx1*dy2)-(dy1*dx2);
	var g:Number = ((sx*dy2)-(sy*dx2))/z;
	var h:Number = ((sy*dx1)-(sx*dy1))/z;
 
	system[0]=P[1].x-P[0].x+g*P[1].x;
	system[1]=P[3].x-P[0].x+h*P[3].x;
	system[2]=P[0].x;
	system[3]=P[1].y-P[0].y+g*P[1].y;
	system[4]=P[3].y-P[0].y+h*P[3].y;
	system[5]=P[0].y;
	system[6]=g;
	system[7]=h;
 
	return system;
}

このように射影変換用の8個のパラメータを求める際に8次元連立一次方程式を行列演算クラスを使って求めてもいないのに、すごく簡単な計算式でパラメータが求まっているわけです。
以前、調査した時にこの計算式で求めたパラメータと8次元連立一次方程式で求めたパラメータと比べた時に数値が大分違っていたので疑問に思いながらも調査するのは後回しにしていたのですが、最近落ち着いたので再調査することにしました。

歪んだ画像を正方形(長方形)画像に補正する際の座標位置
転送元 S1(65,57),S2(236,52),S3(290,282),S4(10,276)
転送先 D1( 0, 0),D2(295, 0),D3(295,295),D4( 0,295)
f:id:Yaju3D:20131110132759j:plain
左側は8次元連立一次方程式で求めたパラメータ、右側が上記計算式で求めたパラメータとなります。

m0 0.5499127216386626 162.0647356264436
m1 -0.19958553965182846 -58.81983894125726
m2 64.77853675151648 65
m3 -0.023646258680639742 -6.968787065359885
m4 0.3853702794342837 113.5724452212997
m5 56.7785367515165 57
m6 -0.00012846968091684285 -0.037861289718459326
m7 -0.0012961351649318775 -0.3819838941257257

m2とm5は近似値なのに、それ以外は結構数値の大きさが違います。これでなぜ射影変換出来るのかと調べたところ、描画処理の部分が下記のようなっており、なんと描画サイズ(dstWidth,dstHeight)で割っておりました。

dst = context.createImageData(dstWidth, dstHeight);

u = x / dstWidth;
v = y / dstHeight;
t = m6 * u + m7 * v + 1;
dx = (m0 * u + m1 * v + m2) / t;
dy = (m3 * u + m4 * v + m5) / t;

そこで描画サイズで各パラメータを割ってみました。(今回は横幅と縦幅は295と同一)
すると、近似値となったのです。

m0 0.5499127216386626 0.549372055133661(162.0647356264436 / 295)
m1 -0.19958553965182846 -0.1993892845466348(-58.81983894125726 / 295)
m2 64.77853675151648 65
m3 -0.023646258680639742 -0.0236230070012199(-6.968787065359885 / 295)
m4 0.3853702794342837 0.3849913397332193(113.5724452212997 / 295)
m5 56.7785367515165 57
m6 -0.00012846968091684285 -0.0001283433549778281(-0.037861289718459326 / 295)
m7 -0.0012961351649318775 -0.0012948606580533(-0.3819838941257257 / 295)

原理が分かったので、実際に以前作成したツールにこのアルゴリズムを組み込んでみました。

var wx = max.x - min.x;
var wy = max.y - min.y;
var param = this.getParam2(dest, wx, wy);
//var param = this.getParam(src, dest);

HomographyApp.prototype.getParam2 = function (dest, wx, wy){
   var sx, sy, dx1, dy1, dx2, dy2, z, g, h;

   var p0 = new Point(dest[0][0], dest[0][1]);
   var p1 = new Point(dest[1][0], dest[1][1]);
   var p2 = new Point(dest[2][0], dest[2][1]);
   var p3 = new Point(dest[3][0], dest[3][1]);
            
   sx = p0.x - p1.x + p2.x - p3.x;
   sy = p0.y - p1.y + p2.y - p3.y;
   dx1 = p1.x - p2.x;
   dy1 = p1.y - p2.y;
   dx2 = p3.x - p2.x;
   dy2 = p3.y - p2.y;

   z = dx1 * dy2 - dy1 * dx2;
   g = (sx * dy2 - sy * dx2) / z;
   h = (sy * dx1 - sx * dy1) / z;

   var param = new Array(8);
   param[0] = (p1.x - p0.x + g * p1.x) / wx;
   param[1] = (p3.x - p0.x + h * p3.x) / wy;
   param[2] = p0.x;
   param[3] = (p1.y - p0.y + g * p1.y) / wx;
   param[4] = (p3.y - p0.y + h * p3.y) / wy;
   param[5] = p0.y;
   param[6] = g / wx;
   param[7] = h / wy;       

   return param;
}

左側は8次元連立一次方程式で求めたパラメータによる描画、右側は上記計算式で求めたパラメータによる描画です。
初期表示の小さめの台形は問題なさそう。
f:id:Yaju3D:20131110141544j:plain
右下を拡大すると画像が小さくなってしまう。
f:id:Yaju3D:20131110141550j:plain
右下を小さくすると角度が合わなくなる。
f:id:Yaju3D:20131110141555j:plain

カメラで撮ったQRコードなど歪んだ画像を正方形(長方形)画像に補正する平面射影変換の用途や台形にする用途には、今回の最適化された計算式を使用しても問題ないでしょうね。