摘要:本文介绍数字图像处理中的图像平滑技术。首先阐述图像噪声的来源与平滑处理的目标,然后介绍空间域平滑滤波的三大类算法------线性平滑、非线性平滑与自适应平滑,最后重点讲解线性平滑中的归一化方框滤波器(均值滤波)与高斯滤波器,包括其原理、OpenCV 函数用法及代码示例。
一幅图像在获取、传输等过程中会受到各种各样的噪声干扰。图像噪声来自多个方面,既有系统外部的干扰,如电磁波或经电源串进系统内部而引起的外部噪声,也有来自系统内部的干扰,如摄像机的热噪声、电器的机械运动而产生的抖动噪声等。这些噪声干扰使图像退化、质量下降,表现为图像模糊、特征淹没,对图像分析不利。因此,去除噪声、恢复原始图像是图像处理中的一个重要内容。消除噪声的工作称为图像平滑。
图像平滑是一种实用的数字图像处理技术。一个较好的平滑处理方法应该既能消除图像噪声,又不使图像边缘轮廓和线条变模糊,这是数字图像平滑处理要追求的目标。由于噪声源众多,噪声种类复杂,因此相应的平滑方法也多种多样。我们可以将图像平滑技术分为空间域图像平滑技术和频率域图像平滑技术。空间域图像平滑技术有邻域平均法、空间低通滤波、多图像平均、中值滤波等。在频率域,由于噪声频谱通常在高频部分,因此可以采用各种形式的低通滤波器来减少噪声。
图像平滑是一种邻域增强的方法。每幅图像都包含某种程度的噪声,噪声可以理解为由一种或多种原因引起的灰度值随机变化,例如由光子通量的随机性造成的噪声等。在大多数情况下,可以通过平滑技术(通常称为滤波技术)来抑制或去除噪声,其中具备保持边缘(Edge Preserving)特性的平滑技术尤为受到关注。常用的平滑处理算法包括基于二维离散卷积的高斯平滑、均值平滑,以及基于统计学方法的中值平滑等。
平滑也称为模糊,是一种简单而常用的图像处理操作。平滑的主要目的有两个:一个是消除噪声,改善图像质量;另一个是抽出对象特征。平滑处理的用途很多,这里我们仅关注它减少噪声的功能。在进行平滑处理时需要用到一个滤波器。
1.平滑处理算法
在空间域,平滑滤波有很多种算法,其中常见的有线性平滑、非线性平滑、自适应平滑。
滤波的意思是对原图像的每个像素周围一定范围内的像素进行运算,运算的范围就称为掩码(掩膜)或领域。运算分两种:如果运算只是对各像素灰度值进行简单处理(如乘一个权值),最后求和,就称为线性滤波;如果对像素灰度值的运算比较复杂,不是最后求和的简单运算,则是非线性滤波。例如,求一个像素周围3×3范围内的最大值、最小值、中值、均值等操作都不是简单的加权,都属于非线性滤波。线性空间滤波和非线性空间滤波如图所示。

常用的滤波器是线性滤波器,线性滤波处理的输出像素值g(i,j)是输入像素值f(i+k,j+l)的加权和:

上面公式中,h(k,l)称为核,它仅仅是一个加权系数。不妨把滤波器想象成一个包含加权系数的窗口,当使用这个滤波器平滑处理图像时,就把这个窗口滑过图像。滤波器的种类有很多,这里仅介绍常用的。
线性平滑就是对每一个像素的灰度值用它的邻域值来代替,其邻域的大小为N×N,N一般取奇数。图像经过线性平滑滤波,相当于经过了一个二维的低通滤波器,虽然降低了噪声,但同时也模糊了图像边缘和细节,这是这类滤波器的通病。
非线性平滑是对线性平滑的一种改进,它不对所有像素都用其邻域平均值来代替,而是取一个阈值,当像素灰度值与其邻域平均值之间的差值大于阈值时,才以均值代替;当像素灰度值与其邻域平均值之间的差值不大于阈值时,取其本身的灰度值。非线性平滑可消除一些孤立的噪声点,对图像的细节影响不大,但会对物体的边缘带来一定的失真。
自适应平滑是一种根据当时、当地情况尽量不模糊边缘轮廓的方法,所以这种算法要有一个适应的目标。根据目标的不同,可以有各种各样的自适应图像处理方法。
2.线性平滑
2.1 归一化方框滤波器
归一化方框滤波器(Normalized Box Filter)是很简单的滤波器,输出像素值是核窗口内像素值的均值(所有像素加权系数相等)。其核(Kernel,用K表示)如图所示。

