前言
泊松融合(Poisson Blending)是图像处理领域著名的图像融合算法,自从2003年发表以来,有很多基于此算法的应用和改进研究出现。泊松融合无需像Alpha blending一样的精确抠图就可以得到很自然的结果。
关于泊松融合原理部分的解析见之前的博客《泊松融合原理浅析》。
本文将针对OpenCV中泊松融合的实现代码(以normalClone为例)进行解读。
代码解读
函数定义
void cv::seamlessClone ( InputArray src,
InputArray dst,
InputArray mask,
Point p,
OutputArray blend,
int flags
)
src是源图像,dst是目标图像,mask是融合区域(闭集),p是融合位置中心点坐标(对dst而言),blend是融合结果,flags是融合类别(3类,分别是cv::NORMAL_CLONE,cv::MIXED_CLONE, cv::MONOCHROME_TRANSFER)。
头文件
#ifndef CV_SEAMLESS_CLONING_HPP___
#define CV_SEAMLESS_CLONING_HPP___
#include "precomp.hpp"
#include "opencv2/photo.hpp"
#include <vector>
namespace cv
{
class Cloning//Cloning对象
{
public:
void normalClone(const cv::Mat& destination, const cv::Mat &mask, const cv::Mat &wmask, cv::Mat &cloned, int flag);//normalClone方法,通过flag选择NORMAL_CLONE、MIXED_CLONE或MONOCHROME_TRANSFER
void illuminationChange(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, cv::Mat &cloned, float alpha, float beta);//光照调整
void localColorChange(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, cv::Mat &cloned, float red_mul, float green_mul, float blue_mul);//局部颜色变化
void textureFlatten(cv::Mat &I, cv::Mat &mask, cv::Mat &wmask, float low_threshold, float high_threhold, int kernel_size, cv::Mat &cloned);//纹理抹平
protected:
void initVariables(const cv::Mat &destination, const cv::Mat &binaryMask);//初始化变量
void computeDerivatives(const cv::Mat &destination, const cv::Mat &patch, const cv::Mat &binaryMask);//计算衍生变量函数入口,包括初始化变量和梯度计算
void scalarProduct(cv::Mat mat, float r, float g, float b);//标量点乘
void poisson(const cv::Mat &destination);//泊松方程求解入口,调用poissonSolver求解
void evaluate(const cv::Mat &I, const cv::Mat &wmask, const cv::Mat &cloned);//泊松方程求解前根据mask对梯度进行处理
void dst(const Mat& src, Mat& dest, bool invert = false);//离散傅里叶变换(离散正弦变换)求解入口
void solve(const Mat &img, Mat& mod_diff, Mat &result);//实际求解入口,被poissonSolver调用
void poissonSolver(const cv::Mat &img, cv::Mat &gxx , cv::Mat &gyy, cv::Mat &result);//方程求解准备工作,被poisson调用
void arrayProduct(const cv::Mat& lhs, const cv::Mat& rhs, cv::Mat& result) const;//矩阵乘积
void computeGradientX(const cv::Mat &img, cv::Mat &gx);//X方向梯度计算
void computeGradientY(const cv::Mat &img, cv::Mat &gy);//Y方向梯度计算
void computeLaplacianX(const cv::Mat &img, cv::Mat &gxx);//X方向拉普拉斯计算
void computeLaplacianY(const cv::Mat &img, cv::Mat &gyy);//Y方向拉普拉斯计算
private:
std::vector <cv::Mat> rgbx_channel, rgby_channel, output;//数组,用于存放RGB三通道的X方向拉普拉斯、Y方向拉普拉斯、融合结果
cv::Mat destinationGradientX, destinationGradientY;//目标图像的X和Y方向的梯度
cv::Mat patchGradientX, patchGradientY;//源图像的X和Y方向的梯度
cv::Mat binaryMaskFloat, binaryMaskFloatInverted;//mask和反mask,float格式
std::vector<float> filter_X, filter_Y;//用于存放离散变换所需的系数
};
}
#endif
主入口函数
#include "precomp.hpp"
#include "opencv2/photo.hpp"
#include "seamless_cloning.hpp"
using namespace std;
using namespace cv;
//mask检查,避免mask通道错误和空Mat
static Mat checkMask(InputArray _mask, Size size)
{
Mat mask = _mask.getMat();
Mat gray;
if (mask.channels() > 1)
cvtColor(mask, gray, COLOR_BGRA2GRAY);
else
{
if (mask.empty())
gray = Mat(size.height, size.width, CV_8UC1, Scalar(255));
else
mask.copyTo(gray);
}
return gray;
}
void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point p, OutputArray _blend, int flags)
{
CV_INSTRUMENT_REGION();
const Mat src = _src.getMat();//从InputArray获得Mat
const Mat dest = _dst.getMat();
Mat mask = checkMask(_mask, src.size());//mask检查
dest.copyTo(_blend);//将目标图拷贝到结果中,这是因为目标图大部分区域是不变的,变化的只有融合区域
Mat blend = _blend.getMat();//从OutputArray获得Mat
Mat mask_inner = mask(Rect(1, 1, mask.cols - 2, mask.rows - 2));//取出不带边框的mask内容
copyMakeBorder(mask_inner, mask, 1, 1, 1, 1, BORDER_ISOLATED | BORDER_CONSTANT, Scalar(0));//给mask_inner创建边框,还原到mask原始大小。
Rect roi_s = boundingRect(mask);//获取mask外接矩形
if (roi_s.empty()) return;//外接矩形为空(即mask为空)直接返回
Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height);//根据点p计算目标图像的外接矩形位置
Mat destinationROI = dest(roi_d).clone();//将目标图像融合区域拷贝出来
Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type());
src(roi_s).copyTo(sourceROI,mask(roi_s));//将源图像融合区域拷贝出来
Mat maskROI = mask(roi_s);//将mask融合区域取出来
Mat recoveredROI = blend(roi_d);//将结果部分的融合区域取出来
Cloning obj;//创建Cloning对象
obj.normalClone(destinationROI,sourceROI,maskROI,recoveredROI,flags);//调用Cloning对象的normalClone方法
}
算法实现部分的函数
#include "seamless_cloning.hpp"
using namespace cv;
using namespace std;
//求X方向的梯度
void Cloning::computeGradientX( const Mat &img, Mat &gx)
{
Mat kernel = Mat::zeros(1, 3, CV_8S);
kernel.at<char>(0,2) = 1;
kernel.at<char>(0,1) = -1;
if(img.channels() == 3)
{
filter2D(img, gx, CV_32F, kernel);
}
else if (img.channels() == 1)
{
filter2D(img, gx, CV_32F, kernel);
cvtColor(gx, gx, COLOR_GRAY2BGR);
}
}
//求Y方向的梯度
void Cloning::computeGradientY( const Mat &img, Mat &gy)
{
Mat kernel = Mat::zeros(3, 1, CV_8S);
kernel.at<char>(2,0) = 1;
kernel.at<char>(1,0) = -1;
if(img.channels() == 3)
{
filter2D(img, gy, CV_32F, kernel);
}
else if (img.channels() == 1)
{
filter2D(img, gy, CV_32F, kernel);
cvtColor(gy, gy, COLOR_GRAY2BGR);
}
}
//求X方向的拉普拉斯
void Cloning::computeLaplacianX( const Mat &img, Mat &laplacianX)
{
Mat kernel = Mat::zeros(1, 3, CV_8S);
kernel.at<char>(0,0) = -1;
kernel.at<char>(0,1) = 1;
filter2D(img, laplacianX, CV_32F, kernel);
}
//求Y方向的拉普拉斯
void Cloning::computeLaplacianY( const Mat &img, Mat &laplacianY)
{
Mat kernel = Mat::zeros(3, 1, CV_8S);
kernel.at<char>(0,0) = -1;
kernel.at<char>(1,0) = 1;
filter2D(img, laplacianY, CV_32F, kernel);
}
//采用离散傅里叶变换(离散正弦变换,dst)求解泊松方程,该方法在每个通道的泊松方程求解过程中都会被调用两次。
//From. TracelessLe - CSDN.
//https://blog.csdn.net/TracelessLe
void Cloning::dst(const Mat& src, Mat& dest, bool invert)
{
Mat temp = Mat::zeros(src.rows, 2 * src.cols + 2, CV_32F);//注意2倍关系,求解dst需要构建对称关系
int flag = invert ? DFT_ROWS + DFT_SCALE + DFT_INVERSE: DFT_ROWS;//cv::dft函数的标记
src.copyTo(temp(Rect(1,0, src.cols, src.rows)));//拷贝到
for(int j = 0 ; j < src.rows ; ++j)
{
float * tempLinePtr = temp.ptr<float>(j);
const float * srcLinePtr = src.ptr<float>(j);
for(int i = 0 ; i < src.cols ; ++i)
{
tempLinePtr[src.cols + 2 + i] = - srcLinePtr[src.cols - 1 - i];
}
}//在temp上构建src的对称关系
Mat planes[] = {temp, Mat::zeros(temp.size(), CV_32F)};//虚数通道
Mat complex;
merge(planes, 2, complex);//合并实数与虚数通道
dft(complex, complex, flag);//调用cv::dft求解传入的复数矩阵
split(complex, planes);//分拆dft结果
temp = Mat::zeros(src.cols, 2 * src.rows + 2, CV_32F);//注意与第一次2倍关系的不同
for(int j = 0 ; j < src.cols ; ++j)
{
float * tempLinePtr = temp.ptr<float>(j);
for(int i = 0 ; i < src.rows ; ++i)
{
float val = planes[1].ptr<float>(i)[j + 1];
tempLinePtr[i + 1] = val;
tempLinePtr[temp.cols - 1 - i] = - val;
}
}//构建对称关系
Mat planes2[] = {temp, Mat::zeros(temp.size(), CV_32F)};
merge(planes2, 2, complex);//合并实数虚数通道
dft(complex, complex, flag);//第2次dft求解
split(complex, planes2);//拆分结果
temp = planes2[1].t();//翻转结果
temp(Rect( 0, 1, src.cols, src.rows)).copyTo(dest);//取出有效部分
}
//solve方法求解
void Cloning::solve(const Mat &img, Mat& mod_diff, Mat &result)
{
const int w = img.cols;
const int h = img.rows;
Mat res;
dst(mod_diff, res);//对传入的散度差调用dst方法求解
for(int j = 0 ; j < h-2; j++)
{
float * resLinePtr = res.ptr<float>(j);
for(int i = 0 ; i < w-2; i++)
{
resLinePtr[i] /= (filter_X[i] + filter_Y[j] - 4);
}
}//调整dst过程参数的系数
dst(res, mod_diff, true);//对第一次dst得到的结果调整后调用dst方法求解
unsigned char * resLinePtr = result.ptr<unsigned char>(0);
const unsigned char * imgLinePtr = img.ptr<unsigned char>(0);
const float * interpLinePtr = NULL;
//对结果进行处理,包括边界处理,用求解结果mod_diff替换输出值Mat(result)的内容
//first col
for(int i = 0 ; i < w ; ++i)
result.ptr<unsigned char>(0)[i] = img.ptr<unsigned char>(0)[i];//第一行,第一列,最后一行,最后一列的值还是保持原始img的值不变
for(int j = 1 ; j < h-1 ; ++j)
{
resLinePtr = result.ptr<unsigned char>(j);
imgLinePtr = img.ptr<unsigned char>(j);
interpLinePtr = mod_diff.ptr<float>(j-1);
//first row
resLinePtr[0] = imgLinePtr[0];
for(int i = 1 ; i < w-1 ; ++i)
{
//saturate cast is not used here, because it behaves differently from the previous implementation
//most notable, saturate_cast rounds before truncating, here it's the opposite.
float value = interpLinePtr[i-1];
if(value < 0.)
resLinePtr[i] = 0;//uint8截断
else if (value > 255.0)
resLinePtr[i] = 255;//uint8截断
else
resLinePtr[i] = static_cast<unsigned char>(value);
}
//last row
resLinePtr[w-1] = imgLinePtr[w-1];
}
//last col
resLinePtr = result.ptr<unsigned char>(h-1);
imgLinePtr = img.ptr<unsigned char>(h-1);
for(int i = 0 ; i < w ; ++i)
resLinePtr[i] = imgLinePtr[i];
}
//方程求解准备工作
void Cloning::poissonSolver(const Mat &img, Mat &laplacianX , Mat &laplacianY, Mat &result)
{
const int w = img.cols;
const int h = img.rows;
Mat lap = laplacianX + laplacianY;//得到散度
Mat bound = img.clone();
rectangle(bound, Point(1, 1), Point(img.cols-2, img.rows-2), Scalar::all(0), -1);
Mat boundary_points;
Laplacian(bound, boundary_points, CV_32F);//得到边界像素的拉普拉斯。注:对一个标量场求梯度后再求散度,等于拉普拉斯算子作用在其上。
boundary_points = lap - boundary_points;//散度差
Mat mod_diff = boundary_points(Rect(1, 1, w-2, h-2));//取innner
solve(img,mod_diff,result);//调用solve方法求解
}
void Cloning::initVariables(const Mat &destination, const Mat &binaryMask)
{
//构建梯度和mask(fp32)的临时变量,用于后续计算
destinationGradientX = Mat(destination.size(),CV_32FC3);
destinationGradientY = Mat(destination.size(),CV_32FC3);
patchGradientX = Mat(destination.size(),CV_32FC3);
patchGradientY = Mat(destination.size(),CV_32FC3);
binaryMaskFloat = Mat(binaryMask.size(),CV_32FC1);
binaryMaskFloatInverted = Mat(binaryMask.size(),CV_32FC1);
//init of the filters used in the dst
//计算后续泊松方程求解时离散傅里叶变换所需的系数
const int w = destination.cols;
filter_X.resize(w - 2);
double scale = CV_PI / (w - 1);
for(int i = 0 ; i < w-2 ; ++i)
filter_X[i] = 2.0f * (float)std::cos(scale * (i + 1));
const int h = destination.rows;
filter_Y.resize(h - 2);
scale = CV_PI / (h - 1);
for(int j = 0 ; j < h - 2 ; ++j)
filter_Y[j] = 2.0f * (float)std::cos(scale * (j + 1));
}
//computeDerivatives方法:计算目标图像和源图像的X/Y方向梯度,同时处理mask
void Cloning::computeDerivatives(const Mat& destination, const Mat &patch, const Mat &binaryMask)
{
initVariables(destination, binaryMask);//初始化变量
computeGradientX(destination, destinationGradientX);//计算目标图像X方向梯度
computeGradientY(destination, destinationGradientY);//计算目标图像Y方向梯度
computeGradientX(patch, patchGradientX);//计算源图像X方向梯度
computeGradientY(patch, patchGradientY);//计算源图像Y方向梯度
Mat Kernel(Size(3, 3), CV_8UC1);//用于mask腐蚀的核
Kernel.setTo(Scalar(1));
erode(binaryMask, binaryMask, Kernel, Point(-1,-1), 3);//对mask进行三次腐蚀,消除边缘毛刺
binaryMask.convertTo(binaryMaskFloat, CV_32FC1, 1.0/255.0);//转float32用于后续乘积
}
//标量点乘,在NORMAL_CLONE中不会用到
void Cloning::scalarProduct(Mat mat, float r, float g, float b)
{
vector <Mat> channels;
split(mat,channels);
multiply(channels[2],r,channels[2]);
multiply(channels[1],g,channels[1]);
multiply(channels[0],b,channels[0]);
merge(channels,mat);
}
//矩阵乘积
void Cloning::arrayProduct(const cv::Mat& lhs, const cv::Mat& rhs, cv::Mat& result) const
{
vector <Mat> lhs_channels;
vector <Mat> result_channels;
split(lhs,lhs_channels);//拆分3个通道
split(result,result_channels);
for(int chan = 0 ; chan < 3 ; ++chan)
multiply(lhs_channels[chan],rhs,result_channels[chan]);//乘积cv::multiply
merge(result_channels,result);//合并3个通道
}
//泊松方程求解入口,调用poissonSolver求解
void Cloning::poisson(const Mat &destination)
{
Mat laplacianX = destinationGradientX + patchGradientX;//将mask区域的源图像X方向梯度和非mask区域的模板图像X方向梯度叠起来,形成新的X方向梯度。此时laplacianX还不是拉普拉斯值
Mat laplacianY = destinationGradientY + patchGradientY;//将mask区域的源图像Y方向梯度和非mask区域的模板图像Y方向梯度叠起来,形成新的Y方向梯度。此时laplacianY还不是拉普拉斯值
computeLaplacianX(laplacianX,laplacianX);//计算X方向的拉普拉斯值
computeLaplacianY(laplacianY,laplacianY);//计算Y方向的拉普拉斯值
split(laplacianX,rgbx_channel);//拆分三个通道的X方向的拉普拉斯
split(laplacianY,rgby_channel);//拆分三个通道的Y方向的拉普拉斯
split(destination,output);//拆分结果,此时还不是最终结果,只是vector形式方便索引和写入
for(int chan = 0 ; chan < 3 ; ++chan)//对三个通道循环做泊松方程求解
{
poissonSolver(output[chan], rgbx_channel[chan], rgby_channel[chan], output[chan]);//调用poissonSolver方法求解泊松方程
}
}
//泊松方程构建函数主入口
void Cloning::evaluate(const Mat &I, const Mat &wmask, const Mat &cloned)
{
bitwise_not(wmask,wmask);//反转mask,用于后续抠dst的梯度图
wmask.convertTo(binaryMaskFloatInverted,CV_32FC1,1.0/255.0);//转float32用于后续乘积
arrayProduct(destinationGradientX, binaryMaskFloatInverted, destinationGradientX);//根据反向mask抠出非mask区域内的目标图像X方向梯度
arrayProduct(destinationGradientY, binaryMaskFloatInverted, destinationGradientY);//根据反向mask抠出非mask区域内的目标图像Y方向梯度
poisson(I);//进入泊松方程求解入口
merge(output,cloned);//合并三个通道的融合图像为最终结果
}
//算法实现主入口
void Cloning::normalClone(const Mat &destination, const Mat &patch, const Mat &binaryMask, Mat &cloned, int flag)
{
const int w = destination.cols;
const int h = destination.rows;
const int channel = destination.channels();
const int n_elem_in_line = w * channel;
computeDerivatives(destination,patch,binaryMask);//调用computeDerivatives方法计算目标图像和源图像的X/Y方向梯度,同时处理mask
switch(flag)//通过flag选择走NORMAL_CLONE/MIXED_CLONE/MONOCHROME_TRANSFER分支
{
case NORMAL_CLONE:
arrayProduct(patchGradientX, binaryMaskFloat, patchGradientX);//根据mask抠出mask区域内的源图像X方向梯度
arrayProduct(patchGradientY, binaryMaskFloat, patchGradientY);//根据mask抠出mask区域内的源图像Y方向梯度
break;
case MIXED_CLONE:
{
...
}
break;
case MONOCHROME_TRANSFER:
...
break;
}
evaluate(destination,binaryMask,cloned);//进入泊松方程构建函数主入口
}
关于采用离散傅里叶变换求解泊松方程的理论原理请查阅文末列出的参考资料。
图示流程
画图说明OpenCV的整个泊松融合算法实现过程(以NORMAL_CLONE为例)。
代码示例-Python
import cv2
src_bgr = cv2.imread('xingye.jpg', 1)
dst_bgr = cv2.imread('xiangrikui.jpg', 1)
resize = (70, 120)
src_bgr_resize = cv2.resize(src_bgr, (resize[1], resize[0]))
mask = np.ones((src_bgr_resize.shape[0], src_bgr_resize.shape[1]))
mask_inner = mask[1:-1, 1:-1]
mask = cv2.copyMakeBorder(mask_inner, 1, 1, 1, 1, cv2.BORDER_ISOLATED | cv2.BORDER_CONSTANT, value=0)
mask = mask*255
p = (290, 610)
out_img = cv2.seamlessClone(src_bgr_resize.astype(np.uint8), dst_bgr.astype(np.uint8), mask.astype(np.uint8), p, cv2.NORMAL_CLONE)
cv2.imwrite('out_img.png', out_img)
xingye.jpg:
xiangrikui.jpg:
out_img.png:
改变src的形状和点p的位置不同结果:
融合的src和dst互换的融合结果:
需要注意,融合到不同的地方,得到的融合效果不一样。由于泊松融合从原理上来讲属于梯度场引导的融合,按照泊松方程构建来说,融合边界的值对生成像素的值具有基准参考作用。所以在某些情况下,由于边界值(dst的值,泊松融合的起始基准值)很小或者很大,比src原值差异较大,得到的融合结果中部分(梯度变化强的)区域可能会过暗或者过亮,这都是需要注意的。对于这一问题,在某些情况下可以通过梯度平滑解决这种明亮度突变。
代码示例-C++
参考OpenCV官网demo:
/*
* cloning_demo.cpp
*
* Author:
* Siddharth Kherada <siddharthkherada27[at]gmail[dot]com>
*
* This tutorial demonstrates how to use OpenCV seamless cloning
* module without GUI.
*
* 1- Normal Cloning
* 2- Mixed Cloning
* 3- Monochrome Transfer
* 4- Color Change
* 5- Illumination change
* 6- Texture Flattening
* The program takes as input a source and a destination image (for 1-3 methods)
* and outputs the cloned image.
*
* Download test images from opencv_extra folder @github.
*
*/
#include "opencv2/photo.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include <iostream>
#include <stdlib.h>
using namespace std;
using namespace cv;
int main()
{
cout << endl;
cout << "Cloning Module" << endl;
cout << "---------------" << endl;
cout << "Options: " << endl;
cout << endl;
cout << "1) Normal Cloning " << endl;
cout << "2) Mixed Cloning " << endl;
cout << "3) Monochrome Transfer " << endl;
cout << "4) Local Color Change " << endl;
cout << "5) Local Illumination Change " << endl;
cout << "6) Texture Flattening " << endl;
cout << endl;
cout << "Press number 1-6 to choose from above techniques: ";
int num = 1;
cin >> num;
cout << endl;
if(num == 1)
{
string folder = "cloning/Normal_Cloning/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "destination1.png";
string original_path3 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat destination = imread(original_path2, IMREAD_COLOR);
Mat mask = imread(original_path3, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(destination.empty())
{
cout << "Could not load destination image " << original_path2 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path3 << endl;
exit(0);
}
Mat result;
Point p;
p.x = 400;
p.y = 100;
seamlessClone(source, destination, mask, p, result, 1);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
else if(num == 2)
{
string folder = "cloning/Mixed_Cloning/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "destination1.png";
string original_path3 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat destination = imread(original_path2, IMREAD_COLOR);
Mat mask = imread(original_path3, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(destination.empty())
{
cout << "Could not load destination image " << original_path2 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path3 << endl;
exit(0);
}
Mat result;
Point p;
p.x = destination.size().width/2;
p.y = destination.size().height/2;
seamlessClone(source, destination, mask, p, result, 2);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
else if(num == 3)
{
string folder = "cloning/Monochrome_Transfer/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "destination1.png";
string original_path3 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat destination = imread(original_path2, IMREAD_COLOR);
Mat mask = imread(original_path3, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(destination.empty())
{
cout << "Could not load destination image " << original_path2 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path3 << endl;
exit(0);
}
Mat result;
Point p;
p.x = destination.size().width/2;
p.y = destination.size().height/2;
seamlessClone(source, destination, mask, p, result, 3);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
else if(num == 4)
{
string folder = "cloning/Color_Change/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat mask = imread(original_path2, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path2 << endl;
exit(0);
}
Mat result;
colorChange(source, mask, result, 1.5, .5, .5);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
else if(num == 5)
{
string folder = "cloning/Illumination_Change/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat mask = imread(original_path2, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path2 << endl;
exit(0);
}
Mat result;
illuminationChange(source, mask, result, 0.2f, 0.4f);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
else if(num == 6)
{
string folder = "cloning/Texture_Flattening/";
string original_path1 = folder + "source1.png";
string original_path2 = folder + "mask.png";
Mat source = imread(original_path1, IMREAD_COLOR);
Mat mask = imread(original_path2, IMREAD_COLOR);
if(source.empty())
{
cout << "Could not load source image " << original_path1 << endl;
exit(0);
}
if(mask.empty())
{
cout << "Could not load mask image " << original_path2 << endl;
exit(0);
}
Mat result;
textureFlattening(source, mask, result, 30, 45, 3);
imshow("Output",result);
imwrite(folder + "cloned.png", result);
}
waitKey(0);
}
参考资料
[1] void cv::seamlessClone
[2] opencv/modules/photo/src/seamless_cloning.hpp
[3] opencv/modules/photo/src/seamless_cloning.cpp
[4] opencv/modules/photo/src/seamless_cloning_impl.cpp
[5] opencv - samples/cpp/tutorial_code/photo/seamless_cloning/cloning_demo.cpp
[6] 图像处理(十二)图像融合(1)Seamless cloning泊松克隆-Siggraph 2004
[7] 图像融合之泊松编辑(Poisson Editing)(2):详解算法和实现
[8] 图像融合之泊松融合(Possion Matting)
[9] csdn - 泊松融合原理浅析
[10] 散度 - 维基百科,自由的百科全书
[11] void cv::Laplacian
[12] AlgoWiki - Poisson equation, solving with DFT
[13] Numerical Analysis of Boundary-Value Problems (AMATH 585)
[14] 矩阵计算
[15] 快速傅里叶变换 - 维基百科,自由的百科全书
[16] OpenCV: Discrete Fourier Transform
[17] cv::cuda::createLaplacianFilter
[18] cv::cuda::createDFT
[19] cv::filter2D
[20] cv::dft
[21] cv::cuda::dft
[22] GitHub - opencv_contrib/modules/cudaarithm/src/arithm.cpp - cv::cuda::createDFT
[23] Opencv中使用cuda进行 dft 与 idft滤波运算