Deeplabv3+概述(语义分割,小白必看)

唐宋宋宋 2024-07-17 13:31:03 阅读 87

哈喽大家好 ! 我是唐宋宋宋,很荣幸与您相见!!

代码奉上:https://github.com/bubbliiiing/deeplabv3-plus-pytorch

图像分割可以分为两类:语义分割(Semantic Segmentation)和实例分割(Instance Segmentation),其区别如图所示。

可以看到语义分割只是简单地对图像中各个像素点分类,但是实例分割更进一步,需要区分开不同物体,这更加困难,从一定意义上来说,实例分割更像是语义分割加检测。这里我们主要关注语义分割。

语义分割使用的网络模型有很多比如pspnet,unet/unet++,deeplabv1,v2,v3,v3+,我们这里使用的是Deeplabv3+进行语义分割。

Deeplabv3+整体架构:

由这幅图我们可以发现,其实deeplabV3+模型仍然是两个部分,一个部分是Encoder,一个部分是Decoder

DeepLabv3+模型的整体架构如图所示,它的Encoder的主体是带有空洞卷积的DCNN,可以采用常用的分类网络如ResNet,然后是带有空洞卷积的空间金字塔池化模块(Atrous Spatial Pyramid Pooling, ASPP)),主要是为了引入多尺度信息;相比DeepLabv3,v3+引入了Decoder模块,其将底层特征与高层特征进一步融合,提升分割边界准确度。

Deeplabv3+对于Deeplabv系列的改进如下:

引入了对于膨胀卷积的膨胀系数。

改进了空洞空间金字塔池化

移除了条件随机场CFRs后处理,目的(精细化分割)

其实deeplabV3+与pspnet、segnet、unet相比,其最大的特点就是引入了空洞卷积,在不损失信息的情况下,加大了感受野,让每个卷积输出都包含较大范围的信息。这有利于提取多尺度信息,如下就是空洞卷积的一个示意图,所谓空洞就是特征点提取的时候会跨像素。

空洞卷积的目的其实也就是提取更有效的特征,所以它位于Encoder网络中用于特征提取。

接着我们先来看一下encoder。

可以看出来这部分由dcnn主干网络加一个aspp组成,dcnn可以使用任意一网络模型,官方使用的是resnet模型,这里我为了更轻量化使用的是基于mobilenet的deeplabv3+,由图可有看到dcnn使用的也是串行结构,然后输出的是两部分,一部分直接输出进入到decoder部分,一部分经过了aspp结构输出进行拼接,然后经过一个1*1的卷积压缩特征。

这里我们重点说一下这个ASPP模型

从上图我们可以看出对于Deeplabv2和Deeplabv3+ aspp的一个改进,在v2中仅仅使用了四个3*3的卷积,然后相加进行一个特征融合,而在v3中我们可以清晰的看到首先是使用了一个1*1的卷积+一个bn+一个relu 紧接着使用了3个3*3的卷积(所使用的膨胀系数也是不一样的 这里是12,24,36), 最后加一个全局平均池化(主要在于这里采用池化层来获取多尺度特征)+1*1大小的卷积+bn+relu+双线性插值来对特征进行上采样来保证特征图大小一样,(注意这里的分支特征大小都是一样规格的才可以拼接)最后对于这五个分支进行一个拼接,然后通过一个1*1的卷积 和一个随机失活进行输出。

然后我们来看一下decoder模块

这里的第一个下箭头是我们encoder,dcnn的输出,一个是我们通过aspp的输出,最后进行一个拼接,一个3*3的卷积和一个上采样,这里的上采样是使用双线性插值的方法,最后输出,就是这么简单。

👀👀 理论总结:

deeplabv3+的缺点:

预测的feature map 直接双线性上采样16倍,到期望的尺寸,这样会 细节信息不够

deeplabv3+的特点:

使用了 【encoder-decoder】(高层特征提供语义,decoder逐步恢复边界信息):提升了分割效果的同时,关注了边界的信息

