语义分割入门系列之 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.

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FCN卷积神经网络)是一种神经网络结构,它的特点是可以接收任意大小的输入图片,而不需要固定大小。相比于传统的CNN(卷积神经网络),FCN在较深的卷积层中融入了浅层卷积的特征,并且使用了三种上采样的方式来恢复原始图片的尺寸,其中FCN-8s是效果最好的一种方式。 与CNN不同,FCN将CNN中的连接层替换为卷积操作,这种操作被称为1x1卷积。这样做的目的是为了保持空间信息的完整性,使得网络能够对输入图片的每个像素进行预测,而不仅仅是对整个图片进行分类。 总之,FCN卷积神经网络通过融入浅层卷积特征和使用1x1卷积操作,实现了对任意大小图片的处理,并且在图像语义分割等任务中取得了良好的效果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [卷积神经网络FCN](https://blog.csdn.net/zqqbb7601/article/details/120704827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [卷积神经网络(FCN)](https://blog.csdn.net/qq_37497304/article/details/126599402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值