FCN图像分割【飞桨】class2(附代码和注释)

FCN 全卷积网络

 

上图是一个FCN-32s网络的结构图。在这个网络中,图片经过骨干网络(VGG Network)提取特征后,得到长宽缩小32倍的特征图,随后直接将特征图上采样32倍,得到和输入图片一样大小的分割图。

是不是特别简单直接。别看这个网络设计的这么简单,但确实可以得到分割图。

当然,因为是直接将特征图上采样32倍,这种方式得到的分割精度不会很高。

为了提高分割精度,作者还提出将骨干网络的中间层提取出来,与上述分割结果融合,得到最终的输出结果。这样就可以得到FCN-16s, FCN-8s, FCN-4s以及FCN-2s网络。

上图展示的是FCN-8s网络,该网络将Pool3和Pool4的输出取出,与FCN-32s的输出结果融合,得到最终的分割图。具体的融合方式见下图:

首先将FC7(FCN-32s的输出)上采样2倍,与Pool4输出结果相加,得到与Pool4融合的特征图。随后将这个特征图再上采样2倍,与Pool3层的结果相加,得到融合了Pool4、Pool3信息的特征图。再将这个特征图上采样8倍,经过一个softmax层,就得到了与输入图片一样大小的分割图。因为最终上采样8倍,所以称为FCN-8s。

import numpy as np
import paddle.fluid as fluid
from paddle.fluid.dygraph import to_variable
from paddle.fluid.dygraph import Conv2D
from paddle.fluid.dygraph import Conv2DTranspose
from paddle.fluid.dygraph import Dropout
from paddle.fluid.dygraph import BatchNorm
from paddle.fluid.dygraph import Pool2D
from paddle.fluid.dygraph import Linear
from vgg import VGG16BN


class FCN8s(fluid.dygraph.Layer):#本质上是复杂的basic_model
    def __init__(self, num_classes=59):
        super(FCN8s, self).__init__()#初始化父类
        backbone=VGG16BN(pretrained=False)#选择vgg作为backbone
        #backbone指的是网络的主干网络,对图像进行特征提取(常见的有vggnet,resnet,谷歌的inception),这一部分是整个CV任务的根基
        self.layer1=backbone.layer1#直接从vgg中拿就好了
        self.layer1[0].conv._padding=[100,100]
        self.pool1=Pool2D(pool_size=2,pool_stride=2,ceil_mode=True)#ceil_mode我们应对不同尺寸的输入时取整等操作
        self.layer2=backbone.layer2
        self.pool2=Pool2D(pool_size=2,pool_stride=2,ceil_mode=True)
        self.layer3=backbone.layer3
        self.pool3=Pool2D(pool_size=2,pool_stride=2,ceil_mode=True)
        self.layer4=backbone.layer4
        self.pool4=Pool2D(pool_size=2,pool_stride=2,ceil_mode=True)
        self.layer5=backbone.layer5
        self.pool5=Pool2D(pool_size=2,pool_stride=2,ceil_mode=True)
        #这里我们只取到5,6和7是FC层,我们用conv来替代
        self.fc6=Conv2D(512,4096,7,act='relu')
        self.fc7=Conv2D(4096,4096,1,act='relu')
        self.drop6=Dropout()
        self.drop7=Dropout()
        #score用是把feature map的通道数跟分割图一样
        self.score=Conv2D(4096,num_classes,1)#1X1卷积用来做分割,跟basicmodel中一样
        self.score_pool3=Conv2D(256,num_classes,1)
        self.score_pool4=Conv2D(512,num_classes,1)

        #上采样(2倍、2倍、8倍)
        self.up_output=Conv2DTranspose(num_channels=num_classes,
                                        num_filters=num_classes,
                                        filter_size=4,
                                        stride=2,
                                        bias_attr=False)
        self.up_pool4=Conv2DTranspose(num_channels=num_classes,
                                        num_filters=num_classes,
                                        filter_size=4,
                                        stride=2,
                                        bias_attr=False)   
        self.up_final=Conv2DTranspose(num_channels=num_classes,
                                        num_filters=num_classes,
                                        filter_size=16,
                                        stride=8,
                                        bias_attr=False)                    

    def forward(self,inputs):
        x=self.layer1(inputs)
        x=self.pool1(x)#1/2
        x=self.layer2(x)
        x=self.pool2(x)#1/4
        x=self.layer3(x)
        x=self.pool3(x)#1/8
        pool3=x
        x=self.layer4(x)
        x=self.pool4(x)#1/16
        pool4=x
        x=self.layer5(x)
        x=self.pool5(x)#1/32

        x=self.fc6(x)
        x=self.drop6(x)
        x=self.fc7(x)
        x=self.drop7(x)

        x=self.score(x)#做分割图按类别数的1X1卷积
        x=self.up_output(x)#上采样2倍

        up_output=x#变成了1/16的feature map
        x=self.score_pool4(pool4)#变成与分割类别数一样的feature map

        x = x[:, :, 5:5+up_output.shape[2], 5:5+up_output.shape[3]]#相加之前做padding(实际为clop),主要目的是为了让模型的尺寸shape一样

        up_pool4 = x                # 保存和类别数通道一致的pool4
        x = up_pool4 + up_output    # FC7上采样后 + pool4,目的:融合更多的通道信息 
        x = self.up_pool4(x)        # 再一次上采样:2 × (2 × FC7 + pool4)
        up_pool4 = x                # 保存 2 × (2 × FC7 + pool4)

        x = self.score_pool3(pool3) # 把pool3的feature map的通道变成和分割类别数一致

        x = x[:, :, 9:9+up_pool4.shape[2], 9:9+up_pool4.shape[3]]   # padding,其中5、9、31是根据论文的设置的,0也可以
        up_pool3=x # 1/8          # 保存和类别数通道一致的pool3
        x=up_pool3+up_pool4 # pool3 + 2 × (2 × FC7 + pool4)
        x = self.up_final(x) # 最终的8倍上采样:8 × (pool3 + 2 × (2 × FC7 + pool4))

        x = x[:, :, 31:31+inputs.shape[2], 31:31+inputs.shape[3]]

        return x

def main():
    with fluid.dygraph.guard():
        x_data = np.random.rand(2, 3, 512, 512).astype(np.float32)
        x = to_variable(x_data)
        model = FCN8s(num_classes=59)
        model.eval()
        pred = model(x)
        print(pred.shape)


if __name__ == '__main__':
    main()

上采样的三个操作:

1、unsample

看到unsample或者interpolate要反应过来是将feature map变大

双线性插值:

#上采样——双线性插值法


import paddle.fluid as fluid
import numpy as np
np.set_printoptions(precision=2)

def main():
    with fluid.dygraph.guard(fluid.CPUPlace()):#调用paddle动态图
        data=np.array([1,2],#输入数据,把2x2的图放大到4x4
                      [3,4]).astype(np.float32)
        data=data[np.newaxis,np.newaxis,:,:]#把np.array的维度变为符合我们深度学习框架用的
        #tensor一般是四维,很少有五维
        data=fluid.dygraph.to_variable(data)#放到cpu或者gpu上去
        out=fluid.layers.interpolate(data,out_shape=(4,4),aligin_corners=True)
        #输入的是我们的数据也就是1x1x2x2的data,这里我们把他放大到4x4
        out=out.numpy()#从cpu上拿回来转为numpy
        print(out.squeeze((0,1)))#去掉newaxis

2、Unpooling

Pooling的反向操作

 3、Transpose conv

卷积的原理:

 反卷积:

 

stride是卷积核移动的距离

总结:三个方法都是用来将feature map变大

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值