inpaint图像修复
利用inpaint函数进行图像修复。函数原型:
1 CV_EXPORTS_W void inpaint( InputArray src, InputArray inpaintMask,
2 OutputArray dst, double inpaintRadius, int flags );
InputArray src 表示要修复的图像;InputArray inpaintMask表示修复模板,
OutputArray dst 表示修复后的图像;double inpaintRadius 表示修复的半径,
int flags 表示修复使用的算法 。 opencv提供了两种选择,flag有两种形式,实现两种算法:
1 INPAINT_NS // Navier-Stokes algorithm
2 INPAINT_TELEA // A. Telea algorithm
两种算法修复需要事先准备修复模板mask,也就是inpaintMask ,例子里面用鼠标在图片上划线,划线的同时也更新了mask。
1 #include<opencv2\imgproc.hpp>
2 #include<opencv2\opencv.hpp>
3 #include<iostream>
4 #include<opencv2\core\core.hpp>
5 #include<cstdlib>
6 using namespace std;
7 using namespace cv;
8
9 #define WINDOW_NAME1 "原始图"
10
11 Mat src2,src_temp, inpaintMask;
12 Point prePoint; //上一个鼠标点
13 static void on_mouse(int event, int x, int y, int flag, void*);
14 int main() {
15 src2 = imread("222.png", 1);
16 if (src2.empty())
17 cout << "open failed" << endl;
18 src_temp = src2.clone();
19 imshow(WINDOW_NAME1, src2);
20 inpaintMask = Mat::zeros(src2.size(), CV_8UC1);
21 setMouseCallback(WINDOW_NAME1, on_mouse, 0);
22 while (1)
23 {
24 int key = waitKey();
25 switch (key) {
26 case 'a':
27 {
28 Mat inpaintedImage;
29 inpaint(src_temp, inpaintMask, inpaintedImage, 5, INPAINT_TELEA);
30 imshow("修补方法:_TELEA", inpaintedImage);
31 }
32 break;
33 case 'b': {
34 Mat inpaintedImage2;
35 inpaint(src_temp, inpaintMask, inpaintedImage2, 5, INPAINT_NS);
36 imshow("修补方法:_NS", inpaintedImage2);
37 }
38 break;
39 if(key == 27)
40 break;
41 }
42 }
43 waitKey(0);
44 }
45
46 static void on_mouse(int event, int x, int y, int flag, void*)
47 {
48 if (event == EVENT_LBUTTONDOWN)
49 prePoint = Point(x, y);
50 else if (event == EVENT_MOUSEMOVE && (flag & EVENT_FLAG_LBUTTON))//左键按下且鼠标移动
51 {
52 Point pt(x, y);
53 line(inpaintMask, prePoint, pt, Scalar::all(255), 3, 8, 0);//构造掩模,就是标记哪些地方需要修复
54 line(src_temp, prePoint, pt, Scalar::all(255), 3, 8, 0);
55 prePoint = pt;
56 imshow(WINDOW_NAME1, src_temp);
57 }
58 if (event == EVENT_RBUTTONDOWN) {按下右键,重置图片和掩模,重新开始画白线并修复图像白色区域
59 src_temp = src2.clone();
60 imshow(WINDOW_NAME1, src_temp);
61 inpaintMask = Mat::zeros(src2.size(), CV_8UC1);
62 }
63 }
结果:
图像去噪fastNlMeansDenoising
很多图像平滑技术,比如高斯平滑,中值平滑等,当噪声比较小时这些技术的效果都是很好的。在这些技术中我们选取像素周围一个小的邻域然后用高斯平均值或者中值平均值取代中心像素。简单来说,像素级别的噪声去除是限制在局部邻域的。
NL-Means的全称是:Non-Local Means,直译过来是非局部平均,在2005年由Baudes提出,该算法使用自然图像中普遍存在的冗余信息来去噪声。与常用的双线性滤波、中值滤波等利用图像局部信息来滤波不同的是,它利用了整幅图像来进行去噪,以图像块为单位在图像中寻找相似区域,再对这些区域求平均,能够比较好地去掉图像中存在的高斯噪声。与我们以前学习的平滑技术相比这种算法要消耗更多的时间,但是结果很好。对于彩色图像,要先转换到 CIELAB 颜色空间,然后对 L 和 AB 成分分别去噪。NL-Means的滤波过程可以用下面公式来表示:
在这个公式中,w(x,y)是一个权重,表示在原始图像v中,像素 x 和像素 y 的相似度。这个权重要大于0,同时,权重的和为1,用公式表示是这样:
Ωx是像素 x 的邻域。这个公式可以这样理解:对于图像中的每一个像素 x ,去噪之后的结果等于它邻域中像素 y 的加权和,加权的权重等于 x 和 y的相似度。这个邻域也称为搜索区域,搜索区域越大,找到相似像素的机会也越大,但同时计算量也是成指数上升。在提出这个算法的文献中,这个区域是整幅图像!导致的结果是处理一幅512x512大小的图像,最少也得几分钟。
衡量像素相似度的方法有很多,最常用的是根据两个像素的亮度值的差的平方来估计(最小二乘万岁!)。但因为有噪声的存在,单独的一个像素并不可靠。对此解决方法是,考虑它们的邻域,只有邻域相似度高才能说这两个像素的相似度高。衡量两个图像块的相似度最常用的方法是计算他们之间的欧氏距离:
其中: n(x)是一个归一化的因子,是所有权重的和,对每个权重除以该因子后,使得权重满足和为1的条件。 h>0 是滤波系数,控制指数函数的衰减从而改变欧氏距离的权重。 V(x) 和V(y) 代表了像素 x 和像素 y 的邻域,这个邻域常称为块(Patch)邻域。块邻域一般要小于搜索区域。是两个邻域的高斯加权欧式距离。其中 a>0是高斯核的标准差。在求欧式距离的时候,不同位置的像素的权重是不一样的,距离块的中心越近,权重越大,距离中心越远,权重越小,权重服从高斯分布。实际计算中考虑到计算量的问题,常常采用均匀分布的权重。
如上图所示,p为去噪的点,因为q1和q2的邻域与p相似,所以权重w(p,q1)和w(p,q2)比较大,而邻域相差比较大的点q3的权重值w(p,q3)很小。如果用一幅图把所有点的权重表示出来,那就得到下面这些权重图:
这6组图像中,左边是原图,中心的白色色块代表了像素x块邻域,右边是计算出来的权重 w(x,y)图,权重范围从0(黑色)到1(白色)。这个块邻域在整幅图像中移动,计算图像中其他区域跟这个块的相似度,相似度越高,得到的权重越大。最后将这些相似的像素值根据归一化之后的权重加权求和,得到的就是去噪之后的图像了。
这个算法参数的选择也有讲究,一般而言,考虑到算法复杂度,搜索区域大概取21x21,相似度比较的块的可以取7x7。实际中,常常需要根据噪声来选取合适的参数。当高斯噪声的标准差 σ 越大时,为了使算法鲁棒性更好,需要增大块区域,块区域增加同样也需要增加搜索区域。同时,滤波系数 h 与 σ 正相关:h=kσ,当块变大时,k需要适当减小。
NL-Means算法的复杂度跟图像的大小、颜色通道数、相似块的大小和搜索框的大小密切相关,设图像的大小为N×N,颜色通道数为Nc,块的大小为k×k,搜索框的大小为n×n,那么算法复杂度为:(看着都可怕)。对512×512的彩色图像而言,设置k=7,n=21,OpenCV在使用了多线程的情况下,处理一幅图像所需要的时间需要几十秒。虽然有人不断基于这个算法进行改进、提速,但离实时处理还是比较远。
OpenCV 提供了这种技术的四个变本。
1 CV_EXPORTS_W void fastNlMeansDenoising( InputArray src, OutputArray dst, float h = 3,
2 int templateWindowSize = 7, int searchWindowSize = 21);
3
4 src // 必须为U8
5 h // Weight decay parameter
6 templateWindowSize = 7, // Size of patches used for comparison
7 searchWindowSize = 21 // Maximum patch distance to consider
8
9 CV_EXPORTS_W void fastNlMeansDenoisingColored( InputArray src, OutputArray dst,
10 float h = 3, float hColor = 3,
11 int templateWindowSize = 7, int searchWindowSize = 21);
12
13 src // Input image 必须为U8C3
14 h = 3, // Luminosity weight decay parameter
15 hColor = 3, // Color weight decay parameter
16
17 CV_EXPORTS_W void fastNlMeansDenoisingMulti( InputArrayOfArrays srcImgs, OutputArray dst,
18 int imgToDenoiseIndex, int temporalWindowSize,
19 float h = 3, int templateWindowSize = 7, int searchWindowSize = 21);
20
21 srcImgs// Sequence of several images
22 imgToDenoiseIndex, // Index of image to denoise
23 temporalWindowSize, // Num images to use (odd)
24 h = 3, // Weight decay parameter
25 templateWindowSize = 7, // Size of comparison patches
26 searchWindowSize = 21 // Maximum patch distance
27
28 CV_EXPORTS_W void fastNlMeansDenoisingColoredMulti( InputArrayOfArrays srcImgs, OutputArray dst,
29 int imgToDenoiseIndex, int temporalWindowSize,
30 float h = 3, float hColor = 3,
31 int templateWindowSize = 7, int searchWindowSize = 21);
32
33 hColor = 3, // Weight decay param for color
1. cv2.fastNlMeansDenoising() 使用对象为灰度图。
2. cv2.fastNlMeansDenoisingColored() 使用对象为彩色图。输入图像中含有方差为 25 的噪声,下面是
结果
3. cv2.fastNlMeansDenoisingMulti() 适用于短时间的图像序列(灰度图像)
要对一段视频使用这个方法。第一个参数是一个噪声帧的列表。第二个参数 imgtoDenoiseIndex 设定那些帧需要去噪,我们可以传入一个帧的索引。第三个参数 temporaWindowSize 可以设置用于去噪的相邻帧的数目,它应该是一个奇数。在这种情况下 temporaWindowSize 帧的图像会被用于去噪,中间的帧就是要去噪的帧。例如,我们传入 5 帧图像,imgToDenoiseIndex = 2 和 temporalW indowSize = 3。那么第一帧,第二帧,第三帧图像将被用于第二帧图像的去噪。
计算消耗了相当可观的时间。第一张图是原始图像,第二个是带噪音个图像,第三个是去噪音之后的图像。
4. cv2.fastNlMeansDenoisingColoredMulti() 适用于短时间的图像序列(彩色图像)
共同参数有:
• h : 决定过滤器强度。h 值高可以很好的去除噪声,但也会把图像的细节抹去。(取 10 的效果不错)
• hForColorComponents : 与 h 相同,但使用与彩色图像。(与 h 相同)
• templateWindowSize : 奇数。(推荐值为 7)
• searchWindowSize : 奇数。(推荐值为 21)