[开源框架]mmdetection3d学习(三):数据加载

时间:2024-02-20 06:58:07

数据加载流程

create data

从数据集的原始数据文件中读取数据,并且按指定的格式组织成 pickle 文件保存,在 data_converter 里有具体的各个数据集的转换方式。

  • 如果想用于训练\测试的数据内容没有发生修改,之后直接使用第一次生成的 pickle 文件即可。

以 KITTI 为例,具体的流程为:

  1. 生成 train , val , test 的 anno 文件

  2. 生成 velodyne_reduced,即在原始点云中移除那些投影之后落在图像之外的点

    与 velodyne 文件夹格式一致。

  3. 生成 groundtruth_database 以及 db_info

    分割每个 sample 里的每个 object 得到相应的 {sample_idx} {class name} {obj_idx}.bin 点云文件及标签信息,按 class name 组织存到 db_info 文件

    db_info: dict(),按照类别名将数据集中所有 object 分为 group

    • key 是类别名
    • value 是 list , 记录了数据集中所有属于该类别的object,其中的一些字段:
      • image_idx: object 所在样本下标
      • gt_idx: object 属于该样本的第几个 object
      • group_id: object 属于该 group 里面的第几个 (也就是 list 的1-based index.)

dataset

以 KITTI 为例,加载数据的流程如下:

初始化 KittiDataset ,从离线生成好的 kitti_infos.pkl 加载数据

Dataset 类都是被封装成迭代器,每次通过调用 dataset.__get_item__() 获取数据。

  1. 调用 get_data_info() 加载指定 img_idx 的 data info , 组织为 input_dict .
    • 获取一些基本参数比如 calib 等
    • 调用 get_anno_info() ,加载 anno 里面的 boxes,将 boxes 从原标签文件的 camera 坐标系转换到 lidar 坐标系,格式为 (x_lidar, y_lidar, z_lidar, dx, dy, dz, yaw)
  2. 调用 pre_pipeline() , 扩展 input_dict 包含的属性信息
  3. 对 input_dict 按顺序执行 pipeline ,得到 example .
    • Collect3D 把在 input_dict 里指定 meta_keys 组织到一起,放在新建的 img_meta 字段,即 result[\'img_meta\'] = [input_dict[k] for k in meta_keys]
  4. 判断是否需要 filter empty gt (empty gt 指的是该 img idx 完全没有 gt data),如果需要并且符合 empty 的定义则返回空,否则返回 example.
    • 因为每次 pipeline 的末尾都会包含 DefaultBundlexx 这一步,是将 tensor 封装成 DataContainer 类,因此 example 里面具体包含的数据类别是 DataContainer 而不是 tensor 。

注意:

  • 先确定所需要加载的 index ,才去执行 pipeline 从而 load data and do preprocessing, 因此关于加载的格式之类的是在一开始定义数据集时设置的,比如 box_mode_3d 这些属性,是先指定好再去 load 。
  • pipelines.LoadAnnotations3D 是过滤的作用,在执行 pipeline 之前,annotaions 已经在 dataset.get_ann_info() 加载好了,pipeline 那一步只是决定要保留哪些字段的 anno 到 result .

pipeline

pipeline模块位于mmdet3d/core/datasets/pipeline ,存放的是各种数据增强方式的实现代码。

在执行的时候,将相应的 augmentation method list , 以 compose 的方式组合为一个函数,由 dataset 对数据预处理时调用:

methods = [RandomFlip, GlobalRotScaleTrans, ...] #需要对数据执行的数据增强方式
pipeline = compose(methods)                      #组合为一个接口
processed_data = pipeline(data)                  #一次调用即可
# 等价于
processed_data_v2 = GlobalRotScaleTrans(RandomFlip(data))  

ObjectSample是什么?

我们在前面的create data步骤建立了一个关于数据集的全体 gt object 库,即 db_infos.pkl,ObjectSample 做的就是,如果当前样本的 gt object 比较少的情况下,从这个库里面采样一些 gt object 填充到当前的样本中(可理解为从其他样本里 copy 一些 gt object 放到当前样本中)

  1. 初始化 db_sampler ,在 db_sampler 初始化里面按照 difficulty 和 min_points 过滤掉 一些 gt object.

  2. 是否选择 sample_2d,调用相应的 db_sampler.sample_all

  3. db_sampler.sample_all() 的过程

    1. 计算每个类别需要 sample 的个数: 要求个数 减去 目前 gt label 中该类别个数
    2. 如果该类别的 gt objects 已经足够多,即需要 sample 的个数 <= 0,则不做任何 sample 操作,返回的 sample 结果为 None
    3. 如果该类别的 gt objects 比较少,则从 db_info 里面对应的类别 sample 所需数量的 object ,也就是从其他文件去 sample 一些 object 出来填充到当前文件的 gt 数据。比如用000005.bin 的一些 object 点云 补充到 000003.bin 去,并补充相应的 gt boxes.
  4. 得到 sample 结果之后,判断是否和已有的 gt boxes and sample boxes 冲突 (注意这里是将某类别的 sample 结果和目前所有类别的已有 boxes 判断冲突),保留那些没有冲突的 sample 结果作为该类别的 sample 结果

  5. concat sample 结果和原本的 gt labels and bboxes, 替换 gt point cloud 里面落在 sample boxes 里的 point 为 sample points

dataloader

torch 的数据读取主要涉及三大类: Dataset, DataLoader, DataLoaderIter

mmdet3d 代码里直接用的就是 torch 的 DataLoader,封装成迭代器:

for data in dataloader:
	#do something...

在 for 循环里,总共有三件事:

  1. 调用了dataloader__iter__() 方法, 产生了一个DataLoaderIter
  2. 反复调用DataLoaderIter__next__()来得到batch, 具体操作就是, 多次调用dataset的__getitem__()方法 (如果num_worker > 0 就多线程调用 ),然后用collate_fn来把它们打包成batch. 中间还会涉及到shuffle , 以及sample 的方法等, 这里就不多说了.
  3. 当数据读完后, __next__()抛出一个StopIteration异常, for循环结束, dataloader 失效.

DataContainer是什么

mmcv 库自己定义的对 tensor 的进一步封装。

​ """A container for any type of objects.

​ Typically tensors will be stacked in the collate function and sliced along

​ some dimension in the scatter function. This behavior has some limitations.

​ \1. All tensors have to be the same size.

​ \2. Types are limited (numpy array or Tensor).

​ We design DataContainer and MMDataParallel to overcome these

​ limitations. The behavior can be either of the following.

​ - copy to GPU, pad all tensors to the same size and stack them

​ - copy to GPU without stacking

​ - leave the objects as is and pass it to the model

​ - pad_dims specifies the number of last few dimensions to do padding

​ """

在将 tensor 封装成 DataContainer 类的时候可以指定是否需要 stack ,如果指定需要 stack ,则后面加载 batch 的时候所使用的 collate_fn 会将这些 tensor stack。注意,stack 是在 collate_fn 完成的,DataContainer 只是相当于为 tensor 多加了一些属性

load data 的过程:

cfg 文件里的 workers_per_gpu 是用于 DataLoader 的,开多几个子进程作为 worker 实现数据读取以及预处理,加快数据加载的效率,而 batch size 仍然是由 samples_per_gpu 决定。

参考资料

https://pytorch.org/docs/stable/data.html

https://zhuanlan.zhihu.com/p/30934236