前回は、自前の行列クラスを使用して射影変換を行いましたが、Javascriptの行列演算用のライブラリが有志によって公開されています。
- glMatrix.js
- mjs.js
- minMatrix.js
- tmlib.js
- Sylvester.js
- closure
- TDL
- J3DIMath.js(以前はCanvasMatrix.jsという名前だった)
この中で特にメジャーであり、enchantjsにも使用されている行列演算ライブラリのglMatrix.jsを使用して射影変換のパラメータを求めてみます。
glMatrix.js内の4x4の正方行列の作成(mat4.create)と逆行列(mat4.inverse)を使用します。
function getParam(src, dest) { function Z(val){ return val == 0 ? 0.5 : val; } var X1 = Z(src[0][0]); var X2 = Z(src[1][0]); var X3 = Z(src[2][0]); var X4 = Z(src[3][0]); var Y1 = Z(src[0][1]); var Y2 = Z(src[1][1]); var Y3 = Z(src[2][1]); var Y4 = Z(src[3][1]); var x1 = Z(dest[0][0]); var x2 = Z(dest[1][0]); var x3 = Z(dest[2][0]); var x4 = Z(dest[3][0]); var y1 = Z(dest[0][1]); var y2 = Z(dest[1][1]); var y3 = Z(dest[2][1]); var y4 = Z(dest[3][1]); //X point var tx = mat4.create([ X1, Y1, -X1 * x1, -Y1 * x1, // 1st column X2, Y2, -X2 * x2, -Y2 * x2, // 2nd column X3, Y3, -X3 * x3, -Y3 * x3, // 3rd column X4, Y4, -X4 * x4, -Y4 * x4 // 4th column ]) mat4.inverse(tx); var kx1 = tx[0] * x1 + tx[1] * x2 + tx[2] * x3 + tx[3] * x4; var kc1 = tx[0] + tx[1] + tx[2] + tx[3]; var kx2 = tx[4] * x1 + tx[5] * x2 + tx[6] * x3 + tx[7] * x4; var kc2 = tx[4] + tx[5] + tx[6] + tx[7]; var kx3 = tx[8] * x1 + tx[9] * x2 + tx[10] * x3 + tx[11] * x4; var kc3 = tx[8] + tx[9] + tx[10] + tx[11]; var kx4 = tx[12] * x1 + tx[13] * x2 + tx[14] * x3 + tx[15] * x4; var kc4 = tx[12] + tx[13] + tx[14] + tx[15]; //Y point var ty = mat4.create([ X1, Y1, -X1 * y1, -Y1 * y1, // 1st column X2, Y2, -X2 * y2, -Y2 * y2, // 2nd column X3, Y3, -X3 * y3, -Y3 * y3, // 3rd column X4, Y4, -X4 * y4, -Y4 * y4 // 4th column ]); mat4.inverse(ty); var ky1 = ty[0] * y1 + ty[1] * y2 + ty[2] * y3 + ty[3] * y4; var kf1 = ty[0] + ty[1] + ty[2] + ty[3]; var ky2 = ty[4] * y1 + ty[5] * y2 + ty[6] * y3 + ty[7] * y4; var kf2 = ty[4] + ty[5] + ty[6] + ty[7]; var ky3 = ty[8] * y1 + ty[9] * y2 + ty[10] * y3 + ty[11] * y4; var kf3 = ty[8] + ty[9] + ty[10] + ty[11]; var ky4 = ty[12] * y1 + ty[13] * y2 + ty[14] * y3 + ty[15] * y4; var kf4 = ty[12] + ty[13] + ty[14] + ty[15]; var det_1 = kc3 * (-kf4) - (-kf3) * kc4; if(det_1 == 0) { det_1 = 0.0001; } det_1 = 1 / det_1; var param = new Array(8); var C = (-kf4 * det_1) * (kx3 - ky3) + (kf3 * det_1) * (kx4 - ky4); var F = (-kc4 * det_1) * (kx3 - ky3) + (kc3 * det_1) * (kx4 - ky4); param[2] = C; param[5] = F; param[6] = kx3 - C * kc3; param[7] = kx4 - C * kc4; param[0] = kx1 - C * kc1; param[1] = kx2 - C * kc2; param[3] = ky1 - F * kf1; param[4] = ky2 - F * kf2; return param; }
※getParam関数内のクロージャ関数である「Z」は何をしているかというと、逆行列を求める際に対角成分が0の場合、処理の途中でゼロ除算が発生して値が求まらないため、座標に0がある場合は0.5にすることで回避しています。
微妙に違いが出る分は画像補完でなんとかなるかなと思ってます。
射影変換パラメータによる描画処理
function draw(param) { var ctx = this.context; var imgW = this.image.width; var imgH = this.image.height; var input = ctx.getImageData(0, 0, imgW, imgH); var output = ctx.createImageData(imgW, imgH); for (var i = 0; i < imgH; ++i) { for (var j = 0; j < imgW; ++j) { var tmp = j * param[6] + i * param[7] + param[8]; var tmpX = (j * param[0] + i * param[1] + param[2]) / tmp; var tmpY = (j * param[3] + i * param[4] + param[5]) / tmp; var floorX = tmpX | 0; var floorY = tmpY | 0; if (floorX >= 0 && floorX < imgW && floorY >= 0 && floorY < imgH) { var pixelData = this.getPixel(input, floorX, floorY); var R = pixelData.R; var G = pixelData.G; var B = pixelData.B; this.setPixel(output, j, i, R, G, B, 255); } } } ctx.putImageData(output, 512, 0); }
射影変換パラメータを取得し、描画用に転送元座標と転送先座標の逆行列を求めて描画する。
逆行列を求めるのにパラメータが8個なので3x3の行列でもいいのですが、大は小を兼ねるので4x4の行列にして求めています。
//射影変換パラメータを取得 var param = getParam(origin, markers); //描画処理用に射影変換パラメータの逆行列を取得 var mx = mat4.create([ param[0], param[1], param[2], 0, // 1st column param[3], param[4], param[5], 0, // 2nd column param[6], param[7], 1, 0, // 3rd column 0, 0, 0, 1 // 4th column ]) mat4.inverse(mx); var inv_param = new Array(9); inv_param[0] = mx[0]; inv_param[1] = mx[1]; inv_param[2] = mx[2]; inv_param[3] = mx[4]; inv_param[4] = mx[5]; inv_param[5] = mx[6]; inv_param[6] = mx[8]; inv_param[7] = mx[9]; inv_param[8] = mx[10]; //描画処理 draw(inv_param); };