最近在学习图像分割,读到一篇论文《Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation》。
摘要
空间金字塔池化模块或者编码解码结构被广泛应用于深度神经网络的语义分割任务中。前者网络能通过filters探测输入特征信息或者带有多尺度rates以及多尺度感受野的池化操作来编码多尺度语义信息。本文,我们提出结合两种方法的优点。具体来说,我们提出的模型DeepLabv3++,扩展了DeepLabv3,通过引入一个简单而有效的编码器模块来细化分割结果,尤其细化了沿着目标边界的部分。我们进一步探索了Xception 模型,并将深度可分离卷积应用于ASPP(Atrous Spatial Pyramid Pooling)以及解码模块,实现了更快更强的编码-解码器网络。我们在PASCAL VOC2012数据集以及Cityscapes数据集上验证了所提模型有效性,测试效果在没有经过任何后期处理的情况下可分别达到89%以及82.1%。
代码:https://github.com/tensorflow/models/tree/master/research/deeplab
摘要小结:
这里其实相当于将ASPP空间金字塔池化(用于获取上下文语义信息)和带有多尺度模块的编码器(用于细化分割边界)结合了,其中ASPP模块还融合了Xception以及深度可分离卷积。
看起来比较简单的思想,我们接下来细看。
introduce
特征金字塔讲解参考https://www.cnblogs.com/hansjorn/p/14295889.html
###################**************************************##############
为了在多个尺度上捕获上下文信息,DeepLabv3[23]应用了几个具有不同速率的并行atrous卷积(称为atrous Spatial Pyramid Pooling,或ASPP),而PSPNet[24]则在不同的网格尺度上执行池化操作。尽管在最后的特征图中编码了丰富的语义信息,但由于网络主干内的striding operations的池化或卷积,与对象边界相关的详细信息丢失了。这可以通过应用atrous卷积来提取更密集的特征图来缓解。然而,考虑到目前最先进的神经网络设计[7,9,10,25,26]和有限的GPU内存,在计算上无法提取比输入分辨率小8倍甚至4倍的输出特征图。以ResNet-101[25]为例,当应用atrous卷积提取比输入分辨率小16倍的输出特征时,最后3个残余块(9层)内的特征必须进行扩张。更糟糕的是,如果输出特征比输入小8倍,26个剩余块(78层!)将受到影响。因此,如果为这类模型提取更密集的输出特征,将是计算密集型的。另一方面,编码器-解码器模型[21,22]在编码器路径中具有更快的计算速度(因为没有特征被扩展),并在解码器路径中逐渐恢复尖锐的目标边界。结合两种方法的优点,我们提出在编码器-解码器网络中加入多尺度的上下文信息来丰富编码器模块。
(a)是SPPhttps://www.cnblogs.com/zongfa/p/9076311.html(b)是编码解码器,(c)是带有atrous卷积的编码解码器。
小结一下:
提出本文网络的原因:采用striding operations操作(直接设置stride=2的池化、卷积等操作)导致对象边界信息丢失,因此采用atrous卷积(因为它包含多rates的卷积),但是该并行结构消耗大量GPU内存。在计算上无法提取比输入分辨率小8倍甚至4倍的输出特征图。可以看上面的图,可以看到(a)SPP右半侧可以提取比输入分辨率小8倍的特征图,但是Atrous不可以(可以提取小4倍),(b)编码解码器结构具备更快的计算速度,因此将两者优势结合,用带有Atrous的编码器以及原始解码器(可提取分辨率小4倍的)如©。
ASPP的结构:
所提的网络为Deeplabv3++,用一个简单有效的编码解码器细化对象边界,从而扩展了Deeplabv3.引入atrous卷积控制编码器的密度,丰富上下文的语义信息提取。受到深度可分离卷积的激励,本文将xception模型与深度可分离卷积结合提高了检测速度和准确率。并将atrous深度可分离卷积应用于aspp模块以及编码器模块。
in summary
我们提出了一种新颖的编码器-解码器结构,采用了DeepLabv3作为一个强大的编码器模块和一个简单而有效的解码器模块。
**-**在我们的结构中,可以通过atrous卷积任意控制提取的编码器特征的分辨率,以平衡精度和运行时间,这是现有的编码器-解码器模型不可能做到的。
**-**我们将Xception模型用于分割任务,并将深度可分离卷积应用于ASPP模块和解码器模块,从而实现更快、更强的编码器-解码器网络。
**-**我们提出的模型在PASCAL VOC 2012和城市景观数据集上获得了新的先进性能。我们也提供详细的设计选择和模型变体的分析。
related work
基于全卷积网络(Fully Convolutional Networks, fns)[8,11]的模型已经证明在几个分割基准上有显著的改进[1,2,3,4,5]。有几种模型变体被提出来利用上下文信息进行分割[12,13,14,15,16,17,32,33],包括那些采用多尺度输入的模型(即,图像金字塔)[34,35,36,37,38,39]或采用概率图形模型(如DenseCRF[40]和高效推理算法[41])[42,43,44,37,45,46,47,48,49,50,51,39]。在本文中,我们主要讨论了使用空间金字塔池和编解码结构的模型。
空间金字塔池:模型,如PSPNet[24]或DeepLab[39,23],在几种网格尺度(包括图像级池化[52])上执行空间金字塔池化[18,19],或应用几种不同速率的并行阿特拉斯卷积(称为阿特拉斯空间金字塔池化,或ASPP)。这些模型利用多尺度信息,在多个分割基准上取得了良好的效果。
编码器-解码器:编码器-解码器网络已成功应用于许多计算机视觉任务,包括人体姿态估计[53],目标检测[54,55,56],语义分割[11,57,21,22,58,59,60,61,62,63,64]。通常,编码器-解码器网络包含(1)逐步减少特征映射并捕获更高语义信息的编码器模块和(2)逐步恢复空间信息的解码器模块。在此基础上,我们建议使用DeepLabv3[23]作为编码器模块,并添加一个简单但有效的解码器模块,以获得更清晰的分割。
我们提出的DeepLabv3+通过使用编码器解码器结构来扩展DeepLabv3。编码器模块通过多尺度的Atrous卷积对多尺度的上下文信息进行编码,而简单有效的解码器模块则沿着目标边界对分割结果进行细化。
深度可分卷积:深度可分卷积[27,28]或群卷积[7,65],这是一种强大的运算,可以在保持相似(或稍好)性能的同时降低计算成本和参数数量。这种操作在最近的许多神经网络设计中被采用[66,67,26,29,30,31,68]。特别是,我们探索了Xception模型[26],类似于[31]的COCO 2017检测挑战提交,在语义分词任务的准确性和速度方面都有提高。
Methods
在这一节中,我们将简要介绍atrous卷积[69,70,8,71,42]和深度可分离卷积[27,28,67,26,29]。然后,在讨论附加到编码器输出的建议解码器模块之前,我们回顾了DeepLabv3[23],它被用作我们的编码器模块。我们还提出了一个改进的Xception模型[26,31],该模型通过更快的计算速度进一步提高了性能。
3.1 Encoder-Decoder with Atrous Convolution
Atrous卷积:Atrous卷积是一个强大的工具,它允许我们明确地控制由深度卷积神经网络计算的特征的分辨率,并调整滤波器的视场以捕获多尺度信息,它推广了标准的卷积运算。对于二维信号,对于输出特征图y上的每个位置i和卷积滤波器w,对输入特征图x进行atrous卷积,如下所示:
图3所示。3 × 3深度可分卷积将一个标准卷积分解为(a)深度卷积(为每个输入通道应用一个滤波器)和(b)逐点卷积(结合通道间深度卷积的输出)。在本文中,我们探索了阿屈可分卷积,在深度卷积中采用阿屈可分卷积,如©所示,其速率为2。
PS:深度可分离卷积在MobileNet网络中出现,不懂的可以去看MobileNet结构
代码解析:
deeplab系列所有代码解析
from __future__ import absolute_import, print_function
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
import warnings
warnings.filterwarnings("ignore")
class _ImagePool(nn.Module):
def __init__(self, in_ch, out_ch):
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1)
self.conv = _ConvBnReLU(in_ch, out_ch, 1, 1, 0, 1)
def forward(self, x):
_, _, H, W = x.shape
h = self.pool(x)
h = self.conv(h)
h = F.interpolate(h, size=(H, W), mode="bilinear", align_corners=False)
return h
class _ASPP(nn.Module):
"""
Atrous spatial pyramid pooling with image-level feature
"""
def __init__(self, in_ch, out_ch, rates):
super(_ASPP, self).__init__()
self.stages = nn.Module()
self.stages.add_module("c0", _ConvBnReLU(in_ch, out_ch, 1, 1, 0, 1))
for i, rate in enumerate(rates):
self.stages.add_module(
"c{}".format(i + 1),
_ConvBnReLU(in_ch, out_ch, 3, 1, padding=rate, dilation=rate),
)
self.stages.add_module("imagepool", _ImagePool(in_ch, out_ch))
def forward(self, x):
return torch.cat([stage(x) for stage in self.stages.children()], dim=1)
class DeepLabV3Plus(nn.Module):
"""
DeepLab v3+: Dilated ResNet with multi-grid + improved ASPP + decoder
"""
def __init__(self, n_classes, n_blocks, atrous_rates, multi_grids, output_stride):
super(DeepLabV3Plus, self).__init__()
# Stride and dilation
if output_stride == 8:
s = [1, 2, 1, 1]
d = [1, 1, 2, 4]
elif output_stride == 16:
s = [1, 2, 2, 1]
d = [1, 1, 1, 2]
# Encoder
ch = [64 * 2 ** p for p in range(6)]
self.layer1 = _Stem(ch[0])
self.layer2 = _ResLayer(n_blocks[0], ch[0], ch[2], s[0], d[0])
self.layer3 = _ResLayer(n_blocks[1], ch[2], ch[3], s[1], d[1])
self.layer4 = _ResLayer(n_blocks[2], ch[3], ch[4], s[2], d[2])
self.layer5 = _ResLayer(n_blocks[3], ch[4], ch[5], s[3], d[3], multi_grids)
self.aspp = _ASPP(ch[5], 256, atrous_rates)
concat_ch = 256 * (len(atrous_rates) + 2)
self.add_module("fc1", _ConvBnReLU(concat_ch, 256, 1, 1, 0, 1))
# Decoder
self.reduce = _ConvBnReLU(256, 48, 1, 1, 0, 1)
self.fc2 = nn.Sequential(
OrderedDict(
[
("conv1", _ConvBnReLU(304, 256, 3, 1, 1, 1)),
("conv2", _ConvBnReLU(256, 256, 3, 1, 1, 1)),
("conv3", nn.Conv2d(256, n_classes, kernel_size=1)),
]
)
)
def forward(self, x):
h = self.layer1(x)
h = self.layer2(h)
h_ = self.reduce(h)
h = self.layer3(h)
h = self.layer4(h)
h = self.layer5(h)
h = self.aspp(h)
h = self.fc1(h)
h = F.interpolate(h, size=h_.shape[2:], mode="bilinear", align_corners=False)
h = torch.cat((h, h_), dim=1)
h = self.fc2(h)
h = F.interpolate(h, size=x.shape[2:], mode="bilinear", align_corners=False)
return h
#
try:
from encoding.nn import SyncBatchNorm
_BATCH_NORM = SyncBatchNorm
except:
_BATCH_NORM = nn.BatchNorm2d
_BOTTLENECK_EXPANSION = 4
class _ConvBnReLU(nn.Sequential):
"""
Cascade of 2D convolution, batch norm, and ReLU.
"""
BATCH_NORM = _BATCH_NORM
def __init__(
self, in_ch, out_ch, kernel_size, stride, padding, dilation, relu=True
):
super(_ConvBnReLU, self).__init__()
self.add_module(
"conv",
nn.Conv2d(
in_ch, out_ch, kernel_size, stride, padding, dilation, bias=False
),
)
self.add_module("bn", _BATCH_NORM(out_ch, eps=1e-5, momentum=0.999))
if relu:
self.add_module("relu", nn.ReLU())
class _Bottleneck(nn.Module):
"""
Bottleneck block of MSRA ResNet.
"""
def __init__(self, in_ch, out_ch, stride, dilation, downsample):
super(_Bottleneck, self).__init__()
mid_ch = out_ch // _BOTTLENECK_EXPANSION
self.reduce = _ConvBnReLU(in_ch, mid_ch, 1, stride, 0, 1, True)
self.conv3x3 = _ConvBnReLU(mid_ch, mid_ch, 3, 1, dilation, dilation, True)
self.increase = _ConvBnReLU(mid_ch, out_ch, 1, 1, 0, 1, False)
self.shortcut = (
_ConvBnReLU(in_ch, out_ch, 1, stride, 0, 1, False)
if downsample
else lambda x: x # identity
)
def forward(self, x):
h = self.reduce(x)
h = self.conv3x3(h)
h = self.increase(h)
h += self.shortcut(x)
return F.relu(h)
class _ResLayer(nn.Sequential):
"""
Residual layer with multi grids
"""
def __init__(self, n_layers, in_ch, out_ch, stride, dilation, multi_grids=None):
super(_ResLayer, self).__init__()
if multi_grids is None:
multi_grids = [1 for _ in range(n_layers)]
else:
assert n_layers == len(multi_grids)
# Downsampling is only in the first block
for i in range(n_layers):
self.add_module(
"block{}".format(i + 1),
_Bottleneck(
in_ch=(in_ch if i == 0 else out_ch),
out_ch=out_ch,
stride=(stride if i == 0 else 1),
dilation=dilation * multi_grids[i],
downsample=(True if i == 0 else False),
),
)
class _Stem(nn.Sequential):
"""
The 1st conv layer.
Note that the max pooling is different from both MSRA and FAIR ResNet.
"""
def __init__(self, out_ch):
super(_Stem, self).__init__()
self.add_module("conv1", _ConvBnReLU(3, out_ch, 7, 2, 3, 1))
self.add_module("pool", nn.MaxPool2d(3, 2, 1, ceil_mode=True))
if __name__ == "__main__":
model = DeepLabV3Plus(
n_classes=21,
n_blocks=[3, 4, 23, 3],
atrous_rates=[6, 12, 18],
multi_grids=[1, 2, 4],
output_stride=16,
)
model.eval()
image = torch.randn(1, 3, 513, 513)
print(model)
print("input:", image.shape)
print("output:", model(image).shape)