kivantium活動日記

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

ポアソン画像合成 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
f:id:kivantium:20170802225043j:plain:w600
trump.jpg
f:id:kivantium:20170802225050j:plain:w200
mask.jpg
f:id:kivantium:20170802225053j:plain:w200

という画像を用意して

./a.out kim.jpg trump.jpg mask.jpg

を実行した結果(result.jpg)が
f:id:kivantium:20170802225139j:plain:w600
です。

マスクをちゃんと用意するのが大事みたいです。おしまい。