2014年10月29日水曜日

【openframeworks】画像処理でどんな顔も無理やり笑顔にしてしまう【ofxFaceTracker】

すこし間が開いてしまいましたが、前回の続きです。

 今回はofxFaceTrackerを使用し、フェイストラッキングの結果得られた顔の各パーツの位置情報を利用してどんな顔も無理やり笑顔にするプログラムを作ります。

基本戦略としてはお札の顔を笑わすあれと同じです。
これ
 考える必要があるのはこの「”山折り・谷折り”を画像処理でどのように表現したらよいか。」ということになります。今回は、ROI(関心領域)を利用することで実装することにします。
 ここで、問題なのはOpenCVではROIの範囲指定が各角が直角の四角形でしかできないということです。そこで、以下のようにROIを1pxづつ細切れにして少しづつずらしながら表示画像に貼り付けていくことで実現します。
 細切れのROI指定し、それを移動させていくことで山形の画像処理範囲をしてすることが可能となります。

では以下ソースコードです。
ofApp.h
#pragma once

#include "ofMain.h"
#include "ofxCv.h"
using namespace ofxCv;
using namespace cv;

#include "ofxFaceTrackerThreaded.h"

#define INPUT_MOVIE_WSIZE   640
#define INPUT_MOVIE_HSIZE   480


class testApp : public ofBaseApp {
public:
 void setup();
    void exit();
 void update();
 void draw();
 void keyPressed(int key);
 
 ofVideoGrabber cam;
 ofxFaceTrackerThreaded tracker;
 ofVec2f position;
 float scale;
 ofVec3f orientation;
 ofMatrix4x4 rotationMatrix;
 
 Mat translation, rotation;
    ofImage leftEyeImg,rightEyeImg;
    cv::Mat processImgMat;
 ofMatrix4x4 pose;
 
 ofEasyCam easyCam;
};

ofApp.c

#include "testApp.h"

using namespace ofxCv;
using namespace FACETRACKER;

void testApp::setup() {
    ofSetVerticalSync(true);
    ofSetDrawBitmapMode(OF_BITMAPMODE_MODEL_BILLBOARD);

    cam.initGrabber(INPUT_MOVIE_WSIZE, INPUT_MOVIE_HSIZE);
    processImgMat = toCv(cam);
    tracker.setup();
}

void testApp::exit() {
    tracker.waitForThread();
}

void testApp::update() {
    cam.update();

    if(cam.isFrameNew()) {
        tracker.update(toCv(cam));
        position = tracker.getPosition();
        scale = tracker.getScale();
        orientation = tracker.getOrientation();
        rotationMatrix = tracker.getRotationMatrix();
    }
}

