语义分割 - DeepLab系列(v1, v2, v3, v3+)

article/2025/11/6 9:06:34

目录

  • 论文精读
    • 四种版本网络的对比
      • v1
      • v2
      • v3
      • v3+
    • 先验知识
      • 分辨率的损失
      • 多尺度特征(multi-scale features)
      • 空洞卷积(Dilated/Atrous Convolution)
        • 标准卷积与空洞卷积
        • 空洞卷积的作用
        • 为了保证输出图像尺寸不变时,padding的设定值
    • DeepLab v3+
      • 深度可分离卷积(depthwise separable convolution)
        • 在Pytorch中的实现
      • 算法架构
  • 算法实现
    • 导入包
    • Xception
      • fixed_padding()
      • 深度可分离卷积
      • 含跳跃连接的块
      • Xception的主体网络
    • ASPP模块
    • DeepLab v3+主体类

论文精读

四种版本网络的对比

v1

  • 修改 VGG16
  • 空洞卷积
  • CRF 作为后处理

v2

  • 设计 ASPP 模块,将空洞卷积的性能发挥到最大
  • 沿用 VGG16 作为主网络
  • 尝试使用 ResNet-101 进行对比实验
  • CRF 作为后处理

v3

  • 以 ResNet 作为主网络
  • 设计了一种串行和一种并行的 DCNN 网络
  • 微调 ASPP 模块
  • 取消 CRF

v3+

  • 以 ResNet 或 Xception为主网络
  • 加入了编解码结构
  • 取消 CRF

先验知识

分辨率的损失

  • 连续的池化和下采样会导致图像分辨率大幅下降,丢失原始信息,且在上采样过程中难以恢复
  • 为了减少分辨率损失,可以采样空洞卷积步长为2的卷积操作代替池化

多尺度特征(multi-scale features)

  • 由于图片中的目标大小不同、远近不同,可能会导致分割效果不尽人意
  • 通过设置不同的不同参数大小的池化层或卷积层,提取到不同尺度的特征
  • 再将这些特征图送入到网络中融合,对网络的性能提升很大

空洞卷积(Dilated/Atrous Convolution)

标准卷积与空洞卷积

标准卷积如下图所示:
在这里插入图片描述

空洞卷积就是在标准卷积核中间填充0,如下图所示:
在这里插入图片描述

空洞卷积的作用

  • 在尽量不丢失分辨率的情况下扩大感受野
  • 通过设定不同参数捕获上下文多尺度信息

为了保证输出图像尺寸不变时,padding的设定值

以实际卷积核尺寸为3为例:
在这里插入图片描述

DeepLab v3+

深度可分离卷积(depthwise separable convolution)

深度可分离卷积可以大大减少参数量,而效果与常规卷积几乎无异甚至要略好

方法:逐通道卷积 + 逐点卷积

逐通道卷积中,一个卷积核负责一个通道,如下图所示:
在这里插入图片描述
逐点卷积类似常规卷积,卷积核尺寸为 1×1×M,M 为上一层的通道数,如下图所示:
在这里插入图片描述

在Pytorch中的实现

分两个

算法架构

架构总览:
在这里插入图片描述
其中,DCNN 为 Xception 网络,如下图所示:
注意:最后一个Res块的最后一个卷积以及跳跃连接的卷积的步长应为1,因为在本次代码中想在下采样最终获得1/8或1/16的尺寸(通过改变Entry_Block3卷积层的步长)
在这里插入图片描述

算法实现

导入包

import math
import torch
import torch.nn as nn
import torch.nn.functional as F

Xception

fixed_padding()

该函数是对图像进行padding,以保证在步长为1的卷积中,其尺寸不变

torch.nn.functional.pad() 用法

根据下图所示公式:
在这里插入图片描述

def fixed_padding(inputs, kernel_size, dilation):kernel_size_effective = kernel_size + (kernel_size - 1) * (dilation - 1)   # 有效kernel_size的公式pad_total = kernel_size_effective - 1pad_beg = pad_total // 2pad_end = pad_total - pad_begpadded_inputs = F.pad(inputs, (pad_beg, pad_end, pad_beg, pad_end))   # 上下左右补零未必是对称的return padded_inputs

深度可分离卷积

此处定义的是流程图中每一个Res小块中相同的、可重复使用的深度可分离卷积

方法:逐通道卷积 + 逐点卷积

