您的位置:首页 > 理论基础 > 计算机网络

FCN(全卷积网络)进行语义分割

2018-03-16 11:52 351 查看
建议大家在阅读本篇博客之前,首先看看这篇论文:A guide to convolution arithmetic for deep learning,仔细理解其中的反卷积操作,注意反卷积之后的通道个数以及对应还原出来的多维数组中代表图像大小的维度的取值范围,就可以很好地理解FCN是如何进行pixel-wise级别的分类任务了!

FCN是一个end-to-end的网络,实现像素级别(pixel-wise)的分类,即对每一个像素点都可以进行分类,在语义分割任务上取得了很不错的效果!

FCN的原理

通过将全连接层全都改为了卷积层,然后对最后一个(原最后一个全连接层)卷积层(heat map)进行upsampling运算,一般采用反卷积操作,通过设置反卷积层的参数可以直接还原为原图大小,不过这样效果不怎么好,所以考虑加入更多前层的细节信息,也就是把倒数底基层的输出和最后的输出做一个fusion(融合),其实就是一个加和操作,来融合更多的信息,通过设置反卷积操作将其还原为原图大小,不过注意通道个数仍旧是最后一个卷积层(原最后一个全连接层)的通道个数!

FCN的代码分析:

with tf.variable_scope("inference"):
image_net = vgg_net(weights, processed_image)
conv_final_layer = image_net["conv5_3"]

pool5 = utils.max_pool_2x2(conv_final_layer)

W6 = utils.weight_variable([7, 7, 512, 4096], name="W6")
b6 = utils.bias_variable([4096], name="b6")
conv6 = utils.conv2d_basic(pool5, W6, b6)
relu6 = tf.nn.relu(conv6, name="relu6")
if FLAGS.debug:
utils.add_activation_summary(relu6)
relu_dropout6 = tf.nn.dropout(relu6, keep_prob=keep_prob)

W7 = utils.weight_variable([1, 1, 4096, 4096], name="W7")
b7 = utils.bias_variable([4096], name="b7")
conv7 = utils.conv2d_basic(relu_dropout6, W7, b7)
relu7 = tf.nn.relu(conv7, name="relu7")
if FLAGS.debug:
utils.add_activation_summary(relu7)
relu_dropout7 = tf.nn.dropout(relu7, keep_prob=keep_prob)

W8 = utils.weight_variable([1, 1, 4096, NUM_OF_CLASSESS], name="W8")
b8 = utils.bias_variable([NUM_OF_CLASSESS], name="b8")
conv8 = utils.conv2d_basic(relu_dropout7, W8, b8)
# annotation_pred1 = tf.argmax(conv8, dimension=3, name="prediction1")

# now to upscale to actual image size
deconv_shape1 = image_net["pool4"].get_shape()
W_t1 = utils.weight_variable([4, 4, deconv_shape1[3].value, NUM_OF_CLASSESS], name="W_t1")
b_t1 = utils.bias_variable([deconv_shape1[3].value], name="b_t1")
conv_t1 = utils.conv2d_transpose_strided(conv8, W_t1, b_t1, output_shape=tf.shape(image_net["pool4"]))
fuse_1 = tf.add(conv_t1, image_net["pool4"], name="fuse_1")

deconv_shape2 = image_net["pool3"].get_shape()
W_t2 = utils.weight_variable([4, 4, deconv_shape2[3].value, deconv_shape1[3].value], name="W_t2")
b_t2 = utils.bias_variable([deconv_shape2[3].value], name="b_t2")
conv_t2 = utils.conv2d_transpose_strided(fuse_1, W_t2, b_t2, output_shape=tf.shape(image_net["pool3"]))
fuse_2 = tf.add(conv_t2, image_net["pool3"], name="fuse_2")

