第4回: インタラクション 2 – 顔をつかったインタラクション、FaceTracker
今回も、前回に引き続き映像とのインタラクションについて考えていきます。
前回はOpenCVを使用して、映像解析の基本を体験しました。今回はより高度な映像解析の例として、顔の表情をトラッキングして解析する「フェイス・トラッキング (Face Tracking)」の技術を実際にプログラミングしながら体験します。
フェイス・トラッキングは計算量が多くProcessing単体では処理が追いつかないので、今回は解析部分はopnenFrameworksを使用します。openFrameworksで解析した結果をOSC (Open Sound Control) でProcessingに送出してその数値を元にProcessing側で視覚化して表現に利用していきます。
この手法は、このWSで今後行うセンサー(久世先生担当)の情報をProcessingで視覚化する際にも使用する手法です。しっかりマスターしましょう。
スライド資料
FaceOSCダウンロード
フェイス・トラッキングした結果をOSCで出力するアプリケーションです。
サンプルプログラム
本日のサンプルプログラム一式はこちらから
サンプルソースコード
Face.pde
import oscP5.*; // FaceOSCからのOSCを解析するFaceクラス class Face { //OscP5のインスタンス OscP5 oscP5; // 検出された顔の数 int found; // ポーズ float poseScale; PVector posePosition = new PVector(); PVector poseOrientation = new PVector(); // ジェスチャー float mouthHeight, mouthWidth; float eyeLeft, eyeRight; float eyebrowLeft, eyebrowRight; float jaw; float nostrils; //コンストラクター Face(int port) { //ポートを指定して、OscP5を初期化 oscP5 = new OscP5(this, port); } // FaceOSCから送られてきたOSCを解析 boolean parseOSC(OscMessage m) { if (m.checkAddrPattern("/found")) { // 顔検出数 found = m.get(0).intValue(); return true; } //ポーズ (スケール、位置、向き) else if (m.checkAddrPattern("/pose/scale")) { poseScale = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/pose/position")) { posePosition.x = m.get(0).floatValue(); posePosition.y = m.get(1).floatValue(); return true; } else if (m.checkAddrPattern("/pose/orientation")) { poseOrientation.x = m.get(0).floatValue(); poseOrientation.y = m.get(1).floatValue(); poseOrientation.z = m.get(2).floatValue(); return true; } // ジェスチャー (口、目、眉、顎、鼻腔) else if (m.checkAddrPattern("/gesture/mouth/width")) { mouthWidth = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/mouth/height")) { mouthHeight = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/eye/left")) { eyeLeft = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/eye/right")) { eyeRight = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/eyebrow/left")) { eyebrowLeft = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/eyebrow/right")) { eyebrowRight = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/jaw")) { jaw = m.get(0).floatValue(); return true; } else if (m.checkAddrPattern("/gesture/nostrils")) { nostrils = m.get(0).floatValue(); return true; } return false; } // 現在の顔の状態を文字で出力 String toString() { return "found: " + found + "\n" + "pose" + "\n" + " scale: " + poseScale + "\n" + " position: " + posePosition.toString() + "\n" + " orientation: " + poseOrientation.toString() + "\n" + "gesture" + "\n" + " mouth: " + mouthWidth + " " + mouthHeight + "\n" + " eye: " + eyeLeft + " " + eyeRight + "\n" + " eyebrow: " + eyebrowLeft + " " + eyebrowRight + "\n" + " jaw: " + jaw + "\n" + " nostrils: " + nostrils + "\n"; } //OSCメッセージが送られてきたら解析へ void oscEvent(OscMessage m) { parseOSC(m); } };
a01_dumpOSC.pde
//ライブラリーのインポート import oscP5.*; import netP5.*; //OscP5のインスタンス OscP5 oscP5; void setup() { size(400, 400); frameRate(30); background(0); //新規にOSCの受信を作成(ポート8338) oscP5 = new OscP5(this, 8338); } void draw() { } //受信したOSCメッセージをコンソールに表示 void oscEvent(OscMessage theOscMessage) { print("addrpattern: "+theOscMessage.addrPattern()); println(", value: "+theOscMessage.get(0).floatValue()); }
a02_faceClass.pde
//Faceクラスのインスタンス Face face; void setup() { size(640, 480); frameRate(30); background(0); //Faceクラスを初期化 face = new Face(8338); } void draw() { //もし顔が検出されたら if (face.found > 0) { //解析結果を文字で出力 print(face.toString()); } }
a03_drawGraph.pde
//Faceクラスのインスタンス Face face; void setup() { size(640, 480); frameRate(30); //Faceクラスを初期化 face = new Face(8338); } void draw() { background(0); //もし顔が検出されたら if (face.found > 0) { //解析結果をグラフに translate(0, 20); drawGraph("poseScale", face.poseScale, 0.0, 10.0); translate(0, 12); drawGraph("posePosition.x", face.posePosition.x, 0.0, width); translate(0, 12); drawGraph("posePosition.y", face.posePosition.y, 0.0, height); translate(0, 12); drawGraph("poseOrientation.x", face.poseOrientation.x, -1.0, 1.0); translate(0, 12); drawGraph("poseOrientation.y", face.poseOrientation.y, -1.0, 1.0); translate(0, 12); drawGraph("poseOrientation.z", face.poseOrientation.z, -1.0, 1.0); translate(0, 12); drawGraph("mouthHeight", face.mouthHeight, 0.0, 100.0); translate(0, 12); drawGraph("mouthWidth", face.mouthWidth, 0.0, 100.0); translate(0, 12); drawGraph("eyeLeft", face.eyeLeft, 0.0, 100.0); translate(0, 12); drawGraph("eyeRight", face.eyeRight, 0.0, 100.0); translate(0, 12); drawGraph("eyebrowLeft", face.eyebrowLeft, 0.0, 100.0); translate(0, 12); drawGraph("eyebrowRight", face.eyebrowRight, 0.0, 100.0); translate(0, 12); drawGraph("jaw", face.jaw, 0.0, 100.0); translate(0, 12); drawGraph("nostrils", face.nostrils, 0.0, 100.0); //解析結果を文字で出力 print(face.toString()); } } //グラフを描画する関数 (名前、値、最小値、最大値) void drawGraph(String name, float value, float min, float max) { float textWidth = 150; fill(255); text(name, 10, 12); float graphWidth = map(value, min, max, 0, width-textWidth); fill(31,127,255); rect(textWidth, 0, graphWidth, 10); }
a04_drawFace.pde
//Faceクラスのインスタンス Face face; void setup() { size(640, 480, P3D); frameRate(60); //Faceクラスを初期化 face = new Face(8338); } void draw() { background(255); stroke(0); //もし顔が検出されたら if (face.found > 0) { //解析結果をもとに、顔を描く pushMatrix(); //位置を移動 translate(face.posePosition.x, face.posePosition.y, 0); //スケールを適用 scale(face.poseScale); //向きにあわせて回転 rotateX(-face.poseOrientation.x); rotateY(-face.poseOrientation.y); rotateZ(face.poseOrientation.z); //描画開始 stroke(0); noFill(); //輪郭 ellipse(0, 0, 40, 50); fill(0); noStroke(); //眉 ellipse(-8, face.eyebrowLeft * -1, 10, 2); ellipse(8, face.eyebrowRight * -1, 10, 2); //目 ellipse(-8, face.eyeLeft * -1, 4, 4); ellipse(8, face.eyeRight * -1, 4, 4); //口 ellipse(0, 14, face.mouthWidth, face.mouthHeight); //鼻の穴 ellipse(-2, face.nostrils, 3, 1); ellipse(2, face.nostrils, 3, 1); popMatrix(); //解析結果を文字で出力 print(face.toString()); } }
a05_drawFace2.pde
//Faceクラスのインスタンス Face face; //画像読み込み用 PImage eyeL, eyeR; PImage eyebrowL, eyebrowR; PImage mouth; void setup() { size(640, 480, P3D); frameRate(60); //Faceクラスを初期化 face = new Face(8338); //画像は中心を基準点に描く imageMode(CENTER); //各パーツの画像を読み込み eyeL = loadImage("eyeL.jpg"); eyeR = loadImage("eyeR.jpg"); eyebrowL = loadImage("eyebrowL.jpg"); eyebrowR = loadImage("eyebrowR.jpg"); mouth = loadImage("mouth.jpg"); } void draw() { background(255); stroke(0); //もし顔が検出されたら if (face.found > 0) { //解析結果をもとに、顔を描く pushMatrix(); //位置を移動 translate(face.posePosition.x, face.posePosition.y, 0); //スケールを適用 scale(face.poseScale); //向きにあわせて回転 rotateX(-face.poseOrientation.x); rotateY(-face.poseOrientation.y); rotateZ(face.poseOrientation.z); //描画開始 stroke(0); noFill(); //輪郭 ellipse(0, 0, 40, 50); fill(0); noStroke(); //目(画像) image(eyeL, -8, face.eyeLeft * -1, 10, 10); image(eyeR, 8, face.eyeRight * -1, 10, 10); //眉(画像) image(eyebrowL, -8, face.eyebrowLeft * -1.2, 10, 5); image(eyebrowR, 8, face.eyebrowRight * -1.2, 10, 5); //口(画像) image(mouth, 0, 14, face.mouthWidth, face.mouthHeight * 2); //鼻の穴 ellipse(-2, face.nostrils, 3, 1); ellipse(2, face.nostrils, 3, 1); popMatrix(); //解析結果を文字で出力 print(face.toString()); } }
a06_faceExpression.pde
//Faceクラスのインスタンス Face face; void setup() { size(640, 480, P3D); frameRate(60); //Faceクラスを初期化 face = new Face(8338); } void draw() { background(0); //もし顔が検出されたら if (face.found > 0) { //解析結果をもとに、顔を描く pushMatrix(); //位置を移動 translate(face.posePosition.x, face.posePosition.y, 0); //スケールを適用 scale(face.poseScale); //向きにあわせて回転 rotateX(-face.poseOrientation.x); rotateY(-face.poseOrientation.y); rotateZ(face.poseOrientation.z); //描画開始 background(0); noFill(); //3Dのメッシュを描く draw3DMesh(); //立方体を描く stroke(255); strokeWeight(0.5); box(20); //口の大きさを球体の大きさにして、球体を描く float diameter = map(face.mouthHeight, 0, 10, 1, 80); strokeWeight(0.2); sphereDetail(5); sphere(diameter); popMatrix(); } } void draw3DMesh() { int num = 10; int size = 40; stroke(190); strokeWeight(2.0); for (int k = -num; k < num; k++) { for (int j = -num; j < num; j++) { for (int i = -num; i < num; i++) { point(i * size, j * size, k * size); } } } }