1. FCN 论文学习
1.1 写作背景
卷积网络是视觉处理中可以有效生成多层特征的架构,是最前沿的技术。因此作者想构造一个“全卷积网络”,来处理任意尺寸的输入图片,并生成相应尺寸的输出。
通过改造当下热门的分类网络(VGG,AlexNet,GoogleLeNet等),我们可以让它们的架构应用于图像分类任务。
该网络得到很好的语义分割的效果,另外处理每张图片的速度也很快只需要五分之一秒。
通过skip-architecture,我们可以把深层的输出特征(更全面但更粗糙)与浅层的输出特征(更细节但更精确)相结合。这种操作有利于生成更加准确、细节饱满的分割结果。
1.2 FCN架构
卷积网络里每一层的数据都是三维数组。如果这三维为h × w × d,则h和w是图片的高和宽,d为图片的特征或者是通道数。
第一层输入图片,图片维度为[高, 宽, 色彩通道数]。网络深层的每个数据都与网络浅层的一片数据有关,这就叫做感知野。
- FCN的输入更灵活。普通的LeNet,AlexNet等卷积网络只能接受固定维度的输入,而FCN可以接受任意维度的输入
- FCN的运算更高效。
- 进行预测时,在普通的GPU上对某个227227的图片进行预测,AlexNet耗费1.2ms;而FCN从500500的图片中生成10*10的输出只需要22ms,其中的效率相差了5倍。
- 进行后向传播时,AlexNet需要2.4ms,而FCN只需要37ms。
1.3 把粗糙输出转换为原尺寸图片
对于如何把coarse output转换得到dense prediction,作者研究过3种方案:
- shift-and-stitch
- filter rarefaction
- deconvolution
具体见分析三种粗糙图片转换为原尺寸图案的方案
3.1 shift-and-stich
另外,此文还详细分析了shift-and-stich方案:
shift-and-stich解释
1.4 Patchwise training is loss sampling
参考[深度学习论文阅读]Fully Convolutional Networks for Semantic Segmentation(FCN网络)
通常做语义分割的方法都是使用Patchwise训练,就是指将一张图片中的重要部分裁剪下来进行训练以避免整张照片直接进行训练所产生的信息冗余,这种方法有助于快速收敛。
但是本文章提出直接使用整张图片也许可能使效果更好而Patchwise可能使信息受损(所以此节名为Patchwise training is loss sampling)。
这里一个直觉得想法是一整张图像可能是有空间相关性的,那么Patchwise就减少了这种相关性
1.5 Segmentation Architecture(skip Architecture)
FCN的skip Achitecture有三种架构:FCN-32s、FCN-16s和FCN-8s。skip Achitecture通过把深层数据的结果与浅层的准确结果相结合,再恢复到原图的输出,可以生成更准确的结果。
- FCN-32s是指用逆卷积把conv7放大到32倍。
- FCN-16s是指先用逆卷积把conv7放大到2倍,将放大结果与pool4的输出相加,再把相加结果放大16倍。
- 同理,FCN-8s是指用逆卷积把conv7放大到2倍,将放大结果与pool4的输出相加,再把相加结果用逆卷积放大两倍,与pool3相加。最后把第二次的相加结果放大8倍到原来的图像尺寸。
根据试验,FCN-8s的效果最好,而如果再叠加层数,反而效果变差了,所以论文做到此处就停止了。
这也是FCN提升预测结果最关键的部分。
2. FCN重写
2.1 概括
在参考了github上别人的FCN框架后,我认真研究了它的代码,并结合自己的想法,重新写了一遍。
我的代码主要分为以下几个模块:
-
FCN.py, FCN_down_sizing.py
. FCN_down_sizing.py定义了FCN网络中downsizing的部分,而FCN.py结合downsizing的部分来组装FCN-8s, FCN-16s和FCN-32s -
ImageReader.py
. 用于读取图片数据集和划分结果数据集,它为train, test和infer三种模式都准备了相应的读取方案。- 由于网上参考的代码有些陈旧,且代码结构不满足train, test与infer分开进行的需求,我对此进行了改动
-
FCN_train, FCN_test, FCN_infer
. 显然是用于train, test和infer的。
2.2 FCN.py, FCN_down_sizing.py
我把FCN网络看做两个部分:
- downsizing,通过卷积使矩阵的尺寸缩小
- upscaling,通过逆卷积使图片恢复原本的尺寸。
不管是FCN-8s, FCN-16s还是FCN-32s,他们都需要用到把图片downsize的过程,所以FCN_down_sizing.py定义了FCN中进行downsize的这个部分。
而FCN.py则利用FCN_down_sizing.py的部分组装成FCN-8s, FCN-16s和FCN-32s(由于时间缘故,只完成了FCN-8s)。
- downsize的部分是用vgg19搭建的(论文里是用vgg16,但效果差不多)。
-
FCN_down_sizing.py
中的get_FCN_8s_net
则实现了论文中的sky-architecture
2.3 ImageReader.py
training中,通常的程序逻辑是这样的:
- 生成image-annotation的文件名集合。遍历images目录,对每个jpg图片在annotations目录中找到对应的png图像分割文件。这样,我们为所有的image都找到了对应的annotation的路径,就可以把它们存储为.pickle文件,供日后训练用。
- 在开始训练之前要读取所有的图片和图片分割。读取.pickle文件,利用其中的信息可以找到所有的image和annotation并读取为矩阵的形式。这样,image_list的维度为(num_images, height, width, 3), annotation_list的维度为(num_annotations, height, width, 1),且类型都是numpy.ndarray。
- 由于训练的数据并非全部是Rgb的三通道图,有些是灰度图(只有单通道)。为了统一处理,要把这些灰度图转换为三通道的形式。重写时在此栽过跟头。
3. 遇到的问题
3.1 问题1 image维度不统一
image数据大部分是三维的(h, w, 3),但有少部分是灰度图,也就是二维的(h, w)
annotation数据则都是二维的(h, w)
因此处理image数据时,如果遇到二维的图片,要先转为三维且有3个通道的图片。
3.2 问题2 scipy.misc.imresize is deprecated
问题描述
原作者的代码中,图片的变形使用的是scipy.misc.imresize函数。
但我发现这个函数除了对图片变形,还会自行做一些多余的动作。它会把数组里的值标准归一化到[0, 255]的区间内,破坏图片原本的信息。
arr = np.array([[[100, 2, 220], [3, 4, 5]], [[1, 2, 3], [3, 4, 5]]])
print(type(arr))
print(arr.shape)
resize_size = 4
arr = misc.imresize(arr, [resize_size, resize_size], interp='nearest')
print(type(arr))
print(arr.shape)
print(arr)
输出
<class 'numpy.ndarray'>
(2, 2, 3)
<class 'numpy.ndarray'>
(4, 4, 3)
[[[115 1 255]
[115 1 255]
[ 2 3 5]
[ 2 3 5]]
[[115 1 255]
[115 1 255]
[ 2 3 5]
[ 2 3 5]]
[[ 0 1 2]
[ 0 1 2]
[ 2 3 5]
[ 2 3 5]]
[[ 0 1 2]
[ 0 1 2]
[ 2 3 5]
[ 2 3 5]]]
解决方法
最后查阅官方文档才知道这个函数已经被废止。
于是我将对图片的操作都改用skimage库实现了。而对图片的变形则使用skimage.transform.resize函数。
3.3 问题3 ValueError: could not broadcast input array from shape (224,224,3) into shape (224,224)
问题描述
Traceback (most recent call last):
File "test.py", line 8, in <module>
reader = ImageReader("train")
File "/root/Desktop/FCN/ImageReader.py", line 58, in __init__
self.image_list = np.array([self.readImage(record["image"]) for record in self.records])
ValueError: could not broadcast input array from shape (224,224,3) into shape (224,224)
在改用skimage库操作图片后,出现了无法把元素合并到一个数组的问题。对image里的图片的操作失败了。
查阅stackoverflow的问题发现原来是元素的维度并不统一。
我原以为所有image里的图片都是三通道的,也就是(h, w, 3)的。这样如果我要得到固定尺寸的图片(比如224 * 224),只需调用skimage.transform.resize,就能把图片转为(224, 224, 3)。理应所有图片都会被转换成(224, 224, 3)的维度。可是既然图片们无法共容在一个数组里,说明有的图片没有转换成这种维度。
问题原因
原来,image里并不是所有图片都是(h, w, 3)形式的,有的图片是灰度图(在20210张图片中有4张是灰度图),也就是(h, w)形式。而我的代码没有考虑到这一点,导致这几张灰度图被转换后的维度错误。
解决方法
对于这几张灰度图,需要将其转换为三通道的形式。只需要把单通道上的值重复三次作为三个通道的值即可。
3.4 问题4 图片转换后内容被破坏
问题描述
在给skimage.transform.resize添加
reserve_range = True
设置后,发现转换后的图片内容完全被破坏。似乎维持值的范围会破坏图片的可见性。
问题原因
查阅了stackoverflow
原来pyplot.imshow
只能显示[0.0, 1.0]范围的图片,而reserve_range = True
会使图片仍然在[0, 255]范围内,且数据类型为float64,被以[0.0, 1.0]的范围来看待,这就无法正确显示了。
另外,查阅官方文档的reserve_range
参数
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input image is converted according to the conventions of img_as_float.
确实如果不设置reserve_range = True
,函数会把值的范围标准归一化到[0.0, 1.0]内,也就是img_as_float.
问题解决
显示图片时先使用image = np.copy(old_image).astype('uint8')
,把类型从float64
转换为uint8
即可。
3.5 问题5 查看源代码的卷积核维度
通过在源代码中添加如下代码可输出各层卷积核的维度
输出:
根据输出,我发现源代码使用的是VGG-19,而论文中使用的是VGG-16。两者的效果应该差不多,为了保持一致,我依旧按照VGG-19来叠加。
3.6 问题6 tf.layers.conv2d_transpose的放大倍数
tf.layers.conv2d_transpose
只能指定strides
来调整输出图片的尺寸。
strides = [2, 2]
时放大两倍,strides = [8, 8]
时放大8倍
感想
- 在测试的时候图方便,总是读取整个数据集,其中等待浪费了很长时间,经常读取完以后才发现bug。以后应该先只读取一部分,保证代码正确运行,再读取整个数据集。
- 代码对内存的优化不好。由于每次测试都读取整个数据集,有时候出现内存用爆导致
Memory Error
的情况,以后编程要注意节省内存空间。 - 第一次从头到尾地进行编程,有些手忙脚乱。以后应当先分析数据集的成分,构建读取器,再构建神经网络,读取部分数据进行测试。保证无bug后才对。
参考
- 深度学习论文阅读Fully Convolutional Networks for Semantic Segmentation(FCN网络)
- 机器学习中使用的神经网络第五讲笔记
- 全卷积网络(FCN)与图像分割
- 翻译
- shift-and-stich解释
- 分析三种粗糙图片转换为原尺寸图案的方案