查看全课大纲(第 8 / 93 节)
- 1.计算机视觉概述
- 2.计算机视觉应用场景
- 3.困难与挑战
- 4.开源库介绍
- 5.图像显示与存储
- 6.CLAHE
- 7.腐蚀膨胀与开闭运算
- 8.滤波与边缘填充
- 9.各种滤波
- 10.图像金字塔
- 11.傅里叶变换
- 12.短时傅里叶变换与小波变换
- 13.颜色特征与颜色直方图
- 14.几何特征_边缘
- 15.几何特征-角点与斑点
- 16.局部特征-SIFT特征描述子
- 17.局部特征-SURF特征描述子
- 18.局部特征-ORB特征描述子
- 19.其他特征描述子
- 20.传统图像分割技术
- 21.GraphCut
- 22.GrabCut
- 23.人脸检测的传统方法
- 24.行人检测的传统方法HOG+SVM
- 25.行人检测DPM
- 26.人工神经网络的架构
- 27.目标函数与梯度下降
- 28.误差反向传播
- 29.可手算的神经网络
- 30.改进目标函数与克服梯度消散
- 31.神经网络之传统神经网络与深度网络的差异
- 32.各种梯度下降算法
- 33.如何选择合适的梯度下降算法
- 34.避免过适应
- 35.CNN的架构
- 36.CNN的误差反向传播_卷积层
- 37.CNN的误差反向传播_池化层
- 38.分类所用的数据集
- 39.Alexnet详解
- 40.VGG详解
- 41.GoogLeNet_v1
- 42.BatchNormalization
- 43.GoogLeNet_v3v4
- 44.ResNet
- 45.ResNeXt
- 46.CNN设计准则
- 47.图像检索与推土机距离
- 48.基于颜色相似性
- 49.基于形状相似性
- 50.基于纹理相似性
- 51.基于局部特征相似性
- 52.DL的二值哈希编码
- 53.高维空间中检索KDTree
- 54.高维空间中检索LSH
- 55.目标识别任务综述
- 56.RCNN
- 57.SPP-Net
- 58.Fast R-CNN
- 59.Faster R-CNN
- 60.R-CNN系列模型回顾
- 61.R-FCN的设计思路
- 62.R-FCN的实现
- 63.YOLOv1
- 64.YOLOv2_9000
- 65.YOLOv3
- 66.图像分割应用场景与数据集
- 67.上采样的实现方式
- 68.FCN网络架构的由来
- 69.FCN构建细节
- 70.DeepLabv1
- 71.CRF
- 72.DeepLabv2
- 73.DeepLabv3
- 74.特定的医学影像分割任务
- 75.U-Net
- 76.3D U-Net
- 77.V-Net
- 78.DenseNet
- 79.病理切片分析任务及数据集
- 80.病理切片分析
- 81.图说任务及其传统处理方法
- 82.深度语言模型与RNN
- 83.LSTM及其变种
- 84.NIC模型
- 85.SAT模型
- 86.图说的性能指标
- 87.判别模型与生成模型
- 88.KL散度与JS散度
- 89.VAE
- 90.GAN的基本原理
- 91.DCGAN与CGAN
- 92.Wasserstein GAN
- 93.SRGAN
滤波与边缘填充
小象实战讲义 · 基于深度学习的计算机视觉
在图像预处理中,我们常常需要对图像进行局部处理,例如平滑去噪、锐化或边缘检测。这些操作的核心是邻域运算,即一个像素点的输出值不仅取决于它自身,还取决于其周围像素的值。本节我们将深入讲解邻域运算的核心——滤波(卷积),以及处理图像边界时必须面对的边缘填充问题。掌握这些基础,是理解后续深度学习卷积神经网络的关键。
💡 核心导读
- 滤波即卷积:理解在图像每个位置上,基于邻域像素和权重模板进行加权求和的运算本质。
- 扫描窗与边界问题:卷积核如何逐像素滑动,以及当它移动到图像边界时,如何处理“越界”的像素。
- 边缘填充策略:学习补零、边界复制、镜像和块复制四种填充方式,并了解为何“补零”最常用。
- 均值滤波示例:通过一个具体的均值滤波例子,直观感受卷积运算过程及其对图像的影响。
从邻域运算到卷积
在前面的知识点中,我们介绍了点运算和形态学运算。现在,我们进入邻域运算。顾名思义,邻域运算是指,图像中某个像素点经过变换后的值,不仅由该点自身的原始值决定,还受到其周围(邻域)像素点值的影响。这种运算方式非常强大,是实现图像平滑、锐化、边缘检测等效果的基础。
在邻域运算中,最核心的操作就是卷积(Convolution)。有时我们也会听到“滤波”(Filtering)这个词。强调运算的具体操作时,我们多用“卷积”;而强调运算达到的效果(如平滑、边缘提取)时,我们则多用“滤波”。但从实际操作层面看,两者基本是等价的。
卷积的数学定义如下:
其中:
- 是原始输入图像。
- 是卷积核(Kernel)、也叫滤波器、卷积模板或滤波模板。它是一个小矩阵,定义了邻域内每个位置的权重。
- 是在图像位置 处的卷积结果。
- 和 是卷积核内部的坐标索引,其变化范围覆盖了整个卷积核。
关键词解析:
- 卷积核/滤波模板:一个带有权重的矩阵,是卷积运算的核心,决定了运算的效果(如平滑、边缘检测)。
- 扫描窗:因为卷积核需要从图像的左上角开始,逐行逐列地滑动到每一个像素位置进行计算,这个过程就像用一个窗口在图像上扫描。
不同的卷积核可以实现截然不同的功能:
- 平滑/去噪:如均值滤波、高斯滤波。
- 梯度/锐化:如Sobel、Prewitt算子,用于边缘检测或增强细节。
- 特征提取:识别边缘、角点、纹理等。
- 模式检测:匹配特定的图案。
这里有一个非常重要的联系:深度学习中的卷积操作,其概念完全来源于这里所讲的图像处理中的卷积。只不过在深度学习中,卷积核里的权重值不是人工设计的,而是通过训练数据自动学习得到的。
卷积运算的直观理解
让我们通过一个具体的例子来直观理解卷积。
假设我们有一小块图像(蓝色部分)和一个3x3的卷积核(黄色部分),卷积核内每个权重都是1。
import numpy as np
# 定义一小块图像(3x3)
image_patch = np.array([[4, 1, 6],
[7, 2, 3],
[9, 5, 8]], dtype=np.float32)
# 定义一个3x3的全1卷积核(均值滤波核的未归一化形式)
kernel = np.ones((3, 3), dtype=np.float32)
# 手动计算卷积:对应位置相乘后求和
result = np.sum(image_patch * kernel)
print(f"卷积运算结果(对应位置乘加):{result}")卷积运算结果(对应位置乘加):45.0运算过程是:将卷积核中心对准图像的一个像素,让卷积核的每个权重与图像对应位置的像素值相乘,然后将所有乘积结果相加,得到该位置卷积后的输出值。上例中,4*1 + 1*1 + 6*1 + 7*1 + 2*1 + 3*1 + 9*1 + 5*1 + 8*1 = 45。
接着,卷积核会像扫描窗一样,向右移动一个像素(步长为1),重复上述计算,得到下一个输出值。如此循环,直至遍历整张图像。
# 使用OpenCV的filter2D函数演示扫描窗式的卷积(这里使用相关运算,对于全1核,相关与卷积结果相同)
import numpy as np
import cv2
kernel = np.ones((3, 3), dtype=np.float32)
# 为了演示,我们构造一个稍大的图像;filter2D 会在内部对越界位置补零
demo_image = np.array([[4, 1, 6, 10],
[7, 2, 3, 11],
[9, 5, 8, 12],
[13,14,15,16]], dtype=np.float32)
# cv2.filter2D默认执行的是相关(correlation),但对于对称核,相关等于卷积。
# anchor 使用默认值 (-1,-1)(即核中心),并用 BORDER_CONSTANT 以 0 填充越界位置,以模拟我们描述的过程。
conv_result = cv2.filter2D(demo_image, -1, kernel, borderType=cv2.BORDER_CONSTANT)
print("使用cv2.filter2D对整个图像进行‘卷积’(相关)的结果:")
print(conv_result)使用cv2.filter2D对整个图像进行‘卷积’(相关)的结果:
[[14. 23. 33. 30.]
[28. 45. 58. 50.]
[50. 76. 86. 65.]
[41. 64. 70. 51.]]可以看到,输出图像左上角第一个值是 14。这是因为卷积核中心对准 (0,0) 时,核的左上部分已经越出图像边界,cv2.filter2D 按 BORDER_CONSTANT 把这些越界位置当作 0,于是参与计算的 3x3 区域是 [0,0,0; 0,4,1; 0,7,2],乘加后正好是 0+0+0+0+4+1+0+7+2=14。这也直观说明了:卷积核在图像上滑动,在每个位置进行乘加运算;当核移动到边界时,部分权重会落在图像外部,这就引出了下一个核心问题——边缘填充。
# 手动验证左上角(0,0)位置的输出
# 假设图像外围补了一圈0,那么参与计算的3x3区域是:
# [0,0,0; 0,4,1; 0,7,2] 与 全1核 相乘相加
manual_calc = 0+0+0 + 0+4+1 + 0+7+2
print(f"手动计算图像左上角经过补零后卷积的结果:{manual_calc}")手动计算图像左上角经过补零后卷积的结果:14手动计算结果(14)与filter2D的输出(14)完全一致,说明在默认锚点(核中心)与补零边界下,卷积核的扫描乘加过程正如我们所描述。当卷积核移动到边界时,部分权重会落在图像外部,这就引出了下一个核心问题——边缘填充。
边界填充(Padding)
观察卷积的扫描过程,你会发现一个问题:对于一个 N x N 的图像和一个 K x K 的卷积核,如果不做任何处理,输出图像会变小。因为卷积核中心需要对准每个像素,当中心对准图像最边缘的像素时,卷积核的一部分已经超出了图像范围。
为了得到与输入图像尺寸相同的输出,或者更一般地,为了控制输出尺寸,我们必须在图像的边缘外侧“虚构”出一些像素值,供越界的卷积核权重使用。这就是边界填充。
常见的填充策略有以下四种:
补零(Zero-padding):在图像外围填充数值0。这是最常用、最简单的方法。可以将其理解为假设图像外部是全黑的区域。
原图 [1,2,3] 补零后 [0,0,0,0,0] [4,5,6] [0,1,2,3,0] [7,8,9] [0,4,5,6,0] [0,7,8,9,0] [0,0,0,0,0]边界复制(Replication):用图像边缘像素的值来填充外部区域。
原图 [1,2,3] 边界复制 [1,1,2,3,3] [4,5,6] [1,1,2,3,3] [7,8,9] [4,4,5,6,6] [7,7,8,9,9] [7,7,8,9,9]镜像(Reflection):以图像边界为轴,镜像反射内部的像素值来填充。
原图 [1,2,3] 镜像填充(BORDER_REFLECT_101,四周各 1 层) [4,5,6] [7,8,9] [5,4,5,6,5] [2,1,2,3,2] [5,4,5,6,5] [8,7,8,9,8] [5,4,5,6,5]块复制(Wraparound):假设图像在平面上是无限周期平铺的,用图像另一侧的像素来填充。类似于Windows桌面背景的“平铺”效果。
原图 [1,2,3] 块复制 [9,7,8,9,7] [4,5,6] [3,1,2,3,1] [7,8,9] [6,4,5,6,4] [9,7,8,9,7] [3,1,2,3,1]
在实际应用中,尤其是深度学习中,补零(Zero-padding) 因其实现简单且不会引入图像外部先验信息而成为最主流的选择。虽然它可能在边界处引入不真实的黑色边缘,但对于网络训练而言,这是可接受的代价。
均值滤波:一个完整的例子
现在我们用一个具体的例子——均值滤波,来串联起卷积和边缘填充的概念。均值滤波使用一个所有元素值相等的卷积核,其作用是取邻域内像素的平均值,从而达到平滑图像的效果。
假设我们有一张6x6的原始图像,其像素值分布如下,具有非常清晰的边界:
原始图像 (6x6):
9 9 9 18 18 18
9 9 9 18 18 18
9 9 9 18 18 18
18 18 18 9 9 9
18 18 18 9 9 9
18 18 18 9 9 9我们使用一个3x3的均值滤波核,其每个权重为 1/9。为了得到相同尺寸的输出,我们采用补零填充。因此,实际参与计算的图像是外围补了一圈零的8x8图像。
# 构造原始图像(6x6)
import numpy as np
import cv2
original_img = np.array([
[9, 9, 9, 18, 18, 18],
[9, 9, 9, 18, 18, 18],
[9, 9, 9, 18, 18, 18],
[18, 18, 18, 9, 9, 9],
[18, 18, 18, 9, 9, 9],
[18, 18, 18, 9, 9, 9]
], dtype=np.float32)
# 定义3x3均值滤波核 (每个元素=1/9)
mean_kernel = np.ones((3, 3), dtype=np.float32) / 9.0
# 应用滤波,使用补零填充(cv2.BORDER_CONSTANT默认填充0)
filtered_img = cv2.filter2D(original_img, -1, mean_kernel, borderType=cv2.BORDER_CONSTANT)
print("原始图像 (左上角 4x4 区域):")
print(original_img[:4, :4])
print("\n经过3x3均值滤波(补零填充)后的图像 (左上角 4x4 区域):")
# 设置打印选项,使输出更紧凑易读
np.set_printoptions(precision=2, suppress=True)
print(filtered_img[:4, :4])原始图像 (左上角 4x4 区域):
[[ 9. 9. 9. 18.]
[ 9. 9. 9. 18.]
[ 9. 9. 9. 18.]
[18. 18. 18. 9.]]
经过3x3均值滤波(补零填充)后的图像 (左上角 4x4 区域):
[[ 4. 6. 8. 10.]
[ 6. 9. 12. 15.]
[ 8. 12. 13. 14.]
[10. 15. 14. 13.]]我们来手动验证第一个值 4.0 是如何计算出来的。对于输出图像位置(0,0),卷积核覆盖的输入区域(已补零)为:
[0, 0, 0]
[0, 9, 9]
[0, 9, 9]计算:(0+0+0 + 0+9+9 + 0+9+9) / 9 = 36 / 9 = 4.0。与输出一致。
观察滤波结果,原本清晰的边界(如从9到18的突变)变得模糊了,取而代之的是一个渐变的过渡区域(如从9渐变到12、15等)。这就是均值滤波的副作用:它在平滑噪声的同时,也会模糊图像的重要细节和边缘。
因此,在实战中,单纯的均值滤波并不常用。我们将在后续知识点中介绍更优秀的平滑滤波器(如高斯滤波)和边缘检测滤波器。
📝 动手练一练
- 概念辨析:假设对一个5x5的图像,使用一个3x3的卷积核进行卷积,步长为1,并且希望输出图像的尺寸仍然是5x5。请问需要在原始图像的四周填充多少层像素?如果使用补零填充,请画出填充后图像的示意图(只需画出轮廓,标明填充区域)。
👉 点击查看参考答案
答案: 需要填充1层像素。
对于一个 N x N 的输入,一个 K x K 的核,步长 S=1,希望输出尺寸不变,填充层数 P 满足公式: N + 2P - K + 1 = N => 2P - K + 1 = 0 => P = (K - 1) / 2。 对于 K=3,P = (3-1)/2 = 1。
补零填充后的图像为7x7,示意图如下(I代表原图像素,0代表填充的零):
0 0 0 0 0 0 0
0 I I I I I 0
0 I I I I I 0
0 I I I I I 0
0 I I I I I 0
0 I I I I I 0
0 0 0 0 0 0 0- 手算卷积:给定一个3x3的图像块
[[1,2,3],[4,5,6],[7,8,9]]和一个3x3的卷积核[[0,-1,0],[-1,5,-1],[0,-1,0]]。请手动计算以图像块中心像素5为卷积中心时的卷积结果(无需填充,只计算中心点对应的输出)。
👉 点击查看参考答案
答案: 卷积计算过程(对应位置相乘后求和):
(1*0) + (2*-1) + (3*0) +
(4*-1) + (5*5) + (6*-1) +
(7*0) + (8*-1) + (9*0)
= 0 + (-2) + 0 + (-4) + 25 + (-6) + 0 + (-8) + 0
= 25 - 20
= 5因此,卷积结果为 5。这个核是一个常见的锐化核,它增强了中心像素相对于邻域的值。
本章小结
本节我们深入探讨了图像预处理中至关重要的邻域运算——滤波(卷积)。我们理解了卷积的数学定义和直观的“扫描窗”操作过程,并解决了随之而来的边界问题,学习了边缘填充的四种策略。最后,通过一个均值滤波的实例,我们完整演练了卷积过程,并认识到简单均值滤波的局限性。
📋 行动清单
- 在脑海中复现卷积核在图像上滑动的“扫描窗”过程。
- 理解并能够解释为什么需要进行边缘填充,以及补零填充为何最常用。
- 尝试手动计算一个3x3图像块与一个3x3卷积核的卷积结果,以巩固乘加运算的概念。
- 思考:如果使用边界复制填充代替补零填充,本节中的均值滤波例子在图像边缘的结果会有什么不同?
—— 小象教研组