PulseFocusPlatform/docs/advanced_tutorials/READER.md

18 KiB
Raw Permalink Blame History

数据处理模块

目录

1.简介

PaddleDetection的数据处理模块的所有代码逻辑在ppdet/data/中,数据处理模块用于加载数据并将其转换成适用于物体检测模型的训练、评估、推理所需要的格式。 数据处理模块的主要构成如下架构所示:

  ppdet/data/
  ├── reader.py     # 基于Dataloader封装的Reader模块
  ├── source  # 数据源管理模块
  │   ├── dataset.py      # 定义数据源基类,各类数据集继承于此
  │   ├── coco.py         # COCO数据集解析与格式化数据
  │   ├── voc.py          # Pascal VOC数据集解析与格式化数据
  │   ├── widerface.py    # WIDER-FACE数据集解析与格式化数据
  │   ├── category.py    # 相关数据集的类别信息
  ├── transform  # 数据预处理模块
  │   ├── batch_operators.py  # 定义各类基于批量数据的预处理算子
  │   ├── op_helper.py    # 预处理算子的辅助函数
  │   ├── operators.py    # 定义各类基于单张图片的预处理算子
  │   ├── gridmask_utils.py    # GridMask数据增强函数
  │   ├── autoaugment_utils.py  # AutoAugment辅助函数
  ├── shm_utils.py     # 用于使用共享内存的辅助函数

2.数据集

数据集定义在source目录下,其中dataset.py中定义了数据集的基类DetDataSet, 所有的数据集均继承于基类,DetDataset基类里定义了如下等方法:

方法 输入 输出 备注
__len__ int, 数据集中样本的数量 过滤掉了无标注的样本
__getitem__ int, 样本的索引idx dict, 索引idx对应的样本roidb 得到transform之后的样本roidb
check_or_download_dataset 检查数据集是否存在如果不存在则下载目前支持COCO, VOCwiderface等数据集
set_kwargs 可选参数,以键值对的形式给出 目前用于支持接收mixup, cutmix等参数的设置
set_transform 一系列的transform函数 设置数据集的transform函数
set_epoch int, 当前的epoch 用于dataset与训练过程的交互
parse_dataset 用于从数据中读取所有的样本
get_anno 用于获取标注文件的路径

当一个数据集类继承自DetDataSet那么它只需要实现parse_dataset函数即可。parse_dataset根据数据集设置的数据集根路径dataset_dir图片文件夹image_dir 标注文件路径anno_path取出所有的样本并将其保存在一个列表roidbs中每一个列表中的元素为一个样本xxx_rec(比如coco_rec或者voc_rec)用dict表示dict中包含样本的image, gt_bbox, gt_class等字段。COCO和Pascal-VOC数据集中的xxx_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中无此字段)
    'gt_class': gt_class,        # 标注框标签名称的ID序号
    'gt_bbox': gt_bbox,          # 标注框坐标(xmin, ymin, xmax, ymax)
    'gt_poly': gt_poly,          # 分割掩码此字段只在coco_rec中出现默认为None
    'difficult': difficult       # 是否是困难样本此字段只在voc_rec中出现默认为0
}

xxx_rec中的内容也可以通过DetDataSet的data_fields参数来控制即可以过滤掉一些不需要的字段但大多数情况下不需要修改按照configs/datasets中的默认配置即可。

此外在parse_dataset函数中保存了类别名到id的映射的一个字典cname2cid。在coco数据集中会利用COCO API从标注文件中加载数据集的类别名并设置此字典。在voc数据集中如果设置use_default_label=False,将从label_list.txt中读取类别列表反之将使用voc默认的类别列表。

2.1COCO数据集

COCO数据集目前分为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数据集类,其继承自DetDataSet并实现了parse_dataset方法调用COCO API加载并解析COCO格式数据源roidbscname2cid,具体可参见source/coco.py源码。将其他数据集转换成COCO格式可以参考用户数据转成COCO数据

2.2Pascal VOC数据集

该数据集目前分为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数据集,它继承自DetDataSet基类,并重写了parse_dataset方法解析VOC数据集中xml格式标注文件更新roidbscname2cid。将其他数据集转换成VOC格式可以参考用户数据转成VOC数据

2.3自定义数据集

如果COCODataSet和VOCDataSet不能满足你的需求可以通过自定义数据集的方式来加载你的数据集。只需要以下两步即可实现自定义数据集

  1. 新建source/xxx.py,定义类XXXDataSet继承自DetDataSet基类,完成注册与序列化,并重写parse_dataset方法对roidbscname2cid更新:
from ppdet.core.workspace import register, serializable

#注册并序列化
@register
@serializable
class XXXDataSet(DetDataSet):
    def __init__(self,
                dataset_dir=None,
                image_dir=None,
                anno_path=None,
                ...
                ):
        self.roidbs = None
        self.cname2cid = None
        ...

    def parse_dataset(self):
        ...
        省略具体解析数据逻辑
        ...
        self.roidbs, self.cname2cid = records, cname2cid
  1. source/__init__.py中添加引用:
from . import xxx
from .xxx import *

完成以上两步就将新的数据源XXXDataSet添加好了,你可以参考配置及运行实现自定义数据集的使用。

3.数据预处理

3.1数据增强算子

PaddleDetection中支持了种类丰富的数据增强算子有单图像数据增强算子与批数据增强算子两种方式您可选取合适的算子组合使用。单图像数据增强算子定义在transform/operators.py中,已支持的单图像数据增强算子详见下表:

名称 作用
Decode 从图像文件或内存buffer中加载图像格式为RGB格式
Permute 假如输入是HWC顺序变成CHW
RandomErasingImage 对图像进行随机擦除
NormalizeImage 对图像像素值进行归一化如果设置is_scale=True则先将像素值除以255.0, 再进行归一化。
GridMask GridMask数据增广
RandomDistort 随机扰动图片亮度、对比度、饱和度和色相
AutoAugment AutoAugment数据增广包含一系列数据增强方法
RandomFlip 随机水平翻转图像
Resize 对于图像进行resize并对标注进行相应的变换
MultiscaleTestResize 将图像重新缩放为多尺度list的每个尺寸
RandomResize 对于图像进行随机Resize可以Resize到不同的尺寸以及使用不同的插值策略
RandomExpand 将原始图片放入用像素均值填充的扩张图中,对此图进行裁剪、缩放和翻转
CropWithSampling 根据缩放比例、长宽比例生成若干候选框,再依据这些候选框和标注框的面积交并比(IoU)挑选出符合要求的裁剪结果
CropImageWithDataAchorSampling 基于CropImage在人脸检测中随机将图片尺度变换到一定范围的尺度大大增强人脸的尺度变化
RandomCrop 原理同CropImage以随机比例与IoU阈值进行处理
RandomScaledCrop 根据长边对图像进行随机裁剪,并对标注做相应的变换
Cutmix Cutmix数据增强对两张图片做拼接
Mixup Mixup数据增强按比例叠加两张图像
NormalizeBox 对bounding box进行归一化
PadBox 如果bounding box的数量少于num_max_boxes则将零填充到bbox
BboxXYXY2XYWH 将bounding box从(xmin,ymin,xmax,ymin)形式转换为(xmin,ymin,width,height)格式
Pad 将图片Pad某一个数的整数倍或者指定的size并支持指定Pad的方式
Poly2Mask Poly2Mask数据增强

批数据增强算子定义在transform/batch_operators.py中, 目前支持的算子列表如下:

名称 作用
PadBatch 随机对每个batch的数据图片进行Pad操作使得batch中的图片具有相同的shape
BatchRandomResize 对一个batch的图片进行resize使得batch中的图片随机缩放到相同的尺寸
Gt2YoloTarget 通过gt数据生成YOLO系列模型的目标
Gt2FCOSTarget 通过gt数据生成FCOS模型的目标
Gt2TTFTarget 通过gt数据生成TTFNet模型的目标
Gt2Solov2Target 通过gt数据生成SOLOv2模型的目标

几点说明:

  • 数据增强算子的输入为sample或者samples每一个sample对应上文所说的DetDataSet输出的roidbs中的一个样本如coco_rec或者voc_rec
  • 单图像数据增强算子(Mixup, Cutmix等除外)也可用于批数据处理中。但是单图像处理算子和批图像处理算子仍有一些差异以RandomResize和BatchRandomResize为例RandomResize会将一个Batch中的每张图片进行随机缩放但是每一张图像Resize之后的形状不尽相同BatchRandomResize则会将一个Batch中的所有图片随机缩放到相同的形状。
  • 除BatchRandomResize外定义在transform/batch_operators.py的批数据增强算子接收的输入图像均为CHW形式所以使用这些批数据增强算子前请先使用Permute进行处理。如果用到Gt2xxxTarget算子需要将其放置在靠后的位置。NormalizeBox算子建议放置在Gt2xxxTarget之前。将这些限制条件总结下来推荐的预处理算子的顺序为
      - XXX: {}
      - ...
      - BatchRandomResize: {...} # 如果不需要可以移除如果需要放置在Permute之前
      - Permute: {} # 必须项
      - NormalizeBox: {} # 如果需要建议放在Gt2XXXTarget之前
      - PadBatch: {...} # 如果不需要可移除如果需要建议放置在Permute之后
      - Gt2XXXTarget: {...} # 建议与PadBatch放置在最后的位置
    

3.2自定义数据增强算子

如果需要自定义数据增强算子,那么您需要了解下数据增强算子的相关逻辑。数据增强算子基类为定义在transform/operators.py中的BaseOperator类,单图像数据增强算子与批数据增强算子均继承自这个基类。完整定义参考源码,以下代码显示了BaseOperator类的关键函数: apply和__call__方法

