六、滤波器
1、卷积
1.1 什么是图像卷积
图像卷积就是卷积核在图像上按行滑动遍历像素时不断的相乘求和的过程。
1.2 步长
步长就是卷积核在图像上移动的步幅。
上面的例子中卷积核每次移动一个像素步长的结果,如果将这个步长修改为2,结果会如何?
为了充分扫描图像,步长一般设为1。
1.3 padding
从上面例子中我们发现,卷积之后图像的长宽会变小,如果要保持图像大小不变,我们需要在图像周围填充0,padding指的就是填充0的圈数。
我们可以通过公式计算出需要填充0的圈数:
- 输入体积大小:H1 * W1 * D1
- 四个超参数:
- Filter数量K
- Filter大小F
- 步长S
- 零填充大小P
- 输出体积大小:H2 * W2 * D2
- H2 = (H1 - F + 2P) / S + 1
- W2 = (W1 - F + 2P) / S +1
- D2 = K
如果要保持卷积之后图像大小不变,可以得出等式:(N + 2P - F + 1) = N 从而可以推导出 P = F − 1 2 P = \frac {F-1} {2}P=2F−1
1.4 卷积核的大小
图像卷积中,卷积核一般为奇数,比如 3 * 3,5 * 5,7 * 7,为什么一般是奇数呢?出于以下两个方面的考虑:
1、根据上面padding的计算公式,如果要保持图像大小不变,采用偶数卷积核的话,比如 4 * 4,将会出现填充1.5圈零的情况。
2、奇数维度的滤波器有中心,便于指出滤波器的位置,即OpenCV卷积中的锚点。
1.5 卷积案例
filter2D()
用法:
cv2.filter2D(src, ddepth, kernel, dst, anchor, delta, borderType)
参数说明:
- ddepth:卷积之后图像的位深,即卷积之后图像的数据类型,一般设为-1,表示和原图像类型一致。
- kernel:卷积核大小,用元组或者ndarray表示,要求数据类型必须是float型。
- anchor:锚点,即卷积核的中心点,是可选参数,默认是(-1, -1)。
- delta:可选参数,表示卷积之后额外加的一个值,相当于线性方程中的偏差,默认是0。
- borderType:边界类型,一般不设置。
代码实现(锐化)
import cv2 import numpy as np img = cv2.imread('../resource/r_cat.jpg') # 相当于原始图像中的每个点都被平均了一下,所以图像变模糊了 # kernel = np.ones((5, 5), np.float32) / 25 # 突出轮廓 # kernel = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]) # 浮雕效果 # kernel = np.array([[-2, 1, 0], [-1, 1, 1], [0, 1, 2]]) # 锐化 kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) dst = cv2.filter2D(img, -1, kernel) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
2、方盒滤波和均值滤波
2.1 方盒滤波
boxFilter()
用法:
cv2.boxFilter(src, ddepth, ksize, dst, anchor, normalize, borderType)
参数说明:
- ddepth:卷积之后图像的位深,即卷积之后图像的数据类型,一般设为-1,表示和原图像类型一致。
- Ksize:方盒滤波卷积核大小
方盒滤波的卷积核形式如下:
- 当 normalize = True 时,a = 1 / (W * H) 滤波器的宽高
- 当 normalize = False 时,a = 1
- 一般情况下都使用 normalize = True,这是 方盒滤波 等价于 均值滤波
代码实现:
import cv2 import numpy as np img = cv2.imread('../resource/r_cat.jpg') # 不用手动创建卷积核,只需要告诉方盒滤波,卷积核的大小是多少 dst = cv2.boxFilter(img, -1, (5, 5), normalize=True) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
2.2 均值滤波
blur()
用法:
cv2.blur(src, ksize, dst, anchorborderType)
参数说明:
- anchorborderType:用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT
代码实现:
import cv2 import numpy as np img = cv2.imread('../resource/r_cat.jpg') dst = cv2.blur(img, (5, 5)) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
3、高斯滤波
要理解高斯滤波首先要知道什么是高斯函数:
高斯函数是符合高斯分布(也叫正态分布)数据的概率密度函数。
高斯函数的特点是以x轴某一点(这一点称为均值)为对称轴,越靠近中心数据发生的概率越高,最终形成一个两边平缓,中间陡峭的钟型(也被称为帽子)图形。
高斯函数的一般形式为:
高斯滤波就是使用符合高斯分布的卷积核对图像进行卷积操作,所以高斯滤波的重点是如何计算符合高斯分布的卷积核,即高斯模板。
假定中心点的坐标是(0, 0),那么取距离它最近的8个点坐标,为了计算,需要设 定σ \sigmaσ 的值。假定 σ \sigmaσ = 1.5,则模糊半径为1的高斯模板如下:
我们可以观察到越靠近中心,数值越大,越边缘的数值越小,符合高斯分布的特点。
通过高斯函数计算出来的是概率分布函数,所以我们还要确保这九个点加起来为1,这九个点的权重总和等于0.4787147,因此,上面九个值还要分别除以0.4787147,得到最终的高斯模板。
注:有些整数高斯模板是在归一化后的高斯模板的基础上每个数除以左上角的值,然后取整。
有了卷积核,计算高斯滤波就简单了。假设现在有9个像素点,灰度值(0 ~ 255)的高斯滤波计算如下:
将这9个值加起来,就是中心点的高斯滤波的值。对所有点重复这个过程,就得到了高斯模糊后的图像。
GaussianBlur()
用法:
cv2.GaussianBlur(src, ksize, sigmaX, dst, sigmaY, borderType)
参数说明:
- ksize:高斯核的大小
- sigmaX:X轴的标准差
- sigmaY:Y轴的标准差,默认为0,这时 sigmaY = sigmaX
- 如果没有指定sigma值,则会分别从ksize的宽度和高度中计算sigma
- 选择不同的sigma值会得到不同的平滑效果,sigma越大,平滑效果越明显。
代码实现一: 模糊
import cv2 import numpy as np img = cv2.imread('../resource/r_cat.jpg') # dst = cv2.GaussianBlur(img, (9, 9), sigmaX=100) # 如果不指定sigmaX,会使用ksize计算sigma dst = cv2.GaussianBlur(img, (9, 9), sigmaX=0) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
代码实现二: 去噪
import cv2 import numpy as np img = cv2.imread('../resource/cv.webp') dst = cv2.GaussianBlur(img, (5, 5), sigmaX=1) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()
4、中值滤波
中值滤波原理非常简单,假设有一个数组[1 5 5 6 7 8 9],取1其中的中间值(即中位数)作为卷积后的结果值即可。
中值滤波对胡椒噪音(也叫椒盐噪音)效果明显。
medianBlur()
用法:
cv2.medianBlur(src, ksize, dst)
参数说明:
- ksize:就是一个数字
代码实现:
import cv2 import numpy as np img = cv2.imread('../resource/cv_noise.webp') dst = cv2.medianBlur(img, 5) cv2.imshow('img', np.hstack((img, dst))) cv2.waitKey(0) cv2.destroyAllWindows()