射影変換についてツールも作成して、まーそれなりに理解は出来てきたわけですが、初回の記事で取り上げた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)
左側は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次元連立一次方程式で求めたパラメータによる描画、右側は上記計算式で求めたパラメータによる描画です。
初期表示の小さめの台形は問題なさそう。
右下を拡大すると画像が小さくなってしまう。
右下を小さくすると角度が合わなくなる。
カメラで撮ったQRコードなど歪んだ画像を正方形(長方形)画像に補正する平面射影変換の用途や台形にする用途には、今回の最適化された計算式を使用しても問題ないでしょうね。