本文主要是介绍OpenCV源码解读:泊松融合seamlessClone(normalClone),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
泊松融合(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_TRANSFERvoid 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));elsemask.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获得Matconst Mat dest = _dst.getMat();Mat mask = checkMask(_mask, src.size());//mask检查dest.copyTo(_blend);//将目标图拷贝到结果中,这是因为目标图大部分区域是不变的,变化的只有融合区域Mat blend = _blend.getMat();//从OutputArray获得MatMat 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 colfor(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 rowresLinePtr[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截断elseresLinePtr[i] = static_cast<unsigned char>(value);}//last rowresLinePtr[w-1] = imgLinePtr[w-1];}//last colresLinePtr = 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));//取innnersolve(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::multiplymerge(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方向梯度,同时处理maskswitch(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 cv2src_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*255p = (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滤波运算
这篇关于OpenCV源码解读:泊松融合seamlessClone(normalClone)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!