PaddleDetection的数据处理模块是一个Python模块,所有代码逻辑在ppdet/data/
中,数据处理模块用于加载数据并将其转换成适用于物体检测模型的训练、评估、推理所需要的格式。
数据处理模块的主要构成如下架构所示:
ppdet/data/
├── reader.py # 数据处理模块的总接口
├── shared_queue # 共享内存管理模块
│ ├── queue.py # 定义共享内存队列
│ ├── sharedmemory.py # 负责分配内存
├── source # 数据源管理模块
│ ├── dataset.py # 定义数据源基类,各类数据集继承于此
│ ├── coco.py # COCO数据集解析与格式化数据
│ ├── voc.py # Pascal VOC数据集解析与格式化数据
│ ├── widerface.py # WIDER-FACE数据集解析与格式化数据
├── tests # 单元测试模块
│ ├── test_dataset.py # 对数据集解析、加载等进行单元测试
│ │ ...
├── transform # 数据预处理模块
│ ├── batch_operators.py # 定义各类基于批量数据的预处理算子
│ ├── op_helper.py # 预处理算子的辅助函数
│ ├── operators.py # 定义各类基于单张图片的预处理算子
├── parallel_map.py # 在多进程/多线程模式中对数据预处理操作进行加速
PaddleDetection目前支持COCO、Pascal VOC 和WIDER-FACE数据源,默认数据集可自动下载,请参考默认数据集安装方法。 同时我们还支持自定义数据源,包括(1)自定义数据源转换成COCO数据集;(2)定义新的数据源。
数据准备分为三步:
下面为您分别介绍下这三步的具体内容:
数据解析逻辑在source
目录中,其中,dataset.py
是数据源定义的基类,所有数据集继承于此,DataSet
基类里定义了如下等方法:
方法 | 输入 | 输出 | 备注 |
---|---|---|---|
load_roidb_and_cname2cid() | 无 | 无 | 加载DataSet 中Roidb数据源list, 类别名到id的映射dict |
get_roidb() | 无 | list[dict], Roidb数据源 | 获取数据源 |
get_cname2cid() | 无 | dict,类别名到id的映射 | 获取标签ID |
get_anno() | 无 | str, 标注文件路径 | 获取标注文件路径 |
get_imid2path() | 无 | dict, 图片路径 | 获取图片路径 |
几点解释:
load_roidb_and_cname2cid()
在Dataset基类中并没有做实际的操作,需要在子类中重写实际操作。roidbs
:
代表数据源列表。load_roidb_and_cname2cid
方法中根据数据集标注文件(anno_path),将每张标注好的图片解析为字典coco_rec
和voc_rec
等:
xxx_rec = {
'im_file': im_fname, # 一张图像的完整路径
'im_id': np.array([img_id]), # 一张图像的ID序号
'h': im_h, # 图像高度
'w': im_w, # 图像宽度
'is_crowd': is_crowd, # 是否是群落对象, 默认为0 (VOC中无此字段,为了使个别模型同时适配于COCO与VOC)
'gt_class': gt_class, # 标注框标签名称的ID序号
'gt_bbox': gt_bbox, # 标注框坐标(xmin, ymin, xmax, ymax)
'gt_score': gt_score, # 标注框置信度得分 (此字段为了适配Mixup操作)
'gt_poly': gt_poly, # 分割掩码,此字段只在coco_rec中出现,默认为None
'difficult': difficult # 是否是困难样本,此字段只在voc_rec中出现,默认为0
}
然后将所有xxx_rec
字典封装到records
列表中,最后传入self.roidbs
中方便后续调用。
cname2cid
:use_default_label=False
,将从label_list.txt
中读取类别列表,
反之则可以没有label_list.txt
文件,PaddleDetection会使用source/voc.py
里的默认类别字典。
label_list.txt
的格式如下所示,每一行文本表示一个类别:
bash
aeroplane
bicycle
bird
...
该数据集目前分为COCO2014和COCO2017,主要由json文件和image文件组成,其组织结构如下所示:
dataset/coco/
├── annotations
│ ├── instances_train2014.json
│ ├── instances_train2017.json
│ ├── instances_val2014.json
│ ├── instances_val2017.json
│ │ ...
├── train2017
│ ├── 000000000009.jpg
│ ├── 000000580008.jpg
│ │ ...
├── val2017
│ ├── 000000000139.jpg
│ ├── 000000000285.jpg
│ │ ...
在source/coco.py
中定义并注册了COCODataSet
数据源类,它继承自DataSet
基类,并重写了load_roidb_and_cname2cid
:
根据标注文件路径(anno_path),调用COCO API加载并解析COCO格式数据源roidbs
和cname2cid
。
该数据集目前分为VOC2007和VOC2012,主要由xml文件和image文件组成,其组织结构如下所示:
dataset/voc/
├── trainval.txt
├── test.txt
├── label_list.txt (optional)
├── VOCdevkit/VOC2007
│ ├── Annotations
│ ├── 001789.xml
│ │ ...
│ ├── JPEGImages
│ ├── 001789.jpg
│ │ ...
│ ├── ImageSets
│ | ...
├── VOCdevkit/VOC2012
│ ├── Annotations
│ ├── 2011_003876.xml
│ │ ...
│ ├── JPEGImages
│ ├── 2011_003876.jpg
│ │ ...
│ ├── ImageSets
│ │ ...
在source/voc.py
中定义并注册了VOCDataSet
数据源类,它继承自DataSet
基类,并重写了load_roidb_and_cname2cid
,解析VOC数据集中xml格式标注文件,更新roidbs
和cname2cid
。
(1)新建./source/xxx.py
,定义类XXXDataSet
继承自DataSet
基类,完成注册与序列化,并重写load_roidb_and_cname2cid
方法对roidbs
与cname2cid
更新:
@register
@serializable
class XXXDataSet(DataSet):
def __init__(self,
dataset_dir=None,
image_dir=None,
anno_path=None,
...
):
self.roidbs = None
self.cname2cid = None
...
def load_roidb_and_cname2cid(self):
...
省略具体解析数据逻辑
...
self.roidbs, self.cname2cid = records, cname2cid
source/__init__.py
中添加引用:
python
from . import xxx
from .xxx import *
完成以上两步就将新的数据源XXXDataSet
添加好了,操作非常简单。PaddleDetection中支持了种类丰富的数据增强算子,有单图像数据增强算子与批数据增强算子两种方式,您可选取合适的算子组合使用,已支持的单图像数据增强算子详见下表:
名称 | 作用 |
---|---|
DecodeImage | 从图像文件或内存buffer中加载图像,格式为BGR、HWC格式,如果设置to_rgb=True,则转换为RGB格式。 |
ResizeImage | 根据特定的插值方式调整图像大小 |
RandomFlipImage | 随机水平翻转图像 |
NormalizeImage | 对图像像素值进行归一化,如果设置is_scale=True,则先将像素值除以255.0,像素值缩放到到[0-1]区间。 |
RandomDistort | 随机扰动图片亮度、对比度、饱和度和色相 |
ExpandImage | 将原始图片放入用像素均值填充(随后会在减均值操作中减掉)的扩张图中,对此图进行裁剪、缩放和翻转 |
CropImage | 根据缩放比例、长宽比例生成若干候选框,再依据这些候选框和标注框的面积交并比(IoU)挑选出符合要求的裁剪结果 |
CropImageWithDataAchorSampling | 基于CropImage,在人脸检测中,随机将图片尺度变换到一定范围的尺度,大大增强人脸的尺度变化 |
NormalizeBox | 对bounding box进行归一化 |
Permute | 对图像的通道进行排列并转为BGR格式。假如输入是HWC顺序,通道C上是RGB格式,设置channel_first=True,将变成CHW,设置to_bgr=True,通道C上变成BGR格式。 |
MixupImage | 按比例叠加两张图像 |
RandomInterpImage | 使用随机的插值方式调整图像大小 |
Resize | 根据特定的插值方式同时调整图像与bounding box的大小 |
MultiscaleTestResize | 将图像重新缩放为多尺度list的每个尺寸 |
ColorDistort | 根据特定的亮度、对比度、饱和度和色相为图像增加噪声 |
NormalizePermute | 归一化图像并改变图像通道顺序 |
RandomExpand | 原理同ExpandImage,以随机比例与角度对图像进行裁剪、缩放和翻转 |
RandomCrop | 原理同CropImage,以随机比例与IoU阈值进行处理 |
PadBox | 如果bounding box的数量少于num_max_boxes,则将零填充到bbox |
BboxXYXY2XYWH | 将bounding box从(xmin,ymin,xmax,ymin)形式转换为(xmin,ymin,width,height)格式 |
几点说明:
sample
,sample
是由{'image':xx, 'im_info': xxx, ...}组成,来自于上文提到的roidbs
中的字典信息。批数据增强算子列表如下:
名称 | 输入与输出 | 作用 |
---|---|---|
RandomShape | samples | 随机对每个batch的数据图片进行resize操作 |
PadMultiScaleTest | samples | 在多尺度测试中对图像进行填充 |
Gt2YoloTarget | samples | 通过gt数据生成YOLOv3目标,此OP仅在拆分后的YOLOv3损失模式下使用 |
samples
,是一个sample
的list。假如我们定义一个新的单图像数据增强算子XXXImage
。
在transform/operators.py
中增加类XXXImage
继承自BaseOperator,并注册:
@register_op
class XXXImage(BaseOperator):
def __init__(self,...):
super(XXXImage, self).__init__()
...
def __call__(self, sample, context=None):
...
省略对输入的sample具体操作
...
return sample
如此就完成新增单图像数据增强算子XXXImage
,操作非常简单。
自定义批量数据增强算子方法同上,在transform/batch_operators.py
中定义即可。
如上面提到,Reader预处理流程为: 单张图像处理 -> 组batch -> batch图像处理。用户一般不需要关注这些方法。
在reader.py
中构建了Reader迭代器类,其中包含如下方法:
方法 | 输入 | 输出 | 备注 |
---|---|---|---|
reset() | 无 | 无 | 重置Reader迭代器 |
next() | 无 | list | 返回Reader迭代器接下来输出的批数据 |
worker() | bool,list | list | 数据预处理的接口,输入(drop_empty, batch_samples),输出批数据list |
size() | 无 | int | 返回roidbs的长度 |
drained() | 无 | bool | 判断当前Reader迭代器中数据是否已经读完 |
stop() | 无 | 无 | 停止Reader迭代器 |
Reader迭代器的创建使用reader.py
中的create_reader
方法,在create_reader函数中需要传入cfg
参数列表,参数列表详细内容如下:
参数名 | 类型 | 含义 |
---|---|---|
sample_transforms | list of BaseOperator | 使用各种单图像数据增强算子的列表 |
batch_transforms | list of BaseOperator | 使用批数据数据增强算子的列表 |
batch_size | int | 批数据大小(图像个数/batch) |
shuffle | bool | 是否随机打乱数据集中图像排序,默认False |
drop_last | bool | 是否删除最后一个batch的数据,默认False |
drop_empty | bool | 是否删除空数据,默认True |
mixup_epoch | int | 在第几个epoch使用Mixup数据增强策略,默认不使用(-1) |
class_aware_sampling | bool | 是否使用class-aware数据增强方法,默认False |
worker_num | int | 数据读取中使用多进程的数量 |
use_process | bool | 数据读取中是否使用多进程 |
bufsize | int | 多进程/多线程缓冲队列的大小,队列中每个元素为一个batch的数据 |
memsize | str | 多进程下共享存储队列的大小,默认3G |
inputs_def | dict | 用在网络输入定义中获取输入字段,该字段用于确定返回数据的顺序。 |
在模型的训练、评估与推理的数据处理,都是通过配置文件来实现,下面为您详细介绍配置的步骤。
训练-数据处理
模块:
```yaml
TrainReader:
inputs_def: # 网络输入的定义
fields: ['image', 'gt_bbox', 'gt_class', 'gt_score']
dataset: # 数据源
!COCODataSet #序列化COCO数据源
dataset_dir: dataset/coco # 数据集根目录
anno_path: annotations/instances_train2017.json # 标注文件基于数据集根目录的相对路径
image_dir: train2017 # 图像数据基于数据集根目录的相对路径
with_background: false # 背景是否作为一类标签
sample_transforms: # 单图像数据增强算子的列表几点说明:
训练-数据处理
模块的名称统一为TrainReader
;!
直接序列化模块实例(可以是函数、类等);dataset
下需要序列化数据源实例,如COCODataSet
、VOCDataSe
和自定义的XXXDataSet
;inputs_def
的具体定义与使用请参考模型技术文档EvalReader:
inputs_def:
fields: ['image', 'im_size', 'im_id']
dataset:
!COCODataSet
dataset_dir: dataset/coco
anno_path: annotations/instances_val2017.json
image_dir: val2017
with_background: false
sample_transforms:
- !DecodeImage
to_rgb: True
...
batch_size: 8
drop_empty: false
几点说明:
评估-数据处理
模块的名称统一为EvalReader
;TestReader:
inputs_def:
image_shape: [3, 608, 608]
fields: ['image', 'im_size', 'im_id']
dataset:
!ImageFolder
anno_path: annotations/instances_val2017.json
with_background: false
sample_transforms:
- !DecodeImage
to_rgb: True
...
batch_size: 1
几点说明:
推理-数据处理
模块的名称统一为TestReader
;dataset
的数据源一般都设置为ImageFolder
数据源。ImageFolder可以指定图片的文件夹地址,将读取该文件夹下的所有图片。到此就完成了yml配置文件中的TrainReader
、EvalReader
和TestReader
的编写,您也可以将Reader部分封装到单独的yml文件xxx_reader.yml
中,利用如下命令进行加载即可:
_READER_: 'xxx_reader.yml'
加载完成后可以重写Reader中的方法,比如:
_READER_: 'xxx_reader.yml'
TrainReader:
batch_size: 2
...
EvalReader:
...
这样就可以复用同一份Reader配置文件了,重写更新其中某些参数也很方便。
在PaddleDetection的训练、评估和测试运行程序中,都通过创建Reader迭代器,然后将reader封装在DataLoader对象中,
DataLoader
的API详见fluid.io.DataLoader。
具体步骤如下:
在train.py、eval.py和infer.py里创建训练时的Reader:
# 创建DataLoader对象
inputs_def = cfg['TestReader']['inputs_def']
_, loader = model.build_inputs(**inputs_def)
# 创建Reader迭代器
from ppdet.data.reader import create_reader
# train
train_reader = create_reader(cfg.TrainReader, max_iter=0, global_cfg=cfg)
# eval
reader = create_reader(cfg.EvalReader)
# infer
reader = create_reader(cfg.TestReader)
# 将reader设置为DataLoader数据源
loader.set_sample_list_generator(reader, place)
在运行程序中设置完数据处理模块后,就可以开始训练、评估与测试了,具体请参考相应运行程序python源码。
关于数据处理模块,如您有其他问题或建议,请给我们提issue,我们非常欢迎您的反馈。