語義分割模型架構演進與相關論文閱讀

本文總結分析了主流語義分割模型架構演進過程,涉及FCN、DeepLab系列、RefineNet、PSPNet、BiSeNet、FastFCN、ConvCRFs、DUpsampling、DFANet、DANet、FickleNet、LedNet、ACNet等在內的20多個模型,本來是2019年一次組會的分享,這裏重新總結,就當複習一下了。

1. FCN-用於語義分割的全卷積神經網絡

Long J , Shelhamer E , Darrell T . Fully Convolutional Networks for Semantic Segmentation[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 2014, 39(4):640-651.

  第一次使用深度學習的方法進行語義分割的,論文的分析基於下面兩個問題:

  • 下采樣過程中的圖像細節丟失
  • 圖像語義信息和位置信息的矛盾

  這兩個問題也是標準語義分割問題的難點,基本上不考慮實時性的語義分割模型架構都是嘗試對這兩個問題進行闡述並提出特定解決方案。

  具體地,下采樣過程中特徵圖尺寸不斷減小,這個過程勢必拋棄了一些信息,這些信息通常是圖像中目標的位置信息。

  那爲啥還要下采樣?是因爲這是對圖像中目標進行分類的要求,比如下圖所示的典型的圖像分類網絡VGG-16,經過全連接層後,變成(1000,1)的向量,表示屬於每個類別的概率。
在這裏插入圖片描述
  這也就引出了第二個問題,越深層也就是尺寸越小的特徵圖,包含更多的語義信息,更有利於鑑別這個目標區域是什麼類別;而越淺層也就是尺寸越大的特徵圖,包含更多的位置信息,更有利於劃分目標的邊界。

  基於語義分割這個兩個互相矛盾的問題,FCN提出使用全卷積和跳躍結構。

  首先是全卷積,也就是最後不把特徵圖映射爲一個列向量,以保持特徵圖尺寸,如下圖所示,這個特徵圖尺寸長寬一直折半,最終分別都是原始輸入尺寸的三十二分之一而非列向量,之後的語義分割模型實際上也都採取了這種全卷機的神經網絡形式,只不過各有各的調整與改進。
在這裏插入圖片描述
  模型第二個特點是跳躍結構,根據上面全卷機網絡生成的不同尺寸的特徵圖,選取不同層產生的特徵圖進行上採樣與融合,如下圖所示:
在這裏插入圖片描述
  基於這種跳躍結構有FCN-32s(32x特徵圖32x上採樣作爲分割結果)、FCN-16s(32x特徵圖先進行2x上採樣得到16x特徵圖然後與“真”16x特徵圖融合後進行2x上採樣作爲分割結果)、FCN-8s,之後甚至產生了FCN-4s和FCN-2s。

  以FCN-16s爲例簡單看一下代碼:

class FCN16s(nn.Module):
    def __init__(self, pretrained_net, n_class):
        super().__init__()
        self.n_class = n_class
        self.pretrained_net = pretrained_net
        self.relu    = nn.ReLU(inplace=True)
        self.deconv1 = nn.ConvTranspose2d(512, 512, kernel_size=3, stride=2, padding=1, dilation=1, output_padding=1)
        self.bn1     = nn.BatchNorm2d(512)
        self.deconv2 = nn.ConvTranspose2d(512, 256, kernel_size=3, stride=2, padding=1, dilation=1, output_padding=1)
        self.bn2     = nn.BatchNorm2d(256)
        self.deconv3 = nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, dilation=1, output_padding=1)
        self.bn3     = nn.BatchNorm2d(128)
        self.deconv4 = nn.ConvTranspose2d(128, 64, kernel_size=3, stride=2, padding=1, dilation=1, output_padding=1)
        self.bn4     = nn.BatchNorm2d(64)
        self.deconv5 = nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2, padding=1, dilation=1, output_padding=1)
        self.bn5     = nn.BatchNorm2d(32)
        self.classifier = nn.Conv2d(32, n_class, kernel_size=1)

    def forward(self, x):
        output = self.pretrained_net(x)
        x5 = output['x5']  # size=(N, 512, x.H/32, x.W/32)
        x4 = output['x4']  # size=(N, 512, x.H/16, x.W/16)

        score = self.relu(self.deconv1(x5))               # size=(N, 512, x.H/16, x.W/16)
        score = self.bn1(score + x4)                      # element-wise add, size=(N, 512, x.H/16, x.W/16)
        score = self.bn2(self.relu(self.deconv2(score)))  # size=(N, 256, x.H/8, x.W/8)
        score = self.bn3(self.relu(self.deconv3(score)))  # size=(N, 128, x.H/4, x.W/4)
        score = self.bn4(self.relu(self.deconv4(score)))  # size=(N, 64, x.H/2, x.W/2)
        score = self.bn5(self.relu(self.deconv5(score)))  # size=(N, 32, x.H, x.W)
        score = self.classifier(score)                    # size=(N, n_class, x.H/1, x.W/1)

        return score  # size=(N, n_class, x.H/1, x.W/1)

2. DeepLabV1- 使用深度卷積網絡與全連接條件隨機場進行圖像語義分割

Chen L, Papandreou G, Kokkinos I, et al. Semantic Image Segmentation with Deep Convolutional Nets and Fully Connected CRFs[C]. international conference on learning representations, 2015.

  論文旨在解決兩個關鍵問題:

  • 下采樣過程中的圖像細節丟失
  • CNN空間不變性

  第一個問題不再贅述,看第二個問題,CNN空間不變形,就是說無論目標怎樣平移、縮放都不太會影響CNN對其類別的判定,如下圖所示,但是這分割任務影響就很大。
在這裏插入圖片描述

在這篇論文之前的不少研究中都認爲空間不變形可能是之前提到的下采樣結構造成的,但最近的研究發現並不是,所以之後的論文也就基本不提這個不變形對DCNN的定位精度的影響了

  DeepLabV1爲平衡這兩個問題使用了全卷積神經網絡、空洞卷積再加上全連接條件隨機場後處理,這個過程如下所示:
在這裏插入圖片描述
  實際上,DeepLabV1的主幹部分和FCN差不多,只不過在最後一部分下采樣操作處使用了空洞卷積,使得特徵圖尺寸保持了十六分之一。空洞卷積是能夠增大感受野同時保持特徵圖尺寸的卷積技術,既不影響特徵描述效果,還可保留豐富的空間位置信息。

  如下圖所示,空洞卷積和標準卷積的一個對比,以輸入5×55\times 5爲例,標準卷積的一步操作如下圖b所示,卷積核尺寸爲3×33\times3,步長爲2,填充值爲1,感受野尺寸3×33\times3;空洞/擴張卷積的一步操作如下圖b所示,卷積核尺寸3×33\times3,擴張率2,步長1,填充值2,感受野尺寸5×55\times5,雖然步長更小填充更大,但是空洞卷積能夠很好的應對,能夠實現上面提到的目的。
在這裏插入圖片描述
  最後是關於全連接條件隨機場,這種後處理的方式是端到端學習不推薦的所以進來直接使用很少,但是可以將其變形爲損失函數甚至直接作爲網絡層的論文取得了不錯的效果。

  條件隨機場是馬爾科夫隨機場的一個特例,我自己畫了幾幅圖粗略的描述了一下全連接條件隨機場的建模過程:

  如圖a,有一副原始圖像,要逐像素點進行分類。簡單地,每一點的像素類別只和它自身有關,得到圖b,這樣效果顯然是不夠的。令每一點的像素它的四鄰域的像素點有關得到圖c,但如果相鄰的點出現了波動,那麼這種程度的關聯是不夠的。令每一點的像素和其他所有的像素點相關,這種相關可以是正的也可以是負的,這樣得到圖d,全連接條件隨機場。也叫Dense CRF。
在這裏插入圖片描述
  下面的公式是全連接條件隨機場的能量函數,其中x指每一個像素點的類別標籤,這個函數計算的是圖像中所有點的能量之和:
在這裏插入圖片描述
  它的前半部分是一個一元函數,其中P(xi)是由深度卷積神經網絡產生的一個概率值:
在這裏插入圖片描述
  後半部分表示一對像素點之間的關係,計算公式如下:
在這裏插入圖片描述
  u(xi,yj)u(x_i,y_j)表示約束力的傳導方向,是增強還是減弱,比如像素1可能是人,像素2可能是人,像素3可能是車,那麼1和2互相增強,2和3互相減弱;後面的加權項是典型得分權重乘以特徵,計算兩個像素點之間的親密度,具體計算過程是這樣的:
