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

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

0 件のコメント:

コメントを投稿