基于语义分割的场景分析是计算机视觉中的一个基本问题,其目的是为图像中的每个像素指定一个类别标签。
版权声明:本文著作权归原作者所有,奇番小编欢迎分享本文,您的收藏是对我们的信任,QFanW谢谢大家支持!
754近年来,以FCN的提出为转折点,语义分割得到了快速的发展,随后==PSPnet(金字塔场景解析网络)==也被提出,较好地结局了FCN中存在的语境关系不匹配的问题。
数据集方面,大量优质的数据集不断涌现,典型的有MS COCO、Pascal Voc 2012、ADE20K_MIT等等,这些数据集不仅为我们提供了大量优质的训练样本,同时也有许多杰出的前辈为我们提供了各种网络在这些数据集的预训练模型。
迁移学习的思想也给了我们很大的启发。在数据极端匮乏的情况下,one-shot、zero-shot等思想大有用武之地。
迁移学习的方法使我们避免了为大量数据进行人工标记,相反,我们只需要对少量的样本进行标记,再基于一些预训练模型进行二次训练,就可以得到与在大量数据下训练的结果。
2.构造数据集
(0).数据集格式
这一点必须首先说明。我们知道,语义分割的输入是一张图片,输出也是一张图片,但事实上输入输出的维度可能并不完全一致,输入往往是一张彩色的图,包含rgb(红黄蓝)三个通道,可以看做一个高×宽×通道数的张量,而输出往往可能是一个高×宽的二维矩阵,其中每一个元素对应输入像素的类别。
因此考虑训练数据,每一行数据都应该有两张图片,一张是原图,可以理解成features,一张是带有类别标记的图片。
keras_segmentation官方博客给出了一个数据集的示例。其结构如下
─dataset1
├─annotations_prepped_test
├─annotations_prepped_train
├─images_prepped_test
└─images_prepped_train
打开注解集发现,图片全都是黑的。
事实上并非纯黑,当我们把亮度调高之后,就能看到大致的轮廓。
官方对此的解释是,注解图片应当是一个与原图大小相同的图片。
import cv2
import numpy as np
ann_img = np.zeros((30,30,3)).astype('uint8')
ann_img处标记为类别1。类别0认为是背景(background)
由于时间、人力等问题,我们人工构造大量动漫角色的数据集并不现实。因此我们制作一个小规模的数据集。
制备方法如下:
(1).准备原始图片
我们可以轻易地从网络上搜集到许多包含动漫人物的图片。在该数据集中我们共收集40张,其中30张作为训练集,10张作为测试集。(如下图)
(2).标记数据
此处我们使用数据标记工具labelme,这款工具可以让我们方便地在图上对关键区域进行标记。
标记数据时间很苦的事情,俗称人体描边。(以下为示例)
尽管描边非常辛苦,但是看着这些卡哇伊的角色,也就不觉得累了(题外话)
标记完成后,labelme会帮助我们保存json文件,这个json文件记录了我们的标记点的顺序和位置。
(3).导出为mask型png
labelme自带了命令
labelme_json_to_dataset <文件名>.json
这条命令可以帮助我们把json转成带有mask的png图片,但缺点是一条命令只能转换一个文件,对于批量文件,可以用python写脚本处理(一下是一个示例)。
import os
for i in range(1,n):
os.system("labelme_json_to_dataset " + "test/" + str(i) + ".json")
这样我们就得到了一个结果集。
其中每个文件夹中包含了四个文件, .png文件是我们需要的,我们把它设法提取到一个文件夹中。
可以看到,这些png图片都是以黑色为背景,以rgb(128,0,0)为mask的图片。这离我们需要的注解格式还有最后一步距离。
(4).注解化
最后一步,我们把上述png图片的所有红色mask区域的值都设置为1.
经过一顿猛如虎的操作,我们就得到了我们自创的数据集——动漫人物数据集,其结构同demo数据集一致,结构如下。
─dataset2
├─test_anno
├─test_images
├─train_anno
└─train_images
3.训练模型
这里我们采用比较先进的PSPnet。在keras_segmentation的框架下,训练这样一个网络并不是难事。
只需要指定好所选的网络、训练的原图和注解文件,并指定好checkpoints的存放路径,以及训练的epochs。
一方面,自己从底层搭建模型,可能会出错,这就会导致浪费大量的计算资源,另一方面,我水平也不够(根本原因),索性就直接用keras_segmentation提供的模型吧。
同样的,我们再基于预训练模型来train一个model,可以看做一种迁移学习
这里要吐槽一下官方文档,官方文档在描述迁移学习的时候,提到了一种transfer_weights方法,但事实上包里面压根没这么个方法。
根据官方的设计,训练似乎只能在linux机上进行,虽然官方没有明说,但当我在Windows上运行训练的时候,报出来一个莫须有的错误,使得我非常恼火。大致是说注解文件夹下没有响应的文件。但事实上我用os.path.exist()方法检查返回结果是True。天无绝人之路,我们碰巧有一台linux机,就是上次想象出来的那台,刚好能够派上用场。
需要特别注意的是这个checkpoints,这里真是太坑爹了,害我白跑了两个晚上。在keras_segmentation中,模型并不通过model.save()方法进行保存,虽然可以这样保存,但是再次读取之后会出现一些莫名其妙的问题。事实上模型的参数都是保存在checkpoints中,加载时可以通过keras_segmentation.predict.model_from_checkpoints(checkpoints_path)进行加载。至于为什么如此设计,咱也不知道,咱也不敢问。
4.模型评估
这里的准确率主要通过IoU(Intersection over Union)来反映,也就是预测结果与实际结果的交集。Keras_segmentation为我们提供了一个评估方法keras_segmentation.predict.evaluate( model=None , inp_images=None , annotations=None , checkpoints_path=None )。该方法接受一个模型、测试图片集和注解集,并输出测试集上的IoU。
5.模型分析
我们可以借助keras 提供的方法对模型进行可视化,这样可以清晰得看到网络结构。
在PSPnet中,作者引入了金字塔池化模块,如下图。
Hengshuang Zhao 等人分析了许多FCN网络结构的失败案例,他们观察到语境关系不匹配、类别混淆和不明显的类别等问题,并认为引入金字塔池化模块可以很好的结局这个问题。
在传统的psp网络中,金字塔池化层前的步骤基本也是普通的下采样操作。其结构如下。
靠近底端的部分即为金字塔池化层。psp_101在上述基础上,在金字塔池化层前添加了许多交替的卷积层和batchNorm层。我推测大概有101层。
我们此处并不讨论why pspnet works(因为我也看不透)。
6.效果展示
此处仅展示测试集上的效果
(下图为直接训练模型的结果)
(下图为迁移学习模型的结果)