void testApp::draw() {
    ofSetHexColor(0xffffff);
    cam.draw(0, 0);
    //フレームレートを表示
    ofDrawBitmapString(ofToString((int) ofGetFrameRate()), 10, 20);

    if(tracker.getFound()) {
        ofSetLineWidth(1);
        int x,y; //ROIの範囲指定用
        int dHeight = 1;  //ROIの横幅
        int dWidth = 1; //ROIの縦幅
        int leftXOffset = -10;
        int yOffset = 5;
        unsigned char * pixels = cam.getPixels(); //Webカメラの画像ピクセル取得
        //OpenCVの変数型にofImageから変換
        cv::Mat camMat(cv::Size(INPUT_MOVIE_WSIZE,INPUT_MOVIE_HSIZE),CV_8UC3,pixels); 
        //左目
        int leftXStart = (int)tracker.getImagePoint(36).x - 20;
        int leftXEnd = (int)tracker.getImagePoint(40).x;
        int leftYStart = (int)tracker.getImagePoint(36).y;
        int leftYEnd = (int)tracker.getImagePoint(40).y;
        int leftXCenter = (int)tracker.getImagePoint(37).x;
        //目尻から目頭までの距離
        int leftEyeDistance = (int)sqrt(powerof2(leftXEnd-leftXStart)+powerof2(leftYEnd-leftYStart));
        //y軸方向にどれだけ移動させるか
        int leftYMax = (int)(leftEyeDistance/2)*dHeight+yOffset;
        for(int i=0;i<leftEyeDistance+abs(leftXOffset);i+=dWidth){
            //目尻から目の中央まで
            if(i+leftXStart <= leftXCenter){
                x = (int)(leftXCenter-((leftEyeDistance/2)+(i*dWidth)));
                y = (leftYMax-(i*dHeight))+yOffset;
                //ROI領域の設定
                cv::Rect cropROI = cv::Rect(x,y,dWidth,INPUT_MOVIE_HSIZE-y);
                camMat = camMat(cropROI).clone();
                //leftEyeImgに切り出した画像を読み込み
                leftEyeImg.setFromPixels(camMat.data, camMat.cols,camMat.rows, OF_IMAGE_COLOR);
                //表示画像へと貼り付け
                leftEyeImg.draw(x,0);
                camMat.release();
                camMat=toCv(cam);
            }
            //目の中央から目頭まで
            if (i+abs(leftXOffset)/2 > leftEyeDistance/2){
                x = (int)(leftXStart+30+((i*dWidth)));
                y = (leftYMax-(i*dHeight))+yOffset;
                cv::Rect cropROI = cv::Rect(x,y,dWidth,INPUT_MOVIE_HSIZE-y);
                camMat = camMat(cropROI).clone();
                leftEyeImg.setFromPixels(camMat.data, camMat.cols,camMat.rows, OF_IMAGE_COLOR);
                leftEyeImg.draw(x,0);
                camMat.release();
                camMat=toCv(cam);
            }
        }
        //右目
        int rightXOffset = 10;
        int rightXStart = (int)tracker.getImagePoint(45).x - 20;
        int rightXEnd = (int)tracker.getImagePoint(47).x;
        int rightYStart = (int)tracker.getImagePoint(45).y;
        int rightYEnd = (int)tracker.getImagePoint(47).y;
        int rightXCenter = (int)tracker.getImagePoint(44).x;

        int rightEyeDistance = (int)sqrt(powerof2(rightXEnd-rightXStart)+powerof2(rightYEnd-rightYStart));
        int rightYMax = (int)(rightEyeDistance/2)*dHeight+yOffset;
        for(int i=0;i<rightEyeDistance+abs(rightXOffset);i+=dWidth){
            if(i+rightXStart <= rightXCenter){
                x = (int)(rightXCenter-((rightEyeDistance/2)+(i*dWidth)));
                y = (rightYMax-(i*dHeight))+yOffset;
                cv::Rect cropROI = cv::Rect(x,y,dWidth,INPUT_MOVIE_HSIZE-y);
                camMat = camMat(cropROI).clone();
                rightEyeImg.setFromPixels(camMat.data, camMat.cols,camMat.rows, OF_IMAGE_COLOR);
                rightEyeImg.draw(x,0);
                camMat.release();
                camMat=toCv(cam);
            }
            if (i+abs(rightXOffset)/2 > rightEyeDistance/2){
                x = (int)(rightXStart+10+((i*dWidth)));
                y = (rightYMax-(i*dHeight))+yOffset;
                cv::Rect cropROI = cv::Rect(x,y,dWidth,INPUT_MOVIE_HSIZE-y);
                camMat = camMat(cropROI).clone();
                rightEyeImg.setFromPixels(camMat.data, camMat.cols,camMat.rows, OF_IMAGE_COLOR);
                rightEyeImg.draw(x,0);
                camMat.release();
                camMat=toCv(cam);
            }
        }
    }
}

void testApp::keyPressed(int key) {
    if(key == 'r') {
        tracker.reset();
    }
}

 マジックナンバーがところどころ散見されますが、自分の顔に合わせて微調整した結果です。
 このプログラムを実行すると以下のように処理されます。

@monitorgazerが投稿した写真 -
顔のピッチ角(首をかしげるような動き)への対応に関してはもう少し勉強したいと思います。

次回はもっと簡単な笑い男プログラムについて書きたいと思います。

2014年10月24日金曜日

【openframeworks】ofxFaceTrackerで遊んでみた。サンプル解説

 スマートホームがなんちゃら…のシリーズの更新が止まってしまっていますが…。
最近openframeworksをはじめまして、その習作としてofxFaceTrackerを利用したプログラムの作成を行ってみました。実行環境はOS X 10.9 Marvericks , MacBook Pro early 2011 13inchです。

