TernausNet

论文地址:https://arxiv.org/abs/1801.05746

github地址:https://github.com/ternaus/TernausNet

数据集:Kaggle Carvana

像素级分割在计算机视觉中是一项艰巨的任务,经典的UNet网络结构在医学影像和卫星图像中非常流行。一般来说神经网权重由一些大型数据集如ImageNet进行初始化后会有更好的效果。在一些实际应用中,尤其是在医学和交通安全方面,模型的精确是至关重要的,本文演示如何使用预训练编码器来改善UNet网络结构。

  1. 经典的UNet网络权重采用随机初始化方式来完成,众所周知训练一个未过拟合的网络需要大量的数据来完成。因此采用经过Imagenet训练后的权重来进行初始化这一方法被广泛应用。通过这种方式来加速学习过程。
  2. 此网络编码器部分采用VGG11(VGG11包含7个3×3卷积层,每个层后加一个ReLU,同时进行5次最大池化操作,具体如下图)

为构造编码器,这里移除了全连接层替换其为一个512通道的单卷积层来分离编码器和解码器。为构造解码器这里采用转置卷积层放大特征图尺寸并且减少一半原通道数。同时将转置卷积的输出与解码器的相应部分的输出串联。特征图的结果通过卷积操作使得其通道数与对应编码器部分相同。这一上采样过程重复5次对应5次池化操作。传统全连接层可接受任意大小图片输入,但因为此处有5个池化层,每次图像缩小到原来一半,即缩小$2^5=32$倍,因此当前网络要求输入图像大小需要能被32整除。下图为本文网络结构图。

3D U-Net

论文:3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation

github: https://github.com/wolny/pytorch-3dunet

论文最早版本arXiv上的发表时间是2016.06,本文是论文v1版本笔记 MICCAI 2016收录

本文提出了一种从稀疏注释的立体数据中学习三维分割的网络。3D U-Net这篇论文的诞生主要是为了处理一些块状图(volumetric images),基本的原理跟U-Net其实并无大差,因为3D U-Net就是用3D卷积操作替换了2D的

3D数据对于生物医学数据分析来说显得非常冗余

  • 在三维层面上标注分割label比较困难,因为电脑屏幕上只能展示2D的切片
  • 同时,逐层标注大量的切片又很繁琐,且相邻层的信息几乎是相同的
  • 因此,完整注释3D数据并不是创建大而丰富的训练数据集的有效方法,尤其是对于需要大量标签数据的学习类算法

生物医学影像(biomedical images)很多时候都是块状的,也就是说是由很多个切片构成一整张图的存在。如果是用2D的图像处理模型去处理3D本身不是不可以,但是会存在一个问题,就是不得不将生物医学影像的图片一个slice一个slice成组的(包含训练数据和标注好的数据)的送进去设计的模型进行训练,在这种情况下会存在一个效率问题,因而很多时候处理块状图的时候会让任感到不适,并且数据预处理的方式也相对比较繁琐(tedious)。

所以,论文的作者就提出来了3D -Net模型,模型不仅解决了效率的问题,并且对于块状图的切割只要求数据中部分切片被标注即可(可参考下图说明)。

模型结构(Network Architecture)

整个3D U-Net的模型是基于之前U-Net(2D)创建而来,同样包含了一个encoder部分和一个decoder部分,encoder部分是用来分析整张图片并且进行特征提取与分析,而与之相对应的decoder部分是生成一张分割好的块状图。论文中使用的输入图像的大小是132 * 132 * 116,整个网络的结构前半部分(analysis path)包含及使用如下卷积操作:

a. 每一层神经网络都包含了两个 3 * 3 * 3的卷积(convolution)

b. Batch Normalization(为了让网络能更好的收敛convergence)

c. ReLU

d. Downsampling:2 * 2 * 2的max_polling,步长stride = 2

而与之相对应的合成路径(synthesis path)则执行下面的操作:

a. upconvolution: 2 * 2 * 2,步长=2

b. 两个正常的卷积操作:3 * 3 * 3

c. Batch Normalization

d. ReLU

于此同时,需要把在analysis path上相对应的网络层的结果作为decoder的部分输入,这样子做的原因跟U-Net博文提到的一样,是为了能采集到特征分析中保留下来的高像素特征信息,以便图像可以更好的合成。

整体的一个网络结构如下图所示,其实可以看出来跟2D结构的U-Net是基本一样,唯一不同的就是全部2D操作换成了3D,这样子做了之后,对于volumetric image就不需要单独输入每个切片进行训练,而是可以采取图片整张作为输入到模型中(PS:但是当图像太大的时候,此时需要运用random crop的技巧将图片随机裁切成固定大小模块的图片放入搭建的模型进行训练,当然这是后话,之后将会在其他文章中进行介绍)。除此之外,论文中提到的一个亮点就是,3D U-Net使用了weighted softmax loss function将未标记的像素点设置为0以至于可以让网络可以更多地仅仅学习标注到的像素点,从而达到普适性地特点。

训练细节(Training)