方框滤波和均值滤波的核基本上是一致的,主要区别是要不要归一化处理。如果使用归一化处理,方框滤波就是均值滤波,实际上均值滤波是方框滤波归一化后的特殊情况。注意:均值滤波不能很好地保护细节。OpenCV提供了blur函数来实现均值滤波操作,该函数声明如下:
void blur(InputArray src, OutputArray dst, Size ksize, Point
anchor=Point(-1,
-1), int borderType=BORDER_DEFA);
其中参数src表示输入图像,可以是Mat类型,图像深度是CV_8U、CV_16U、CV_16S、CV_32F以及CV_64F其中的一个;dst表示输出图像,深度和类型与输入图像一
致;ksize表示滤波模板核的尺寸,一般使用Size(w,h)来指定,如Size(3,3);anchor表示锚
点,也就是处理的像素位于核的什么位置,默认值为(-1, -1),即位于核的中心点,如果没
有特殊需要,就不需要更改;borderType用于推断图像外部像素的某种边界模式,默认值
为BORDER_DEFAULT。
【例6.1】实现均值滤波

cpp
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <stdio.h>
using namespace std;
using namespace cv;
//定义全局变量
Mat g_srcImage; //定义输入图像
Mat g_dstImage; //定义目标图像
const int g_nTrackbarMaxValue = 9; //定义轨迹条最大值
int g_nTrackbarValue; //定义轨迹条初始值
int g_nKernelValue; //定义kernel尺寸
void on_kernelTrackbar(int, void*); //定义回调函数
int main()
{
g_srcImage = imread("cat.png");
//判断图像是否加载成功
if (g_srcImage.empty())
{
cout << "Fail to open file." << endl;
return -1;
}
namedWindow("src", WINDOW_AUTOSIZE); //定义窗口显示属性
imshow("src", g_srcImage);
g_nTrackbarValue = 1;
namedWindow("Mean filtering", WINDOW_AUTOSIZE); //定义滤波后图像显示窗口属性
//定义轨迹条名称和最大值
char kernelName[20];
sprintf_s(kernelName, "kernel size: %d", g_nTrackbarMaxValue);
//创建轨迹条
createTrackbar(kernelName, "Mean filtering", &g_nTrackbarValue, g_nTrackbarMaxValue, on_kernelTrackbar);//均值滤波
on_kernelTrackbar(g_nTrackbarValue, 0);
waitKey(0);
return 0;
}
void on_kernelTrackbar(int, void*)
{
//根据输入值重新计算kernel尺寸
g_nKernelValue = g_nTrackbarValue * 2 + 1;
//均值滤波函数
blur(g_srcImage, g_dstImage, Size(g_nKernelValue, g_nKernelValue));
imshow("Mean filtering", g_dstImage);//显示均值滤波结果图
}
2.2 高斯滤波器
高斯滤波是一种线性平滑滤波,对于除去高斯噪声有很好的效果。高斯滤波是对输入数组的每个点与输入的高斯滤波模板执行卷积计算,然后将这些结果一块组成滤波后的输出数组。通俗地讲,高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩码)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去代替模板中心像素点的值。
在图像处理中,高斯滤波一般有两种实现方式:一种是用离散化窗口滑窗卷积,另一种是通过傅里叶变换。常见的是第一种滑窗实现,只有当离散化的窗口非常大、滑窗计算量非常大的情况下,才会考虑基于傅里叶变换的方法。
学习高斯滤波前,有必要预先学习卷积的知识,并且知道以下几个术语:
- 卷积核(convolution kernel):用来对图像矩阵进行平滑的矩阵,也称为过滤器。
- 锚点:卷积核和图像矩阵重叠,进行内积运算后,锚点位置的像素点会被计算值取 代。一般选取奇数卷积核的中心点作为锚点。
- 步长:卷积核沿着图像矩阵每次移动的长度。
- 内积:卷积核和图像矩阵对应像素点相乘,然后相加得到一个总和(不要和矩阵乘 法混淆)。
高斯平滑即采用高斯卷积核对图像矩阵进行卷积操作。高斯卷积核是一个近似服从高斯分布的矩阵,随着与中心点的距离的增加,其值变小。这样进行平滑处理时,图像矩阵中锚点处的像素值权重大,边缘处的像素值权重小。
我们在参考其他文献的时候,会看到高斯模糊和高斯滤波两种说法,其实这两种说法是有一定区别的。我们知道滤波器分为高通、低通、带通等类型,高斯滤波和高斯模糊就是依据滤波器是低通滤波器还是高通滤波器来区分的。比如低通滤波器,像素能量低的通过;而对于像素能量高的部分,将会采取加权平均的方法重新计算像素的值,将能量较高的值变成能量较低的值。
对于图像而言,其高频部分展现图像细节,所以经过低通滤波器之后,整幅图像变成低频,造成图像模糊,这就被称为高斯模糊。与之相反,高通滤波器允许高频通过,而过滤掉低频,这样可以对低频像素进行锐化操作,使图像变得更加清晰。简单地讲就是高斯滤波是指用高斯函数作为滤波函数的滤波操作,而高斯模糊是用高斯低通滤波器的滤波操作。
高斯滤波在图像处理中常用来对图像进行预处理,虽然耗时,但是对于除去高斯噪声有很好的效果。高斯低通滤波器(Gaussian Low Pass Filter)是一类传递函数为高斯函数的线性平滑滤波器。又由于高斯函数是正态分布的密度函数,因此高斯低通滤波器对于去除服从正态分布的噪声非常有效。一维高斯函数和二维高斯函数(高斯低通滤波器的传递函数)的表达形式如下:


上面公式中,δ为标准差,由于图像通常是二维信号,因此图像去噪通常使用二维高斯函数作为传递函数;而高斯函数具有可分离的特性,因此可以先对行进行高斯滤波,再对列进行高斯滤波,这样二维高斯函数就可以降为一维高斯滤波函数。图6-4所示的左图和右图分别模拟了标准差为10和标准差为50的高斯函数。

从图中可以看出,高斯函数的标准差越大,高斯曲线越平滑,去噪能力越强,图像越模糊。在OpenCV中,提供函数GaussianBlur实现高斯滤波,该函数声明如下:
cpp
void GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX,
double sigmaY=0, int borderType=BORDER_DEFAULT );
其中参数src表示输入图像;dst表示输出图像;ksize表示内核大小;sigmaX表示高斯核函数在X方向的标准偏差;sigmaY表示高斯核函数在Y方向的标准偏差;borderType表示边界模式,默认值为BORDER_DEFAULT,一般可以忽略。
【例6.2】实现高斯滤波
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("lena.png");
if (!src.data)
{
printf("could not load image3...\n");
return -1;
}
//定义窗口名称
char input_title[] = "src";
char output_title[] = "Mean filtering";//均值滤波
//新建窗口
namedWindow(input_title, WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(input_title, src);//原图显示
/*均值滤波,Size里面都要奇数,正数。内核内的数值分别表示宽和高。Point(-1,-1)表示锚点,一般取-1,表示锚点在核中心*/
blur(src, dst, Size(15, 15), Point(-1, -1));
imshow(output_title, dst); //均值滤波操作
Mat gblur;
//高斯滤波操作
GaussianBlur(src, gblur, Size(15, 15), 11, 11);//高斯滤波
imshow("Gaussian filter", gblur);//显示高斯滤波结果图
waitKey(0);
return 0;
}
