ポアソン画像合成 revisited
2014年の記事で何回かポアソン画像合成をやりましたが、どれもうまくいきませんでした。
kivantium.hateblo.jp
kivantium.hateblo.jp
授業の課題でポアソン画像合成を書いたので供養としてソースコードを上げておきます。
前回との差分は勾配ベースの画像編集:Poisson Image Editingで「厳密な実装」とされている方の実装に書き換えたことと、画素アクセスをatから高速だと言われているポインタに差し換えたことです。(自分の環境で速度を測った結果、atとポインタで分かるほどの差はありませんでしたが)
入力画像から顔を認識して、その上に別の顔を合成するサンプルです。
#include <opencv2/opencv.hpp> #include <string> #include <sstream> #include <cstdio> #include <chrono> using namespace cv; const int LOOP_MAX = 1000; const float EPS = 2.2204e-016; const int NUM_NEIGHBOR = 4; Mat poisson_solver(const Mat &img_src, const Mat &img_dst, const Mat &img_mask, int offset[]){ Mat result = Mat::zeros(img_dst.size(), img_dst.type()); for(int channel=0; channel<3; channel++) { int i, j, loop, neighbor, count_neighbors, flag_edge, ok; float error, sum_f, sum_fstar, sum_vpq, fp, fq, gp, gq; int naddr[NUM_NEIGHBOR][2] = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}}; 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.ptr<double>(i)[j] = (double)img_dst.ptr<Vec3b>(i)[j][channel]; } } for(loop=0; loop<LOOP_MAX; loop++){ ok = 1; for(i=0; i<img_mask.rows; i++){ for(j=0; j<img_mask.cols; j++){ if((int)img_mask.ptr<Vec3b>(i)[j][0] > 0){ sum_f = 0.0; sum_fstar = 0.0; sum_vpq = 0.0; count_neighbors = 0; flag_edge = 0; for(neighbor=0; neighbor<NUM_NEIGHBOR; neighbor++){ if((int)img_mask.ptr<Vec3b>(i+naddr[neighbor][0])[j+naddr[neighbor][1]][0] == 0){ flag_edge = 1; break; } } if(flag_edge == 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.ptr<double>(i+offset[0]+naddr[neighbor][0])[j+offset[1]+naddr[neighbor][1]]; sum_vpq += (float) img_src.ptr<Vec3b>(i)[j][channel] - (float) img_src.ptr<Vec3b>(i+naddr[neighbor][0])[j+naddr[neighbor][1]][channel]; count_neighbors++; } } } else { 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){ fp = (float) img_dst.ptr<Vec3b>(i+offset[0])[j+offset[1]][channel]; fq = (float) img_dst.ptr<Vec3b>(i+offset[0]+naddr[neighbor][0])[j+offset[1]+naddr[neighbor][1]][channel]; gp = (float) img_src.ptr<Vec3b>(i)[j][channel]; gq = (float) img_src.ptr<Vec3b>(i+naddr[neighbor][1])[j+naddr[neighbor][1]][channel]; sum_fstar += fq; if(fabs(fp - fq) > fabs(gp - gq)) { sum_vpq += fp - fq; } else { sum_vpq += gp - gq; } count_neighbors++; } } } fp = (sum_f + sum_fstar + sum_vpq) / (float)count_neighbors; error = fabs(fp - img_new.ptr<double>(i+offset[0])[j+offset[1]]); if(ok && error > EPS * (1+fabs(fp))) { ok = 0; } img_new.ptr<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.ptr<double>(i)[j] > 255){ img_new.ptr<double>(i)[j] = 255.0; } else if(img_new.ptr<double>(i)[j] < 0){ img_new.ptr<double>(i)[j] = 0.0; } result.ptr<Vec3b>(i)[j][channel] = (uchar)img_new.ptr<double>(i)[j]; } } } return result; } int main(int argc, char* argv[]){ if(argc != 4) { std::cerr << "Usage: " << argv[0] << " <original> <face> <mask>" << std::endl; std::exit(EXIT_FAILURE); } CascadeClassifier face_cascade; face_cascade.load("/usr/share/opencv/haarcascades/haarcascade_frontalface_alt.xml"); Mat org = imread(argv[1]); if(org.empty()){ std::cerr << argv[1] << ": falied to open" << std::endl; std::exit(EXIT_FAILURE); } Mat syn = imread(argv[2]); if(syn.empty()){ std::cerr << argv[2] << ": failed to open" << std::endl; std::exit(EXIT_FAILURE); } Mat mask = imread(argv[3]); if(mask.empty()){ std::cerr << argv[3] << ": failed to open" << std::endl; std::exit(EXIT_FAILURE); } std::vector<Rect> faces_org, faces_syn; Mat frame_gray; cvtColor(org, frame_gray, COLOR_BGR2GRAY ); equalizeHist(frame_gray, frame_gray); face_cascade.detectMultiScale(frame_gray, faces_org); cvtColor(syn, frame_gray, COLOR_BGR2GRAY ); equalizeHist(frame_gray, frame_gray); face_cascade.detectMultiScale(frame_gray, faces_syn); Mat face(syn, Rect(faces_syn[0].x, faces_syn[0].y, faces_syn[0].width, faces_syn[0].height)); Mat face_resized; resize(face, face_resized, Size(faces_org[0].width, faces_org[0].height)); Mat mask_face(mask, Rect(faces_syn[0].x, faces_syn[0].y, faces_syn[0].width, faces_syn[0].height)); Mat mask_resized; resize(mask_face, mask_resized, Size(faces_org[0].width*1.1, faces_org[0].height)); int offset[] = {faces_org[0].y, faces_org[0].x-int(faces_org[0].width*0.03)}; // 手動で決めたパラメータ auto start = std::chrono::system_clock::now(); Mat result = poisson_solver(face_resized, org, mask_resized, offset); auto end = std::chrono::system_clock::now(); auto diff = end - start; std::cout << "elapsed time = " << std::chrono::duration_cast<std::chrono::milliseconds>(diff).count() << " msec." << std::endl; imshow("Result", result); imwrite("result.jpg", result); waitKey(0); }
コンパイルはlibopencv-dev
を入れた状態で
g++ poisson.cpp -std=c++11 -O2 `pkg-config opencv --cflags --libs`
のようにやります。
kim.jpg
trump.jpg
mask.jpg
という画像を用意して
./a.out kim.jpg trump.jpg mask.jpg
を実行した結果(result.jpg)が
です。
マスクをちゃんと用意するのが大事みたいです。おしまい。