3D U-Net同样采用了数据增强(data augmentation)地手段,主要由rotation、scaling和将图像设置为gray,于此同时在训练数据上和真实标注的数据上运用平滑的密集变形场(smooth dense deformation field),主要是通过从一个正态分布的随机向量样本中选取标准偏差为4的网格,在每个方向上具有32个体素的间距,然后应用B样条插值(B-Spline Interpolation,不知道什么是B样条插值法的可以点连接进行查看,在深度学习模型的创建中有时候也不需要那么复杂,所以这里仅限了解,除非本身数学底子很好已经有所了解),B样条插值法比较笼统地说法就是在原本地形状上找到一个类似地形状来近似(approximation)。之后就对数据开始进行训练,训练采用的是加权交叉熵损失(weighted cross-entropy loss function)以至于减少背景的权重并增加标注到的图像数据部分的权重以达到平衡的影响小管和背景体素上的损失。

实验的结果是用IoU(intersection over union)进行衡量的,即比较生成图像与真实被标注部分的重叠部分。

论文针对肾脏的生物医学影像的分割结果达到了IoU=86.3%的结果。3D U-Net的诞生在医学影像分割,特别是那些volumetric images都是由很大帮助的,因为它很大程度上解决了3D图像一个个slice送入模型进行训练的尴尬局面,也大幅度的提升训练效率,并且保留了FCN和U-Net本来具备的优秀特征。

U2-Net

论文: U2-Net: Going Deeper with Nested U-Structure for Salient Object Detection

CVPR2020

github: https://github.com/xuebinqin/U-2-Net

U²-Net给我们带来了什么?

得益于在SOTA SOD方法取得了不错的竞争力,U²-Net可以应用在很多场景。首先,U²-Net现在已经是Python的抠图工具Rembg的基础算法。抠图就是将照片的主体人或物品从图片中抠出来,以便贴到别处使用除了被用来作为抠图外,素描肖像生成(Portrait Drawing)也是其非常有趣且流行的新应用。

显着物体检测(SOD)

显着物体检测(SOD)是基于视觉注意机制的任务,其中算法旨在探索比场景或图像周围区域更专注的物体或区域,因此很适合于做抠图应用。

从自然场景中检测和分割最具视觉吸引力的对象的过程,在计算机视觉领域称为显着对象检测(SOD)。现有的大多数SOD网络都具有类似的设计,并且专注于利用由AlexNet,VGG,ResNet,ResNeXt,DenseNet等骨干网络提取的深度特征。这些骨干网络最初是为图像分类任务而构建的,因此它们提取特征代表语义含义,而不是对显着物体检测至关重要的局部细节或全局参考信息。这样的网络还倾向于在ImageNet上进行数据效率低下的预训练。

U²-Net是一种简单而强大的深度网络体系结构,具有新颖的两层嵌套U形结构,旨在解决这些问题。提出的ReSidual U (RSU)具有各种不同大小的感受野,从而使其能够更好地捕获不同规模的上下文信息。 RSU还使用池化操作来增加总体体系结构深度,而不会显着增加计算成本。

Architecture

RSU

RSU具有三个主要组成部分:输入卷积层,类似U-Net的对称编码器-解码器结构,以及通过求和融合局部和多尺度特征的残差连接。

RSU和原始残差块之间的主要区别在于,RSU用类似U-Net的结构,替换了普通的单流卷积,并用通过加权层转换的局部特征替换了原始特征。

U-2-Net

在RSU的基础上开发了U²-Net,这是用于显着物体检测的新型堆叠U形结构。 U²-Net包括一个6级编码器,一个5级解码器和一个显着度图融合模块,该模块连接到解码器级和最后一个编码器级。

总体而言,U²-Net网络结构具有丰富的多尺度特征,以及较低的计算和内存成本。 另外,由于U²-Net体系结构仅建立在RSU块上,并且不使用任何经过预训练的骨干网络进行图像分类处理,因此可以灵活,轻松地适应不同的工作环境,而性能损失最小。

Dual Attention Network for Scene Segmentation

论文地址 : https://arxiv.org/abs/1809.02983

github:https://github.com/junfu1115/DANetCVPR2019)

 为了有效地完成场景分割的任务,我们需要区分一些混淆的类别,并考虑不同外观的对象。例如,草原与牧场有时候是很难区分的,公路上的车也存在尺度、视角、遮挡与亮度等的变化。因此,像素级识别需要提高特征表示的识别能力。

创新点:

通过基于Self Attention mechanism来捕获上下文依赖,并提出了Dual Attention Networks (DANet)来自适应地整合局部特征和全局依赖。该方法能够自适应地聚合长期上下文信息,从而提高了场景分割的特征表示。

  • 提出了Dual Attention Networks (DANet)在spatial和channle维度来捕获全局特征依赖。
  • 提出position attention module去学习空间特征的相关性,提出channel attention module去建模channle的相关性。

在一贯的dilated FCN中加入两种类型地attention module。其中position attention module选择性地通过所有位置的加权求和聚集每个位置的特征,channel attention module通过所有channle的feature map中的特征选择性地强调某个特征图。最后将两种attention module的output 求和得到最后的特征表达。

