图像的平滑处理是在尽量图像原有信息的情况下,过滤掉图像内部的噪声。由于图像平滑处理的同时通常伴随着图像的模糊操作,有时图像平滑处理也称为图像模糊处理。在计算机中有时也被成为图像滤波。这只是一个名称,不同的地方可能有不同的说法,不要太过于纠结。
- 图像平滑处理的目的是:过滤掉图像内部噪声
图像平滑处理的实质:是将图像中与周围像素点的像素值差异较大的进行处理,将其值调整为周围像素点像素值的近似值。
如图所示:
位于第3行第3列的像素点,与周围像素点值的大小存在明显差异。反映在图像上,该点周围的像素点都是灰度点,而该点的颜色较深,是一个黑色点可能是噪声,需要将该点的值调整为周围像素值的近似值。
图像平滑处理的方式有很多,下面主要介绍:
- 均值滤波
- 方框滤波
- 高斯滤波
- 中值滤波
- 双边滤波
- 2D卷积(自定义滤波)
一、均值滤波:
均值滤波是指以当前像素点为中心,使用当前像素点周围N * N个像素值的均值来代替当前像素值。使用该方法遍历处理图像内每一个像素点,既可以完成整幅图像的均值滤波。
针对图像边缘像素点,可以只取图像内存在的周围邻域点的像素值的均值。也可以将图像边缘扩展后,可以在新增的行和列内填充不同的像素值,在此基础上,再针对原始图像进行N · N邻域像素点的均值计算。OpenCV提供了多种边界处理方式,我们可以根据实际需要选用不同的边界处理模式。
1、函数语法:
-
在OpenCV中,实现均值的函数是cv2.blur(),其语法格式:
-
dst = cv2.blur(src, ksize, anchor, borderType)
-
dst:返回值,均值滤波处理后得到的处理结果。
-
src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
-
ksize:是滤波核的大小。滤波核大小指在均值处理过程中,其邻域图像的高度和宽度。例如,其值可以是(5, 5),表示以5×5大小的邻域均值作为图像均值滤波处理的结果。
M和N分别对应高度和宽度。一般情况下,M和N是相等的,例如比较常用的3×3、5×5、7×7等。如果M和N的值越大,参与运算的像素点数量就越多,图像失真越严重。
-
anchor:锚点,其默认值是(-1,-1), 表示当前计算均值的点位于核的中心点位置,该值使用默认值即可,在特殊情况下可以指定不同的点作为锚点。
-
borderType:边界样式,该值决定了以何种方式来处理边界,一般情况下不需要考虑该值的取值,采用默认情况即可。
-
-
示例:
import cv2
img = cv2.imread('../lena512color_noise.tiff')
dst1 = cv2.blur(img, (7, 7))
dst2 = cv2.blur(img, (15, 15))
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()
cv2.destroyAllWindows()
二、方框滤波:
方框滤波与均值滤波不同,方框滤波不会计算像素均值。在均值滤波中,滤波结果是任意一点的邻域平均值。方框滤波可以自由选择对均值滤波的结果是否进行归一化,既可以自由选择滤波结构是邻域之和,还是邻域之和的平均值。
1. 方框滤波语法:
在OpenCV中,实现方框滤波的函数是cv2.boxFilter(),其语法格式是:
dst = cv2.boxFilter(src, ddepth, ksize, anchor, normalize, borderType)
-
dst:返回值,均值滤波处理后得到的处理结果。
-
src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
-
ddepth:结果图像的图像深度,一般使用-1表示与原始图像使用相同的图像深度。
-
ksize:是滤波核的大小。滤波核大小指在均值处理过程中,其邻域图像的高度和宽度。例如,其值可以是(5, 5),表示以5×5大小的邻域均值作为图像均值滤波处理的结果。
-
anchor:锚点,其默认值是(-1,-1), 表示当前计算均值的点位于核的中心点位置,改制使用默认值即可,在特殊情况下可以指定不同的点作为锚点。
-
normalize:表示在滤波时是否进行归一化处理(归一化:这里指的是将计算结果规范到合理的范围内,即将计算结果规范到当前像素值范围内的值)处理。该参数是一个逻辑值。
- 当参数normalize=1时,表示进行归一化处理,要用邻域像素值得和处理面积。
- 当参数normalize=0时,表示不需要进行归一化处理,直接使用邻域像素值的和。(当和超出255时为255)
通常情况下,针对方框滤波,卷积核可以表示为:
上述对应关系为:
f
(
n
)
=
{
1
/
w
i
d
t
h
⋅
h
e
i
g
h
t
,
normalize=1
1
,
normalize=0
f(n)= \begin{cases} 1/width·height, & \text{normalize=1}\\ 1,& \text{normalize=0} \end{cases}
f(n)={1/width⋅height,1,normalize=1normalize=0
- borderType:边界样式,该值决定了以何种方式来处理边界。
通常情况下,在使用方框滤波函数时,对于参数anchor、normalize、borderType,直接采用默认值即可。
示例:
import cv2
img = cv2.imread('../lena512color_noise.tiff')
rst = cv2.boxFilter(img, -1, (7, 7))
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
三、高斯滤波:
在均值滤波和方框滤波中,其邻域内每个像素的权重是相等的。在高斯滤波中,会将离中心点近的权重加大,离中心点远的权重减小,在此基础上计算邻域内各个像素值不同权重的和。
1. 基本原理
-
在高斯滤波中,卷积核的值不在都是1。如图
-
在高斯滤波中,卷积核的宽度和高度可以不相同,但是他们必须都是奇数。
-
每一种尺寸的卷积核都可以有多种不同形式的权重比例。如图同样是5x5的卷积核。
在不同的资料中,卷积核有多种不同的表示方式。它们可能写在一个表格内,也可能示写在一个矩阵内。在实际计算中,卷积核是归一化处理的,这种处理可以表示为小数形式的卷积核,也可以表示为分数形式。没有经过归一化处理的卷积核,是为了说明问题用的。严格来讲,使用没有进行归一化处理的卷积核进行滤波,得到的结果往往是错误的。
2. 函数语法:
在OpenCV中,实现高斯滤波的函数cv2.GaussianBlur(),该函数的语法格式是:
dst = cv2.GauusianBlur(src, ksize, sigmaX, sigmaY, borderType)
-
dst:返回值,均值滤波处理后得到的处理结果。
-
src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
-
ksize:是滤波核的大小。滤波核大小指在均值处理过程中,其邻域图像的高度和宽度。但需要注意,滤波核的值必须是奇数。
-
sigmaX:卷积核在水平方向上(X轴方向)的标准差,其控制的是权重比例。
-
sigmaY:卷积核在垂直方向上(Y轴方向)的标准差,如果将该值设置为0,则只采用sigmaX的值;如果sigmaX、sigmaY都是0,则通过ksize.width和ksize.height计算得到:
- sigmaX = 0.3 × [(ksize.width-1) × 0.5-1] + 0.8
- sigmaY = 0.3 × [(ksize.height-1) × 0.5-1] + 0.8
-
borderType:边界样式,该值决定了以何种方式来处理边界。一般情况下,不需要考虑该值,直接采用默认值。
注意:在该函数中sigmaY、borderType是可选参数。sigmaX是必选参数,但是可以将其设置为0,让函数自己去计算sigmaX的具体值。
官方文档建议显示的指定ksize、sigmaX、sigmaY三个参数的值,以避免将来参数修改后可能造成的语法错误。当然,实际处理中,可以显示的指定sigmaX、sigmaY为默认值0。因此,函数cv2.GaussianBlur()的常用形式为:
-
dst = cv2.GaussianBlur(src, ksize, 0, 0)
import cv2
o=cv2.imread("image\\lenaNoise.png")
r=cv2.GaussianBlur(o,(5,5),0,0)
cv2.imshow("original",o)
cv2.imshow("result",r)
cv2.waitKey()
cv2.destroyAllWindows()
四、中值滤波
中值滤波不同于前面的滤波方式,不再采用滤值的方式计算滤波的结果。它采用的是邻域内所有像素值的中间值来替代当前像素点的像素值。
1. 基本原理:
中值滤波会取当前像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将其进行排序,然后将位于中间位置的像素值作为当前像素点的像素值。
2. 函数语法:
在OpenCV中,实现中值滤波的函数是cv2.medianBlur(),其语法格式是:
dst = cv2.medianBlur(src, ksize)
-
dst:返回值,均值滤波处理后得到的处理结果。
-
src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
-
ksize:是滤波核的大小。滤波核大小指在均值处理过程中,其邻域图像的高度和宽度。但需要注意,滤波核的值必须是比1大的奇数, 比如3、5、7等。
示例:
import cv2
img = cv2.imread('../lena512color_noise.tiff')
rst = cv2.medianBlur(img, 5)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
从结果可以看出,由于没有进行均值处理,中值滤波不存在均值滤波等滤波方式带来的细节模糊问题。在中值滤波处理中,噪声成分很难被选上,所以可以在几乎不影响原有图像的情况下取出全部噪声。但缺点是由于需要进行排序等操作,中值滤波需要的运算量较大。
五、双边滤波:
双边滤波是综合考虑空间信息和色彩信息的滤波方式,在滤波过程中能够有效地保护图像内的边缘信息。
1. 基本原理:
前面的滤波方式都只考虑了空间的权重信息,这种情况计算起来比较方便,但在处理边缘信息时会存在较大问题。例如,图像左侧是黑色,右侧是白色,中间是很明显的边缘。
在均值滤波、方框滤波、高斯滤波中,都会计算边缘上各个像素点的加权平均值,从而模糊边缘信息。如下图是高斯滤波处理过的图像:
双边滤波在计算某一像素点的新值时,不仅考虑距离信息(距离越远,权重越小)还考虑色彩信息(色彩差别越大,权重越小)。双边滤波综合考虑距离和色彩的权重结构,既能有效的去除噪声,又能够较好的保护边缘信息。
在双边滤波中,当处在边缘时,与当前点色彩相近的像素点(颜色距离很近)会被给予较大的权重值;而与当前色彩差别较大的像素点(颜色距离很远)会被给予较小的权重值(极端情况下权重可能为0,直接忽略该点),这样就保护了边缘信息。
2. 函数语法:
在OpenCV中,实现双边滤波的函数是cv2.bilateralFilter(),该函数的语法是:
dst = cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace, borderType)
- dst:返回值,均值滤波处理后得到的处理结果。
- src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
- d:在滤波时选取的空间距离参数,这里表示以当前为中心点的直径。如果该值为非正数,则会自动从参数sigmaSpace计算得到如果滤波空间较大(d>5),则速度较慢。因此,在实际应用中推荐d=5。对于较大噪声的离线滤波可以选择d=9。
- sigmaColor:滤波处理时选取的颜色差值范围,该值决定了周围哪些像素点能够参与到滤波中来。与当前像素点的像素值差值小于sigmaColor的像素点,能够参与到当前的滤波中。该值越大,说明周围有越多的像素点可以参与到运算。该值为0时,滤波失去意义;该值为255时,指定直径内所有点都能够参与运算。
- sigmaSpagce:是坐标空间内的sigma值。它的值越大,说明有越多的点能够参与到滤波计算中来。当d>0时,无论sigmaSpagce的值如何,d都指定邻域大小;否则,d与sigmaSpace的值成比例。
- borderType:边界样式,该值决定了以何种方式来处理边界。一般情况下,不需要考虑该值,直接采用默认值。
简单起见的话可以将sigmaColor、sigmaSpagce的值设为相同的。如果他们的值比较小(小于10)滤波效果不明显;如果他们的值比较大(例如:大于150),则滤波效果会比较明显,会产生卡通效果。
在cv2.bilateralFilter()函数中,除了borderType为可选参数外,其他均为必选参数。
import cv2
img = cv2.imread('../lena512color_noise.tiff')
rst = cv2.bilateralFilter(img, 25, 100, 100)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
由上面结果可以看出,双边滤波对去除噪声的效果并不好。双边滤波的优势体现在对边缘信息的处理上。
import cv2
import numpy as np
img = np.zeros((500, 500), dtype=np.uint8)
img[:, :250] = 255
rst = cv2.bilateralFilter(img, 25, 100, 100)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()
六、2D卷积
OpenCV提供了多种滤波方式,来是实现平滑图像的效果,例如均值滤波、方框滤波、高斯滤波、中值滤波等。大多数滤波方式所使用的卷积核都具有一定的灵活性,能够方便地设置卷积核的大小和数量。但是,我们有时候希望使用特定的卷积核来实现卷积操作,例如使用如下卷积核来进行卷积操作。
前面介绍过的滤波函数都无法将卷积核确定为上述形式,这时要使用OpenCV的自定义卷积函数。
在OpenCV中,允许用户自定义卷积核实现卷积操作,使用自定义卷积核实现卷积操作的函数是cv2.filer2D(),其语法格式为:
dst = cv2.filter2D(src, ddepth, kernel, anchor, delta, borderType)
-
dst:返回值,均值滤波处理后得到的处理结果。
-
src:需要处理的图像,即原始图像。可以有任意数量的通道,并能对各个通道独立处理。图像的深度应该是CV_8U、CV_16U、CV_16S、CV_32F、CV_64F中的一种。
-
ddepth:结果图像的图像深度,一般使用-1表示与原始图像使用相同的图像深度。
-
kenel:卷积核,是一个单通道的数组。如果想在处理彩色图像时,让每个通道使用不同的核,则必须将彩色图像分解后使用不同的核进行操作。
-
anchor:锚点,其默认值是(-1,-1), 表示当前计算均值的点位于核的中心点位置,改制使用默认值即可,在特殊情况下可以指定不同的点作为锚点。
-
delta:修正值。可选项,如果该值存在,会在基础滤波的结果上加上该值作为最终的滤波处理结果。
-
borderType:边界样式,该值决定了以何种方式来处理边界。一般情况下,不需要考虑该值,直接采用默认值。
通常情况下,使用滤波函数cv2.filter2D()时,对于参数anchor、delta、borderType直接采用默认值即可。
示例: 自定义一个卷积核,通过函数cv2.filter2D()应用该卷积核对图像进行滤波操作,并显示滤波结果。
import cv2
import numpy as np
img = cv2.imread('../lena512color_noise.tiff')
kernal = np.ones((9, 9), dtype=np.float32) / 81
rst = cv2.filter2D(img, -1, kernal)
cv2.imshow('img', img)
cv2.imshow('rst', rst)
cv2.waitKey()
cv2.destroyAllWindows()