今回は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();
}
}
マジックナンバーがところどころ散見されますが、自分の顔に合わせて微調整した結果です。
このプログラムを実行すると以下のように処理されます。
顔のピッチ角(首をかしげるような動き)への対応に関してはもう少し勉強したいと思います。
次回はもっと簡単な笑い男プログラムについて書きたいと思います。