采用移除down-sampling的dilated ResNet(与DeepLab相同)的预训练网络基础网络为,最后得到的feature map大小为输入图像的1/8。之后是两个并行的attention module分别捕获spatial和channel的依赖性,最后整合两个attention module的输出得到更好的特征表达。

Position Attention Module

捕获特征图的任意两个位置之间的空间依赖,对于某个特定的特征,被所有位置上的特征加权和更新。权重为相应的两个位置之间的特征相似性。因此,任何两个现有相似特征的位置可以相互贡献提升,而不管它们之间的距离。

  • 特征图A(C×H×W)首先分别通过3个卷积层(BN和ReLU)得到3个特征图{B,C,D}.shape∈(CxHxW),然后reshape为C×N,其中N=H×W。
  • 矩阵C和B的转置相乘,再通过softmax得到spatial attention map S(N×N)。
  • 矩阵D和S的转置相乘,reshape result到(CxHxW)再乘以尺度系数 α 再reshape为原来形状,,最后与A相加得到最后的输出E 其中α初始化为0,并逐渐的学习分配到更大的权重。可以看出E的每个位置的值是原始特征每个位置的加权求和得到的。

 Channel Attention Module

每个高层次特征的通道映射都可以看作是一个特定于类的响应,不同的语义响应相互关联。通过探索通道映射之间的相互依赖关系,可以强调相互依赖的特征映射,提高特定语义的特征表示。

医学分割评价指标

可以看到,常见的Dice、mIou等指标作为图像分割的主要指标:

1.5.1、混淆矩阵

TP:真阳性(True Positive),被预测为正样本,事实上也是正样本

TN:真阴性(True Negative),被预测为负样本,事实上也是负样本

FP:假阳性(False Positive),被判定为正样本,但事实上是负样本(误报)

FN:假阴性(False Negative),被判定为负样本,但事实上是正样本(漏报)

1.5.2、Dice系数(dice similarity coefficient)(常用)

计算两个样本间相似度,现多用于三维医学图像分割领域

15.3、交并比(intersection over union,IoU)

预测(predict label)与真值(ground truth) 集合的交集与两个集合的并集之比

1.5.4、均交并比(mean intersection over union,mIoU)(常用)

是对所有类的IoU取均值得到的,在语义分割广为使用。

1.5.5、像素精度(Pixel Accuracy)

标记正确的像素占总像素的百分比

1.5.6、召回率(Recall)

预测值为正且真实值也为正在真实值为正的所有样本中所占的比例

1.5.7、频权交并比(FWIoU)

频权交并比(Frequency Weighted Intersection-over-Union, FWIoU)是根据每一类出现的频率设置权重,权重乘以每一类的IoU并进行求和。

RefineNet

论文地址(2016):RefineNet: Multi-Path Refinement Networks with Identity Mappings for High-Resolution Semantic Segmentation

对于高分辨率的图像分割问题,基于编解码结构的分割网络虽然有效,但因为卷积和池化下采样的存在,特征图在变小的过程会逐渐损失一些细粒度的信息,非常不利于高分辨率图像的像素稠密预测。针对这个问题,此前的各项研究归纳而言提出了如下三点处理方法:

(1)类似于FCN和UNet,直接使用转置卷积上采样来恢复图像像素,但转置卷积对于下采样过程中丢失的低层信息的恢复能力有限。

(2)使用空洞卷积,通过给常规卷积中插入空洞的方式来增大卷积感受野,并且没有缩小图像尺寸,但这种方式计算开销增大,模型运行效率降低,并且空洞卷积作为一种较为粗糙的子采样(sub-sampling),也会存在图像重要信息损失的问题。

(3)使用跳跃连接。类似于UNet中编解码器间的跳跃连接,直接将编码器每一层的特征图连接到解码器上采样结果上,能够对解码图像进行信息补充。

相关研究认为,对于编解码结构而言,所有层次的特征对语义分割都是有帮助的。高层次的特征用于识别图像中的语义信息,低层次的特征则有助于恢复高分辨率图像的边界细节。但如何有效利用中间层次的信息值得进一步探索,前述充分使用跳跃连接的方法或许会更加有效。基于此,研究人员提出了一种针对高分辨率图像语义分割的多层次特征精细化网络:RefineNet。提出RefineNet的论文为RefineNet: Multi-Path Refinement Networks for High-Resolution Semantic Segmentation,该网络基于ResNet结构和跳跃连接,使用多路径的精细化网络结构来获取最佳的分割结果,是高分辨率图像分割的经典网络。

RefineNet简要结构如图5-9所示。RefineNet总体上仍然是编解码结构,编码器部分根据预训练的ResNet网络划分了4个卷积块,与之对应的是级联了4个RefineNet单元到解码器部分。每个编码器ResNet卷积块的输出特征图都会被连接到对应的RefineNet单元,如图中的ResNet-4连接到RefineNet-4单元,到了RefineNet-3单元,除了接收来自ResNet-3的输出外,还需要接收RefineNet-4单元的输出,对于RefineNet-3单元而言就构成了两路径的输入。这样层层向上级联,就构成了多路径的RefineNet。其中编码器中每个特征图到解码器RefineNet单元的连接也叫长程残差连接(long-range residual connections)。

