《世说新语》记载了东晋的一则轶事:在一个寒冷的冬天,时任宰相的谢安,召集了一大家族的人,在和子侄辈们谈论诗文时,忽然飘起了大雪。
谢安有意考考晚辈们,于是就问:"白雪纷纷何所似?" 谢安的侄子答道:"空中撒盐差可拟",而谢安的侄女却说了一句:"未若柳絮因风起"。
回到现在,从图像处理的角度来看,无论是白雪、柳絮还是空中撒的盐,都可视为 "椒盐噪声",因此,就引出了消除这些噪声的方法 - 图像滤波
1 图像滤波
图像滤波,也称图像平滑或图像模糊化,是图像处理中最简单、最常用的一种运算,
最简单和是一种图像空间滤波方法 (低通滤波),可对图像进行去噪 或 模糊化 (blurring)
以 3X3 的滤波器为例 (即 a=b=1),则矩阵 Mx 和 Mf 对应的元素乘积之和,就是 g(x, y)
其中,$ M_x = \begin{bmatrix} w(-1,-1) & w(-1,0) & w(-1,1) \\ w(0,-1) & w(0,0) & w(1,1) \\ w(1,-1) & w(1,0) & w(1,1) \\ \end{bmatrix} $
$ M_f = \begin{bmatrix} f(x-1,y-1) & f(x-1,y) & f(x-1,y+1) \\ f(x,y-1) & f(x,y) & f(x+1,y+1) \\ f(x+1,y-1) & f(x+1,y) & f(x+1,y+1) \\ \end{bmatrix}$
均值 -> 中值 高斯 双边
2 OpenCV 函数
OpenCV 中的四个函数分别为:均值滤波 blur()、高斯滤波 GaussianBlur()、中值滤波 medianBlur()、双边滤波 bilateralFilter()
2.1 均值滤波
先 blur 后 boxFilter??
2.1.1 boxFilter
输出图像的任一像素灰度值,等于其所有邻域像素灰度值的平均值
模糊化核为,$ K = \alpha \begin{bmatrix} 1 & 1 & ... & 1 & 1 \\ 1 & 1 & ... & 1 & 1 \\ \: & \: & ... & & & \\ 1 & 1 & ... & 1 & 1 \end{bmatrix} $ 其中,$\alpha = \begin{cases} \dfrac{1}{ksize.width * ksize.height} & \text{when normalize = true} \\ 1 & \text{otherwise} \\ \end{cases} $
void cv::boxFilter (
InputArray src, // 输入图像
OutputArray dst, // 输出图像
int ddepth, // 输出图像深度,-1 表示等于 src.depth()
Size ksize, // 模糊化核 (kernel) 的大小
Point anchor = Point(-1,-1), // 锚点位置,缺省值表示 anchor 位于模糊核的正中心
bool normalize = true, // 是否归一化处理
int borderType = BORDER_DEFAULT // 边界模式
)
2.1.2 blur
取 ddepth = -1,normalize = true,则可由 boxFilter 得到模糊化函数 (blur)
boxFilter( src, dst, -1, ksize, anchor, true, borderType );
blur 本质上是一个输入和输出图像深度 (ddepth) 相同,并且做归一化处理的盒式滤波器
void cv::blur (
InputArray src,
OutputArray dst,
Size ksize,
Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT
)
2.2 高斯滤波
高斯滤波最为有用,它是根据当前像素和邻域像素之间,空间距离的不同,计算得出一个高斯核 (邻域像素的加权系数)
然后,高斯核从左至右、从上到下遍历输入图像,与输入图像的像素值求卷积和,得到输出图像的各个像素值
$\quad G_{0}(x, y) = A e^{ \dfrac{ -(x - \mu_{x})^{2} }{ 2\sigma^{2}_{x} } + \dfrac{ -(y - \mu_{y})^{2} }{ 2\sigma^{2}_{y} } } $
无须理会公式的复杂,只需要记住一点即可:邻域像素距离当前像素越远 (saptial space),则其相应的加权系数越小
为了便于直观理解,可看下面这个一维高斯核,推而广之将 G(x) 曲线以 x=0 这条轴为中心线,旋转360度可想象其二维高斯核
void GaussianBlur (
InputArray src,
OutputArray dst,
Size ksize, // 高斯核大小
double sigmaX, // 高斯核在x方向的标准差
double sigmaY = 0, // 高斯核在y方向的标准差,默认为 0,表示 sigmaY == sigmaX
int borderType = BORDER_DEFAULT
)
注意:高斯核大小 Size(width, height),w 和 h 不必相同但必须是奇数,若都设为 0,则通过 sigma 自动计算核大小
2.3 双边滤波
上面三种方法都是低通滤波,因此在消除噪声的同时,也常会将边缘信息模糊化。双边滤波和高斯滤波类似,但是它将邻域像素的加权系数分为两部分,
第一部分与高斯滤波的完全相同,第二部分则考虑当前像素和邻域像素之间灰度值的差异,从而在消除噪声的基础上,也较好的保留了图像的边缘信息
void cv::bilateralFilter (
InputArray src,
OutputArray dst,
int d, // 像素邻域直径,若为非正值,则从 sigmaSpace 自动计算得出
double sigmaColor, // 颜色空间的标注方差
double sigmaSpace, // 坐标空间的标准方差
int borderType = BORDER_DEFAULT
)
注意 1) 双边滤波相比以上三种滤波方法,其处理速度很慢,因此,一般建议取 d=5 用于实时图像处理,d=9 适合于非实时的图像领域
注意 2) sigmaColor 和 sigmaSpace 可取相同值,一般在 10 ~ 150 之间,小于 10,则没什么效果,大于 150,则效果太强烈,看起来明显“卡通化”
2.4 中值滤波(非线性)
图像中像素点 (x,y),经过中值滤波后,像素值 g(x, y) 等于以 (x, y) 为中心点的邻域像素的 "中值",也即按顺序排列的一组像素值中居于中间位置的值
椒盐噪声,OpenCV 的中值滤波函数 medianBlur() 定义如下:
void medianBlur (
InputArray src,
OutputArray dst,
int ksize // 滤波核大小,一般为奇数且大于1,如 3, 5, 7, ...
)
3 代码示例
3.1 OpenCV
OpenCV 中的示例,通过逐渐增大像素邻域的大小 Size(w, h),将上述滤波过程动态化,非常形象的展示了邻域大小对滤波效果的影响
1 /**
2 * file Smoothing.cpp
3 * brief Sample code for simple filters
4 * author OpenCV team
5 */
6 #include <iostream>
7 #include <vector>
8
9 #include "opencv2/imgproc/imgproc.hpp"
10 #include "opencv2/imgcodecs.hpp"
11 #include "opencv2/highgui/highgui.hpp"
12 #include "opencv2/features2d/features2d.hpp"
13
14 using namespace std;
15 using namespace cv;
16
17 /// Global Variables
18 int DELAY_CAPTION = 1500;
19 int DELAY_BLUR = 100;
20 int MAX_KERNEL_LENGTH = 31;
21
22 Mat src; Mat dst;
23 char window_name[] = "Smoothing Demo";
24
25 /// Function headers
26 int display_caption( const char* caption );
27 int display_dst( int delay );
28
29
30 /**
31 * function main
32 */
33 int main( void )
34 {
35 namedWindow( window_name, WINDOW_AUTOSIZE );
36
37 /// Load the source image
38 src = imread( "../data/lena.jpg", 1 );
39
40 if( display_caption( "Original Image" ) != 0 ) { return 0; }
41
42 dst = src.clone();
43 if( display_dst( DELAY_CAPTION ) != 0 ) { return 0; }
44
45
46 /// Applying Homogeneous blur
47 if( display_caption( "Homogeneous Blur" ) != 0 ) { return 0; }
48
49 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
50 { blur( src, dst, Size( i, i ), Point(-1,-1) );
51 if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
52
53
54 /// Applying Gaussian blur
55 if( display_caption( "Gaussian Blur" ) != 0 ) { return 0; }
56
57 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
58 { GaussianBlur( src, dst, Size( i, i ), 0, 0 );
59 if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
60
61
62 /// Applying Median blur
63 if( display_caption( "Median Blur" ) != 0 ) { return 0; }
64
65 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
66 { medianBlur ( src, dst, i );
67 if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
68
69
70 /// Applying Bilateral Filter
71 if( display_caption( "Bilateral Blur" ) != 0 ) { return 0; }
72
73 for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 )
74 { bilateralFilter ( src, dst, i, i*2, i/2 );
75 if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }
76
77 /// Wait until user press a key
78 display_caption( "End: Press a key!" );
79
80 waitKey(0);
81
82 return 0;
83 }
84
85 /**
86 * @function display_caption
87 */
88 int display_caption( const char* caption )
89 {
90 dst = Mat::zeros( src.size(), src.type() );
91 putText( dst, caption,
92 Point( src.cols/4, src.rows/2),
93 FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255) );
94
95 imshow( window_name, dst );
96 int c = waitKey( DELAY_CAPTION );
97 if( c >= 0 ) { return -1; }
98 return 0;
99 }
100
101 /**
102 * @function display_dst
103 */
104 int display_dst( int delay )
105 {
106 imshow( window_name, dst );
107 int c = waitKey ( delay );
108 if( c >= 0 ) { return -1; }
109 return 0;
110 }
View Code
3.2 滤波对比
实际中,可直接调用以上四个滤波函数,代码如下:
1 #include "opencv2/imgproc/imgproc.hpp"
2 #include "opencv2/highgui/highgui.hpp"
3
4 using namespace cv;
5
6 int main()
7 {
8 Mat src = imread("E:/smooth/bird.jpg");
9 if(src.empty()) {
10 return -1;
11 }
12 imshow("original", src);
13
14 Mat dst;
15
16 blur(src, dst, Size(3,3));
17 imshow("blur", dst);
18
19 medianBlur(src,dst,3);
20 imshow("medianBlur",dst);
21
22 GaussianBlur(src,dst,Size(3,3),0);
23 imshow("GaussianBlur",dst);
24
25 bilateralFilter(src,dst,9,50,50);
26 imshow("bilateralFilter",dst);
27
28 waitKey(0);
29 }
四种滤波方法的效果图,如下所示:
参考资料
《Digital Image Processing》 4th, ch3
《Learning OpenCV3》
OpenCV Tutorials \ Image Processing (imgproc module) \ Smoothing Images
图像卷积与滤波的一些知识点,zouxy09