OpenCV源码解读:泊松融合seamlessClone(normalClone)

2024-04-21 04:18

本文主要是介绍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)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/922138

相关文章

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

MCU7.keil中build产生的hex文件解读

1.hex文件大致解读 闲来无事,查看了MCU6.用keil新建项目的hex文件 用FlexHex打开 给我的第一印象是:经过软件的解释之后,发现这些数据排列地十分整齐 :02000F0080FE71:03000000020003F8:0C000300787FE4F6D8FD75810702000F3D:00000001FF 把解释后的数据当作十六进制来观察 1.每一行数据

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象

opencv 滚动条

参数介绍:createTrackbar( trackbarname , "hello" , &alpha_slider ,alpha_max ,  on_trackbar )  ;在标签中显示的文字(提示滑动条的用途) TrackbarName创建的滑动条要放置窗体的名字 “hello”滑动条的取值范围从 0 到 alpha_max (最小值只能为 zero).滑动后的值存放在

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。