上图仅给出了包含了编码器在内的RefineNet简要结构,而RefineNet单元的具体结构如下图所示。一个RefineNet单元由残差卷积单元(Residual convolution unit,RCU)、多分辨率融合(Multi-resolution Fusion)和链式残差池化(Chained Residual Pooling,CRP)组成。RCU较为简单,就是常规的ResNet结构,每一个输入路径都会经过两次RCU操作后再输出到下一个单元。RCU的跳跃连接在RefineNet中也被称为短程残差连接(short-range residual connections)。紧接着是一个多分辨率特征图融合层,将上一层RCU输出的多路径特征图经过一个33的卷积和上采样操作后进行加总,得到合并后的特征图。最后是一个CRP单元,这也是RefineNet的特色结构,通过3个链式的池化和卷积残差组合来捕捉大图像区域的背景上下文信息。将CRP之后得到特征图再经过一次RCU即可到最终的分割输出。

作为一种针对高分辨率图像的精细化分割网络,RefineNet的结构设计无疑是成功的,当时在多个公开数据集上均取得了SOTA性能表现。这种多路径的精细化网络能够通过迭代精炼的方式将粗糙的语义特征精炼为细粒度的语义特征。其次,基于长短程的残差连接能够使得模型进行端到端的训练,推理时也非常高效。最后,链式残差池化也使得网络能够更好的捕捉大图像的上下文信息。

RefineNet代码完整实现可参考:

https://github.com/DrSleep/refinenet-pytorch

其中关于RCU和CRP模块的实现如下代码所示。

class RCUBlock(nn.Module):
    
    def __init__(self, in_planes, out_planes, n_blocks, n_stages):
        super(RCUBlock, self).__init__()
        for i in range(n_blocks):
            for j in range(n_stages):
                setattr(self, '{}{}'.format(i + 1, stages_suffixes[j]),
                        conv3x3(in_planes if (i == 0) and (j == 0) else out_planes,
                                out_planes, stride=1,
                                bias=(j == 0)))
        self.stride = 1
        self.n_blocks = n_blocks
        self.n_stages = n_stages
    
    def forward(self, x):
        for i in range(self.n_blocks):
            residual = x
            for j in range(self.n_stages):
                x = F.relu(x)
                x = getattr(self, '{}{}'.format(i + 1, stages_suffixes[j]))(x)
            x += residual
        return 
        
class CRPBlock(nn.Module):

    def __init__(self, in_planes, out_planes, n_stages):
        super(CRPBlock, self).__init__()
        for i in range(n_stages):
            setattr(self, '{}_{}'.format(i + 1, 'outvar_dimred'),
                    conv3x3(in_planes if (i == 0) else out_planes,
                            out_planes, stride=1,
                            bias=False))
        self.stride = 1
        self.n_stages = n_stages
        self.maxpool = nn.MaxPool2d(kernel_size=5, stride=1, padding=2)

    def forward(self, x):
        top = x
        for i in range(self.n_stages):
            top = self.maxpool(top)
            top = getattr(self, '{}_{}'.format(i + 1, 'outvar_dimred'))(top)
            x = top + x
        return x

预测效果:

UNET 3+

论文: https://arxiv.org/abs/2004.08790

github: https://github.com/ZJUGiveLab/UNet-Version

UNet是医学影像分割领域应用最广泛的的网络,其性能和网络中多尺度特征的融合密切相关。此后的UNet++通过嵌套结构和密集的跳过连接原始网络进行了改进。本文提出的UNet3+通过全尺度的连接和深度监督来融合深层和浅层特征的同时对各个尺度的特征进行监督。提出的UNet3+网络可以在减少网络参数的同时提高计算效率,在两个数据集上验证了方法有效性。相关代码已经开源。

现有的分割网络如UNET、PSPNET和DeepLab等网络都通常会通过多尺度的方式提取图像的信息。低层次的细节特征图中具有更丰富的例如边界这样空间信息,高层特征图中包含更多的例如物体位置这样的高级语义特征。然而,随着网络的下采样和上采样,这些高低层的信息并没有被充分地利用。因此,文章提出的UNet3+对网络的编码器与解码器连接以及解码器内部之间的连接进行了改进。此外,文章通过提出的混合损失函数对各层进行深度监督和分类分支指导分割的方式,进一步提高了分割的精度。总结来说,文章主要有以下四点贡献:

  1. 设计了一种新的网络结构UNet3+,通过引入全尺度的跳过连接,在全尺度特征映射中融合了低层细节和高层语义,充分利用了多尺度特征的同时具有更少的参数;
  2. 通过深度监督让网络从全尺度特征中学习分割表示,提出了更优的混合损失函数以增强器官的边界;
  3. 提出分类指导模块,通过与图像分类分支联合训练的方式,减少了网络在非器官图像的过度分割(over-segmentation);
  4. 在肝脏和脾脏数据集上进行了广泛的实验,证明了UNet 3+的有效性。
  5. 从图中可以看到,UNet3+与UNet主体上非常相似,不同之处在于从编码器到解码器的跳过连接以及不同层级的编码器之间的连接。以图中的节点 XDe3 为例,它的信息来自于两方面,一是比其更浅(包括同一层级)的编码器,二十比其更深的解码器。不同层级的特征通过maxpooling和双线性上采样的方式进行尺寸统一。解码层的卷积分两步,第一步是对来及各个节点的信息进行各自的卷积,第二步是对堆叠的特征通过卷积来进行信息的融合和提取。值得注意的一个细节是,进行第一个卷积层时,来自各层的数据被卷积到相同的特征图数(在这里是n/5,n为所在层的特征图数)。