この記事では
  1. ofxFaceTrackerのサンプルプログラムのレビュー
  2. Webカメラから取得した画像の一部に切り取り、貼り付けを行い、笑顔いっぱいデモの実装
  3. 複数の画像パーツとその並進、回転を利用した笑い男デモの実装
の3点の内容をお送りいたします。

1. ofxFaceTrackerのサンプルプログラムのレビュー  

 レビューといってもそれぞれがどんな動きをするプログラムなのか確認した程度になりますが…ofxFaceTrackerのサンプルプログラムは以下のようなものがあります。
  • example-advanced
  • example-align-eyes
  • example-calibration
  • example-blink
  • example-cutout
  • example-expression
  • example-extraction
  • example-non-realtime
  • example-threaded
全てに共通することなのですが、それぞれのbinディレクトリにdataディレクトリを作成し、modelディレクトリをコピーする必要があります。元のmodelディレクトリのパスはofxFaceTracker->libs->FaceTracker->modelみたいな感じです。あとはそれぞれのサンプルごとに必要なディレクトリやファイルが変わってくるので注意が必要です。

 example-non-realtimeはdataディレクトリ内のvideo.movを読み込んでフェイストラッキングを施すというサンプルに成っています。video.movは自分で用意する必要があります。Webカメラ等を使用してリアルタイムに取り込みを行う必要がないので、超高画質な動画ファイルや特殊なカメラで撮影した動画ファイルに対してフェイストラッキングを行いたいようなときには役立ちそうです。

 example-align-eyesは静止画像を読み込んで、顔を認識し、両目を繋ぐ直線と画像の縦軸が直交するように画像を回転させるプログラムになっています。静止画像に対する処理を行いたいときに参考になりそうです。dataフォルダの中にrawフォルダを作成し、その中に画像ファイルを入れることでプログラムで画像を読み込ませます。

 example-expressionは表情認識を行うプログラムです。例えば笑った状態のフェイストラッキングの結果をプログラムに学習させておいて、後からプログラムに「この顔は笑顔である、この顔は笑顔でない」などの評価させることができます。

 example-calibration, example-cutoutはi386 Architecture(32bitアーキテクチャ?)では実行できませんでした。自分の環境で実行できないものはしょうがないのでパスします。

example-advancedはファイストラッキングを行い、顔パーツや輪郭の特徴点を三次元座標上で表示するプログラムです。実行してみた感じでは、顔を探す部分の処理がとても重く、顔を探している間は2fpsほどしかでません。顔を見つけた後は60fpsで動作していました。以下は実行している時に撮影した動画です。
example-blinkは瞬きを認識するプログラムです。瞬きを使って他のプログラムをコントロールすることを目指しているプログラムの用で、ofxOSCのインクルードが必要です。XcodeのBuild SettingsでHeader Search Pathを書き足す必要があり、少し厄介です。
僕が引っかかった点を紹介します。

 画像にあるようにBuild Settingには簡易表示であるBasicと全設定項目を表示するAllがあるのですが、BasicになっているとHeader Search Pathsが表示されず、設定することができないので、気を付けましょう。ここに引っかかるのは僕だけかもしれませんが。

 以下は実行している時に撮影した動画です。example-advancedと同じように顔を見つけるまでは処理に時間がかかりますが、見つけた後は比較的滑らかです。瞬きに合わせて左上のグラフ表示の四角の中が白くフラッシュするのがわかるかと思います。まばたき上表をOSCを介して他のプログラムに伝えることができるようです。
 
 example-extractionは何をするプログラムなのかよくわかりませんでした…。勉強不足ですみません。実際の顔の画像を3Dテクスチャ上に貼り付けているような感じを受けますが…よくわかりませんでした。一応実行中に撮影した動画があります。
 
example-threadedはthreadを使用して、顔を見つけるまでの画像描画の遅延を解消しています。advancedとプログラムの表示結果は同じなのですが、advancedで見受けられた顔を発見するまでに”2fpsしか出ない”というような症状がありませんでした。実行している際の動画が以下になります。

以上でofxFaceTrackerの簡単なレビューを終わります。
2. Webカメラから取得した画像の一部に切り取り、貼り付けを行い、笑顔いっぱいデモの実装
3.複数の画像パーツとその並進、回転を利用した笑い男デモの実装
についてはレビューの記述だけでこの記事がいっぱいになってしまったので次回以降に回します。