FCN论文简述与代码实现

目录

FCN论文简述

FCN代码实现

FCN论文简述

论文地址

FCN是Fully Convolutional Networks的缩写,论文的全称是Fully Convolutional Networks for Semantic Segmentation。我觉得论文的主要贡献在于两点:

  1. 提出了一种全卷积网络,去掉了以前VGG网络常规的FC连接
  2. 使用像素级别的比较来进行语义分割

全卷积网络

用卷积计算替代FC计算

这个部分其实比较容易理解,假设我们通过一系列卷积计算后得到一个7x7x512的数据,按照以前FC的方式,需要先对这个矩阵进行flatten得到长度为25088的向量,然后对这个向量与4096个向量进行全连接。如果将这个部分用卷积计算来替代,就是对7x7x512的数据,进行kernel为7x7x512的卷积计算,kernel个数为4096个,那么计算会得到1x1x4096的矩阵,与FC得到长度为4096的向量是一样的意思。

为什么要用卷积计算来替代FC计算?

如果使用FC计算,我们必须要知道输入的尺寸和输出的尺寸,因为中间的参数个数是由输入和输出尺寸共同决定的,但是如果使用卷积,kernel的尺寸可以固定,并不受输入和输出尺寸影响,这样不同的输入尺寸都可以计算。

上图取自论文,这张图片展示的我觉得有点像简化slide window计算的思想,就是说如果有一个已经训练好的分类猫的网络,当我们用更大的一张图片时也可以用同样的网络进行计算,这样最后得到的feature map里面的每一个位置的其实代表了原图中的一个区域,这样一次性计算就得到了很多个区域多猫分类的结果,就像论文中说的得到了关于猫的heatmap。

网络结构

上图是论文中的网络结构示意图,通过全卷积计算进行downsampling后,再进行upsampling到原图的尺寸,这样就可以和segmentation的ground truth进行pixel级别的比较了。

但是存在一个问题,当我们进行5次pool计算,把图片downsampling到原图的1/32后,这个heatmap恢复到原图的尺寸就会丢失掉很多像素信息,从而丢失很多细节信息。作者在这里加了一点小技巧,将heatmap与前面拥有比较多细节信息的层相加,然后进行upsampling,这样能找到更多细节。论文中如下图展示:

因为上图展示的三种连接方式,所以有FCN32,FCN16和FCN8之分,代表这upsampling多少倍可以恢复到原图的尺寸。

FCN代码实现

FCN32

比较简单的实现了一下代码,可以参考fcn32_vgg.py

网络前面是VGG模型,后面将VGG中的FC改成了卷积计算,upsampling使用的是Conv2DTranspose方法。

conv_x = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(conv_x)
conv_x = Conv2DTranspose(num_classes, kernel_size=(64, 64), strides=(32, 32), padding='same', use_bias=False)(conv_x)

FCN16

相对FCN32把pool4的数据加入进行upsampling,可以参考fcn16_vgg.py

conv_pool5 = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(conv_pool5)
conv_pool5 = Conv2DTranspose(num_classes, kernel_size=(4, 4), strides=(2, 2), padding='same', use_bias=False)(conv_pool5)

conv_pool4 = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(pool4)

conv_x = Add()([conv_pool4, conv_pool5])
conv_x = Conv2DTranspose(num_classes, kernel_size=(32, 32), strides=(16, 16), padding='same', use_bias=False)(conv_x)

FCN8

相对FCN16把pool3也加入参与upsampling,从而拿到更多细节信息,参考fcn8_vgg.py

conv_pool5 = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(conv_pool5)
conv_pool5 = Conv2DTranspose(num_classes, kernel_size=(8, 8), strides=(4, 4), padding='same', use_bias=False)(conv_pool5)

conv_pool4 = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(pool4)
conv_pool4 = Conv2DTranspose(num_classes, kernel_size=(4, 4), strides=(2, 2), padding='same', use_bias=False)(conv_pool4)

conv_pool3 = Conv2D(num_classes, (1, 1), kernel_initializer='he_normal')(pool3)

conv_x = Add()([conv_pool3, conv_pool4, conv_pool5])
conv_x = Conv2DTranspose(num_classes, kernel_size=(16, 16), strides=(8, 8), padding='same', use_bias=False)(conv_x)

Loss

如果分类的个数是10类,那么upsampling后得到的矩阵尺寸是[h, w, 10],在axis=2的方向上做了softmax分类,代表这每个像素属于某个类别的概率。同时也需要将ground truth处理成这在在axis=2上是one hot的格式,比如如果某个位置的像素是属于第1类,那么这个位置的数据应该是[0,1,0,0,0,0,0,0,0,0],如果某个位置像素属于第5类,那么这个位置的数据应该是[0,0,0,0,0,1,0,0,0,0],以此类推。

这样就可以将upsampling的数据与ground truth数据进行crossentropy loss计算了。

猜你喜欢

转载自blog.csdn.net/stesha_chen/article/details/87784989
今日推荐