OpenCV4入门系列教程58:图像去水印与修复

入门系列索引:系列教程索引地址

上一篇:OpenCV4入门系列教程57:漫水算法

图像修补

利用那些已经被破坏的区域的边缘,即边缘的颜色和结构,根据这些图像留下的信息去推断被破坏的信息区域的信息内容,然后对破坏区进行填补,以达到图像修补的目的。

函数原型:

1
2
3
4
5
6
7
void inpaint(
InputArray src,
InputArray inpaintMask,
OutputArray dst,
double inpaintRadius,
int flags
);

使用区域邻域恢复图像中的选定区域。

函数实现关键是图像掩码的确定,可以通过阈值筛选或者手工选定。

该功能从区域边界附近的像素重建选定的图像区域。该功能可用于去除扫描照片上的灰尘和划痕,或去除静止图像或视频中不需要的物体。

参数说明:

  • src 输入8位或16位无符号或32位浮点型1通道或8位3通道图像。
  • inpaintMask 图像的掩码,单通道图像,大小跟原图像一致,inpaintMask图像上除了需要修复的部分之外其他部分的像素值全部为0;
  • dst 输出的经过修复的图像;
  • inpaintRadius 修复算法取的邻域半径,用于计算当前像素点的差值;
  • flag 修复算法,有两种:INPAINT_NS 和I NPAINT_TELEA;

修复算法

INPAINT_NS : 基于Navier-Stokes的修复方法

在下图中,我们的目标是填充暗区并获得一个看起来像右边的图像。

floodfill

我们如何填充这个黑色的区域?

  • 第一个约束:边缘进入点A应该连接边缘离开点B。
  • 另一个约束:连接A和B的曲线右边的区域应该是白色,而左边的区域应该是蓝色的。

以上两个约束基本上要求:保留渐变(即边缘特征)和继续在平滑区域中传播颜色信息。

INPAINT_TELEA : 基于图像梯度的快速匹配方法(Telea法)

该方法实现使用不同的技术解决了相同的约束。作者不使用图像拉普拉斯算子作为平滑度的估计,而是使用像素的已知图像邻域上的加权平均值来补绘。已知的邻域像素和梯度用于估计要修复的像素的颜色。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//--------------------------------------【程序说明】-------------------------------------------
// 程序说明:《OpenCV3编程入门》OpenCV2版书本配套示例程序78
// 程序描述:图像修补示例
// 2014年11月 Created by @浅墨_毛星云
// 2014年12月 Revised by @浅墨_毛星云
//------------------------------------------------------------------------------------------------

//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#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 "Original Reference" //为窗口标题定义的宏
#define WINDOW_NAME1 "Original" //为窗口标题定义的宏
#define WINDOW_NAME2 "Result" //为窗口标题定义的宏

//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
Mat srcImage0, srcImage1, inpaintMask;
Point previousPoint(-1, -1); //原来的点坐标

//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
static void ShowHelpText() {
//输出欢迎信息和OpenCV版本
printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
printf("\n\n\t\t\t此为本书OpenCV3版的第78个配套示例程序\n");
printf("\n\n\t\t\t 当前使用的OpenCV版本为:" CV_VERSION);
printf("\n\n ----------------------------------------------------------------------------\n");

//输出一些帮助信息
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 == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON))
previousPoint = Point(-1, -1);
//鼠标左键按下消息
else if (event == EVENT_LBUTTONDOWN)
previousPoint = Point(x, y);
//鼠标按下并移动,进行绘制
else if (event == EVENT_MOUSEMOVE && (flags & 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) {
//显示帮助文字
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, INPAINT_TELEA);
imshow(WINDOW_NAME2, inpaintedImage);
}
}

return 0;
}

测试代码:

inpaint

下一篇:OpenCV4入门系列教程59:重映射