开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天
1 概述
在实际应用中,我们的图像常常会被噪声腐蚀,这些噪声或者是镜头上的灰尘或水滴,或者是旧照片的划痕,或者由于图像的部分本身已经损坏。而“图像修复”(Inpainting),就是妙手回春,解决这些问题的良方。
图像修复技术简单来说,就是利用那些已经被破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,以达到图像修补的目的。
如果被破坏的区域不是太大,并且在被破坏区域边缘包含足够多的纹理和颜色,那么图像修补技术可以很好地恢复图像。当然,当图像损坏区域过大时,我们“妙手回春”的能力也是有限的。
2 实现图像修补:inpaint()函数
在新版 OpenCV中,图像修补技术由inpaint函数实现,它可以用来从扫描的照片中清除灰尘和划痕,或者从静态图像或视频中去除不需要的物体。其原型声明如下。
C++:
void inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double inpaintRadius, int flags)
- 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为8位单通道或者三通道图像。
- 第二个参数,InputArray类型的inpaintMask,修复掩膜,为8位的单通道 图像。其中的非零像素表示需要修补的区域。
- 第三个参数,OutputArray 类型的 dst,函数调用后的运算结果存在这里,和源图片有一样的尺寸和类型。
- 第四个参数,double类型的inpaintRadius,需要修补的每个点的圆形邻域,为修复算法的参考半径。
- 第五个参数,int类型的flags,修补方法的标识符,可以是下表所示两者之一。
| 标识符 | 说明 |
|---|---|
| INPAINT_NA | 基于Navier-Stokes方程的方法 |
| INPAINT_TELEA | Alexandru Telea方法 |
3 综合示例程序:图像修补
下面代码是图像修补例子。
示例程序会先让我们在图像中用鼠标绘制出白色的线条破坏图像,然后按下键盘按键【1】或【SPACE】进行图像修补操作。且如果对自己的绘制不够满意,可以按下键盘按键【2】恢复原始图像。
代码:
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//--------------------------------------------------------------------------------------
#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);//原来的点坐标
//-----------------------------------【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;
}
原图
涂鸦图
修复图
操作过程(动态图)