mmdetection版本:2.0
mmcv版本:0.5.5
mmdetection和mmcv的关系是,mmdetection一些功能代码是直接通过调用mmcv的api实现的。
============================================================================
目录
一、传入build_dataset()的配置信息
二、如何通过数据的配置文件json读取图片和label并进行处理
三、根据传入 build_dataset()的数据集名字来初始化对应的数据集对象----dataset
四、创建 data_loader
为了观察在mmdetection中数据是怎么读入和做了哪些操作,变成我们训练时的样子的,我们还是从程序的起始文件 tools/train.py看起。在 tools/train.py里有下面这么两句,第一句是用调用build_detector()方法来构造目标检测model的,而下一句则是用build_dataset()方法来构造数据集,build_xxx()方法在第一篇(一)有详细说明。
datasets = [build_dataset(cfg.data.train)]
可以看到 build_dataset()的参数里有个 cfg ,这是表示相关的配置文件,在运行mmdetection2.0时,配置文件主要有四大块:
1. 模型的配置文件 (例如你要跑哪个模型,faster rcnn?还是mask rcnn?还是ssd,每个模型的配置文件记录着它的组成部分)
2. 数据集的配置文件 (例如你要用coco数据集?还是voc?还是cityscapes?)
3. 训练策略的配置文件(例如设定epoch、batchsize、lr之类的)
4. 模型保存和日记配置文件(用于设置模型几个epoch保存一次,和日记的记录)
如运行 mask_rcnn、coco数据集时的配置文件(configs/_base_/models/mask_rcnn_r50_fpn.py):
mmdetection2.0在运行时,会把这4个文件合在一起一起,形成一个大的配置文件 cfg。
言归正传,我们还是说回 dataset的build过程:
(下面讲述的部分是可以在模型的配置文件里找到的,如configs/_base_/datasets/coco_detection.py)
从上图可以看到,传入build_dataset()的配置数据。在train里:
type=dataset_type,表示了要用的是哪一个数据集。
ann_file= xxx 表示训练数据的信息(上图的json文件记录了训练图片的名字和对应的标注信息)
img_prefix 当然就表示训练数据的位置
pipeline 表示处理处理的整个流程,train_pipeline在coco_instance.py其他地方定义,如下图:
可以看到这个数据的 pipeline 一共有 8 个操作。简单说,就是对数据的处理一共 8 个操作。
首先要清楚一个概念:
dataset和dataloader的作用与区别。
dataset是决定如何读入训练数据,如何通过训练图片的路径来找到并读取这种图片,读取图片后要进行什么操作,这都是dataset控制的,每个训练的数据集,都会写一个自己的dataset,如coco.py ,voc.py等,用来展示如何读数取这个数据集的数据。dataloader:主要是把dataset读到的图片,按照一定顺序排起来,就像一条管道,一次输入一个ba
tch size的数据给网络训练。
好了,接下来我们就分析 dataset的代码,以coco数据集为例子,所以要看的代码是:mmdet/datasets/coco.py
从里面我们可以看到 :
CocoDataset是coco数据集的类,可以看到它是继承了 CustomDataset类,CustomDataset类是每个数据集类都必须继承的,因为CustomDataset类里面包含了数据集类都一定要用到的操作,所以我们无论是初始化CocoDataset还是VocDataset等等,都一定会包含CustomData类中的所有方法。
但是如果 CocoDataset类与CustomData类有相同名字的方法A的话,那么继承CustomData类的CocoDataset中的方法A将会覆盖原来CustomData类的方法A,这是基本的语言语法问题,这里就不细说了。
1. __getitem__(idx) 的作用:
作为一个dataset的类,最关键的一个方法肯定是 __getitem__(idx) ,(而且这个方法的位置是在CustomData类中的)为什么呢:
因为 __getitem__(idx) 控制着dataset这个类怎么输出要训练的数据,dataloader的输入就是__getitem__(idx)的输出,其中idx表示训练数据的索引,因为dataloader按顺序输出数据给网络训练时,是按自动生成的索引来输出图片的。所以__getitem__(idx)返回的图片数据,就是输出到 dataloader中的。
2. __getitem__(idx)做了什么
接下来就要展示程序如何从 训练数据的配置信息获取图片和预处理图片 这个过程了。
可以看到,__getitem__直接是调用了 prepare_train_img()来读取训练集数据,调用prepare_text_img()来读取验证集数据。
从上图可以看到,data_info 包含了很多张图片的信息,每张图片分别有文件路径,宽度,长度,和id ,id表示这是第几章图片,假设训练集中的图片总共有494张,那么id的最大值就是494。而img_info则是单张图片的信息。
而ann_info则表示某张图片的bbox标注和segm(分割)标注、分类标注。
当得到 img_info 和 对应的 ann_info后,就表示图片的信息(例如路径和长宽信息)和标注信息(例如bbox和segm标注)已经得到了,然后用一个result字典把二者合在一起就可以了。
然后经过一个pipeline()函数就可以返回了,有人会问,这个pipeline是什么啊?其实就是第一节中提到的 train_pipeline里面的几个对数据的操作。
在第一节中,已经说了 dataset是通过build_dataset()出来的,传入build_dataset()的配置文件如下,里面有pipeline作为参数传进去,train_pipeline在第一节有提到过,就是训练前的几个处理数据的过程。所以在 build_dataset的过程中,train_pipeline作为 pipeline就传入了 dataset中。如下图:
我们可视化一下,输入build_dataset()的参数,看看是否和上图的 train 的字典一样:
可以看到,type,ann_file,img_prefix,pipeline字段都是能一一对应的。
type:数据集的类型,如COCO,VOC等等
ann_file:数据集的配置文件
img_prefix:图片数据所在的目录路径
pipeline:包括了数据处理的过程
到这里为止,pipeline的内容都只是字典,都是字符串,怎么变成能处理数据的操作呢?下面讲一讲:
所有dataset都是继承自一个类 CustomDataset(位于mmdet/datasets/custom.py) ,在初始化dataset时,pipeline其实就是传给了CustomDataset里的方法进行处理,因为你可以看CustomDataset的初始化参数,是有pipeline的。
下图展示了CustomDataset怎么利用pipeline来处理数据:
上图的PIPELINES跟DATASETS都是一类东西,只不过前者是所有预处理方法的类集合,后者是所有数据集的类的集合。
PIPELINES里的类的代码一般都在 mmdet/datasets/pipelines 里。
接下来我们按照上图红框里的pipeline的操作一个个看:
由于pipeline的定义是按照如下图这样操作的,所以按下图的顺序说明:(此图在文章第一章节处)
这些pipeline的操作,在上图红框处有标明路径,所以大家在拿到mmdetection2.0的代码直接去该路径找就行了。
LoadImageFromFile的作用顾名思义就是跟图片的路径读图片,具体看下图:
由于LoadAnnotations的代码比较长,因此用图来表示。作用是载入图片的bbox和segm和label等标注信息。
解读:LoadAnnotations本质是一个类,首先从LoadAnnotations的初始化函数__init__()来看,一些参数入with_bbox,with_mask...等等是默认都已经被设定好是True还是False的,但是LoadAnnotations被初始化时,__init__()的参数又是可以被修改的,如上图中,coco_instance.py的train_pipeline中所示,把with_bbox和with_mask设成了True。若with_bbox被设置成了False,则当前图片的所有目标的bbox标注都不放入results里,即程序不会去读取bbox的标注,同理 with_mask也是一个道理。
__call__()方法是LoadAnnotations被调用时执行的过程,我们可以看到,__call__()主要做的就是读取图片的bbox标注,label标注,segm标注。在_load_masks()里有一个_poly2mask()方法,这个方法是把原始的segm的标注(边界点的坐标)转成mask形式,即上图黑色矩阵那样子,矩阵的长宽跟图片一样,而且属于前景的像素的值为1,属于背景的则为0。
总的来说,resize就做了几件事:
1. 得到要如resize的尺寸大小scale。
2.根据得到的scale对图片进行resize。resize后的图片除resize前的图片,得到scale_factor,即比例。
3.resize bbox标注和segm标注。由于bbox和segm标注都是坐标点的形式,所以只要把这些坐标点乘scale_factor,就能得到resize后的bbox和segm标注。
3.4 RandomFlip
我们看看 build_dataset()的定义:
看到下图红色框那里,可以知道,dataset主要是由 build_from_cfg()方法来产生,DATASETS是一个全局的变量,里面包含了mmdetection里定义的所有数据集的类。所以 build_from_cfg()会根据传入的 cfg 里的数据集信息来从 DATASETS中找对应的数据集的类,然后进行初始化。 build_from_cfg()和 DATASETS的信息可以从之前一篇文章(一)里细看(一)
然后再看看build_dataset是如何调用 build_from_cfg()来根据配置文件来构造dataset的:
由下图可以看出,cfg中包含了type字段,是代表要从registry 中找的对应的类的名字。
最后return的obj_cls(**args)是初始化从registry中找到的类的意思,输入的初始化参数是 args 。
到这里,dataset就创建完成了。然后就看data_loader的创建了。
跟着程序的流程:
可以很轻松地追溯到dataloader的创建,期间并没用什么tricks。到这里的话,就完成了dataloader了,dataloader是让训练集按一定顺序被读取,被训练的。然后dataloader会被一个run方法调用,run方法主要负责接下来的训练过程,详细可看这里 。