透視投影変換は諦めます
というのも当てにしていたコードが,XYZ軸それぞれの角度を指定して変換 という方法だったため角度を指定しなければならず,ユーザにそれを強いるのは面倒くさいし,かと言ってそれぞれの角度を自動的に求める関数は用意されていないためです.一応本家 OpenCV にはあるし,行列についてちゃんと理解すれば実装できないこともないと思うけど,今月はもう時間がないので諦めます.
今日は背景周りの処理と透過に対応してないフィルタの置き換え.OpenCVjsに元々搭載されているフィルタは,何もない部分を黒にするためせっかくの透過が無駄になってしまいました.なのでそれを避けるために置き換えようと思ったけれど,複数の関数を呼んでいてそれをすべて置き換えるのは面倒くさいので,自分で新しく作ることに.
元画像
虹色縁取り
//輪郭を取って色付けする function cvSharpEdge(imgId, srcImage, mode){ //画像サイズを指定して画像領域を確保 var dstImage = cvCreateImage(srcImage.width, srcImage.height); var max = dstImage.width*dstImage.width + dstImage.height*dstImage.height; for(var i = 0 ; i < srcImage.height ; i++){ for(var j = 0 ; j < srcImage.width ; j++){ var alpha = getAlphas(srcImage,i,j); //輪郭に接していたらピクセルを追加 if(alpha[4]===0&&((alpha[0]===0&&alpha[8]!==0)||(alpha[1]===0&&alpha[7]!==0)||(alpha[2]===0&&alpha[6]!==0)||(alpha[3]===0&&alpha[5]!==0) ||(alpha[5]===0&&alpha[3]!==0)||(alpha[6]===0&&alpha[2]!==0)||(alpha[7]===0&&alpha[1]!==0)||(alpha[8]===0&&alpha[0]!==0))){ var v = j*j + i*i; switch(mode){ case 'rainbow' : //HSV指定で虹色を表現 dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 0] = 255*v/max; //H dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 1] = 255; //S dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 2] = 255; //V dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 3] = 255; //A break; //中略 } } else{ dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 0] = 0; //R dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 1] = 0; //G dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 2] = 0; //B dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 3] = 0; //A } } } //RGBに戻す if(mode==='rainbow')cvCvtColor(dstImage, dstImage, CV_CODE.HSV2RGB); //元画像に重ねあわせて表示 var newIplImage = cvCloneImage(srcImage); cvMixIplImages(dstImage, newIplImage, dstImage, 0.5); cvShowImage(imgId, dstImage); } //対象の周囲を含めた9マスのアルファ値を取得. function getAlphas(srcImage,i,j){ //外周の場合は内側を参照 var l = (j===0)? j : j-1; var r = (j===srcImage.width)? j : j+1; var t = (i===0)? i : i-1; var b = (i===srcImage.height)? i : i+1; var alpha = [srcImage.RGBA[(l+t*srcImage.width)*CHANNELS+3],srcImage.RGBA[(j+t*srcImage.width)*CHANNELS+3],srcImage.RGBA[(r+t*srcImage.width)*CHANNELS+3], srcImage.RGBA[(l+i*srcImage.width)*CHANNELS+3],srcImage.RGBA[(j+i*srcImage.width)*CHANNELS+3],srcImage.RGBA[(r+i*srcImage.width)*CHANNELS+3], srcImage.RGBA[(l+b*srcImage.width)*CHANNELS+3],srcImage.RGBA[(j+b*srcImage.width)*CHANNELS+3],srcImage.RGBA[(r+b*srcImage.width)*CHANNELS+3]]; return alpha; } //RGBA値を重ねあわせ. function cvMixIplImages(bg, fg, dst, rate){ for(var i = 0 ; i < dst.height ; i++){ for(var j = 0 ; j < dst.width ; j++){ if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]===0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]===0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = 0; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = 0; //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = 0; //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 0; //A } else if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]!==0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]===0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = bg.RGBA[(j+i*dst.width)*CHANNELS+0]; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = bg.RGBA[(j+i*dst.width)*CHANNELS+1]; //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = bg.RGBA[(j+i*dst.width)*CHANNELS+2]; //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } else if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]===0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]!==0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = fg.RGBA[(j+i*dst.width)*CHANNELS+0]; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = fg.RGBA[(j+i*dst.width)*CHANNELS+1]; //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = fg.RGBA[(j+i*dst.width)*CHANNELS+2]; //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } else{ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = fg.RGBA[(j+i*dst.width)*CHANNELS+0]*rate + bg.RGBA[(j+i*dst.width)*CHANNELS+0]*(1-rate); //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = fg.RGBA[(j+i*dst.width)*CHANNELS+1]*rate + bg.RGBA[(j+i*dst.width)*CHANNELS+1]*(1-rate); //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = fg.RGBA[(j+i*dst.width)*CHANNELS+2]*rate + bg.RGBA[(j+i*dst.width)*CHANNELS+2]*(1-rate); //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } } } }
アルゴリズム的に左上から右下への虹色になっちゃってるのでここは HSV 色相環に直したいところ.注意点として画像に透明な画素があることを前提にしているため,塗りつぶされている画像に対しては何も起こらない.
照明操作
//内側を明るく,外側を暗くする function cvLightControll(imgId, srcImage){ var dstImage = cvCreateImage(srcImage.width, srcImage.height); var max = dstImage.width*dstImage.width + dstImage.height*dstImage.height/4; for(var i = 0 ; i < srcImage.height ; i++){ for(var j = 0 ; j < srcImage.width ; j++){ var v = (j-dstImage.width/2)*(j-dstImage.width/2)*4 + (i-dstImage.height/2)*(i-dstImage.height/2)*4; //内側は白く,外側は黒い画像を用意 if(srcImage.RGBA[(j + i * dstImage.width) * CHANNELS + 3]!==0){ dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 0] = 255-255*v/max; //R dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 1] = 255-255*v/max; //G dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 2] = 255-255*v/max; //B dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 3] = 255; //A } //透明箇所は透明に else{ dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 0] = 0; //R dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 1] = 0; //G dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 2] = 0; //B dstImage.RGBA[(j + i * dstImage.width) * CHANNELS + 3] = 0; //A } } } //HSV形式にして重ね合わせる var newIplImage = cvCloneImage(srcImage); cvCvtColor(dstImage, dstImage, CV_CODE.RGB2HSV); cvCvtColor(newIplImage, newIplImage, CV_CODE.RGB2HSV); cvMixIplImagesHSV(newIplImage, dstImage, dstImage, 0.2); //RGBに戻して表示 cvCvtColor(dstImage, dstImage, CV_CODE.HSV2RGB); cvShowImage(imgId, dstImage); } //HSV形式で重ねあわせる関数 function cvMixIplImagesHSV(bg, fg, dst, rate){ for(var i = 0 ; i < dst.height ; i++){ for(var j = 0 ; j < dst.width ; j++){ if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]===0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]===0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = 0; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = 0; //R dst.RGBA[(j+i*dst.width)*CHANNELS+2] = 0; //R dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 0; //R } else if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]!==0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]===0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = bg.RGBA[(j+i*dst.width)*CHANNELS+0]; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = bg.RGBA[(j+i*dst.width)*CHANNELS+1]; //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = bg.RGBA[(j+i*dst.width)*CHANNELS+2]; //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } else if(bg.RGBA[(j+i*dst.width)*CHANNELS+3]===0&&fg.RGBA[(j+i*dst.width)*CHANNELS+3]!==0){ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = fg.RGBA[(j+i*dst.width)*CHANNELS+0]; //R dst.RGBA[(j+i*dst.width)*CHANNELS+1] = fg.RGBA[(j+i*dst.width)*CHANNELS+1]; //G dst.RGBA[(j+i*dst.width)*CHANNELS+2] = fg.RGBA[(j+i*dst.width)*CHANNELS+2]; //B dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } else{ dst.RGBA[(j+i*dst.width)*CHANNELS+0] = bg.RGBA[(j+i*dst.width)*CHANNELS+0]; //H dst.RGBA[(j+i*dst.width)*CHANNELS+1] = fg.RGBA[(j+i*dst.width)*CHANNELS+1]*rate + bg.RGBA[(j+i*dst.width)*CHANNELS+1]*(1-rate); //S dst.RGBA[(j+i*dst.width)*CHANNELS+2] = fg.RGBA[(j+i*dst.width)*CHANNELS+2]*rate + bg.RGBA[(j+i*dst.width)*CHANNELS+2]*(1-rate); //V dst.RGBA[(j+i*dst.width)*CHANNELS+3] = 255; //A } } } }
HSVを利用したのはRGBだとただ灰色を重ねただけのような結果になってしまいそうだったため.cvMixIplImagesHSV() では元画像の Hue はそのままに Saturationと Value を重ねあわせる画像の値を使用することで色味を残している.
その他
あとはフィルタの重ねがけをできるようにしました.相変わらず前回作ったフィルタは汚いな・・・
参考
射影変換(ホモグラフィ)について理解してみる その13 - デジタル・デザイン・ラボラトリーな日々
床井研究室 - 第5回 座標変換