图像的卷积过程可以看成一个卷积模板在一幅的图像上移动,然后对每个卷积模板覆盖的区域对应位乘积并求和,以得到中心像素的输出值。
卷积模板又称为卷积核或内核,是一个固定大小的二维矩阵,矩阵中存放着预先设定的数值。
图像的卷积过程可分为以下5个步骤。
1)将卷积模板旋转 180° 当卷积模板中的数据是中心对时,这一步可以省略
。但如果卷积模板不中心对称,则必须将模板进行旋转。
2)将 3 x 3 的卷积模板放在原图像中需要计算卷积的区域上,如下图所示。卷积模板和待卷积矩阵中的阴影区域分别是卷积模板的中心和对应点,结果中的阴影区域为模板覆盖的区域。
3)用卷积模板中的系数乘以图像总对应位置的像素值,并对所有结果求和,针对上图中的例子,计算过程如下所示。
r e s u l t = 1 ∗ 1 + 2 ∗ 2 + 3 ∗ 1 + 6 ∗ 2 + 7 ∗ 0 + 8 ∗ 2 + 11 ∗ 1 + 12 ∗ 2 + 13 ∗ 1 = 84 result=1*1+2*2+3*1+6*2+7*0+8*2+11*1+12*2+13*1=84 result=1∗1+2∗2+3∗1+6∗2+7∗0+8∗2+11∗1+12∗2+13∗1=84
最终计算结果为84。
4)将计算结果存放在原图像中与卷积模板中心对应的像素处,如下图所示
5)将卷积模板在图像中从左至右、从上到下,重复第(2)、(3)、(4)步,直到处理完所有的像素值。计算过程如下图所示。
从上图中可以看出,图像的边缘位置没有进行卷积运算,为了解决这个问题,我们可以将图像的边缘外推出去。例如,在用 3 x 3 的卷积模板进行运算时,可以用 0 在原图像周围增加一圈像素,从而解决模板图像中部分数据没有对应像素的问题。
此外,我们还能发现最后一个像素值已经接近 uint8 数据类型的最大值。如果卷积模板选择不当,很有可能发生卷积结果超出数据范围的情况。因此,我们可以缩放卷积模板使所有数值的和为1,从而解决卷积后数值越界的问题。
在OpenCV4中可以使用cv.filter2D()函来实现图像和卷积模板之间的卷积运算。
#cv.filter2D()函数原型
dst = cv.filter2D(src,
ddepth,
kernel
[, dst
[, alpha
[, anchor
[, delta
[, borderTyp]]]])
其中各返回值和参数的含义分别为:
src:输入图像
ddepth:输入图像的数据类型(深度)
kernel:单通道卷积核,数据类型为 float32,卷积核大小多为 3 x 3、5 x 5等。
dst:输出图像
anchor:卷积模板的内核基准点(锚点)
delta:偏差,在计算结果中加上偏差
border:像素边界外推法选择标志
该函数用于求取图像和卷积模板之间的卷积,并将卷积后的结果通过值返回。
示例代码
# -*- coding:utf-8 -*-
import cv2 as cv
import numpy as np
import sys
if __name__ == '__main__':
# 1. 以矩阵为例
src = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]], dtype='float32')
kernel1 = np.array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype='float32') / 9
result = cv.filter2D(src, -1, kernel=kernel1)
print('卷积前矩阵:\n{}'.format(src))
print('卷积后矩阵:\n{}'.format(result))
# 2. 以图像为例
# 读取图像并判断是否读取成功
img = cv.imread('../images/cat.jpg')
if img is None:
print('Failed to read cat.jpg.')
sys.exit()
kernel2 = np.ones((7, 7), np.float32) / 49
result2 = cv.filter2D(img, -1, kernel=kernel2)
# 展示结果
cv.imshow('Origin Image', img)
cv.imshow('Filter Result', result2)
cv.waitKey(0)
cv.destroyAllWindows()
运行结果如下图所示。
从运行结果可以看出,虽然图像内容一致,但是图像整体变模糊了,由此可见该卷积模板有模糊图像的作用。
在OpenCV4中可以使用cv.filp()函来实卷积模板旋转180°。
示例代码
# -*- coding:utf-8 -*-
import cv2 as cv
import numpy as np
if __name__ == '__main__':
src = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20],
[21, 22, 23, 24, 25]], dtype='float32')
dst = cv.flip(src, -1)
print('原卷积模板为:\n{}'.format(src))
print('旋转180°后的卷积模板为:\n{}'.format(dst))