从图中可以看到,UNet3+与UNet主体上非常相似,不同之处在于从编码器到解码器的跳过连接以及不同层级的编码器之间的连接。以图中的节点 XDe3 为例,它的信息来自于两方面,一是比其更浅(包括同一层级)的编码器,二是比其更深的解码器。不同层级的特征通过maxpooling和双线性上采样的方式进行尺寸统一。解码层的卷积分两步,第一步是对来及各个节点的信息进行各自的卷积,第二步是对堆叠的特征通过卷积来进行信息的融合和提取。值得注意的一个细节是,进行第一个卷积层时,来自各层的数据被卷积到相同的特征图数(在这里是n/5,n为所在层的特征图数)。

2.全尺度的深度监督

为了进一步优化网络对图像边界的分割,文章借鉴了图像质量评估中常用的多尺度SSIM(MS-SSIM)提出了MS-SSIM loss。

本文最终采用了混合损失函数(focal loss,ms-ssim loss和iou loss)来对各层进行监督。

ℓseg=ℓfl+ℓms−ssim+ℓiou

3. 分类指导模块(CGM)

在大多数医学图像分割中,非器官图像中出现假阳性不可避免。这通常是保留在较浅层中背景噪声信息导致的过分割现象。为了实现更精确的分割,文章尝试通过添加一个额外的分类任务来解决这个问题,该分类任务被设计用于预测输入图像是否有器官。简单来说,当预测到图像包含待分割器官的概率较小时,对输出图像乘以0使得输出全黑。

文章采用了LITS的肝脏数据集和自己采集的脾脏数据集通过两组实验来进行验证。

第一组对UNet、UNet++、UNet3+(带深度监督和不带)以Vgg和ResNet101作为backbone进行了对比。可以在以Vgg为backbone时,UNet3+比其UNet在两个数据集上分别有2.8%和4.1%的提升。网络相比于UNet++也有较大的提升。另外,可以看到UNet3+使用了更少的参数得到了更好的结果。可视化的结果表明即使在器官较小的情况下网络也能得到更加精细连贯的分割。

文章进一步以ResNet作为backbone,将网络与当前比较先进的分割网络进行对比。在这里,在验证网络有效性的同时,文章对提出的损失函数和分类分支进行了消融实验。

文章对之前在UNet解码器只接收的来自同一层编码器和深一层解码器的连接方式进行了改进,使得解码器都能获得来自每一个更浅的编码器和更深的解码器的信息,使得网络能够更好地提取和融合多尺度的信息。网络的结构设计简洁优雅,是一篇非常不错的UNet改进文章。另外文章提出的MS-SSIM损失和分类指导模块也挺有意思。当然我对文章也有一些思考。第一,网络结构设计中,对于来自不同层级的特征,进行融合时可以考虑通过PSP或者Deeplab的方式(JPU是一种很好选择),也可以考虑通过SE的方式来进行通道的选择。第二,文章提出的MS-SSIM能够更好地分割图像的边界,那么选取豪斯多夫距离这样的指标可以更好地证明方法的有效性。第三,文章通过CGM来对输出进行限制,但是对于器官的顶端和底端这样本身有比较多歧义性图像,容易造成无法分割的情况,可以考虑进行soft的指导。

深度学习语义分割理论与实战指南

看到github中一个写的很棒的图像分割的介绍和代码实现:

https://github.com/luwill/Semantic-Segmentation-Guide

对于刚入门的同学来说,十分友好。

引言

图像分类、目标检测和图像分割是基于深度学习的计算机视觉三大核心任务。三大任务之间明显存在着一种递进的层级关系,图像分类聚焦于整张图像,目标检测定位于图像具体区域,而图像分割则是细化到每一个像素。基于深度学习的图像分割具体包括语义分割、实例分割和全景分割。语义分割的目的是要给每个像素赋予一个语义标签。语义分割在自动驾驶、场景解析、卫星遥感图像和医学影像等领域都有着广泛的应用前景。本文作为基于PyTorch的语义分割技术手册,对语义分割的基本技术框架、主要网络模型和技术方法提供一个实战性指导和参考。

1. 语义分割概述