shape = tf.shape(image)
deconv_shape3 = tf.stack([shape[0], shape[1], shape[2], NUM_OF_CLASSESS])
W_t3 = utils.weight_variable([16, 16, NUM_OF_CLASSESS, deconv_shape2[3].value], name="W_t3")
b_t3 = utils.bias_variable([NUM_OF_CLASSESS], name="b_t3")
conv_t3 = utils.conv2d_transpose_strided(fuse_2, W_t3, b_t3, output_shape=deconv_shape3, stride=8)

annotation_pred = tf.argmax(conv_t3, dimension=3, name="prediction")

return tf.expand_dims(annotation_pred, dim=3), conv_t3


这里我们主要对deconv层进行分析,可以看到作者先将conv_t1反卷积到和pool4输出格式一样的数据类型[B,width,height,outchannels],然后和pool4进行一个加和运算,然后将新得到的数据继续通过反卷积得到和pool3类型相同的数据,[B,height,wight,channels],然后和pool3层进行加和运算,然后得到新的数据,通过设置反卷积(tf.nn.conv2d_transpose)所需要的参数(fileter=[filter_size,filter_size,output_channels,input_hannels](注意这儿设置的格通道格式的先后顺序和其所代表的意义),strides,padding,output_shape=[N,new_width,new_height,output_channels]),至于为什么在tf.nn.conv2d_transpose中设置输出格式的原因和如何反卷积得到所需的输出格式,请参见上一篇博客!

FCN的网络结构

标准跳跃结构:





通过上图我们可以看到FCN中通过将所有的全连接层都设置为了使用1*1的卷积核进行卷积操作后得到的卷积层,然后进行反卷积操作得到对应的和原图大小一致的N通道图片,通过tf.argmax操作对最后一维度进行求最大值,这样就可以得到对应的每个像素属于每个类了,通过训练就可以得到比传统的语义分割方法更好的准确率!



作者采用的方式(融合了更多信息的反卷积层)



FCN做语义分割结果:

采用了32倍,16倍和8被上采样得到的结果对比,可以看到对应的结果,但是请注意并不是采用倍率越低效果越好啊,没有这个说法,4倍的效果就比较差,可能是融合的消息过多,产生大量冗余信息,造成网络的过拟合,需要注意一下,结果如下:



FCN的优缺点

优点:

较CNN进行图像分割方法相比,FCN有两大明显的优点:

1)可以接受任意大小的输入图像(相比CNN,FCN为什么有这样的特性呢?答案就在于CNN中有全连接层,全连接层是通过两个矩阵相乘得到,将最后一个卷积层展开,生成[N,final_conv_flatten]的输入矩阵,然后求出[final_conv_flatten,fc1_size]矩阵,要求每一个输入图像都要被展开为final_conv_flatten,因此每个输入图像的大小必须相同!)

2)相比使用传统的滑动像素块方法更加高效,避免了由于使用像素块而带来的重复存储和计算卷积的问题!

缺点:

- 1.得到的结果还不够精细,虽然8倍上采样效果虽然好了一些,但是上采样之后得到的结果还是比较模糊和平滑,对图像中的细节并不敏感;

2.对各个像素进行分类,没有充分考虑像素与像素之间的关系,忽略了通常的基于像素分类的分割方法中使用的空间规整步骤,缺乏空间一致性!

PS:

注意作者上采样的过程中采用了三种方法,由于前两个的效果没有达到预期,因此,作者采用了第三种反卷积(其实是转置卷积的方法,因为转置卷积只是给出了对应的通道,并没有直接得到对应的值,转置卷积中的值需要在训练过程中求解出来)方式,可以取得较好的效果!

同时大家参考底下的文章参考中的第三篇博客理解线性差值和双线性插值,还有反卷积操作,还有反卷积操作为什么实际不是反卷积而是转置卷积操作,相信思考这些问题一定会很纠结啦!!!

参考

文章参考:

全卷积网络(FCN)

Fully Convolutional Networks for Semantic Segmentation

FCN中反卷积、上采样、双线性插值之间的关系

代码参考:

FCN.tensorflow

论文:

Fully Convolutional Networks

for Semantic Segmentation
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: