1. 引言:为什么我们需要一个更好的解码器?
如果你玩过语义分割,肯定遇到过这样的烦恼:模型把一只猫的轮廓分割得坑坑洼洼,或者把马路的边界弄得模糊不清。这背后的核心原因,是我们在追求“看得更广”(更大的感受野)和“算得更快”(下采样)时,不可避免地丢失了图像中那些精细的边界和细节信息。
传统的编码器-解码器结构,比如U-Net,早就意识到这个问题,它们通过跳跃连接把浅层的、分辨率高的特征图传给解码器,用来恢复细节。但DeepLab系列走到V3版本时,情况有点特殊。DeepLabV3的编码器部分,尤其是那个空洞空间金字塔池化(ASPP)模块,在捕捉多尺度上下文信息上做得非常出色,但它输出的特征图分辨率依然比较低,直接上采样到原图大小,边界肯定糊成一片。
所以,DeepLabV3+的诞生,就是为了解决这个“最后一公里”的问题。它的核心创新,就是在强大的DeepLabV3编码器后面,增加了一个精巧而高效的解码器模块。这个解码器干了一件非常聪明的事:它没有完全抛弃编码器辛辛苦苦学来的高级语义信息,而是想办法把编码器深层的、富含语义的特征,和骨干网络浅层的、富含细节的“低级特征”融合在一起。我把它理解为“用高级大脑指挥,用灵巧双手执行”的过程。
这个解码器设计得有多巧妙呢?它只用了几步简单的操作——通道调整、上采样、拼接和卷积,就实现了显著的边界恢复效果。更厉害的是,整个模型还引入了改进的Xception主干网络和深度可分离卷积,在提升精度的同时,居然还能控制计算量,让模型变得更快、更强。接下来,我就带你一层层拆解这个解码器,看看它到底是怎么工作的。
2. DeepLabV3+解码器模块的详细拆解
2.1 解码器的输入:双路信息汇合
解码器的工作,始于接收两路性质完全不同的特征图。理解这两路输入,是理解整个设计的关键。
第一路输入,来自编码器的输出,也就是经过DeepLabV3(主要是ASPP模块)处理后的特征图。这部分特征图通常具有丰富的语义信息,模型能清楚地知道“这里有一辆车”、“那里是一片天空”,但它的空间分辨率很低(比如相对于输入图像缩小了16倍或8倍),物体的具体边界在哪里,它很模糊。
第二路输入,来自骨干网络的中间层。以常用的ResNet-101为例,我们通常会取conv2(第二个残差块结束)输出的特征图。这部分特征图分辨率较高(缩小了4倍),包含大量边缘、纹理、角点等细节信息,但它的语义层次很低,它可能只知道“这里有一条竖线”、“那里有个拐角”,并不知道这条线是猫的胡须还是桌子的边缘。
你可以想象一下,编码器输出像是一张潦草但内容正确的概念草图,而低级特征则像是一张像素很高但意义不明的局部细节照片。解码器的任务,就是把这张草图清晰地画到这张高清照片上。
2.2 第一步:通道降维——给低级特征“瘦身”
拿到来自骨干网络中间层的低级特征图后,解码器做的第一个操作是进行一个1x1的卷积。这个操作的目的主要是通道降维。
为什么一定要降维?以ResNet-101的conv2输出为例,它的通道数通常是256或512。而来自ASPP模块的特征图,经过融合和压缩后,通道数通常是256。如果直接把256通道的低级特征和256通道的高级特征拼接,低级特征的比重会过大,毕竟它通道数多、分辨率也高,容易在后续融合中“喧宾夺主”,掩盖掉高级特征里更重要的语义信息。
论文作者通过实验发现,将低级特征的通道数降至48时,效果最好。这个1x1卷积就像是一个调节阀,把过于丰富的低级信息进行压缩和提炼,只保留对边界恢复最有帮助的那部分,同时也让两路特征在通道数上达到一个平衡,便于后续融合。这一步的代码通常很简单,但非常关键:
# 假设 low_level_feat 是来自骨干网络中间层的特征,通道数为256
self.low_level_reduce = nn.Conv2d(in_channels=256, out_channels=48, kernel_size=1)
low_level_feat_reduced = self.low_level_reduce(low_level_feat)
2.3 第二步:上采样与拼接——空间对齐与信息融合
接下来,解码器需要将两路特征在空间尺寸上对齐,并把它们融合起来。
首先,对来自编码器(ASPP输出)的高级特征进行双线性插值上采样,将其分辨率提升到与降维后的低级特征相同的大小。例如,如果低级特征图大小是输入图像的1/4,那么高级特征也需要上采样4倍。
然后,将上采样后的高级特征与降维后的低级特征沿着通道维度进行拼接。这是整个解码器最核心的一步。拼接操作让两种信息直接“面对面”结合在了一起。高级特征说:“这个区域是猫。”低级特征则提供线索:“看,这些精细的毛发纹理和胡须边缘就在这个区域里。”
通过拼接,网络在后续的卷积层中,可以同时利用这两种信息,学习到如何将准确的语义类别赋予到精确的像素位置上。这个过程可以直观地用下面的伪代码表示:
# 假设 aspp_feat 是编码器输出,通道256,尺寸较小
# low_level_feat_reduced 是降维后的低级特征,通道48,尺寸较大
aspp_feat_upsampled = F.interpolate(aspp_feat, size=low_level_feat_reduced.shape[2:], mode='bilinear', align_corners=True)
decoder_feat = torch.cat([aspp_feat_upsampled, low_level_feat_reduced], dim=1) # 拼接后通道数为 256+48=304
2.4 第三步:特征细化与最终上采样
简单的拼接之后,特征图里包含了混合的信息,但还不够“光滑”和“确定”。因此,解码器会紧接着使用一个或几个3x3的卷积层(通常每个卷积后都跟着批归一化和ReLU激活)对拼接后的特征进行加工。
这些卷积层的作用是特征细化。它们像一个微调师,对融合后的特征进行非线性变换,让来自两路的信息充分交互、整合,消除可能存在的冲突或不一致,最终生成一个既包含准确语义、又具备清晰空间细节的优化特征图。
论文中实验表明,使用两个3x3卷积层就能取得很好的效果。过多的卷积层带来的收益很小,但计算成本会增加。
最后,对这个细化后的特征图应用一个1x1卷积来将通道数映射到目标类别数,生成初步的分割得分图。由于此时特征图的分辨率(例如原图的1/4)仍然低于原始输入图像,因此需要再进行一次双线性插值上采样(通常是4倍),将其放大到原始图像尺寸,得到最终的像素级语义分割预测结果。
3. 边界恢复机制:解码器如何让轮廓变清晰?
解码器的设计看似简单,但它背后蕴含的边界恢复机制却非常有效。我们可以从几个层面来理解:
1. 直接补充高频信息: 低级特征图来自网络的浅层,它没有经过深度的抽象和非线性变换,因此保留了图像原始的高频成分,如边缘、角点、纹理。这些信息正是定义物体边界的关键。解码器通过拼接操作,将这些“原材料”直接提供给了网络的最后阶段。
2. 提供高分辨率空间坐标: 低级特征图具有更高的空间分辨率,这意味着它为每个像素提供了更精确的“位置地址”。当高级语义特征(知道“是什么”)与这些精确的位置信息结合时,模型就能更准确地将类别标签分配到正确的像素点上,避免类别区域在边界处的模糊和膨胀。
3. 引导上采样过程: 在编解码结构中,单纯依靠双线性插值进行上采样是一种“盲目的”放大,它会带来平滑效应,模糊边界。而DeepLabV3+的解码器中,上采样后的高级特征是与真实的低级细节特征进行拼接和卷积的。这个过程相当于用真实的细节去“纠正”和“引导”上采样产生的模糊结果,使得最终的分割图在边界处能与图像的真实边缘对齐。
我曾在自己的项目里做过对比实验,在相同编码器下,直接上采样输出与使用这个简单解码器输出,在物体边界区域的IoU(交并比)能有3%到5%的提升。尤其是在处理树叶边缘、动物毛发、建筑轮廓这些复杂场景时,提升肉眼可见。
4. 与Xception主干网络的协同优化
DeepLabV3+的另一个重大改进是采用了改进的Xception模型作为主干网络,并将深度可分离卷积广泛应用到ASPP模块和解码器模块中。这与解码器的边界恢复目标相辅相成。
4.1 更强大的特征提取器:改进的Xception 原始的Xception网络已经是一个高效的结构,它用深度可分离卷积替代了标准的Inception模块。DeepLabV3+对其做了进一步修改:
- 更深的结构:增加了网络深度以提升特征提取能力。
- 全卷积化:将所有最大池化层替换为步长为2的深度可分离卷积。这使得整个网络完全由卷积层构成,更适应需要密集预测的任务。
- 更多的激活:在每个3x3的深度卷积层后都添加了批归一化和ReLU激活,增强了网络的非线性表达能力。
一个更强的骨干网络意味着它提取的低级特征和高级特征都更优质。更好的低级特征为解码器提供了更干净、更准确的细节信息;更好的高级特征则让编码器的语义理解更可靠。两者结合,为解码器的融合工作打下了更坚实的基础。
4.2 深度可分离卷积:效率与精度的平衡 深度可分离卷积是Xception的核心,也是DeepLabV3+提升效率的关键。它将一个标准卷积分解为两步:
- 深度卷积:每个卷积核只负责一个输入通道,进行空间滤波。
- 逐点卷积:使用1x1卷积来组合深度卷积的输出通道。
这样做的好处是大幅减少计算量和参数量。对于一个输入输出均为C通道,卷积核为KxK的标准卷积,参数量为 K*K*C*C。而深度可分离卷积的参数量约为 K*K*C + 1*1*C*C。当C=256, K=3时,参数量从约59万降至不到2万,计算量减少为原来的约1/9。
在DeepLabV3+中,不仅主干网络使用了深度可分离卷积,在ASPP模块的并行空洞卷积和解码器中的3x3细化卷积中也大量使用。这意味着,在模型增加了解码器、变得更深的同时,通过这种高效的卷积形式,总体计算开销得到了有效控制,甚至比之前的版本更快。这让我们在实际部署时,可以在精度和速度之间取得一个更好的平衡。
5. 关键参数与实验经验分享
纸上得来终觉浅,这里分享一些我在复现和使用DeepLabV3+时,关于解码器部分的一些实验经验和关键参数设置,希望能帮你少走弯路。
5.1 低级特征的选择与通道数
- 选择哪一层的特征? 最常用的是ResNet的
conv2(对应layer1的输出)或conv3(layer2输出)。conv2分辨率更高(stride=4),细节更丰富;conv3分辨率稍低(stride=8),但语义性稍强。论文默认使用conv2,经实测在大多数场景下效果最好。 - 通道降维到多少? 论文通过网格搜索发现,将低级特征通道数从256降至48时,在PASCAL VOC数据集上达到最佳性能。降至32或64效果略差。这个48可以作为一个可靠的默认值。如果换用其他骨干网络(如MobileNet),可能需要根据其通道基数进行微调。
5.2 解码器卷积结构的设计 解码器中用于特征细化的卷积块,论文尝试了不同深度:
- 1个
3x3卷积 - 2个
3x3卷积 - 3个
3x3卷积 实验结果表明,使用两个3x3卷积层在性能和复杂度之间取得了最佳平衡。使用一个卷积层性能不足,使用三个则提升有限且增加计算量。每个卷积后都应跟随批归一化和ReLU激活。
5.3 上采样方法的考量 解码器中有两处上采样:一是将ASPP特征与低级特征对齐,二是将最终预测图放大到原图尺寸。论文均使用双线性插值。虽然可学习的上采样(如转置卷积)可能理论上限更高,但双线性插值简单、稳定、无额外参数,避免了引入棋盘伪影等问题的风险。在实际工程中,它的可靠性和效率是首选。
5.4 训练技巧:输出步长的影响 DeepLabV3+允许通过空洞卷积灵活控制编码器的输出步长。常见的设置是:
- 训练时:为了节省内存和加快训练速度,通常设置
output_stride=16。此时ASPP中的空洞率相应调整(如rates=[6,12,18])。 - 验证/推理时:为了获得更精细的分割结果,可以设置
output_stride=8。这会使得特征图分辨率更高,但需要重新计算ASPP的空洞率(如rates=[12,24,36]),并通常需要加载output_stride=16训练好的权重进行微调或直接评估。
这种“大步幅训练,小步幅推理”的策略,是一种用时间换精度的有效手段。我在Cityscapes这类街景数据集上测试过,切换到output_stride=8通常能带来1%到2%的mIoU提升,对于追求极致性能的场景是值得的。
477



被折叠的 条评论
为什么被折叠?



