中西研の関口です。4年生になりました。気づいたら22歳。最近、疲れが抜けません。
さて、L.A.S.E.R. Tagをいじる機会があり、現在、P5用に移植してる真っ最中です。その中で重要な機能であるキャリブレーションの実装について今回はメモ程度に残しておきます。
L.A.S.E.R. Tagでは、読み込んだカメラ画像に対しキャリブレーションを施して正確にポインタと投影面が重なるように設定が可能となっています。これによって、斜めからカメラで落書き面を捉えても、正確に落書きすることが可能となっているんですね。
今回は、これとおんなじようなことを、Processingでやろうというわけです。
多分、L.A.S.E.R. TagではOpenCVを使ってキャリブレーションしてる気がするのですが、ProcessingにはOpenCVのライブラリがあるのですが、キャリブレーションする機能は使えないようだったので、結局頑張って計算することにしました。
そもそも、こういった平面の変換は射影変換と呼ばれていて、任意の4点が定まれば、一意にパラメータが定めることができます。(詳しくは、http://www.teu.ac.jp/clab/kondo/research/cadcgtext/Chap5/Chap503.html)
ということで、キャプチャした画像から任意の4点を選択し、そこからパラメータを計算して表示すればなんとなくOKっぽいです。パラメータの計算は連立方程式をとけば出てきます。
ただ、あんまりProcessingにないライブラリやらを使いたくなかったので、今回はMatrixを使わずに計算することにしました。この際、AS3で同じことをしている人を発見したので、大いにパクリました。というかむしろ、AS3をP5に移植したといって過言ではないです。(参考:Homography | HIDIHO!)
ということで、ソースコードです。
[java] import JMyron.*; JMyron jmyron; PImage capture; PImage convert; boolean dragged = false; int selector = 0; final static int CAP_WIDTH = 320; final static int CAP_HEIGHT = 240; Point[] points = { new Point(),new Point(),new Point(),new Point()}; void setup(){ size(CAP_WIDTH*3,CAP_HEIGHT); jmyron = new JMyron(); jmyron.start(CAP_WIDTH,CAP_HEIGHT); capture = new PImage(CAP_WIDTH,CAP_HEIGHT); convert = new PImage(CAP_WIDTH,CAP_HEIGHT); points[0].setPoint(0,0); points[1].setPoint(320,0); points[2].setPoint(320,240); points[3].setPoint(0,240); } void mousePressed(){ if(dragged == false){ for(int i=0; i < 4; i++){ if(points[i].x -10 < mouseX && points[i].x + 10>mouseX){ if(points[i].y -10 < mouseY && points[i].y + 10>mouseY){ dragged = true; selector = i; } } } } } void mouseDragged(){ if(dragged){ if(mouseX > 0 && mouseX < CAP_WIDTH){ points[selector].setPoint(mouseX,points[selector].y); } if(mouseY > 0 && mouseY < CAP_HEIGHT){ points[selector].setPoint(points[selector].x,mouseY); } } } void mouseReleased(){ dragged = false; } void draw(){ jmyron.update(); int[] jImage = jmyron.image(); capture.loadPixels(); for(int i=0;i < capture.width*capture.height; i++){ capture.pixels[i] = jImage[i]; } capture.updatePixels(); image(capture,0,0); ellipseMode(CENTER); noFill(); strokeWeight(1); for(int i=0; i < 4; i++){ stroke(255*(i%2),64*(i%4),0); ellipse(points[i].x,points[i].y,10,10); line(points[i%4].x,points[i%4].y,points[(i+1)%4].x,points[(i+1)%4].y); } convert = setTransform(capture,CAP_WIDTH,CAP_HEIGHT,points[0],points[1],points[2],points[3]); image(convert,CAP_WIDTH,0); } PImage setTransform(PImage pimg, int destWidth, int destHeight, Point p0, Point p1, Point p2, Point p3){ if ( p0 == null || p1 == null || p2 == null || p3 == null ) return null; float[] system = new float[8]; system = getSystem(p0,p1,p2,p3); PImage target = new PImage(destWidth,destHeight); int i,j,x,y,u,v; Point p; for(i = 0; iprocessingのcaptureを使えばいいんですが、ちょっと都合上JMyronを使わせて頂きました。と言っても、Captureでも特に問題はありません。その場合は、captureをPImageからCaptureに変えればすんなり動く気がします。
実行結果はこんな感じです。
このように、ポスターが長方形ぴったりに収まるように表示されるようになりました。
これで、カメラの位置に関係なく色々することができますね!
それでわ。