使用【ASPP】, 并将【深度可分离卷积(depthwise deparable conv)】应用在了ASPP 和 encoder 模块中,使网络更快。

decoder结构:采用一个简单的模块,用于恢复目标边界细节

deeplabv3+的效果:

在PASCAL VOC 2012 和 Cityscaps 数据集上获得 89.0% 和 82.1% 的分割效果,没有添加任何的后处理。

大家看一下我的代码。

dcnn模块使用的是MobileNetV2模型

class MobileNetV2(nn.Module):

def __init__(self, downsample_factor=8, pretrained=True):

super(MobileNetV2, self).__init__()

from functools import partial

model = mobilenetv2(pretrained)

self.features = model.features[:-1]

self.total_idx = len(self.features)

self.down_idx = [2, 4, 7, 14]

if downsample_factor == 8:

for i in range(self.down_idx[-2], self.down_idx[-1]):

self.features[i].apply(

partial(self._nostride_dilate, dilate=2)

)

for i in range(self.down_idx[-1], self.total_idx):

self.features[i].apply(

partial(self._nostride_dilate, dilate=4)

)

elif downsample_factor == 16:

for i in range(self.down_idx[-1], self.total_idx):

self.features[i].apply(

partial(self._nostride_dilate, dilate=2)

)

def _nostride_dilate(self, m, dilate):

classname = m.__class__.__name__

if classname.find('Conv') != -1:

if m.stride == (2, 2):

m.stride = (1, 1)

if m.kernel_size == (3, 3):

