語義分割入門系列之 FCN(全卷積神經網絡)

FCN論文解讀及代碼分析

Fully Convolutional Networks for Semantic Segmentation

FCN是卷積神經網絡用於語義分割的開山之作,文章的出發點在於如何將普通的分類卷積神經網絡用於語義分割網絡。卷積神經網絡在分類任務上取得了重大突破,因爲隨着卷積網絡的層數加深,網絡從圖像中提取出越來越抽象的語義信息特徵圖的通道數越來越多,並且爲了控制計算量,特徵圖的分辨率越來越小,並且最後還有全連接層做softmax預測,將整個圖片的空間位置信息全部破壞。
FCN便從這方面入手,提出方案將普通分類卷積神經網絡改造成能夠進行語義分割的網絡。下面來介紹FCN到底是如何做的。

1.修改分類器,使網絡能夠密集預測

在這裏插入圖片描述
如圖所示,普通的分類神經網絡最後的全連接層,輸出的是該圖片對應於每個分類的概率。將最後的全連接層全部替換成1x1的卷積卷積核,這樣既實現全連接層的計算功能,又保持了特徵圖的空間分辨率,可以生成一個分類網絡的熱力圖,熱力圖的分辨率很低,但是依然可以表示出讓網絡做出該判斷的響應位置,這樣就有了位置信息(很粗糙的位置信息)。並且,替換掉全連接層後帶來的好處是,輸入圖片的尺寸可以是任意size的。

2. 深層與淺層的融合

上一步將全連接層替換成卷積層之後,特徵響應的位置得以保留,但是網絡最後特徵圖的尺寸已將很小(VGG經過5次下采樣,resnet也經過5次下采樣,最後一個特徵圖的分辨率是原圖的2^5=32倍,雖然有特徵響應的位置,但是無法對應到原圖的精確位置),所以,FCN提出將深層的、分辨率粗糙的語義信息,與淺層的高分辨率的精細特徵結合,以此來修復熱力圖的分辨率,使其不斷上採樣到原圖尺寸,這樣就得到了原圖分辨率的結果。具體操作如下圖:
圖片轉載於:https://zhuanlan.zhihu.com/p/31428783
圖片來自:https://zhuanlan.zhihu.com/p/31428783,比論文原圖更好理解
FCN32s是將最後一個特徵圖,直接上採樣32倍(5次步長爲2的3x3的反捲積操作),得到最後的分割結果。代碼如下(pytorch實現):其中pretrained_net表示的是backbone網絡

class FCN32s(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']  

        score = self.bn1(self.relu(self.deconv1(x5)))     
        score = self.bn2(self.relu(self.deconv2(score)))  
        score = self.bn3(self.relu(self.deconv3(score)))  
        score = self.bn4(self.relu(self.deconv4(score)))  
        score = self.bn5(self.relu(self.deconv5(score)))  
        score = self.classifier(score)                    

        return score 

FCN16s則是將最後一個特徵圖(pool5的結果)用步長爲2的3x3反捲積(參數可學)上採樣一倍,得到的結果與pool4的結果相加,它們倆的分辨率相同,都是原圖的1/16。這樣得到的結果在通過16倍上採樣得到與原圖相同的分辨率。

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']  
        x4 = output['x4']  

        score = self.relu(self.deconv1(x5))               
        score = self.bn1(score + x4)                      
        score = self.bn2(self.relu(self.deconv2(score)))  
        score = self.bn3(self.relu(self.deconv3(score)))  
        score = self.bn4(self.relu(self.deconv4(score)))  
        score = self.bn5(self.relu(self.deconv5(score)))  
        score = self.classifier(score)                   

        return score  

FCN8s 則是將FCN16s中1/16的特徵圖,再次通過一個2倍的上採樣,與pool3的結果相加,得到原圖分辨率1/8的特徵圖,再通過8倍的上採樣得到原圖相同分辨率分割結果。

class FCN8s(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']  
        x4 = output['x4']  
        x3 = output['x3']  

        score = self.relu(self.deconv1(x5))              
        score = self.bn1(score + x4)                      
        score = self.relu(self.deconv2(score))            
        score = self.bn2(score + x3)                      
        score = self.bn3(self.relu(self.deconv3(score)))  
        score = self.bn4(self.relu(self.deconv4(score)))  
        score = self.bn5(self.relu(self.deconv5(score)))  
        score = self.classifier(score)                    

        return score  

注意,以上步驟中的上採樣全部都由3x3的反捲積操作完成,參數可學。
三者效果如下:
在這裏插入圖片描述
可見,直接上採樣32倍的FCN32s效果最差,因爲直接上採樣太多倍導致結果粗糙,而後面逐步結合了淺層特徵的網絡結果效果則越來越好。
後續很多工作延續了這個思想,從分類網絡的結果開始逐步結合前面的淺層特徵,以實現精細的分割。

參考文獻: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.

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