在opencv中实现修复有两种算法,这里只介绍Telea的算法,即基于快速行进(FMM)的修复算法。首先看c++接口中,函数的定义。
void cv::inpaint( const Mat& src, const Mat& mask, Mat& dst, double inpaintRange, int flags )
//src:要修复的图像;
//mask:修复模板,必须是单通道图像;
//dst:目标图像;
//inpaintRange:选取邻域半径;
//flags:要使用的方法,可以是CV INPAINT NS或CV INPAINT TELEA(本文介绍的方法)。
其实c++接口实现的inpaint方法,只是调用了一下c接口中的cvinpaint。
1,cvInpaint( const vArr*_input_img,const CvArr* _inpaint_mask,CvArr* _output_img, double inpaintRange, int flags )
首先,FMM算法基于的思想是,先处理待修复区域边缘上的像素点,然后层层向内推进,直到修复完所有的像素点。
下面以灰度图为例,我们只需要计算出像素新的灰度值即可。对于彩色图像,分别用同样的方法处理各个通道即可。
一、先说一下如何修复一个像素点的。
参考上图,Ω区域是待修复的区域;δΩ指Ω的边界);要修复Ω中的像素,就需要计算出新的像素值来代替原值。
现在假设p点是我们要修复的像素。以p为中心选取一个小邻域B(ε),该邻域中的点像素值都是已知的(只要已知的)。(这个ε就是opencv函数中参数 inpaintRadius)
显然,我们需要的是用邻域Bε(p)中的所有点计算p点的新灰度值。显然,各个像素点所起的作用应该是不同的,也就引入了权值函数来决定哪些像素的值对新像素值影响更大,哪些比较小。采用下面的公式(公式2):
这里的w(p, q)就是权值函数,是用来限定邻域中各像素的贡献大小的。
w(p, q) = dir(p, q) · dst(p, q) · lev(p, q).
其中,d0和 T0分别为距离参数和水平集参数,一般都取为 1。方向因子 dir(p,q)保证了越靠近法线方向 N = ?T的像素点对 p 点的贡献最大;几何距离因子 dst(p,q)保证了离 p 点越近的像素点对p 点贡献越大;水平集距离因子lev(p,q)保证了离经过点 p 的待修复区域的轮廓线越近的已知像素点对点 p 的贡献越大。
把红色像素区域作为掩膜,类似可以定义想操作的区域和像素:
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include <iostream>
using namespace std;
using namespace cv;
//该方法可能产生误检点,但在可容忍的错范围内
Mat GetRedComponet(Mat srcImg)
{
//如果直接对srcImg处理会改变main()函数中的实参
Mat dstImg = srcImg.clone();
Mat_<Vec3b>::iterator it = dstImg.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = dstImg.end<Vec3b>();
for(; it != itend; it++)
{
if((*it)[2] > 190)//对红色分量做阈值处理
{
(*it)[0] = 0;
(*it)[1] = 0;
//(*it)[2] = 255;//红色分量保持不变
}
else
{
(*it)[0] = 0;
(*it)[1] = 0;
(*it)[2] = 0;
}
}
return dstImg;
}
void Inpainting(Mat oriImg, Mat maskImg)
{
Mat grayMaskImg;
Mat element = getStructuringElement(MORPH_RECT, Size(7, 7));
dilate(maskImg, maskImg, element);//膨胀后结果作为修复掩膜
//将彩色图转换为单通道灰度图,最后一个参数为通道数
cvtColor(maskImg, grayMaskImg, CV_BGR2GRAY, 1);
//修复图像的掩膜必须为8位单通道图像
Mat inpaintedImage;
inpaint(oriImg, grayMaskImg, inpaintedImage, 3, INPAINT_TELEA);
imshow("原图", oriImg);
imshow("图像复原结果图", inpaintedImage);
waitKey(0);
}
int main(int argc, char* argv[])
{
Mat srcImg;
srcImg = imread("D:/openCV/data/naturalImage/data/opencv.jpg", 1);
Mat imgComponet = GetRedComponet(srcImg);
Inpainting(srcImg, imgComponet);
return 0;
}
鼠标确定掩膜的代码:
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//----------------------------------------------------------------------------------------------
#define WINDOW_NAME0 "【原始图参考】" //为窗口标题定义的宏
#define WINDOW_NAME1 "【原始图】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【修补后的效果图】" //为窗口标题定义的宏
//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat srcImage0,srcImage1, inpaintMask;
Point previousPoint(-1,-1);//原来的点坐标
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText( )
{
//输出一些帮助信息
printf("\n\n\n\t欢迎来到【图像修复】示例程序~\n");
printf( "\n\t请在进行图像修复操作之前,在【原始图】窗口中进行适量的绘制"
"\n\n\t按键操作说明: \n\n"
"\t\t【鼠标左键】-在图像上绘制白色线条\n\n"
"\t\t键盘按键【ESC】- 退出程序\n\n"
"\t\t键盘按键【1】或【SPACE】-进行图像修复操作 \n\n" );
}
//-----------------------------------【On_Mouse( )函数】--------------------------------
// 描述:响应鼠标消息的回调函数
//----------------------------------------------------------------------------------------------
static void On_Mouse( int event, int x, int y, int flags, void* )
{
//鼠标左键弹起消息
if( event == CV_EVENT_LBUTTONUP || !(flags & CV_EVENT_FLAG_LBUTTON) )
previousPoint = Point(-1,-1);
//鼠标左键按下消息
else if( event == CV_EVENT_LBUTTONDOWN )
previousPoint = Point(x,y);
//鼠标按下并移动,进行绘制
else if( event == CV_EVENT_MOUSEMOVE && (flags & CV_EVENT_FLAG_LBUTTON) )
{
Point pt(x,y);
if( previousPoint.x < 0 )
previousPoint = pt;
//绘制白色线条
line( inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0 );
line( srcImage1, previousPoint, pt, Scalar::all(255), 5, 8, 0 );
previousPoint = pt;
imshow(WINDOW_NAME1, srcImage1);
}
}
//--------------------------------------【main( )函数】-----------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
//改变console字体颜色
system("color 2F");
//显示帮助文字
ShowHelpText();
//载入原始图并进行掩膜的初始化
Mat srcImage = imread("1.jpg", -1);
if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
srcImage0 = srcImage.clone();
srcImage1 = srcImage.clone();
inpaintMask = Mat::zeros(srcImage1.size(), CV_8U);
//显示原始图参考
imshow(WINDOW_NAME0, srcImage0);
//显示原始图
imshow(WINDOW_NAME1, srcImage1);
//设置鼠标回调消息
setMouseCallback( WINDOW_NAME1, On_Mouse, 0 );
//轮询按键,根据不同的按键进行处理
while (1)
{
//获取按键键值
char c = (char)waitKey();
//键值为ESC,程序退出
if( c == 27 )
break;
//键值为2,恢复成原始图像
if( c == '2' )
{
inpaintMask = Scalar::all(0);
srcImage.copyTo(srcImage1);
imshow(WINDOW_NAME1, srcImage1);
}
//键值为1或者空格,进行图像修补操作
if( c == '1' || c == ' ' )
{
Mat inpaintedImage;
inpaint(srcImage1, inpaintMask, inpaintedImage, 3, CV_INPAINT_TELEA);
imshow(WINDOW_NAME2, inpaintedImage);
}
}
return 0;
}
原文:https://blog.csdn.net/maxhn0/article/details/53394833