Advertisement

[轻量化语义分割] Rethinking BiSeNet For Real-time Semantic Segmentation(CVPR2021)

阅读量:

该研究提出了一种基于双向感知器网络的实时语义分割方法,并通过整合上下文感知器与边缘感知器的技术框架实现这一目标。该方法不仅能够高效处理图像数据,在实验结果中还展现了显著的竞争优势;此外,在实际应用中能够显著降低计算开销的同时保证较高的分割精度水平;值得注意的是,在实验部分采用了与现有方法不同的评估指标体系以全面验证其性能优势

code:

https://github.com/MichaelFan01/STDC-Seg

目录

一. 动机

二. 贡献

三、相关工作

四、方法

1、编码网络

2、解码器

五、实验

1、消融实验

2、对比实验

一. 动机

引入新增分支以提取空间特征的过程较为耗时。
受限于现有任务特定设计的局限性。
依赖于从预训练任务中获取的知识可能导致图像分割效率低下。

(a) BiSeNet 通过专门用于提取空间信息的空间路径来编码空间信息。
(b) Ours 的细节导向模块在低层特征中提取并表示空间关系,并且其设计避免了冗余计算开销。

二. 贡献

开发了一个**短时密集连接(Short-Term Dense Connection, STDC系统)**来收集广域且多层次的感受野以及多层次的细节信息的深层特征

2、开发了一个细节聚合(Detail Aggregation)模块 来优化解码器性能,并通过这一创新手段显著提升了低层空间细节的捕捉能力。(Train only)

3、在Cityscapes测试集上,STDC1-Seg50取得71.9% mIoU,250.4 FPS;STDC2-Seg75取得76.8% mIoU,97.0 FPS。

三、相关工作

高效率的网络架构旨在缩减模型参数规模,并优化推理阶段的计算复杂度。该架构在ResNet的基础上展现出卓越的性能,在提升效率的同时准确率却不受影响,并与MobileNet V2和ShuffleNet等主流模型相媲美。

专为图像分类任务而设计的方法,在向语义分割应用程序延伸时应当采取谨慎的处理方式。

  1. 通用技术手段:经典的图像分割方法(基于阈值设定和超像素划分)。基于深度学习的方法包括FCN(全卷积神经网络)、Deeplabv3+(增强通道感知)、SegNet(卷积神经网络反向传播)、PSPNet(金字塔特征融合)等技术框架。

由于高分辨率特征和复杂的网络连接,大多数方法需要大量的计算成本。

3、在线语义分割 :轻量化主干网络(包括DGANet系列)、多分支结构(如Interpretable Convolutional Networks、二进制分割网版本1/版本2)。

四、方法

网络架构

基于预训练的STDC构建编码器的骨干网络,并通过借鉴BiSeNet模型中所采用的上下文路径来完成对上下文信息的编码。

1、编码网络

1.1 短时密集连接(Short-Term Dense Concatenate,STDC)模块

在图像分类任务中,在高层部分采用较多的通道是一种常见策略;而在语义分割任务中,则侧重于可扩展的感受野与多尺度信息;低层必须具备足够数量的通道以表示或捕捉感受野较小区域内的细致特征;而感受野较大的高层则会更加注重如何归纳与整合这些高层次的信息;如果在同一层次设置相同的通道配置,则可能造成重复或冗余的信息存储。

​ (a)STDC 网络 ,(b)和 (c)STDC 模块

STDC网络包含除输入层和预测层之外的6个阶段。通常情况下,在第1至第5阶段中,网络依次通过步长为2的方式对输入图像进行空间分辨率下采样,在第6阶段中,网络经过一个Convolutional块(ConvX)、一个全局平均池化层以及两个全连接层来完成预测任务。

阶段1与2 常被视作用于提取外观特征的基础层次。为了提高效率,在第一阶段与第二阶段我们仅配置一个卷积块,并据我们的实验结果表明该设置已足够完善。

在本网络设计中,在阶段 3至5期间对STDC模块的数量进行了细致优化安排。具体而言,在这些阶段中,每个阶段的第一个STDC模块通过2倍的步长降低了其空间分辨率。其余后续STDC模块则维持了原有不变的空间分辨率水平。