m.dilation = (dilate // 2, dilate // 2)

m.padding = (dilate // 2, dilate // 2)

else:

if m.kernel_size == (3, 3):

m.dilation = (dilate, dilate)

m.padding = (dilate, dilate)

def forward(self, x):

low_level_features = self.features[:4](x)

x = self.features[4:](low_level_features)

return low_level_features, x

ASPP特征提取模块 ,利用不同膨胀率的膨胀卷积进行特征提取,如下

class ASPP(nn.Module):

def __init__(self, dim_in, dim_out, rate=1, bn_mom=0.1):

super(ASPP, self).__init__()

self.branch1 = nn.Sequential(

nn.Conv2d(dim_in, dim_out, 1, 1, padding=0, dilation=rate, bias=True),

nn.BatchNorm2d(dim_out, momentum=bn_mom),

nn.ReLU(inplace=True),

)

self.branch2 = nn.Sequential(

nn.Conv2d(dim_in, dim_out, 3, 1, padding=6 * rate, dilation=6 * rate, bias=True),

nn.BatchNorm2d(dim_out, momentum=bn_mom),

nn.ReLU(inplace=True),

)

self.branch3 = nn.Sequential(

nn.Conv2d(dim_in, dim_out, 3, 1, padding=12 * rate, dilation=12 * rate, bias=True),

nn.BatchNorm2d(dim_out, momentum=bn_mom),

nn.ReLU(inplace=True),

)

self.branch4 = nn.Sequential(

nn.Conv2d(dim_in, dim_out, 3, 1, padding=18 * rate, dilation=18 * rate, bias=True),

nn.BatchNorm2d(dim_out, momentum=bn_mom),

nn.ReLU(inplace=True),

)

self.branch5_conv = nn.Conv2d(dim_in, dim_out, 1, 1, 0, bias=True)

self.branch5_bn = nn.BatchNorm2d(dim_out, momentum=bn_mom)

self.branch5_relu = nn.ReLU(inplace=True)

self.conv_cat = nn.Sequential(

nn.Conv2d(dim_out * 5, dim_out, 1, 1, padding=0, bias=True),

nn.BatchNorm2d(dim_out, momentum=bn_mom),

nn.ReLU(inplace=True),

)

def forward(self, x):

[b, c, row, col] = x.size()

# 一共五个分支

conv1x1 = self.branch1(x)

conv3x3_1 = self.branch2(x)

conv3x3_2 = self.branch3(x)

conv3x3_3 = self.branch4(x)

# -----------------------------------------#

# 第五个分支,全局平均池化+卷积

# -----------------------------------------#

global_feature = torch.mean(x, 2, True)

global_feature = torch.mean(global_feature, 3, True)

global_feature = self.branch5_conv(global_feature)

global_feature = self.branch5_bn(global_feature)

global_feature = self.branch5_relu(global_feature)

global_feature = F.interpolate(global_feature, (row, col), None, 'bilinear', True)

# -----------------------------------------#

# 将五个分支的内容堆叠起来

# 然后1x1卷积整合特征。

# -----------------------------------------#

feature_cat = torch.cat([conv1x1, conv3x3_1, conv3x3_2, conv3x3_3, global_feature], dim=1)

result = self.conv_cat(feature_cat)

return result

接着是对于这两个模型进行一个总结,我这里做了个选择 可以选择使用原始论文的xception,可以选择我使用的mobilenet

class DeepLab(nn.Module):

def __init__(self, num_classes, backbone="mobilenet", pretrained=True, downsample_factor=16):code>

super(DeepLab, self).__init__()

if backbone == "xception":

# ----------------------------------#

# 获得两个特征层

# 浅层特征 [128,128,256]

# 主干部分 [30,30,2048]

# ----------------------------------#

self.backbone = xception(downsample_factor=downsample_factor, pretrained=pretrained)

in_channels = 2048

low_level_channels = 256

elif backbone == "mobilenet":

# ----------------------------------#

# 获得两个特征层

# 浅层特征 [128,128,24]

# 主干部分 [30,30,320]

# ----------------------------------#

self.backbone = MobileNetV2(downsample_factor=downsample_factor, pretrained=pretrained)

in_channels = 320

low_level_channels = 24

else:

raise ValueError('Unsupported backbone - `{}`, Use mobilenet, xception.'.format(backbone))

# -----------------------------------------#

# ASPP特征提取模块

# 利用不同膨胀率的膨胀卷积进行特征提取

# -----------------------------------------#

self.aspp = ASPP(dim_in=in_channels, dim_out=256, rate=16 // downsample_factor)

# ----------------------------------#

# 浅层特征边

# ----------------------------------#

self.shortcut_conv = nn.Sequential(

nn.Conv2d(low_level_channels, 48, 1),

nn.BatchNorm2d(48),

nn.ReLU(inplace=True)

)

self.cat_conv = nn.Sequential(

nn.Conv2d(48 + 256, 256, 3, stride=1, padding=1),

nn.BatchNorm2d(256),

nn.ReLU(inplace=True),

nn.Dropout(0.5),

nn.Conv2d(256, 256, 3, stride=1, padding=1),

nn.BatchNorm2d(256),

nn.ReLU(inplace=True),

nn.Dropout(0.1),

)

self.cls_conv = nn.Conv2d(256, num_classes, 1, stride=1)

def forward(self, x):

H, W = x.size(2), x.size(3)

# -----------------------------------------#

# 获得两个特征层

# low_level_features: 浅层特征-进行卷积处理

# x : 主干部分-利用ASPP结构进行加强特征提取

# -----------------------------------------#

low_level_features, x = self.backbone(x)

x = self.aspp(x)

low_level_features = self.shortcut_conv(low_level_features)

# -----------------------------------------#

# 将加强特征边上采样

# 与浅层特征堆叠后利用卷积进行特征提取

# zykandqss

# -----------------------------------------#

x = F.interpolate(x, size=(low_level_features.size(2), low_level_features.size(3)), mode='bilinear',code>

align_corners=True)

x = self.cat_conv(torch.cat((x, low_level_features), dim=1))

x = self.cls_conv(x)

x = F.interpolate(x, size=(H, W), mode='bilinear', align_corners=True)code>

return x

最后我们可以看一下训练好的评价指标,运行代码部分的get_miou.py可以得到一些评价指标,这里可以看一下我的例子。

可以看到有miou,map,pre,recall各项指标。

感谢大家阅读!🙏🙏🙏



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。