kivantium活動日記

プログラムを使っていろいろやります

今日も一日がんばるぞい!(失敗作)

今日画像補完技術による衣服の除去 - ぱろすけ's websiteが話題になっていたので論文を読んだところ、Poisson Image Editingという技術がかなりすごそうだと知りました。検索をかけると100行で書く画像処理最先端 勾配ベースの画像編集:Poisson Image Editing(PDF)のように顔画像に合成して遊ぶのがおすすめということが分かりました。

ネタを考えていると、画像フォルダの中に「今日も一日がんばるぞい!」の顔だけ書き換えた画像を発見しました。
f:id:kivantium:20141203210240j:plain:h120f:id:kivantium:20141203210253j:plain:h120
というわけで今回は頑張るぞい画像の自動生成を目指します。

使う画像

合成先の画像には例の画像に誰かが色をつけた
f:id:kivantium:20141203210524j:plain:h150
を使います。
絶望した顔を合成した方が面白そうなので
f:id:kivantium:20141203210845j:plain:h150
この顔を合成することにしました。

ソースコード

Poisson Image Editingの実装はPoisson Image Editing をOpenCVのMatを使って実装した : とある大学院生のプログラミングソースコードを使いました。比較的低速なAtメソッドを使っているなどいろいろ改善の余地がありそうですが、時間がなかったのでマスク処理を省略しただけであとはそのままです。
双方の画像から顔画像を切り出してサイズを合わせた後に合成しています。

#include <opencv2/opencv.hpp>
#include <string>
#include <sstream>
#include <cstdio>

#define LOOP_MAX 1000
#define EPS 2.2204e-016
#define NUM_NEIGHBOR 4

using namespace std;
using namespace cv;

void detectAndDisplay(Mat image, int* pos);

CascadeClassifier face_cascade;
int quasi_poisson_solver(Mat &img_src, Mat &img_dst, int channel, int offset[]){
    int i,j,loop,neighbor,count_neighbors,ok;
    float error,sum_f,sum_vpq,fp;
    int naddr[NUM_NEIGHBOR][2] = {{-1,0},{0,-1},{0,1},{1,0}};
    cv::Mat img_new = (cv::Mat_<double>(img_dst.rows,img_dst.cols));
    for(i=0;i<img_dst.rows;i++){
        for(j=0;j<img_dst.cols;j++){
            img_new.at<double>(i,j)=(double)img_dst.at<Vec3b>(i,j)[channel];
        }
    }
    for(loop=0;loop<LOOP_MAX;loop++){
        ok = 1;
        for(i=0;i<img_src.rows;i++){
            for(j=0;j<img_src.cols;j++){
                sum_f=0.0;
                sum_vpq=0.0;
                count_neighbors=0;
                for(neighbor=0;neighbor<NUM_NEIGHBOR;neighbor++){
                    if(i+offset[0]+naddr[neighbor][0]>=0
                            &&j+offset[1]+naddr[neighbor][1]>=0
                            &&i+offset[0]+naddr[neighbor][0]<img_dst.rows
                            &&j+offset[1]+naddr[neighbor][1]<img_dst.cols){
                        sum_f+=img_new.at<double>(i+offset[0]+naddr[neighbor][0],j+offset[1]+naddr[neighbor][1]);
                        sum_vpq+=(float)img_src.at<Vec3b>(i,j)[channel]
                            -(float)img_src.at<Vec3b>(i+naddr[neighbor][0],j+naddr[neighbor][1])[channel];
                        count_neighbors++;
                    }
                }  
                fp = (sum_f + sum_vpq)/(float)count_neighbors;
                error = fabs(fp - img_new.at<double>(i+offset[0],j+offset[1]));
                if(ok&&error>EPS*(1+fabs(fp))){
                    ok=0;
                }
                img_new.at<double>(i+offset[0],j+offset[1])=fp;
            }
        }
        if(ok){
            break;
        }
    }
    for(i=0;i<img_dst.rows;i++){
        for(j=0;j<img_dst.cols;j++){
            if(img_new.at<double>(i,j)>255){
                img_new.at<double>(i,j)=255.0;
            }
            else if(img_new.at<double>(i,j)<0){
                img_new.at<double>(i,j)=0.0;
            }
            img_dst.at<Vec3b>(i,j)[channel] = (uchar)img_new.at<double>(i,j);
        }
    }
    return 1;
}

int main(int argc, char* argv[]){
    //カスケードのロード
    face_cascade.load("lbpcascade_animeface.xml");
    Mat org = imread("zoi.jpg");
    Mat syn = imread(argv[1]);
    int zoi[4], face[4];
    int offset[2] = {0, 0};
    if(syn.empty()){
        cout << "file open error" << endl;
        return -1;
    }

    detectAndDisplay(org, zoi);
    detectAndDisplay(syn, face);
    Mat zoi_face = org(Rect(zoi[0], zoi[1], zoi[2], zoi[3]));
    Mat syn_face = syn(Rect(face[0], face[1], face[2], face[3]));
    imshow("zoi_face", zoi_face);
    imshow("syn_face", syn_face);
    Mat dst(zoi[2], zoi[3], zoi_face.type());
    resize(syn_face, dst, zoi_face.size(), cv::INTER_CUBIC);
    imshow("dst", dst);
    for(int i=0;i<3;i++){
        quasi_poisson_solver(dst,zoi_face,i,offset);
    }
    imshow("Result", org);
    imwrite("gambaru.png", org);
    waitKey(0);

    return 0;
}

void detectAndDisplay(Mat image, int* pos)
{
    vector<Rect> faces;
    Mat frame_gray;
    stringstream name;

    //画像のグレースケール化
    cvtColor(image, frame_gray, COLOR_BGR2GRAY );
    //ヒストグラムの平坦化
    equalizeHist(frame_gray, frame_gray);
    //顔の認識 小さい顔は除外
    face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3, 0, Size(80,80));
    if(faces.size()>0){
        pos[0] = faces[0].x;
        pos[1] = faces[0].y;
        pos[2] = faces[0].width;
        pos[3] = faces[0].height;
    }
}

結果
f:id:kivantium:20141203212015p:plain:h120

画像処理とは何だったのか……と言いたくなるような悲惨な合成結果でした。

Poisson Image Editingは貼り付ける画像の明るさなどを周囲に合わせて自然にする技術なので何も考えずに貼っても輪郭線などが全く合わず求める結果にはならないようです。
改善するにはマスクを作って貼り付ける部分を指定し貼り付け先を手動で調整すればいいのですが、それなら手動でコラ画像を作るのと変わらないですね……。

というわけで、頑張るぞい画像ジェネレータ計画は放棄されました。合掌