多摩美 – インタラクション・デザイン 2015年度
第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);
}
}
}
}

