查看全课大纲(第 70 / 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
DeepLabv1
DeepLab v1:空洞卷积与条件随机场
小象实战讲义 · 基于深度学习的计算机视觉
在上一节中,我们学习了全卷积网络(FCN),它通过卷积化、反卷积和跳层结构,首次实现了端到端的语义分割。然而,FCN也存在一些不足,例如其输出分辨率较低,边界分割不够精细。本节我们将介绍谷歌提出的DeepLab v1模型,它通过引入空洞卷积(Atrous/Dilated Convolution) 和全连接条件随机场(Fully Connected CRF),在保持感受野的同时获得了更高分辨率的特征图,并显著提升了分割边界的精度。
💡 核心导读
- 核心问题:FCN因多次池化导致特征图分辨率过低,边界信息丢失严重。
- 关键创新:DeepLab v1把
pool4、pool5的步长改为1,使特征图只下采样8倍;再用空洞卷积在不增加参数和计算量的前提下扩大感受野;最后用全连接条件随机场对分割边界进行精细化优化。 - 设计思路:将VGG网络的最后两个池化层步长改为1,降低下采样倍数;在后续卷积层中使用空洞卷积恢复感受野;最后用CRF模型迭代优化分割结果。
- 技术细节:理解空洞卷积中扩张率(rate) 的定义与作用,以及它与传统卷积、反卷积的联系与区别。
- 历史定位:DeepLab v1(2014年底提出,2015年发表于ICLR)是语义分割领域的重要里程碑,其空洞卷积思想被后续的DeepLab系列及其他分割模型广泛沿用。
从FCN的不足到DeepLab的改进
DeepLab v1的设计初衷源于对FCN缺点的深入思考。回顾FCN,为了获得最终的分类得分图,输入图像需要经过多次池化(例如VGG16有5次步长为2的池化),导致特征图尺寸缩小了32倍。虽然通过反卷积可以上采样回原图尺寸,但丢失的细节信息难以完全恢复,尤其是物体边界变得模糊。FCN-8s虽然融合了pool3、pool4的浅层特征来改善边界,但边界精度仍然有限。
讲师在视频中形象地指出了FCN的一个“不优雅”之处:它在输入端就“补了100圈的0”,以便在32倍下采样后仍保留一定尺度,最后再靠反卷积放大、裁剪回原图尺寸。这虽然是一种实现方式,但本质上仍是信息量被严重压缩后再进行“猜测性”恢复的过程。
DeepLab v1的改进思路非常直接:
- 减少信息损失:降低网络中的下采样倍数,让特征图本身的分辨率更高。
- 保持宏观视野:在减少下采样的同时,必须保证神经元的感受野足够大,才能理解图像的全局语义。
密集预测与空洞卷积
降低下采样倍数:密集池化
DeepLab v1基于VGG16网络进行改造。其第一个关键操作是将pool4和pool5两个池化层的步长(stride)由2改为1,同时进行适当的填充(padding)以保证空间尺寸不变。
这一改动带来了什么变化?
- 在传统VGG16中,经过5次步长为2的池化,下采样倍数为 。
- 在DeepLab v1中,
pool4和pool5不再进行下采样,因此总的下采样倍数变为 (仅来自pool1,pool2,pool3)。
这意味着网络最终输出的特征图(得分图)尺寸是输入的1/8,而不是1/32。我们得到了一个更高分辨率、更密集的得分图。这种步长为1的池化操作,可以理解为一种“密集池化”,它在局部窗口内仍然选取最大值(或平均值),但窗口是逐像素滑动的,因此输出的特征图尺寸与输入相同(在适当填充下)。
引入空洞卷积:扩大感受野
然而,仅仅减少下采样会带来一个新的问题:感受野变小。
感受野(Receptive Field) 是指卷积神经网络中,输出特征图上的一个像素点,对应输入图像上的区域大小。它代表了该像素点能“看到”的输入图像范围。网络越深,经过的卷积和池化越多,感受野通常越大,神经元就能看到更宏观的语义信息。
当我们把pool4和pool5的步长改为1后,后续卷积层的输入特征图尺寸变大了,但如果我们继续使用传统的3x3卷积,每个卷积核“看到”的范围(即感受野)相对于原始图像的比例实际上变小了。这就像“管中窥豹”,每个神经元只关注非常局部的细节,缺乏对物体整体和上下文的理解,不利于做出准确的分类判断。
为了解决这个矛盾,DeepLab v1引入了第二个关键创新:空洞卷积(Atrous Convolution),也称为膨胀卷积(Dilated Convolution)。
空洞卷积的核心思想是:在标准卷积核的权重之间插入“空洞”(零值),从而在不增加参数数量和计算量的情况下,扩大卷积核的感受野。
我们通过一个一维示例来理解:
import numpy as np
# 假设我们有一个一维输入信号
x = np.array([1, 2, 3, 4, 5, 6, 7], dtype=np.float32)
# 一个标准的一维卷积核(尺寸为3)
kernel = np.array([0.5, 1.0, 0.5])
print("输入信号 x:", x)
print("标准卷积核 kernel:", kernel)
# 1. 标准卷积 (stride=1,这里忽略边界,只计算中心部分)
def standard_conv1d(signal, k):
# k尺寸为3
output = []
for i in range(1, len(signal)-1): # 忽略边界,仅演示
patch = signal[i-1:i+2]
output.append(np.sum(patch * k))
return np.array(output)
result_std = standard_conv1d(x, kernel)
print("\n标准卷积结果(中心部分):", result_std)
print(" 感受野:每个输出点看到输入中连续的3个值。")
# 2. 空洞卷积 (rate=2)
# 空洞卷积核可以看作是在标准核元素间插入 (rate-1) 个0
rate = 2
# 构造一个“等效”的空洞卷积核(用于理解感受野)
# 实际计算时,我们是通过修改采样位置来实现的。
print(f"\n--- 空洞卷积 rate={rate} ---")
print("概念上,卷积核变为: [0.5, 0, 1.0, 0, 0.5]")
print("实际计算时,卷积核权重仍是[0.5, 1.0, 0.5],但作用于输入的间隔为rate的像素上。")
# 模拟空洞卷积的计算:卷积核作用于 x[0], x[2], x[4]
def dilated_conv1d(signal, k, rate):
output = []
k_size = len(k)
# 计算输出长度(无填充情况)
out_len = len(signal) - (rate * (k_size - 1))
for i in range(out_len):
# 计算采样位置
indices = [i + j*rate for j in range(k_size)]
patch = signal[indices]
output.append(np.sum(patch * k))
return np.array(output)
# 为了比较,我们给输入两边补零,模拟padding
x_padded = np.pad(x, (2, 2), 'constant') # rate=2时卷积核跨度由3格扩到5格,两侧各留2格
result_dil = dilated_conv1d(x_padded, kernel, rate)
# 取中间部分与标准卷积结果长度一致
mid_start = (len(result_dil) - len(result_std)) // 2
result_dil_mid = result_dil[mid_start:mid_start+len(result_std)]
print("空洞卷积结果(中心部分,模拟):", result_dil_mid)
print(" 感受野:每个输出点看到输入中间隔为2的3个值,例如覆盖了输入中更广的区域(如位置1,3,5)。")输入信号 x: [1. 2. 3. 4. 5. 6. 7.]
标准卷积核 kernel: [0.5 1. 0.5]
标准卷积结果(中心部分): [ 4. 6. 8. 10. 12.]
感受野:每个输出点看到输入中连续的3个值。
--- 空洞卷积 rate=2 ---
概念上,卷积核变为: [0.5, 0, 1.0, 0, 0.5]
实际计算时,卷积核权重仍是[0.5, 1.0, 0.5],但作用于输入的间隔为rate的像素上。
空洞卷积结果(中心部分,模拟): [ 4. 6. 8. 10. 8.]
感受野:每个输出点看到输入中间隔为2的3个值,例如覆盖了输入中更广的区域(如位置1,3,5)。对比两组结果时,最右端数值不同是因为该点用到了补零边界;这里只需关注每个输出点的采样间隔,不必逐值对齐。
在二维情况下,对于一个3x3的卷积核,当rate=2时,它作用的像素不再是连续的3x3区域,而是像棋盘格一样,跳过相邻像素,覆盖一个5x5区域中的9个点。这样,感受野就从3x3扩大到了5x5,而参数量依然是9个。
扩张率(rate):定义了卷积核权重之间的间隔。rate=1即为标准卷积;rate=2表示在水平和垂直方向上都间隔1个像素进行采样(等价于在权重之间插入1个零);rate=4则间隔3个像素,也就是插入3个零。
空洞卷积与反卷积还有一个对称关系:反卷积(转置卷积)可以看作先在输入特征图的元素之间插零,再用卷积核去乘;空洞卷积则保持输入不动,改在卷积核内部插零。两者插零的对象不同,但都在拉大“间隔”——前者放大空间尺寸,后者扩大感受野。理解这一点,就能明白DeepLab为什么可以用“密集池化 + 空洞卷积”替代“先降采样32倍、再用反卷积上采样”。
在DeepLab v1中,空洞卷积被应用在改造后的VGG网络的后几层:
- 在
conv5层使用rate=2的空洞卷积。 - 在
conv6层(即原VGG的fc6卷积化后的层)使用rate=4的空洞卷积。
通过结合密集池化(减少下采样) 和空洞卷积(扩大感受野),DeepLab v1成功获得了高分辨率(仅下采样8倍)且具有足够大感受野的特征图。这比FCN先下采样32倍再进行上采样的方式“优雅”得多,因为它保留了更多原始空间信息。
稀疏特征提取与稠密特征提取。 把两条路线放在一起对比会更清楚。传统做法是“降采样→卷积→上采样”:降采样把特征图缩小,卷积层的感受野虽然够大,但特征点变得稀疏,很多相邻位置的特征组合(例如连续四个位置的特征)已经不存在了;最后的上采样本质上只是用少数离散特征做双线性插值,放大后的图并不会凭空增加有效信息。DeepLab则是“密集池化 + 空洞卷积”:池化步长为1,信息量基本不减少;空洞卷积每次只平移一格,但每个卷积核覆盖的范围很大。这样得到的得分图既稠密(相邻输出点之间没有跳过输入),又有足够大的感受野,这才是空洞卷积想要达到的效果。
全连接条件随机场(Fully Connected CRF)优化边界
尽管空洞卷积提供了更好的特征图,但CNN本身的性质决定了它在深度传递过程中会逐步丢失细节的位置信息,导致分割边界不够锐利、存在模糊区域。讲师提到:“图像在CNN中逐层传递,是一个逐步抽象的过程,原来的位置信息会随着深度而减少甚至消失。”
为了恢复精确的边界,DeepLab v1引入了后处理步骤:全连接条件随机场(Fully Connected Conditional Random Field, FC-CRF)。
条件随机场(CRF) 是一种概率图模型,常用于考虑上下文信息的结构化预测任务。在分割中,它可以建模像素间标签的依赖关系。“全连接”意味着CRF模型考虑图像中每一对像素之间的关系,而不仅仅是相邻像素。这使得模型能够利用全局上下文信息来优化每个像素的标签。
DeepLab v1中FC-CRF的能量函数通常包含两部分(这里只给出形式,具体的推导与求解留到下一节):
- 一元势函数(Unary Potential):来源于CNN的预测得分图,表示每个像素独立属于某个类别的概率。即 ,其中 是CNN把像素 判为类别 的概率。
- 二元势函数(Pairwise Potential):鼓励相似像素(颜色接近、位置邻近)分配相同的标签,反之则鼓励分配不同标签。DeepLab采用高斯核函数来定义像素间的相似性: 其中 是像素的特征(如RGB颜色和坐标位置), 是高斯核, 是标签兼容性函数。
通过最小化这个总能量函数,CRF可以对CNN输出的粗糙分割图进行迭代优化,使边界与图像中物体的真实边缘对齐,消除内部的不一致性。
DeepLab v1 整体架构与流程
现在,我们可以勾勒出DeepLab v1的整体架构:
- 输入图像:送入改造后的CNN。
- 改造的CNN骨干网络(基于VGG16):
- 将
pool4和pool5的步长设为1(密集池化)。 - 在
conv5与conv6(conv6由VGG的fc6卷积化而来)使用空洞卷积(rate分别为2和4)。 - 输出一个下采样8倍的得分图(Score Map)。
- 将
- 双线性插值上采样:将得分图上采样8倍至原始图像尺寸,得到初步的分割结果。
- 全连接CRF优化:将上采样后的分割结果(以及原始图像的RGB信息)输入FC-CRF模型,进行迭代推理,优化边界,得到最终精细的分割图。
这个过程清晰地将问题分解为两步:CNN负责识别和粗略定位(是什么物体,大概在哪),CRF负责精修边界(物体的精确轮廓在哪)。
本章小结
DeepLab v1于2014年底提出、2015年发表在ICLR上,是语义分割发展史上的一个重要模型,它巧妙地解决了FCN存在的分辨率与感受野之间的矛盾。
- 核心贡献:提出了空洞卷积,这是一种高效扩大感受野的工具,使得网络能够在保持高分辨率特征图的同时,捕获足够的上下文信息。
- 系统设计:采用“CNN + CRF”的两阶段范式,CNN负责语义理解,CRF负责细节优化,显著提升了分割边界的质量。
- 历史意义:DeepLab v1的成功验证了空洞卷积在密集预测任务中的有效性,为后续的DeepLab系列(v2, v3, v3+)以及众多其他分割模型奠定了基础。
站在今天回看,DeepLab v1属于深度学习分割的早期范式:后续的DeepLabv2用空洞空间金字塔池化(ASPP)做多尺度特征提取,DeepLabv3起不再使用全连接CRF后处理,DeepLabv3+又引入轻量解码器;近年主流的Transformer/掩码分类式分割模型(如Mask2Former)与提示式分割模型(如SAM)进一步成为研究与工程的主流,这套“CNN + 全连接CRF”的两阶段流程已基本退出实际使用。不过空洞卷积本身并未过时,它仍是现代分割网络和实时分割模型中扩大感受野的常用组件。
📋 行动清单
- 理解空洞卷积中
rate参数的含义,并能够计算给定rate和卷积核大小下的实际感受野。 - 对比FCN与DeepLab v1在“下采样—上采样”流程上的区别,说出各自优劣。
- 阐述全连接CRF在DeepLab v1中扮演的角色,以及为什么它比只考虑局部邻居的CRF更有效。
📝 动手练一练
- 概念辨析:假设我们有一个3x3的标准卷积核,当设置空洞卷积的
rate=3时,这个卷积核在输入特征图上实际覆盖的感受野尺寸是多少?(假设输入足够大,不考虑边界效应)
👉 点击查看参考答案
对于一个尺寸为 的标准卷积核,当使用扩张率 时,其等效的感受野尺寸 可以通过以下公式计算: 代入 : 因此,一个3x3卷积核在rate=3时,其感受野尺寸为7x7。你可以想象这个3x3的权重,被“拉伸”到了一个7x7的网格上(中间有空洞),覆盖了更广的输入区域。
- 代码理解:以下代码模拟了标准卷积和空洞卷积在小型矩阵上的操作,请观察输出并思考感受野的变化。
import numpy as np
# 定义一个小的输入特征图 (6x6)
input_map = np.arange(36).reshape(6, 6).astype(np.float32)
print("输入特征图 (6x6):")
print(input_map)
# 定义一个简单的3x3卷积核(边缘检测类似)
kernel = np.array([[1, 0, -1],
[1, 0, -1],
[1, 0, -1]], dtype=np.float32)
print("\n卷积核 (3x3):")
print(kernel)
# 1. 标准卷积 (stride=1, padding='valid',即无填充)
def standard_conv2d(input_img, k):
k_h, k_w = k.shape
i_h, i_w = input_img.shape
o_h, o_w = i_h - k_h + 1, i_w - k_w + 1
output = np.zeros((o_h, o_w))
for i in range(o_h):
for j in range(o_w):
patch = input_img[i:i+k_h, j:j+k_w]
output[i, j] = np.sum(patch * k)
return output
result_std = standard_conv2d(input_map, kernel)
print("\n标准卷积结果 (4x4,无填充):")
print(result_std)
print("说明:每个输出值由输入中一个连续的3x3区域计算得出。")
# 2. 空洞卷积 (rate=2)
# 我们通过手动构建一个“膨胀后”的等效核视图来理解,实际实现是间隔采样。
rate = 2
print(f"\n--- 空洞卷积 rate={rate} ---")
print("概念上,卷积核被‘拉伸’到一个更大的网格上,权重间有空洞。")
print("对于3x3核,rate=2时,其作用的输入区域相当于一个5x5区域中的9个特定点。")
# 为了简化演示,我们计算一个输出点,展示其采样的输入位置
# 计算输出点(0,0)的值:它应该对应输入位置 (0,0), (0,2), (0,4), (2,0), (2,2), (2,4), (4,0), (4,2), (4,4)
sample_positions = [(i*rate, j*rate) for i in range(3) for j in range(3)]
print(f"输出点(0,0)采样的输入位置: {sample_positions}")
# 提取这些位置的值(6x6的输入足以覆盖这9个位置,这里只打印前6个)
sample_values = [float(input_map[pos]) for pos in sample_positions if pos[0] < 6 and pos[1] < 6]
print(f"对应的输入值(部分): {sample_values[:6]}...")
print("可以看到,它看到了更广的输入区域(从第0行到第4行)。")输入特征图 (6x6):
[[ 0. 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. 26. 27. 28. 29.]
[30. 31. 32. 33. 34. 35.]]
卷积核 (3x3):
[[ 1. 0. -1.]
[ 1. 0. -1.]
[ 1. 0. -1.]]
标准卷积结果 (4x4,无填充):
[[-6. -6. -6. -6.]
[-6. -6. -6. -6.]
[-6. -6. -6. -6.]
[-6. -6. -6. -6.]]
说明:每个输出值由输入中一个连续的3x3区域计算得出。
--- 空洞卷积 rate=2 ---
概念上,卷积核被‘拉伸’到一个更大的网格上,权重间有空洞。
对于3x3核,rate=2时,其作用的输入区域相当于一个5x5区域中的9个特定点。
输出点(0,0)采样的输入位置: [(0, 0), (0, 2), (0, 4), (2, 0), (2, 2), (2, 4), (4, 0), (4, 2), (4, 4)]
对应的输入值(部分): [0.0, 2.0, 4.0, 12.0, 14.0, 16.0]...
可以看到,它看到了更广的输入区域(从第0行到第4行)。👉 点击查看参考答案与思考
从输出可以看出:
- 标准卷积:每个输出点(如
result_std[0,0])由输入中一个连续的3x3区块(input_map[0:3, 0:3])计算得到。 - 空洞卷积(概念):对于
rate=2,计算输出点(0,0)时,卷积核作用的输入位置是(0,0),(0,2),(0,4),(2,0),(2,2),(2,4),(4,0),(4,2),(4,4)。这些点散布在一个5x5的区域内(行和列索引从0到4),而不是连续的3x3区域。因此,感受野扩大到了5x5,但参与计算的权重数量仍然是9个(3x3)。
思考:如果输入图像尺寸很小(例如14×14),而rate设置得非常大(例如15),3×3卷积核的采样跨度会达到31个像素,绝大部分采样点都落在图像之外,只剩下中心一个权重真正参与计算,于是退化成类似1×1卷积的效果,失去了捕获空间上下文的能力。因此,rate的设置需要与输入特征图的尺寸相匹配。
—— 小象教研组