2003年にコンピュータグラフィクス界でのトップカンファレンスの一つであるSIGGRAPHで発表されたPoisson Image Editing を使えば、違和感なく複数の画像を合成することが可能となります。
下の画像を見てください。

poisson01
Poisson Image Editingの論文から引用した画像です。

destination の画像に sourcesの太陽がseamless cloning では違和感なく合成されていることが分かります。
下の画像では、子供のそばにあわや熊!という状況が作り出されています。
そもそも、元画像には子供も熊もいません。
このような合成は、ポアソン方程式により求められています。

今回はPoissonを用いた画像合成を行います。
例により、「100行で書く画像処理最先端 勾配ベースの画像編集:Poisson Image Editing」を参考にしています。 
 

実装し、試しに画像を合成してみました。
プログラムは最後に載せています。
全体で94行と100行以内に収まりました。
これだけ短いプログラムで実現できるのは、すごいですね。

なお、画像はフリー素材を利用しています。
改変自由なラインセンスの以下の画像を使用しました。


David Photo Studio
 copy right. David Photo Studio

 Wolfgang Lonien_min
copy right. Wolfgang Lonien.jpg

この二枚の画像を合成してみます。
まず、簡単のために、女の子と猫ちゃんの顔部分のサイズを揃えて切り出します。
これを入力画像にします。

 David Photo Studio_min

Wolfgang Lonien_min

次に、合成するためのマスクを用意します。
今回は画像サイズを揃えているので1枚で問題ありません。


cat_mask


これらを入力として、

猫ちゃんの顔→女の子の顔

および、

女の子の顔→猫ちゃんの顔

と合成します。

結果は以下のとおりになりました。

cat_people

people_cat


1枚めは、劇団四季のライオンキングのようです。
2枚めは…人面猫・・・?

人間の顔と猫の顔が合成されていることに違和感はありますが、
画像としては自然に合成されていることが分かります。
今回はこの程度ですが、上手く利用できれば新たなアプリケーションへの発展も考えられそうです。



ソースコード

 // Poisson.cpp 
#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include "OpenCVheader.h"

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

using namespace cv;

int quasi_poisson_solver(Mat &img_src, Mat &img_dst, Mat &img_mask, 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; printf("%d\n",loop); for(i=0;i<img_mask.rows;i++){ for(j=0;j<img_mask.cols;j++){ if((int)img_mask.at<Vec3b>(i,j)[0]>0){
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 poisson(){ cv::Mat source=imread("cat_min.png"); cv::Mat destination=imread("girl_min.png"); cv::Mat mask=imread("girl_mask.png"); int offset[2] = {0,0}; imshow("destination",destination); cv::waitKey(0); int i; for(i=0;i<3;i++){ quasi_poisson_solver(source,destination,mask,i,offset); } imwrite("destination",destination); imshow("destination",destination); cv::waitKey(0); return 1; } int main() { poisson(); return 0; }