我们将各阶段的输出通道数表示为

N_l

​,其中

l

​是阶段索引。在实践中凭经验将

N_{6}

将该网络的通道数量配置为 1024,并通过精细调节其余各阶段的通道数量直至实现一个良好平衡点,在此过程中实现了精度与效率的有效协调。该网络主要由 Short-Term Dense Concatenate 模块构成,并因此我们将该网络命名为 STDC 网络

​ STDC网络结构

STDC1 代码:

复制代码
 # STDC1Net

    
 class STDCNet813(nn.Module):
    
     def __init__(self, base=64, layers=[2,2,2], block_num=4, type="cat", num_classes=1000, dropout=0.20, pretrain_model='', use_conv_last=False):
    
     super(STDCNet813, self).__init__()
    
     if type == "cat":
    
         block = CatBottleneck
    
     elif type == "add":
    
         block = AddBottleneck
    
     self.use_conv_last = use_conv_last
    
     self.features = self._make_layers(base, layers, block_num, block)
    
     self.conv_last = ConvX(base*16, max(1024, base*16), 1, 1)
    
     self.gap = nn.AdaptiveAvgPool2d(1)
    
     self.fc = nn.Linear(max(1024, base*16), max(1024, base*16), bias=False)
    
     self.bn = nn.BatchNorm1d(max(1024, base*16))
    
     self.relu = nn.ReLU(inplace=True)
    
     self.dropout = nn.Dropout(p=dropout)
    
     self.linear = nn.Linear(max(1024, base*16), num_classes, bias=False)
    
  
    
     self.x2 = nn.Sequential(self.features[:1])
    
     self.x4 = nn.Sequential(self.features[1:2])
    
     self.x8 = nn.Sequential(self.features[2:4])
    
     self.x16 = nn.Sequential(self.features[4:6])
    
     self.x32 = nn.Sequential(self.features[6:])
    
  
    
     if pretrain_model:
    
         print('use pretrain model {}'.format(pretrain_model))
    
         self.init_weight(pretrain_model)
    
     else:
    
         self.init_params()
    
  
    
     def init_weight(self, pretrain_model):
    
     
    
     state_dict = torch.load(pretrain_model)["state_dict"]
    
     self_state_dict = self.state_dict()
    
     for k, v in state_dict.items():
    
         self_state_dict.update({k: v})
    
     self.load_state_dict(self_state_dict)
    
  
    
     def init_params(self):
    
     for m in self.modules():
    
         if isinstance(m, nn.Conv2d):
    
             init.kaiming_normal_(m.weight, mode='fan_out')
    
             if m.bias is not None:
    
                 init.constant_(m.bias, 0)
    
         elif isinstance(m, nn.BatchNorm2d):
    
             init.constant_(m.weight, 1)
    
             init.constant_(m.bias, 0)
    
         elif isinstance(m, nn.Linear):
    
             init.normal_(m.weight, std=0.001)
    
             if m.bias is not None:
    
                 init.constant_(m.bias, 0)
    
  
    
     def _make_layers(self, base, layers, block_num, block):
    
     features = []
    
     features += [ConvX(3, base//2, 3, 2)]
    
     features += [ConvX(base//2, base, 3, 2)]
    
  
    
     for i, layer in enumerate(layers):
    
         for j in range(layer):
    
             if i == 0 and j == 0:
    
                 features.append(block(base, base*4, block_num, 2))
    
             elif j == 0:
    
                 features.append(block(base*int(math.pow(2,i+1)), base*int(math.pow(2,i+2)), block_num, 2))
    
             else:
    
                 features.append(block(base*int(math.pow(2,i+2)), base*int(math.pow(2,i+2)), block_num, 1))
    
  
    
     return nn.Sequential(*features)
    
  
    
     def forward(self, x):
    
     feat2 = self.x2(x)
    
     feat4 = self.x4(feat2)
    
     feat8 = self.x8(feat4)
    
     feat16 = self.x16(feat8)
    
     feat32 = self.x32(feat16)
    
     if self.use_conv_last:
    
        feat32 = self.conv_last(feat32)
    
  
    
     return feat2, feat4, feat8, feat16, feat32
    
  
    
     def forward_impl(self, x):
    
     out = self.features(x)
    
     out = self.conv_last(out).pow(2)
    
     out = self.gap(out).flatten(1)
    
     out = self.fc(out)
    
     # out = self.bn(out)
    
     out = self.relu(out)
    
     # out = self.relu(self.bn(self.fc(out)))
    
     out = self.dropout(out)
    
     out = self.linear(out)
    
     return out
    
    
    
    
    代码解释

2、解码器

在图5(b)中展示了BiSeNet的空间路径特征,在这种情况下, 相较于图(c), 其主体结构(第三阶段)采用了相同的下采样率, 但能够提取更多空间细节, 包括边界和角点等具体实例. 基于这一发现, 我们开发并提出了一个细节引导模块(Detail Aggregation Module), 旨在指导底层网络以单一流程学习并提取空间信息, 并将细节预测建模为一种二元分割任务

细节聚合模块

利用细节聚合模块(Detail Aggregation Module)从语义分割ground truth生成二进制细节ground-truth。如图(c)所示区域中,该操作可通过Laplacian核的二维卷积层与可训练的1×1卷积层共同完成。具体而言,这一操作可通过Laplacian核的二维卷积层与可训练的1×1卷积层共同完成。采用基于图(e)设计的拉普拉斯算子能够生成不同尺度分辨率下的细节特征图,从而获取多尺度上的精细特征。随后,将这些特征图上采样至原始尺寸,并将它们与可训练 learnable 的 1×1 卷积模块进行融合,实现动态权重更新过程。最后,使用阈值0.1将预测结果转化为包含边界和角点信息的真实二进制细化ground-truth

在阶段3中引入Detail Head 以生成细节特征图。基于详细ground-truth作为指导信息来引导低层学习空间中的细节特征。通过带有多项式拟合能力的多项式网络结构实现这一目标。

Detail Loss

因为细节像素的数量远远少于非细节像素,在实际应用中往往会导致细胞性质的数据稀少性问题。这使得单纯的细胞性质预测容易成为一个典型的类别不平衡问题。为了避免加权交叉熵方法带来的欠精细效果(under-fine-grained results),我们采用了二元交叉熵损失函数与Dice损失函数的组合优化策略(combined optimization strategy)。其中,Dice损失衡量了预测图与真实图之间的重叠程度(Dice similarity coefficient)。其对前景区域与背景区域的比例变化具有较强的鲁棒性(resilience against proportion changes of foreground and background regions)。因此,对于一个高度为H,宽度为W的预测细节图,其中h代表图像的高度维度,w代表图像的宽度维度,D(h,w)表示在高度h、宽度w处的真实值,D'(h,w)表示对应的预测值.

L_{detail}

的公式如下:

L_{detail} = L_{dice} + L_{bce}
L_{dice}

表示binary cross-entropy loss,

L_{bce}

表示dice loss。

补充:

Dice系数是一种用于评估集合间相似程度的指标,在计算两个样本之间相关性方面具有重要应用,并且其取值范围限定在闭区间[0,1]内

dice= rac{2eft | Xigcap Y ight |}{eft | X ight |+eft | Y ight |}

dice Loss:

L_{dice} = 1- rac{2eft | Xigcap Y ight |}{eft | X ight |+eft | Y ight |}

因为dice loss作为一种基于区域的相关损失函数,在计算每个像素损失时不仅与当前像素预测值相关而且还与其周围像素值密切相关。其中损失函数的设计可视为应用掩码操作的过程这一特点使得无论图像尺寸如何变化 保证了不同尺寸图像中正样本区域损失计算的一致性 这种设计确保了监督信号强度在不同尺寸下的稳定性 在实际训练过程中该特性有利于模型更加关注前景物体的重要区域 当 foreground 占比较低时 就会出现正负样本失衡的问题 而交叉熵损失则能够较为公平地处理这种平衡问题

Detail Aggregation 代码:

复制代码
 class DetailAggregateLoss(nn.Module):

    
     def __init__(self, *args, **kwargs):
    
     super(DetailAggregateLoss, self).__init__()
    
     
    
     self.laplacian_kernel = torch.tensor(
    
         [-1, -1, -1, -1, 8, -1, -1, -1, -1],
    
         dtype=torch.float32).reshape(1, 1, 3, 3).requires_grad_(False).type(torch.cuda.FloatTensor)
    
     
    
     self.fuse_kernel = torch.nn.Parameter(torch.tensor([[6./10], [3./10], [1./10]],
    
         dtype=torch.float32).reshape(1, 3, 1, 1).type(torch.cuda.FloatTensor))
    
  
    
     def forward(self, boundary_logits, gtmasks):
    
  
    
     # boundary_logits = boundary_logits.unsqueeze(1)
    
     boundary_targets = F.conv2d(gtmasks.unsqueeze(1).type(torch.cuda.FloatTensor), self.laplacian_kernel, padding=1)
    
     boundary_targets = boundary_targets.clamp(min=0)
    
     boundary_targets[boundary_targets > 0.1] = 1
    
     boundary_targets[boundary_targets <= 0.1] = 0
    
  
    
     boundary_targets_x2 = F.conv2d(gtmasks.unsqueeze(1).type(torch.cuda.FloatTensor), self.laplacian_kernel, stride=2, padding=1)
    
     boundary_targets_x2 = boundary_targets_x2.clamp(min=0)
    
     
    
     boundary_targets_x4 = F.conv2d(gtmasks.unsqueeze(1).type(torch.cuda.FloatTensor), self.laplacian_kernel, stride=4, padding=1)
    
     boundary_targets_x4 = boundary_targets_x4.clamp(min=0)
    
  
    
     boundary_targets_x8 = F.conv2d(gtmasks.unsqueeze(1).type(torch.cuda.FloatTensor), self.laplacian_kernel, stride=8, padding=1)
    
     boundary_targets_x8 = boundary_targets_x8.clamp(min=0)
    
     
    
     boundary_targets_x8_up = F.interpolate(boundary_targets_x8, boundary_targets.shape[2:], mode='nearest')
    
     boundary_targets_x4_up = F.interpolate(boundary_targets_x4, boundary_targets.shape[2:], mode='nearest')
    
     boundary_targets_x2_up = F.interpolate(boundary_targets_x2, boundary_targets.shape[2:], mode='nearest')
    
     
    
     boundary_targets_x2_up[boundary_targets_x2_up > 0.1] = 1
    
     boundary_targets_x2_up[boundary_targets_x2_up <= 0.1] = 0
    
     
    
     
    
     boundary_targets_x4_up[boundary_targets_x4_up > 0.1] = 1
    
     boundary_targets_x4_up[boundary_targets_x4_up <= 0.1] = 0
    
    
    
     
    
     boundary_targets_x8_up[boundary_targets_x8_up > 0.1] = 1
    
     boundary_targets_x8_up[boundary_targets_x8_up <= 0.1] = 0
    
     
    
     boudary_targets_pyramids = torch.stack((boundary_targets, boundary_targets_x2_up, boundary_targets_x4_up), dim=1)
    
     
    
     boudary_targets_pyramids = boudary_targets_pyramids.squeeze(2)
    
     boudary_targets_pyramid = F.conv2d(boudary_targets_pyramids, self.fuse_kernel)
    
  
    
     boudary_targets_pyramid[boudary_targets_pyramid > 0.1] = 1
    
     boudary_targets_pyramid[boudary_targets_pyramid <= 0.1] = 0
    
     
    
     
    
     if boundary_logits.shape[-1] != boundary_targets.shape[-1]:
    
         boundary_logits = F.interpolate(
    
             boundary_logits, boundary_targets.shape[2:], mode='bilinear', align_corners=True)
    
     
    
     bce_loss = F.binary_cross_entropy_with_logits(boundary_logits, boudary_targets_pyramid)
    
     dice_loss = dice_loss_func(torch.sigmoid(boundary_logits), boudary_targets_pyramid)
    
     return bce_loss,  dice_loss
    
  
    
     def get_params(self):
    
     wd_params, nowd_params = [], []
    
     for name, module in self.named_modules():
    
             nowd_params += list(module.parameters())
    
     return nowd_params
    
    
    
    
    代码解释

五、实验

dataset :ImageNet、Cityscapes和CamVid

classification evaluation :top-1 accuracy

segmentation evaluation

1、消融实验

1.1 Effectiveness of STDC Module

给定输入通道维度M和输出通道维度N,计算STDC模型的参数量:

参考图3可知, STDC 模块的主要参数数量取决于预先设定的输入与输出通道尺寸, 相比之下, 在其他因素固定的情况下(比如通道数固定), 模块的数量不会显著影响整体计算量. 特别地, 在n达到其最大可能值时, STDC模块的实际参数个数几乎不再变化, 仅取决于M和N这两个变量.

优化了STDC2模块的结构设置后,具体结果可见于图7。根据公式3所示的关系式,在组的数量增加时(当组的数量增加时),计算量指标FLOPs呈现出下降趋势(呈下降趋势)。经过实验验证发现最佳效果出现在4个块配置中(最佳效果出现在4个块配置中)。进一步分析表明额外增加块数带来的优势显著降低(虽然能够带来一定程度的优势),而深层网络架构不利于并行计算和帧率维持(不利于并行处理和帧率稳定)。基于以上观察,在本文实验中将STDC1和STDC2模块的块数量统一设定为4个

1.2 Effectiveness of Our backbone

为了系统性评估实时分割设计主干的有效性, 我们采用了与 STDC2 相当但性能更优的新轻量级主干, 并开发了一个新的解码器来构建语义分割网络。 如表3所示, 相较于其他轻量级架构, STDC2 实现了最佳的速度-准确度权衡。

1.3 Effectiveness of Detail Guidance

如图6所示,在未提供详细指导的情况下进行阶段3处理后,在特征提取上有所改进。相较于未提供详细指导的阶段3(即阶段3),具有详细指导的阶段3捕获了更为丰富的空间信息细节,并且能够更好地描述物体及其边界区域的位置关系。由此可知,在预测精度方面,在处理小物体以及它们边界区域时表现得更为出色。

表4列出了相关量化结果。为了验证我们的细节指导策略的有效性,在Cityscapes val数据集上展示了不同细节指导策略的效果对比。为进一步验证细节引导能力,在实验中我们采用了BiSeNetV1模型的空间路径技术来处理空间信息,并将生成的特征替代了传统3D特征提取模块。其中,Spatial Path实验设置与其它实验保持一致,如表4所示,采用STDC2-Seg框架中的Detail Guidance模块能够在不显著影响推理速度的前提下提升mIoU指标(Mean Intersection over Union)。此外,引入空间路径对空间信息进行编码处理能够显著提高模型精度,但同时也带来了计算开销的增加。进一步研究发现,我们的Detail Aggregation模块能够有效地整合丰富细节信息,并通过1x、2x、4x多尺度特征融合实现了最佳的mIoU值

2、对比实验

2.1 Results on ImageNet

如表 5 所示,在速度和准确率方面均表现出色。 相较于同类轻量级骨干网络,在测试集上的 top-1 分类精度较 ImageNet 基线提升了约 4.1%。 相较于流行的轻量化架构(如 DF1Net),STDC2 在 FPS 上显著超越了 baseline,并展现出强大的分类能力。

2.2 Results on Cityscapes

如表6所示,则列出了我们提出的方法在Cityscapes验证集与测试集上的分割准确率及推理效率数据对比。基于先前的研究[27,22] ,我们采用了训练集与验证集数据进行模型训练,并将模型结果上传至Cityscapes在线平台进行评估。在测试环节中 ,首先我们将输入图像调整为固定尺寸——分别为512×1024像素或768×1536像素——以便于后续的推理运算;随后通过对预测结果执行上采样处理 ,最终获得大小为1024×2048像素的结果图像 。综合来看 ,我们的方法相较于其他现有技术实现了最优的速度-准确性平衡 。

Results on CamVid

表 7 对比展示了其他方法的表现。当输入尺寸设定为 720 \times 960时,在该配置下 STDC1-Seg 达到了令人瞩目的平均帧率(FPS)197.6,并获得了高达 mIoU 的准确性指标达到 73.0\%。这一结果在性能与速度之间的平衡上实现了最佳优化。这些数据进一步验证了我们所提出的方法在性能上的卓越表现。

END

全部评论 (0)

还没有任何评论哟~