图像分割主要包括语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。那语义分割和实例分割具体都是什么含义?二者又有什么区别和联系?语义分割是对图像中的每个像素都划分出对应的类别,即实现像素级别的分类;而类的具体对象,即为实例,那么实例分割不但要进行像素级别的分类,还需在具体的类别基础上区别开不同的个体。例如,图像有多个人甲、乙、丙,那边他们的语义分割结果都是人,而实例分割结果却是不同的对象。另外,为了同时实现实例分割与不可数类别的语义分割,相关研究又提出了全景分割(Panoptic Segmentation)的概念。语义分割、实例分割和全景分割具体如图1(b)、(c)和(d)图所示。

Fig1. Image Segmentation
在开始图像分割的学习和尝试之前,我们必须明确语义分割的任务描述,即搞清楚语义分割的输入输出都是什么。输入是一张原始的RGB图像或者单通道图像,但是输出不再是简单的分类类别或者目标定位,而是带有各个像素类别标签的与输入同分辨率的分割图像。简单来说,我们的输入输出都是图像,而且是同样大小的图像。如图2所示。 

Fig2. Pixel Representation
类似于处理分类标签数据,对预测分类目标采用像素上的one-hot编码,即为每个分类类别创建一个输出的通道。如图3所示。 

Fig3. Pixel One-hot
图4是将分割图添加到原始图像上的叠加效果。这里需要明确一下mask的概念,在图像处理中我们将其译为掩码,如Mask R-CNN中的Mask。Mask可以理解为我们将预测结果叠加到单个通道时得到的该分类所在区域。 

Fig4. Pixel labeling
所以,语义分割的任务就是输入图像经过深度学习算法处理得到带有语义标签的同样尺寸的输出图像。

。。。。。。。(剩余部分在github可查看)

Attention UNet

论文: https://arxiv.org/abs/1804.03999

以CNN为基础的编解码结构在图像分割上展现出了卓越的效果,尤其是医学图像的自动分割上。但一些研究认为以往的FCN和UNet等分割网络存在计算资源和模型参数的过度和重复使用,例如相似的低层次特征被级联内的所有网络重复提取。针对这类普遍性的问题,相关研究提出了给UNet添加注意力门控(Attention Gates, AGs)的方法,形成一个新的图像分割网络结构:Attention UNet。提出Attention UNet的论文为Attention U-Net: Learning Where to Look for the Pancreas,发表在2018年CVPR上。注意力机制原先是在自然语言处理领域被提出并逐渐得到广泛应用的一种新型结构,旨在模仿人的注意力机制,有针对性的聚焦数据中的突出特征,能够使得模型更加高效。

Attention UNet的网络结构如下图所示,需要注意的是,论文中给出的3D版本的卷积网络。其中编码器部分跟UNet编码器基本一致,主要的变化在于解码器部分。其结构简要描述如下:编码器部分,输入图像经过两组3*3*3的3D卷积和ReLU激活,然后再进行最大池化下采样,经过3组这样的卷积-池化块之后,网络进入到解码器部分。编码器最后一层的特征图除了直接进行上采样外,还与来自编码器的特征图进行注意力门控计算,然后再与上采样的特征图进行合并,经过三次这样的上采样块之后即可得到最终的分割输出图。相比于普通UNet的解码器,Attention UNet会将解码器中的特征与编码器连接过来的特征进行注意力门控处理,然后再与上采样进行拼接。经过注意力门控处理后得到的特征图会包含不同空间位置的重要性信息,使得模型能够重点关注某些目标区域。

我们将Attention UNet的注意力门控单独拿出来进行分析,看AGs是如何让模型能够聚焦到目标区域的。如图中上图所示,将Attention UNet网络中的一个上采样块单独拿出来,其中x_l为来自同层编码器的输出特征图,g表示由解码器部分用于上采样的特征图,这里同时也作为注意力门控的门控信号参数与x_l的注意力计算,而x^hat_l即为经过注意力门控计算后的特征图,此时x^hat_l是包含了空间位置重要性信息的特征图,再将其与下一层上采样后的特征图进行合并才得到该上采样块最终的输出。

将x_l和g_i计算得到的注意力系数再次与x_l相乘即可得到x^hat_l,这种经过与注意力系数相乘后的特征图会让图像中不相关的区域值变小,目标区域的值相对会变大,提升网络预测速度同时,也会提高图像的分割精度。论文中的各项实验结果也表明,经过注意力门控加成后后UNet,效果均要优于原始的UNet。下述代码给出了Attention UNet的一个2D参考实现,并且下采样次数由论文中的3次改为了4次。