torch.nn.Conv2d() 中关于groups参数的说明

class SeparableConv2d_same(nn.Module):def __init__(self, inplanes, planes, kernel_size=3, stride=1, dilation=1, bias=False):super(SeparableConv2d_same, self).__init__()self.conv1 = nn.Conv2d(inplanes, inplanes, kernel_size, stride, 0, dilation,groups=inplanes, bias=bias)self.pointwise = nn.Conv2d(inplanes, planes, 1, 1, 0, 1, 1, bias=bias)def forward(self, x):x = fixed_padding(x, self.conv1.kernel_size[0], dilation=self.conv1.dilation[0])   x = self.conv1(x)   x = self.pointwise(x)   return x

含跳跃连接的块

ReLU() 中的inplace参数的含义

*放在实参前时的用法

class Block(nn.Module):# reps: 重复的卷积层的数量  stride: Block中最大的stridedef __init__(self, inplanes, planes, reps, stride=1, dilation=1, start_with_relu=True, grow_first=True, is_last=False):super(Block, self).__init__()# 跳跃连接的路径上是否有卷积块 (判断条件:通道数发声改变 或 尺寸减半)if planes != inplanes or stride != 1:self.skip = nn.Conv2d(inplanes, planes, 1, stride=stride, bias=False)self.skipbn = nn.BatchNorm2d(planes)else:self.skip = Noneself.relu = nn.ReLU(inplace=True)rep = []   # 建立一个空列表,后续解包后放入Sequence函数中filters = inplanes# 通道数是否在第一个卷积增加if grow_first:rep.append(self.relu)rep.append(SeparableConv2d_same(inplanes, planes, 3, stride=1, dilation=dilation))rep.append(nn.BatchNorm2d(planes))filters = planes   # 在第一个卷积后把filter设为输出通道数for i in range(reps - 1):rep.append(self.relu)rep.append(SeparableConv2d_same(filters, filters, 3, stride=1, dilation=dilation))rep.append(nn.BatchNorm2d(filters))# (对于最后一个块)第二个卷积才增加通道数if not grow_first:rep.append(self.relu)rep.append(SeparableConv2d_same(inplanes, planes, 3, stride=1, dilation=dilation))rep.append(nn.BatchNorm2d(planes))# (对于第一个块)开头没有ReLUif not start_with_relu:rep = rep[1:]# (对于第一、二、三个块)最后一个卷积要增加通道数if stride != 1:rep.append(SeparableConv2d_same(planes, planes, 3, stride=2))# 最后一块的最后一层的卷积步长为1if stride == 1 and is_last:rep.append(SeparableConv2d_same(planes, planes, 3, stride=1))self.rep = nn.Sequential(*rep)   # 此处*的作用是解包def forward(self, inp):x = self.rep(inp)if self.skip is not None:skip = self.skip(inp)skip = self.skipbn(skip)else:skip = inpx += skipreturn x

Xception的主体网络

torch.nn.init 对应的多种参数初始化方法