在這裏插入圖片描述
  其中pp是像素的2維位置,II是圖像的3維像素值,有三個不同的方差符號表示三個高斯分佈的方差;後半部分就是一個平滑的處理。從這裏可以看出:像素距離越近,顏色越接近,特徵就越強;而分母即方差大的話feature就很難強起來;所以這裏就是一個在五維空間尋找相近像素並進行特徵增強的過程。

  DenseCRF看起來很難實現,但實際上python中有封裝好的包,pydensecrf可以實現,而且作爲後處理的手段也不參與訓練。

  在DeepLabV1論文中,w2,σγ=3w_2,\sigma_{\gamma}=3w1,σalpha,σβw_1 ,\sigma_{alpha},\sigma_{\beta}在一個100張圖片的小數據集上交叉驗證,得到一個粗略的範圍值,其中w1[5;10]w_1\in[5; 10]σalpha[50:10:100]\sigma_{alpha}\in[50 : 10 : 100]σβ[3:1:10]\sigma_{\beta}\in[3 : 1 : 10]

3. 使用空洞卷積的多尺度語義聚合模型

Yu F, Koltun V. Multi-Scale Context Aggregation by Dilated Convolutions[C]. international conference on learning representations, 2016.

  論文提出了兩個問題:

  • 是否真的需要下采樣層?
  • 對多個重新縮放的圖像進行分開分析是否必要?(deeplabV1等模型就是將多個不同分辨率的圖像作爲訓練樣本的)

  論文對上面兩個問題進行分析並設計了這樣一個模型架構,即全卷積神經網絡+空洞卷積、前端模塊+上下文模塊,如下圖所示:
在這裏插入圖片描述
  前端模塊就是fc-final之前的,之後的是上下文模塊,看起來像個網絡的堆疊,因爲前端模塊本質上是個改進過後的VGG16(將VGG-16最後兩個poooling層移除,並且隨後的卷積層被空洞卷積代替,pool4和pool5之間的空洞卷積的空洞率爲2,在pool5之後的空洞卷積的空洞率爲4),其本身就能獨立完成語義分割任務而且效果不錯。  而上下文模塊旨在通過聚合多尺度的上下文信息來提高語義分割網絡結構的性能,它接收的輸入和輸出的尺寸保持一致,所以可以在其他的模型中進行嵌入,具體地,一共有7層,每一層都採用具有不同空洞率的3×33\times3卷積核進行空洞卷積。最後通過執行1×1×C1\times1\times C的卷積核產生輸出模塊。特別地,基礎上下文模塊根據卷積的通道不同又分爲兩種形式:basic和large,本文C取64。

論文中實現的最好效果也需要全連接條件隨機場作爲後處理手段。

4. DeepLabV2-DeepLabV1+ASPP

Chen L C , Papandreou G , Kokkinos I , et al. DeepLab: Semantic Image Segmentation with Deep Convolutional Nets, Atrous Convolution, and Fully Connected CRFs[J]. IEEE Transactions on Pattern Analysis & Machine Intelligence, 2016, 40(4):834-848.

  DeepLabV2相較於V1,多了一個空洞卷積空間池化特徵金字塔(ASPP),更關注多尺度目標的問題。ASPP進一步利用了空洞卷積,該模塊結構如下所示:
在這裏插入圖片描述
  論文使用這個模塊來進行多尺度語義的聚合,它拋棄了之前使用的方法,比如將輸入進行多個尺度的縮放然後進行訓練,或者是剛纔提到的上下文模塊的線性聚合。該模塊先對相同的輸入進行並行的不同採樣率的空洞卷積然後進行特徵融合,該版本的DeepLab中使用的空洞率是6、12、18、24。實際上這種空洞率的擴張卷積對顯卡資源要求很高,速度也很慢。而且由於ResNet的出現,論文主幹網絡用了調整後的ResNet101,這就導致模型更加的“重”。

5. RefineNet-用於高分辨率圖像語義分割的多路精細網絡

  論文提到的問題除了下采樣過程中的圖像細節丟失外,提到了空洞卷積的兩個問題:

  • 本質上是一種粗糙的欠採樣
  • 計算代價巨大

  使用的架構包括:

  • 全卷機神經網絡(ResNet)
  • 多路並行網絡特徵抽取
  • 鏈式殘差池化

  重點看一下這種多路並行網絡架構設計,這是一種很靈活的方式,具體有單路、雙路級聯、多路四級級聯以及二級級聯和四級級聯的混合架構,架構分別如下圖所示:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
  本質上是對原始圖片進行了更多尺度的縮放,這模型訓練起來也不簡單。四種結構都有一個RefineNet模塊,這個模塊如下圖所示:
在這裏插入圖片描述
  其中,RCU是全卷積形式的ResNet;Multi-Resolution Fusion是對輸入的同尺寸的特徵圖進行處理使其尺寸一致並融合;Chained Residual Pooling這個模塊主要用於較大圖像區域中捕獲背景的上下文信息,實際上就是一個多重的殘差連接,Output Convolutions主要用於產生帶有分類概率的特徵圖。

6. PSPNet-金子塔型場景解析網絡

Zhao H, Shi J, Qi X, et al. Pyramid Scene Parsing Network[C]. computer vision and pattern recognition, 2017: 6230-6239.

  這篇論文並不是標準語義分割的模型,但是也差不多,其分析了四個問題:

  • 缺乏對上下文關係的捕獲【即各個像素的互相影響】
  • 不能充分利用類別間的關係【比如水上出現的大概率的船而非汽車】
  • 對不顯眼的目標捕獲能力弱【小的目標或者相似目標互相遮擋】
  • 全局平均池化導致位置信息丟失【爲了獲得分類,基於FCN的模型多直接使用全局平均池化處理特徵圖】

  基於這些問題,論文設計瞭如下圖所示的模型架構,包括金字塔池化和更加有效的ResNet訓練損失函數,模型架構如下圖所示:
在這裏插入圖片描述
  模型通過帶有空洞卷積的ResNet作爲主幹網絡提取特徵圖,大小是輸入圖像的1/8。經過金字塔池化模塊來獲取語義信息,使用四種不同的層級的金字塔並上採樣將其融合爲原始特徵圖大小。經過一個卷積層得到預測的輸出。

  而第二點,更加有效的ResNet訓練損失函數如下圖所示,就是在ResNet101的基礎上做了改進,除了使用後面的softmax分類做loss,額外的在第四階段添加了一個輔助的loss,兩個loss一起傳播,使用不同的權重,共同優化參數。後續的實驗證明這樣做有利於快速收斂。
在這裏插入圖片描述

7. DeepLabV3-重新思考用於圖像語義分割的空洞卷積

Chen L, Papandreou G, Schroff F, et al. Rethinking Atrous Convolution for Semantic Image Segmentation[J]. arXiv: Computer Vision and Pattern Recognition, 2017.

  模型的架構重新在於:

  • 改進的空洞卷積空間金字塔池化
  • 拋棄全連接條件隨機場

  其中改進的金字塔池化,加入ImagePooling,就是一個全局池化,加大位置信息的權重,我理解是避免空洞卷積信息損失的問題,但是這一個系列的論文基於空洞卷積,所以,論文並不直說。論文拋棄了繁瑣的後處理,使得模型能夠端到端的訓練、推斷,並且在實現細節,包括學習率策略,批量正則化層,主幹網絡池化和空洞卷積都進行了一些列調整
在這裏插入圖片描述

8. DeepLabV3+ 用於圖像語義分割的帶有空洞卷積的編解碼結構

Chen L, Zhu Y, Papandreou G, et al. Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation[C]. european conference on computer vision, 2018: 833-851.

  論文將DeepLabV3作爲編碼模塊,設計簡單的解碼模塊進行上採樣,提出編解碼結構,論文結構如下圖所示:
在這裏插入圖片描述
  可以看到,這裏其實還在解碼器中加入了一個淺層特徵,增加位置信息所佔的權重。

  論文爲了加快推斷,將Xception主幹網絡進行改進得到一個具有如下特徵的主幹網絡:

  • more layers
  • 將所有的最大池化操作修改爲帶步長的深度可分離卷積,以便應用空洞可分離卷積
  • 額外的BN層和RELU激活層附加在每一個3x3 DW Conv後

  深度可分離卷積就是把標準卷積分解爲一個Depthwise Conv和一個Pointwise Conv,其中DW獨立地對每個輸入通道做空間卷積,PW用於結合DW的輸出。將空洞卷積與深度可分離卷積結合可以減少運算量的同時保持不錯的效果。
在這裏插入圖片描述
  簡單看一下代碼:

class DeepLab(nn.Module):
    def __init__(self, backbone='resnet', output_stride=16, num_classes=21,
                 sync_bn=True, freeze_bn=False):
        super(DeepLab, self).__init__()
        if backbone == 'drn':
            output_stride = 8

        if sync_bn == True:
            BatchNorm = SynchronizedBatchNorm2d
        else:
            BatchNorm = nn.BatchNorm2d

        self.backbone = build_backbone(backbone, output_stride, BatchNorm)
        self.aspp = build_aspp(backbone, output_stride, BatchNorm)
        self.decoder = build_decoder(num_classes, backbone, BatchNorm)

        if freeze_bn:
            self.freeze_bn()

    def forward(self, input):
        x, low_level_feat = self.backbone(input)
        x = self.aspp(x)
        x = self.decoder(x, low_level_feat)
        x = F.interpolate(x, size=input.size()[2:], mode='bilinear', align_corners=True)

        return x

