商汤开源目标检测工具箱mmdetection代码详解(三)----------mmdetection数据的输入、处理过程

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()的配置信息

可以看到 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 个操作。

 

二、如何通过数据的配置文件json读取图片和label并进行处理

首先要清楚一个概念:

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里面的几个对数据的操作。

 

3. 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的定义是按照如下图这样操作的,所以按下图的顺序说明:(此图在文章第一章节处)

3.1 LoadImageFromFile----载入图片

这些pipeline的操作,在上图红框处有标明路径,所以大家在拿到mmdetection2.0的代码直接去该路径找就行了。

LoadImageFromFile的作用顾名思义就是跟图片的路径读图片,具体看下图:

3.2 LoadAnnotations----载入图片标注

由于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。

3.3 Resize

总的来说,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

RandomFlip是翻转操作,默认是水平翻转,会把图片,bbox标注和segm标注都进行翻转。

传入时有2个参数,一个是决定翻转方式(水平或垂直),另一个是决定翻转的几率,例如可以设置成0.5,即每张图片有0.5的几率进行翻转,

3.5 Normalize ---归一化

这也没啥好说的,就是进行归一化而已,归一化的参数mean和std由配置文件传入。

3.6 Pad 

Pad 主要做了两个操作

1.padding 输入的图片img

2.padding 所有的mask (此时的mask已经是BitMapMask形式,即矩阵尺寸和img一样,但mask部分的值为1,其余为0)

padding img:

padding的方式如下,橙色为原img大小,蓝色为padding后的img大小。他pading的方式并不是在原有的img四周padding的。padding的默认值为0。

那有人会问,那么padding后的尺寸该如何设定呢?在配置文件里,是有传入一个参数size_divisor=32,然后看下面代码:

这就很明确了。

padding mask:

由于一张图片的mask的个数是不固定的,所以每张图片对mask做padding的次数也不同,例如A图片有35个mask,对每个mask都做一次padding,就做了35次,得到的结果也是(35,H,W),H,W分别是padding后的高和宽。padding的方法跟padding img是一样的。

 

3.7 DefaultFormatBundle

 

三、根据传入 build_dataset()的数据集名字来初始化对应的数据集对象----dataset

我们看看 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的创建了。

 

四、创建 data_loader

跟着程序的流程:

可以很轻松地追溯到dataloader的创建,期间并没用什么tricks。到这里的话,就完成了dataloader了,dataloader是让训练集按一定顺序被读取,被训练的。然后dataloader会被一个run方法调用,run方法主要负责接下来的训练过程,详细可看这里 。

 

 

  • 49
    点赞
  • 155
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值