### 定义Attention UNet类
class Att_UNet(nn.Module):
    def __init__(self,img_ch=3,output_ch=1):
        super(Att_UNet, self).__init__()
        self.Maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.Conv1 = conv_block(ch_in=img_ch, ch_out=64)
        self.Conv2 = conv_block(ch_in=64, ch_out=128)
        self.Conv3 = conv_block(ch_in=128, ch_out=256)
        self.Conv4 = conv_block(ch_in=256, ch_out=512)
        self.Conv5 = conv_block(ch_in=512, ch_out=1024)

        self.Up5 = up_conv(ch_in=1024, ch_out=512)
        self.Att5 = Attention_block(F_g=512, F_l=512, F_int=256)
        self.Up_conv5 = conv_block(ch_in=1024, ch_out=512)

        self.Up4 = up_conv(ch_in=512, ch_out=256)
        self.Att4 = Attention_block(F_g=256, F_l=256, F_int=128)
        self.Up_conv4 = conv_block(ch_in=512, ch_out=256)
        
        self.Up3 = up_conv(ch_in=256, ch_out=128)
        self.Att3 = Attention_block(F_g=128, F_l=128, F_int=64)
        self.Up_conv3 = conv_block(ch_in=256, ch_out=128)
        
        self.Up2 = up_conv(ch_in=128, ch_out=64)
        self.Att2 = Attention_block(F_g=64, F_l=64, F_int=32)
        self.Up_conv2 = conv_block(ch_in=128, ch_out=64)

        self.Conv_1x1 =
       nn.Conv2d(64, output_ch, kernel_size=1, stride=1, padding=0)
    
  ### 定义前向传播流程
    def forward(self,x):
        # 编码器部分
        x1 = self.Conv1(x)
        x2 = self.Maxpool(x1)
        x2 = self.Conv2(x2)
        x3 = self.Maxpool(x2)
        x3 = self.Conv3(x3)
        x4 = self.Maxpool(x3)
        x4 = self.Conv4(x4)
        x5 = self.Maxpool(x4)
        x5 = self.Conv5(x5)

        # 解码器+连接部分
        d5 = self.Up5(x5)
        x4 = self.Att5(g=d5,x=x4)
        d5 = torch.cat((x4,d5),dim=1)        
        d5 = self.Up_conv5(d5)        
        d4 = self.Up4(d5)
        x3 = self.Att4(g=d4,x=x3)
        d4 = torch.cat((x3,d4),dim=1)
        d4 = self.Up_conv4(d4)

        d3 = self.Up3(d4)
        x2 = self.Att3(g=d3,x=x2)
        d3 = torch.cat((x2,d3),dim=1)
        d3 = self.Up_conv3(d3)
        d2 = self.Up2(d3)
        x1 = self.Att2(g=d2,x=x1)
        d2 = torch.cat((x1,d2),dim=1)
        d2 = self.Up_conv2(d2)
        d1 = self.Conv_1x1(d2)
        return d1
  
  ### 定义Attention门控块
class Attention_block(nn.Module):
    def __init__(self, F_g, F_l, F_int):
        super(Attention_block, self).__init__()
    # 注意力门控向量
        self.W_g = nn.Sequential(
            nn.Conv2d(F_g, F_int,
            kernel_size=1, stride=1, padding=0, bias=True),
            nn.BatchNorm2d(F_int)
            )
        # 同层编码器特征图向量
        self.W_x = nn.Sequential(
            nn.Conv2d(F_l, F_int,
            kernel_size=1,stride=1,padding=0,bias=True),
            nn.BatchNorm2d(F_int)
        )
    # ReLU激活函数
    self.relu = nn.ReLU(inplace=True)
    # 卷积+BN+sigmoid激活函数
        self.psi = nn.Sequential(
            nn.Conv2d(F_int, 1,
            kernel_size=1, stride=1, padding=0, bias=True),
            nn.BatchNorm2d(1),
            nn.Sigmoid()
        )
        
    ###  Attention门控的前向计算流程 
    def forward(self,g,x):
        g1 = self.W_g(g)
        x1 = self.W_x(x)
        psi = self.relu(g1+x1)
        psi = self.psi(psi)
        return x*psi

总结来说,Attention UNet提出了在原始UNet基础添加注意力门控单元,注意力得分能够使得图像分割时聚焦到目标区域,该结构作为一个通用结构可以添加到任何任务类型的神经网络结构中,在语义分割网络中对前景目标区域的像素更具有敏感度。Attention UNet壮大了UNet家族网络,此后基于其的改进版本也层出不穷。

SegNet

论文(2015):SegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation

Github:https://github.com/alexgkendall/caffe-segnet

  • 把本文提出的架构和FCN、DeepLab-LargeFOV、DeconvNet做了比较,这种比较揭示了在实现良好分割性能的前提下内存使用情况与分割准确性的权衡。
  • SegNet的主要动机是场景理解的应用。因此它在设计的时候考虑了要在预测期间保证内存和计算时间上的效率。
  • 定量的评估表明,SegNet在和其他架构的比较上,时间和内存的使用都比较高效。

SegNet论文提出了max pooling的改进版,使用该pooling操作既可以进行下采样操作,也可以进行上采样操作。在下采样操作中同时输出pooling后的结果和pooling过程中的索引。在上采样操作中,利用下采样对应位置的索引,进行上采样操作,这样的优势在于记住了最亮特征像素的空间位置。(去除了unet里面的反卷积操作)

优点,

  1. 可以提高物体边界的分割效果
  2. 相比反卷积操作,减少了参数数量,减少了运算量,相比resize操作,减少了插值的运算量,而实际增加的索引参数也很少。
  3. 该pooling操作可以应用于任何基于编码-解码的分割模型。

