yoppa.org


第4回: インタラクション 2 – 顔をつかったインタラクション、FaceTracker

screenshot_78

今回も、前回に引き続き映像とのインタラクションについて考えていきます。

前回は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);
      }
    }
  }
}

第3回: インタラクション 1 – OpenCV 映像とのインタラクション

前回までは、プロジェクション・マッピングについて実際にプログラムを作成しながら考えてきました。今回からは、プロジェクションした映像と、どのようにしてインタラクションをしていくのか考えていきたいと思います。

インタラクションのための手段は、センサーを使う方法や、KinectやLeap Motionなどのデバイスを使用する方法などいろいろ考えられます。その中で、今回は、最もシンプルな機材構成で可能な方法として、カメラの映像を解析してそこから動きや物体の輪郭を取り出す手法について取り上げます。

コンピュータで、映像から実世界の情報を取得して認識するための研究分野で「コンピュータ・ビジョン (Conputer Vision)」というものが存在します。わかり易く言うなら「ロボットの目」をつくるような研究です。コンピュータ・ビジョンの様々な成果をオープンソースで公開しているOpenCVというライブラリーがあります。今回は、このOpenCVをProcessingで使用できるようにした、OpenCV for Processingライブラリーを使用したプログラミングを体験します。

スライド資料

第3回: インタラクション 1 – OpenCV 映像とのインタラクション

サンプルプログラム

tau_interaction_examples151124.zip


第2回: Processingでプロジェクション・マッピングをプログラミング

modified copy

前回は、「MadMapper」という市販のアプリケーションを使用して、プロジェクションマッピングの基礎を体験しました。市販のアプリケーションはとても便利なのですが、制限なく使用するには購入する必要があり、完全に自由にプログラムすることもできません。

今回は、プロジェクションマッピングの応用編として、Processingを使用して、他のアプリケーションと連携することなく単体でプロジェクションマッピングを実現します。

Processignでプロジェクションマッピングを実現するには、「射影変換 (Homography)」という手法を使って画面を変形する仕組みが必要となります。この部分を自作するのは大変なので、既存のライブラリを使用します。今回は、Processingの「Keystone」というライブラリを使用して実現します。

スライド資料

サンプルプログラム


第1回: プロジェクションマッピングの基礎

田所担当の火曜日の授業では、主に映像のプロジェクションを用いて、ソフトウェアからのアプローチで「ウェアラブル」な作品制作を目指します。身体へのプロジェクション、身体に装着したセンサーの情報から生成される映像表現など、様々なアイデアを紹介します。

初回の授業では、このワークショップで作品を制作するための基盤となる技術「プロジェクション・マッピング」の基礎について解説します。まず、様々なプロジェクション・マッピングによる表現の事例を紹介したあと、プロジェクション・マッピングを用いて表現するための技術的な基礎を解説します。

今回は、市販のアプリケーションである「MadMapper」を使用して、3Dの形状にプロジェクションをする方法について実際に体験しながら習得します。また応用として、Processingで作成したスケッチをMadMapperでプロジェクションする方法についても取り上げます。

授業スライド