图像修复技术实现

在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 
 

猜你喜欢

转载自blog.csdn.net/zhuimengshaonian66/article/details/83751715