SegNet网络结构如下图所示,是一个编解码完全对称的结构。其编码器直接用了VGG16的结构,并将全连接层全部改为卷积层,实际训练时可使用VGG16的预训练权重进行初始化;编码器将13层卷积层分为5组卷积块,每组卷积块之间用最大池化层进行下采样。作为一个对称结构,SegNet解码器也有13层卷积层,同样分为5组卷积块,每组卷积块之间用双线性插值和最大池化位置索引进行上采样,这也是SegNet最大的特色。

SegNet研究团队认为编码器下采样过程中图像信息损失较多,直接存储所有卷积块的特征图又非常占用内存,因而在SegNet中提出在每一次最大池化下采样前存储最大池化的位置索引(Max-pooling indices),即记住最大池化操作中,最大值在2*2池化窗口中的位置。每个2*2窗口仅需要2 bits内存存储量,这种池化位置索引可用于上采样解码时恢复图像信息。下图给出了SegNet与FCN之间的上采样方法对比。可以观察到,SegNet使用双线性插值并结合最大池化位置索引进行上采样,而FCN则是基于去卷积结合编码器卷积特征图进行上采样。

SegNet这种轻量化的上采样方式,不仅能够提升图像边界分割效果,在端到端的实时分割项目中速度也非常快,并且这种结构设计可以配置到任意的编解码网络中,是一种优秀的分割网络设计方式。下述代码给出了SegNet的一个简易的结构实现,因为SegNet解码器的特殊性,我们单独定义了一个解码器类,编码器部分直接使用VGG16的预训练权重层,然后在编解码器基础上搭建SegNet并定义前向计算流程。


# 导入PyTorch相关模块
import torch
import torch.nn as nn
import torch.nn.init as init
import torch.nn.functional as F
from torchvision import models

# 定义SegNet解码器类
class SegNetDec(nn.Module):
    def __init__(self, in_channels, out_channels, num_layers):
        super().__init__()
        layers = [
            nn.Conv2d(in_channels, in_channels // 2, 3, padding=1),
            nn.BatchNorm2d(in_channels // 2),
            nn.ReLU(inplace=True),
        ]
        layers += [
            nn.Conv2d(in_channels // 2, in_channels // 2, 3, padding=1),
            nn.BatchNorm2d(in_channels // 2),
            nn.ReLU(inplace=True),
        ] * num_layers
        layers += [
            nn.Conv2d(in_channels // 2, out_channels, 3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
        ]
        self.decode = nn.Sequential(*layers)

    def forward(self, x):
        return self.decode(x)

### 定义SegNet类
class SegNet(nn.Module):
    def __init__(self, classes):
        super().__init__()
    # 编码器使用vgg16预训练权重
        vgg16 = models.vgg16(pretrained=True)
        features = vgg16.features
        self.enc1 = features[0: 4]
        self.enc2 = features[5: 9]
        self.enc3 = features[10: 16]
        self.enc4 = features[17: 23]
        self.enc5 = features[24: -1]
    # 编码器卷积层不参与训练
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                m.requires_grad = False
    
        self.dec5 = SegNetDec(512, 512, 1)
        self.dec4 = SegNetDec(512, 256, 1)
        self.dec3 = SegNetDec(256, 128, 1)
        self.dec2 = SegNetDec(128, 64, 0)

        self.final = nn.Sequential(*[
            nn.Conv2d(64, classes, 3, padding=1),
            nn.BatchNorm2d(classes),
            nn.ReLU(inplace=True)
        ])
  # 定义SegNet前向计算流程
    def forward(self, x):
        x1 = self.enc1(x)
        e1, m1 = F.max_pool2d(x1, kernel_size=2, stride=2,
 return_indices=True)
        x2 = self.enc2(e1)
        e2, m2 = F.max_pool2d(x2, kernel_size=2, stride=2,
 return_indices=True)
        x3 = self.enc3(e2)
        e3, m3 = F.max_pool2d(x3, kernel_size=2, stride=2,
 return_indices=True)
        x4 = self.enc4(e3)
        e4, m4 = F.max_pool2d(x4, kernel_size=2, stride=2,
 return_indices=True)
        x5 = self.enc5(e4)
        e5, m5 = F.max_pool2d(x5, kernel_size=2, stride=2,
 return_indices=True)

        def upsample(d):
            d5 = self.dec5(F.max_unpool2d(d, m5, kernel_size=2,
 stride=2, output_size=x5.size()))
            d4 = self.dec4(F.max_unpool2d(d5, m4, kernel_size=2,
 stride=2, output_size=x4.size()))
            d3 = self.dec3(F.max_unpool2d(d4, m3, kernel_size=2,
 stride=2, output_size=x3.size()))
            d2 = self.dec2(F.max_unpool2d(d3, m2, kernel_size=2,
 stride=2, output_size=x2.size()))
            d1 = F.max_unpool2d(d2, m1, kernel_size=2, stride=2,
 output_size=x1.size())
            return d1

        d = upsample(e5)
        return self.final(d)