class Decoder(nn.Module):
    def __init__(self, num_classes, backbone, BatchNorm):
        super(Decoder, self).__init__()
        if backbone == 'resnet' or backbone == 'drn':
            low_level_inplanes = 256
        elif backbone == 'xception':
            low_level_inplanes = 128
        elif backbone == 'mobilenet':
            low_level_inplanes = 24
        else:
            raise NotImplementedError

        self.conv1 = nn.Conv2d(low_level_inplanes, 48, 1, bias=False)
        self.bn1 = BatchNorm(48)
        self.relu = nn.ReLU()
        self.last_conv = nn.Sequential(nn.Conv2d(304, 256, kernel_size=3, stride=1, padding=1, bias=False),
                                       BatchNorm(256),
                                       nn.ReLU(),
                                       nn.Dropout(0.5),
                                       nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=False),
                                       BatchNorm(256),
                                       nn.ReLU(),
                                       nn.Dropout(0.1),
                                       nn.Conv2d(256, num_classes, kernel_size=1, stride=1))
        self._init_weight()
class ASPP(nn.Module):
    def __init__(self, backbone, output_stride, BatchNorm):
        super(ASPP, self).__init__()
        if backbone == 'drn':
            inplanes = 512
        elif backbone == 'mobilenet':
            inplanes = 320
        else:
            inplanes = 2048
        if output_stride == 16:
            dilations = [1, 6, 12, 18]
        elif output_stride == 8:
            dilations = [1, 12, 24, 36]
        else:
            raise NotImplementedError

        self.aspp1 = _ASPPModule(inplanes, 256, 1, padding=0, dilation=dilations[0], BatchNorm=BatchNorm)
        self.aspp2 = _ASPPModule(inplanes, 256, 3, padding=dilations[1], dilation=dilations[1], BatchNorm=BatchNorm)
        self.aspp3 = _ASPPModule(inplanes, 256, 3, padding=dilations[2], dilation=dilations[2], BatchNorm=BatchNorm)
        self.aspp4 = _ASPPModule(inplanes, 256, 3, padding=dilations[3], dilation=dilations[3], BatchNorm=BatchNorm)

        self.global_avg_pool = nn.Sequential(nn.AdaptiveAvgPool2d((1, 1)),
                                             nn.Conv2d(inplanes, 256, 1, stride=1, bias=False),
                                             BatchNorm(256),
                                             nn.ReLU())
        self.conv1 = nn.Conv2d(1280, 256, 1, bias=False)
        self.bn1 = BatchNorm(256)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.5)
        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)
        x5 = F.interpolate(x5, size=x4.size()[2:], mode='bilinear', align_corners=True)
        x = torch.cat((x1, x2, x3, x4, x5), dim=1)

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)

        return self.dropout(x)

9. 2019年的前沿展望

  1. BiSeNet:用於實時語義分割的雙向分割網絡
  2. 重新思考空洞卷積: 爲弱監督和半監督語義分割設計的簡捷方法
  3. FastFCN: 重新思考語義分割模型主幹網絡中的擴張卷積
  4. ConvCRFs:用於語義分割的卷積條件隨機場
  5. DUpsampling-新型上採樣模塊:能夠聚合豐富特徵的數據相關型解碼方式
  6. DFANet:用於實時語義分割的深層特徵聚合網絡
  7. DANet-集成雙路注意力機制的場景分割網絡
  8. 使用知識蒸餾的語義分割方法
  9. 聯合傳播數據增廣+標籤鬆弛提升邊界精度=語義分割效果提升
  10. FickleNet-使用隨機推理的用於弱監督和半監督的圖像語義分割
  11. LEDNet-用於實時語義分割的輕量級編解碼網絡
  12. ACNet-使用注意力網絡的RGBD圖像語義分割方法
  13. Gated CRF Loss -一種用於弱監督圖像語義分割的新型損失函數

歡迎掃描二維碼關注微信公衆號 深度學習與數學   [每天獲取免費的大數據、AI等相關的學習資源、經典和最新的深度學習相關的論文研讀,算法和其他互聯網技能的學習,概率論、線性代數等高等數學知識的回顧]
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章