卷积网络是视觉处理中可以有效生成多层特征的架构,是最前沿的技术。因此作者想构造一个“全卷积网络”,来处理任意尺寸的输入图片,并生成相应尺寸的输出。
通过改造当下热门的分类网络(VGG,AlexNet,GoogleLeNet等),我们可以让它们的架构应用于图像分类任务。
该网络得到很好的语义分割的效果,另外处理每张图片的速度也很快只需要五分之一秒。 通过skip-architecture,我们可以把深层的输出特征(更全面但更粗糙)与浅层的输出特征(更细节但更精确)相结合。这种操作有利于生成更加准确、细节饱满的分割结果。
卷积网络里每一层的数据都是三维数组。如果这三维为h × w × d,则h和w是图片的高和宽,d为图片的特征或者是通道数。
第一层输入图片,图片维度为[高, 宽, 色彩通道数]。网络深层的每个数据都与网络浅层的一片数据有关,这就叫做感知野。
对于如何把coarse output转换得到dense prediction,作者研究过3种方案:
另外,此文还详细分析了shift-and-stich方案: shift-and-stich解释
参考[深度学习论文阅读]Fully Convolutional Networks for Semantic Segmentation(FCN网络) 通常做语义分割的方法都是使用Patchwise训练,就是指将一张图片中的重要部分裁剪下来进行训练以避免整张照片直接进行训练所产生的信息冗余,这种方法有助于快速收敛。
但是本文章提出直接使用整张图片也许可能使效果更好而Patchwise可能使信息受损(所以此节名为Patchwise training is loss sampling)。
这里一个直觉得想法是一整张图像可能是有空间相关性的,那么Patchwise就减少了这种相关性
FCN的skip Achitecture有三种架构:FCN-32s、FCN-16s和FCN-8s。skip Achitecture通过把深层数据的结果与浅层的准确结果相结合,再恢复到原图的输出,可以生成更准确的结果。
根据试验,FCN-8s的效果最好,而如果再叠加层数,反而效果变差了,所以论文做到此处就停止了。 这也是FCN提升预测结果最关键的部分。
在参考了github上别人的FCN框架后,我认真研究了它的代码,并结合自己的想法,重新写了一遍。 我的代码主要分为以下几个模块:
FCN.py, FCN_down_sizing.py
. FCN_down_sizing.py定义了FCN网络中downsizing的部分,而FCN.py结合downsizing的部分来组装FCN-8s, FCN-16s和FCN-32sread_MITSceneParsingData.py
.用于为每个image找到对应的annotation,并把这些关系组保存为一个.pickle文件,供处理图片时读取。BatchDatsetReader.py
.给定image与annotation相互对应的关系组,找到并读取这些图片的数据。FCN_train.py, FCN_test.py, FCN_infer.py
. 显然是用于train, test和infer的。我把FCN网络看做两个部分:
不管是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)。
FCN_down_sizing.py
中的get_FCN_8s_net
则实现了论文中的sky-architecture 用于从.pickle文件中读取image-annotation的路径数组,供BatchDatsetReader.py读取图片数据。 如果没有.pickle文件,则要先生成一个.pickle文件。具体做法是:遍历images目录,对每个jpg图片在annotations目录中找到对应的png图像分割文件。由此生成image-annotation的文件名集合。 这样,我们为所有的image都找到了对应的annotation的路径,就可以把它们存储为.pickle文件,供日后训练用。
在开始训练之前要读取所有的图片和图片分割。读取.pickle文件,利用其中的信息可以找到所有的image和annotation并读取为矩阵的形式。 这样,image_list的维度为(num_images, height, width, 3), annotation_list的维度为(num_annotations, height, width, 1),且类型都是numpy.ndarray。
training中,通常的程序逻辑是这样的:
image数据大部分是三维的(h, w, 3),但有少部分是灰度图,也就是二维的(h, w) annotation数据则都是二维的(h, w)
因此处理image数据时,如果遇到二维的图片,要先转为三维且有3个通道的图片。
问题描述 原作者的代码中,图片的变形使用的是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函数。
问题描述
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)形式。而我的代码没有考虑到这一点,导致这几张灰度图被转换后的维度错误。
解决方法 对于这几张灰度图,需要将其转换为三通道的形式。只需要把单通道上的值重复三次作为三个通道的值即可。
问题描述
在给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
即可。
通过在源代码中添加如下代码可输出各层卷积核的维度
输出:
仅截取部分输出
根据输出,我发现源代码使用的是VGG-19,而论文中使用的是VGG-16。两者的效果应该差不多,为了保持一致,我依旧按照VGG-19来叠加。
tf.layers.conv2d_transpose
只能指定strides
来调整输出图片的尺寸。
strides = [2, 2]
时放大两倍,strides = [8, 8]
时放大8倍
代码中此段是用来打乱images和annotations的,开始看的时候不懂,感觉这不符合Python的语法,后来查了官方文档发现,原来这是numpy.ndarray重载过的行为,是numpy.ndarray的特殊的索引方式。
基本用法如下:
所以,通过传递一
问题描述 发现不管如何调优,mean_iou的数值很低。 问题原因 原来是因为在缩放annotation的过程中,使用的方法会拉扯那些值,使得annotation出现了本来不存在的分类。比如原本annotation里的值只有3,4,5,经过缩放,值被拉扯,变成了二点几、三点几、和六点几,等等。而图原本是没有6这个分类的,这就导致mean_iou的计算出现巨大的偏差。 解决方法 图片缩放函用回scipy.sisc.imresize,因为这个函数有按nearest模式缩放的功能,在缩放图片的同时不改变图片内的值的种类。
Memory Error
的情况,以后编程要注意节省内存空间。