class Xception(nn.Module):"""Modified Alighed Xception"""def __init__(self, inplanes=3, os=16):super(Xception, self).__init__()if os == 16:entry_block3_stride = 2middle_block_dilation = 1exit_block_dilations = (1, 2)elif os == 8:entry_block3_stride = 1middle_block_dilation = 2exit_block_dilations = (2, 4)else:raise NotImplementedError# Entry flowself.conv1 = nn.Conv2d(inplanes, 32, 3, stride=2, padding=1, bias=False)self.bn1 = nn.BatchNorm2d(32)self.relu = nn.ReLU(inplace=True)self.conv2 = nn.Conv2d(32, 64, 3, stride=1, padding=1, bias=False)self.bn2 = nn.BatchNorm2d(64)self.block1 = Block(64, 128, reps=2, stride=2, start_with_relu=False)self.block2 = Block(128, 256, reps=2, stride=2, start_with_relu=True, grow_first=True)self.block3 = Block(256, 728, reps=2, stride=entry_block3_stride, start_with_relu=True, grow_first=True,is_last=True)# Middle flowself.block4 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block5 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block6 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block7 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block8 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block9 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block10 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block11 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block12 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block13 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block14 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block15 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block16 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block17 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block18 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)self.block19 = Block(728, 728, reps=3, stride=1, dilation=middle_block_dilation, start_with_relu=True, grow_first=True)# Exit flowself.block20 = Block(728, 1024, reps=2, stride=1, dilation=exit_block_dilations[0],start_with_relu=True, grow_first=False, is_last=True)self.conv3 = SeparableConv2d_same(1024, 1536, 3, stride=1, dilation=exit_block_dilations[1])self.bn3 = nn.BatchNorm2d(1536)self.conv4 = SeparableConv2d_same(1536, 1536, 3, stride=1, dilation=exit_block_dilations[1])self.bn4 = nn.BatchNorm2d(1536)self.conv5 = SeparableConv2d_same(1536, 2048, 3, stride=1, dilation=exit_block_dilations[1])self.bn5 = nn.BatchNorm2d(2048)# Init weightsself._init_weight()def forward(self, x):# Entry flowx = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.conv2(x)x = self.bn2(x)x = self.relu(x)x = self.block1(x)# 观察DeepLab v3+的结构图可知:有一个1/4尺寸的特征图被送入decoderlow_level_feat = xx = self.block2(x)x = self.block3(x)# Middle flowx = self.block4(x)x = self.block5(x)x = self.block6(x)x = self.block7(x)x = self.block8(x)x = self.block9(x)x = self.block10(x)x = self.block11(x)x = self.block12(x)x = self.block13(x)x = self.block14(x)x = self.block15(x)x = self.block16(x)x = self.block17(x)x = self.block18(x)x = self.block19(x)# Exit flowx = self.block20(x)x = self.conv3(x)x = self.bn3(x)x = self.relu(x)x = self.conv4(x)x = self.bn4(x)x = self.relu(x)x = self.conv5(x)x = self.bn5(x)x = self.relu(x)return x, low_level_featdef _init_weight(self):for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()

ASPP模块

torch.nn.functional.interpolate() 用法

class ASPP_module(nn.Module):def __init__(self, inplanes, planes, os):   # inplanes: 输入通道 planes: 输出通道 os:output_stridesuper(ASPP_module, self).__init__()# 不同的output_stride对应不同的空洞率组合if os == 16:dilations = [1, 6, 12, 18]elif os == 8:dilations = [1, 12, 24, 36]self.aspp1 = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=1, stride=1,padding=0, dilation=dilations[0], bias=False),nn.BatchNorm2d(planes),nn.ReLU())self.aspp2 = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=3, stride=1,padding=dilations[1], dilation=dilations[1], bias=False),nn.BatchNorm2d(planes),nn.ReLU())self.aspp3 = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=3, stride=1,padding=dilations[2], dilation=dilations[2], bias=False),nn.BatchNorm2d(planes),nn.ReLU())self.aspp4 = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=3, stride=1,padding=dilations[3], dilation=dilations[3], bias=False),nn.BatchNorm2d(planes),nn.ReLU())self.global_avg_pool = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)),nn.Conv2d(inplanes, planes, 1, stride=1, bias=False),nn.BatchNorm2d(planes),nn.ReLU())self.conv1 = nn.Conv2d(1280, 256, 1, bias=False)   # 1280? 五个特征图concatenate后得到的,即256*5self.bn1 = nn.BatchNorm2d(256)self._init_weight()def forward(self, x):x1 = self.aspp1(x)x2 = self.aspp2(x)x3 = self.aspp3(x)x4 = self.aspp4(x)x5 = self.global_avg_pool(x)# 全局池化后特征图尺寸为(batch_size, channels, 1, 1),这里选择双线性插值来恢复尺寸,使其与其他四张特征图一致x5 = F.interpolate(x5, size=x4.size()[2:], mode='bilinear', align_corners=True)x = torch.cat((x1, x2, x3, x4, x5), dim=1)return xdef _init_weight(self):for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()

DeepLab v3+主体类

class DeepLabv3_plus(nn.Module):def __init__(self, nInputChannels=3, n_classes=21, os=16, _print=True):if _print:print("Constructing DeepLabv3+ model...")print("Backbone: Xception")print("Number of classes: {}".format(n_classes))print("Output stride: {}".format(os))print("Number of Input Channels: {}".format(nInputChannels))super(DeepLabv3_plus, self).__init__()# Atrous Convself.xception_features = Xception(nInputChannels, os)self.ASPP = ASPP_module(2048, 256, 16)self.conv1 = nn.Conv2d(1280, 256, 1, bias=False)self.bn1 = nn.BatchNorm2d(256)self.relu = nn.ReLU()# adopt [1x1, 48] for channel reduction.self.conv2 = nn.Conv2d(128, 48, 1, bias=False)self.bn2 = nn.BatchNorm2d(48)self.last_conv = nn.Sequential(nn.Conv2d(304, 256, kernel_size=3, stride=1, padding=1, bias=False),   # 403 = 256+48nn.BatchNorm2d(256),nn.ReLU(),nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False),nn.BatchNorm2d(256),nn.ReLU(),nn.Conv2d(256, n_classes, kernel_size=1, stride=1))def forward(self, input):x, low_level_features = self.xception_features(input)x = self.ASPP(x)x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = F.interpolate(x, size=(int(math.ceil(input.size()[-2]/4)),int(math.ceil(input.size()[-1]/4))), mode='bilinear', align_corners=True)   # math.ceil(): 向上取整,因为size参数只接受intlow_level_features = self.conv2(low_level_features)low_level_features = self.bn2(low_level_features)low_level_features = self.relu(low_level_features)x = torch.cat((x, low_level_features), dim=1)x = self.last_conv(x)x = F.interpolate(x, size=input.size()[2:], mode='bilinear', align_corners=True)return xdef _init_weight(self):for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()

http://chatgpt.dhexx.cn/article/eqLaYqVI.shtml

相关文章

从一维卷积、因果卷积(Causal CNN)、扩展卷积(Dilation CNN) 到 时间卷积网络 (TCN)

来源: AINLPer微信公众号(每日干货分享!!) 编辑: ShuYini 校稿: ShuYini 时间: 2022-09-30 引言 卷积神经网络 (CNN) 尽管通常与图像分类任务相关,但经过改造,同样可以用于序列建模预测。在本文中&#xf…

Pytorch之经典神经网络语义分割(3.1) —— 空洞卷积 Dilated conv/Atrous Conv (膨胀卷积/扩张卷积)(扩张率dilation rate) pytorch实现

2016年提出的 空洞卷积广泛应用于语义分割与目标检测等任务中 空洞卷积(膨胀卷积/扩张卷积) Dilated/Atrous Convolution 空洞卷积是一种不增加参数量(运算量)同时增加输出单元感受野的一种方法。Atrous 卷积,就是带洞的卷积,卷积核是稀疏的 此外&#x…

pytorch的函数中的dilation参数的作用

如果我们设置的dilation0的话,效果如图: 蓝色为输入,绿色为输出,可见卷积核为3*3的卷积核 如果我们设置的是dilation1,那么效果如图: 蓝色为输入,绿色为输出,卷积核仍为3*3&#x…

Pytorch nn.conv2d 的 参数 dilation =1 究竟是?

在pytorch 的 nn.conv2d 和 nn.ConvTranspose2d都有这个参数 dilation。而且默认参数值是 1。那么,当 dilation1 的时候,对应的究竟是 图1 还是 图2 ?是图2。 图1. 图2. 参考资料 Pytorch Tutorial

nn.Conv2d()中dilation参数的作用

nn.Conv2d()中dilation参数的作用 下面这张图很好的描述了这个参数的作用 优点: 这样每次进行单次计算时覆盖的面积(感受域)增大,最开始时3*3 9 然后是5*5 25最后是7*749,增加了感受域却并未增加计算量&#xff0c…

常规-分组-深度分离-分组深度分离卷积-参数量-计算量-dilation-python实现

不同的卷积操作 本文所用样本一般的卷积(常规的)分组卷积深度分离卷积逐通道卷积(depthwise conv)逐点卷积 (pointwise conv) 分组深度分离卷积上面卷积总结卷积的dilation 本文所用样本 x t o r c h . z …

深度学习中常用的几种卷积(下篇):膨胀卷积、可分离卷积(深度可分离、空间可分离)、分组卷积(附Pytorch测试代码)

卷积分类 一、膨胀卷积1.介绍2.调用方式3.实例4.缺点 二、可分离卷积1.空间可分离卷积1)介绍2)优点及缺点3)实例 2.深度可分离卷积1)介绍2)优点及缺点3)实例 三、分组卷积1.介绍2.调用方式以及nn.Conv2d的g…

tensorlfow 卷积操作解释

解释一下: tf.keras.layers.Conv2D(filters,kernel_size,strides=(1, 1),padding=valid,data_format=None,dilation_rate=(1, 1),groups=1,activation=None,use_bias=True,kernel_initializer=glorot_uniform,bias_initializer=zeros,kernel_regularizer=None,bias_regulariz…

dilation conv 和 deconv

最近工作要用到dilation conv,在此总结一下空洞卷积,并和deconv进行对比。 dilation conv 首先空洞卷积的目的是为了在扩大感受野的同时,不降低图片分辨率和不引入额外参数及计算量(一般在CNN中扩大感受野都需要使用s>1的con…

Halcon 形态学(膨胀(Dilation)、腐蚀(Erosion))

文章目录 1 形态学概念2 膨胀(Dilation) 算子介绍3 腐蚀(Erosion)算子介绍4 膨胀腐蚀 示例15 腐蚀膨胀 示例26 示例原图7 补充:结构元素概念1 形态学概念 图像的形态学处理是对图像的局部像素进行处理,用于从图像中提取有意义的局部图像细节。 通过改变局部区域的像素形态…

Torch 池化操作大全 MaxPool2d MaxUnpool2d AvgPool2d FractionalMaxPool2d LPPool2d AdaptivePool2d dilation详解

torch 池化操作 1 池化操作2 类型2.1 MaxPool2d()2.2 MaxUnPool2d()2.3 AvgPool2d()2.4 FractionalMaxPool2d()2.5LPPool2d()2.6AdaptiveMaxPool2d()2.7 AdaptiveAvgPool2d() 3 总结 自我学习记录,他人转载请注明出处 1 池化操作 作用: 提取图片重点特征…

nn.Conv2d中的dilation

参考链接:https://blog.csdn.net/jokerxsy/article/details/108614661 nn.Conv2d中的dilation dilation 1: dilation2:

nn.Conv1d\nn.Conv2d以及groups\dilation参数的理解

文章目录 nn.Conv1dnn.Conv2dnn.Conv2d中groups参数和dilation参数的理解groupsdilation 参考 nn.Conv1d 代码: nn.Conv1d(in_channels 16, out_channels 16, kernel_size (3,2,2), stride (2,2,1), padding [2,2,2])如果输入为: x torch.randn(10,16,30,32,34)则: 1…

剪胀角 angle of dilation

Abaqus选择库伦摩尔塑性模型的时候,需要选择摩擦角和剪胀角。摩擦角物理意义明确,但何为剪胀角?如何影响结果呢?(这两个问题,暂无统一解,Abaqus默认剪胀角最小是0.1) The angle of d…

3.TransposedConv2d实现 (含dilation)

[C 基于Eigen库实现CRN前向推理] 第三部分:TransposedConv2d实现 (含dilation) 前言:(Eigen库使用记录)第一部分:WavFile.class (实现读取wav/pcm,实现STFT)第二部分:Conv2d实现第三部分:Tran…

对卷积层dilation膨胀的作用的理解,caffe-ssd dilation Hole算法解析

先转一篇大佬的博客论文阅读笔记:图像分割方法deeplab以及Hole算法解析 下面是caffe-ssd的vgg16输出后的变化,减少了一个pooling,stride1,然后下一层采用了 dilation方法,作用按上面博客说是 既想利用已经训练好的模…

理解Dilation convolution

论文:Multi-scale context aggregation with dilated convolutions 简单讨论下dilated conv,中文可以叫做空洞卷积或者扩张卷积。首先介绍一下dilated conv诞生背景[4],再解释dilated conv操作本身,以及应用。 首先是诞生背景&a…

卷积膨胀 Dilation

参考:卷积的dilation操作 如果我们设置的dilation0的话,效果如图: 蓝色为输入,绿色为输出,可见卷积核为3*3的卷积核 如果我们设置的是dilation1,那么效果如图: 蓝色为输入,绿色为输出,卷积核仍…

卷积核膨胀(dilation)解析

本文转自http://blog.csdn.net/tangwei2014和https://blog.csdn.net/jiongnima/article/details/69487519,转载目的便于自己查看学习 第一位大神解析: deeplab发表在ICLR 2015上。论文下载地址:Semantic Image Segmentation with Deep Conv…

【DL】卷积膨胀 Dilation

Backto DeepLearning Index dilation dilation 是对 kernel 进行膨胀,多出来的空隙用 0 padding。用于克服 stride 中造成的 失真问题。 对应关系是 k d = ( k − 1 ) d + 1 k_{d} = (k -1)\times d + 1 kd​