图像融合之泊松融合(Poisson Blending)

论文为2003 Patrick P´erez 《Poisson Image Editing》,Microsoft Research UK

译作,泊松图像编辑,因为此算法的功能有很多:

1.Insertion

图像融合之泊松融合(Poisson Blending)_第1张图片

2. Feature exchange

图像融合之泊松融合(Poisson Blending)_第2张图片

3. Inserting objects with holes

图像融合之泊松融合(Poisson Blending)_第3张图片

4. Texture flattening

 

图像融合之泊松融合(Poisson Blending)_第4张图片

5.Local illumination changes           

图像融合之泊松融合(Poisson Blending)_第5张图片

6. Local color changes

图像融合之泊松融合(Poisson Blending)_第6张图片

 

OpenCV3中photo模块已经实现了该算法,包括seamlessClone() ,illuminationChange(),colorChange()。

seamlessClone的API如下:

图像融合之泊松融合(Poisson Blending)_第7张图片

泊松融合的原理,可以参考https://blog.csdn.net/hjimce/article/details/45716603,这里也有较详细的代码。理解到这个博客的程度已经不影响对泊松融合算法的使用了。

图像融合之泊松融合(Poisson Blending)_第8张图片

f*f代表像素值。

博客中提到的直接用拉普拉斯卷积核求ROI和背景的散度进行泊松重建会出现边界过渡不自然的现象。出现这种情况的原因是泊松融合要解决的问题之一就是在边界处要使梯度变化小,

(f表示融合后的结果图,▽f是其梯度,v是原图像的梯度。f*是目标图像,Ω是原图,\partial ΩΩ是边界)

而这个变化最小的解就是泊松等式的解,

因此,要在梯度场的基础上求图像的散度,去减轻边界处的来自两个图像的梯度差,如果你直接在原图上求散度再叠加,减轻的是各自原图上此处的梯度差。

这里有一张直观的图理解“使边界处的梯度变化小”:

图像融合之泊松融合(Poisson Blending)_第9张图片

 

这里整理一下算法的步骤:

1.需要原图source和背景图destination,需要mask抠出原图中的ROI,需要点P指定这个ROI放到背景图什么位置,注意P点是ROI的中心点所在位置;

2.计算ROI和destination的梯度场;

3.计算融合图像的梯度场,就是用ROI的梯度场替换背景图相应处的梯度场;

4.计算融合图的散度场laplace;

5.用这个laplace和原图求解泊松等式,也就是求解Ax=b。这里A是由泊松方程得到的,b是散度,x就是融合图像的像素值。

 

注:求 A的逆是一个可以专门研究的领域——优化。经典方法有牛顿迭代,雅克比迭代,共轭梯度法。还有一些更复杂的方法,参见https://zhuanlan.zhihu.com/p/31680396。

这里用泊松融合实现一个长在手上的眼睛:

原图可在这里取:

链接:https://pan.baidu.com/s/1DJQAy6hGfzj6VYLc4tEJ3A 
提取码:ezsc 

#include 
#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat img_eye = imread("eye.jpg");
	Mat img_hand = imread("hand.jpg");

	Mat mask = 255 * Mat::ones(img_eye.rows, img_eye.cols, img_eye.depth());

	//Rect ROI(400, 950, img_eye.cols, img_eye.rows);
	rectangle(img_hand, ROI, Scalar(0, 0, 255), 3, 4);
	//img_eye.copyTo(img_hand(ROI));//copy and paste

	//namedWindow("img_hand", CV_WINDOW_NORMAL);
	//imshow("img_hand", img_hand);
	//waitKey(0);

	Point p(580, 1050);
	Mat img_blend;
	seamlessClone(img_eye, img_hand, mask, p, img_blend, NORMAL_CLONE);
	namedWindow("img_blend", CV_WINDOW_NORMAL);
	imshow("img_blend", img_blend);
	waitKey(0);

	//imwrite("eye_in_hand.jpg", img_blend);
	return 0;
}

图像融合之泊松融合(Poisson Blending)_第10张图片

由于src无用的边界信息太多,融合后视觉差还是较大,最好的做法是将需要的ROI准确的抠出来。

改进的代码:

#include 
#include 
#include 
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat img_eye = imread("eye.jpg");
	Mat img_hand = imread("hand.jpg");

	Mat mask = Mat::zeros(img_eye.rows, img_eye.cols, img_eye.depth());

	// Define the mask as a closed polygon
	Point poly[1][16];
	poly[0][0] = Point(26, 162);
	poly[0][1] = Point(32, 121);
	poly[0][2] = Point(49, 82);
	poly[0][3] = Point(71, 46);
	poly[0][4] = Point(121, 4);
	poly[0][5] = Point(170, 1);
	poly[0][6] = Point(250, 5);
	poly[0][7] = Point(325, 45);
	poly[0][8] = Point(387, 115);
	poly[0][9] = Point(385, 135);
	poly[0][10] = Point(342, 166);
	poly[0][11] = Point(296, 190);
	poly[0][12] = Point(234, 218);
	poly[0][13] = Point(174, 219);
	poly[0][14] = Point(116, 211);
	poly[0][15] = Point(59, 194);

	const Point* polygons[1] = { poly[0] };
	int num_points[] = { 16 };

	// Create mask by filling the polygon
	fillPoly(mask, polygons, num_points, 1, Scalar(255, 255, 255));

	Point center(580, 1050);
	Mat img_blend;
	seamlessClone(img_eye, img_hand, mask, center, img_blend, NORMAL_CLONE);
	namedWindow("img_blend", CV_WINDOW_NORMAL);
	imshow("img_blend", img_blend);
	waitKey(0);
	//imwrite("eye_in_hand_improve.jpg", img_blend);
}

图像融合之泊松融合(Poisson Blending)_第11张图片

抠出需要融合的图像轮廓之后再做融合效果好了很多,可以说很惊艳。

注:找轮廓点可以用Windows自带的画图工具,鼠标放的地方会显示像素坐标。

你可能感兴趣的:(数字图像处理,OpenCV)