class BaseOperator(object):

  ...

  def apply(self, sample, context=None):
      return sample

  def __call__(self, sample, context=None):
      if isinstance(sample, Sequence):
          for i in range(len(sample)):
              sample[i] = self.apply(sample[i], context)
      else:
          sample = self.apply(sample, context)
      return sample

__call__方法为BaseOperator的调用入口接收一个sample(单图像)或者多个sample(多图像)作为输入并调用apply函数对一个或者多个sample进行处理。大多数情况下你只需要继承BaseOperator重写apply方法或者重写__call__方法即可如下所示定义了一个XXXOp继承自BaseOperator并注册

@register_op
class XXXOp(BaseOperator):
  def __init__(self,...):

    super(XXXImage, self).__init__()
    ...

  # 大多数情况下只需要重写apply方法
  def apply(self, sample, context=None):
    ...
    省略对输入的sample具体操作
    ...
    return sample

  # 如果有需要可以重写__call__方法如Mixup, Gt2XXXTarget等
  # def __call__(self, sample, context=None):
  #   ...
  #   省略对输入的sample具体操作
  #   ...
  #   return sample

大多数情况下只需要重写apply方法即可transform/operators.py中除Mixup和Cutmix外的预处理算子。对于批处理的情况一般需要重写__call__方法transform/batch_operators.py的预处理算子。

4.Reader

Reader相关的类定义在reader.py, 其中定义了BaseDataLoader类。BaseDataLoaderpaddle.io.DataLoader的基础上封装了一层,其具备paddle.io.DataLoader的所有功能,并能够实现不同模型对于DetDataset的不同需求如可以通过对Reader进行设置以控制DetDataset支持Mixup, Cutmix等操作。除此之外数据预处理算子通过Compose类和BatchCompose类组合起来分别传入DetDatasetpaddle.io.DataLoader中。 所有的Reader类都继承自BaseDataLoader类,具体可参见源码。

5.配置及运行

5.1配置

与数据预处理相关的模块的配置文件包含所有模型公用的Datas set的配置文件以及不同模型专用的Reader的配置文件。关于Dataset的配置文件存在于configs/datasets文件夹。比如COCO数据集的配置文件如下

metric: COCO # 目前支持COCO, VOC, OID WiderFace等评估标准
num_classes: 80 # num_classes数据集的类别数不包含背景类

TrainDataset:
  !COCODataSet
    image_dir: train2017 # 训练集的图片所在文件相对于dataset_dir的路径
    anno_path: annotations/instances_train2017.json # 训练集的标注文件相对于dataset_dir的路径
    dataset_dir: dataset/coco #数据集所在路径相对于PaddleDetection路径
    data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd'] # 控制dataset输出的sample所包含的字段

EvalDataset:
  !COCODataSet
    image_dir: val2017 # 验证集的图片所在文件夹相对于dataset_dir的路径
    anno_path: annotations/instances_val2017.json # 验证集的标注文件相对于dataset_dir的路径
    dataset_dir: dataset/coco # 数据集所在路径相对于PaddleDetection路径

TestDataset:
  !ImageFolder
    anno_path: dataset/coco/annotations/instances_val2017.json # 验证集的标注文件所在路径相对于PaddleDetection的路径

在PaddleDetection的yml配置文件中使用!直接序列化模块实例(可以是函数,实例等)上述的配置文件均使用Dataset进行了序列化。 不同模型专用的Reader定义在每一个模型的文件夹下如yolov3的Reader配置文件定义在configs/yolov3/_base_/yolov3_reader.yml。一个Reader的示例配置如下

worker_num: 2
TrainReader:
  sample_transforms:
    - Decode: {}
    ...
  batch_transforms:
    ...
  batch_size: 8
  shuffle: true
  drop_last: true
  use_shared_memory: true

EvalReader:
  sample_transforms:
    - Decode: {}
    ...
  batch_size: 1
  drop_empty: false

TestReader:
  inputs_def:
    image_shape: [3, 608, 608]
  sample_transforms:
    - Decode: {}
    ...
  batch_size: 1

你可以在Reader中定义不同的预处理算子每张卡的batch_size以及DataLoader的worker_num等。

5.2运行

在PaddleDetection的训练、评估和测试运行程序中都通过创建Reader迭代器。Reader在ppdet/engine/trainer.py中创建。下面的代码展示了如何创建训练时的Reader

from ppdet.core.workspace import create
# build data loader
self.dataset = cfg['TrainDataset']
self.loader = create('TrainReader')(selfdataset, cfg.worker_num)

相应的预测以及评估时的Reader与之类似具体可参考ppdet/engine/trainer.py源码。

关于数据处理模块如您有其他问题或建议请给我们提issue我们非常欢迎您的反馈。