數據加載流程
create data
從數據集的原始數據文件中讀取數據,並且按指定的格式組織成 pickle 文件保存,在 data_converter 里有具體的各個數據集的轉換方式。
- 如果想用於訓練\測試的數據內容沒有發生修改,之后直接使用第一次生成的 pickle 文件即可。
以 KITTI 為例,具體的流程為:
-
生成 train , val , test 的 anno 文件
-
生成 velodyne_reduced,即在原始點雲中移除那些投影之后落在圖像之外的點
與 velodyne 文件夾格式一致。
-
生成 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__() 獲取數據。
- 調用 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)
- 調用 pre_pipeline() , 擴展 input_dict 包含的屬性信息
- 對 input_dict 按順序執行 pipeline ,得到 example .
- Collect3D 把在 input_dict 里指定 meta_keys 組織到一起,放在新建的 img_meta 字段,即
result['img_meta'] = [input_dict[k] for k in meta_keys]
- Collect3D 把在 input_dict 里指定 meta_keys 組織到一起,放在新建的 img_meta 字段,即
- 判斷是否需要 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 放到當前樣本中)
-
初始化 db_sampler ,在 db_sampler 初始化里面按照 difficulty 和 min_points 過濾掉 一些 gt object.
-
是否選擇 sample_2d,調用相應的 db_sampler.sample_all
-
db_sampler.sample_all() 的過程
- 計算每個類別需要 sample 的個數: 要求個數 減去 目前 gt label 中該類別個數
- 如果該類別的 gt objects 已經足夠多,即需要 sample 的個數 <= 0,則不做任何 sample 操作,返回的 sample 結果為 None
- 如果該類別的 gt objects 比較少,則從 db_info 里面對應的類別 sample 所需數量的 object ,也就是從其他文件去 sample 一些 object 出來填充到當前文件的 gt 數據。比如用000005.bin 的一些 object 點雲 補充到 000003.bin 去,並補充相應的 gt boxes.
-
得到 sample 結果之后,判斷是否和已有的 gt boxes and sample boxes 沖突 (注意這里是將某類別的 sample 結果和目前所有類別的已有 boxes 判斷沖突),保留那些沒有沖突的 sample 結果作為該類別的 sample 結果
-
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 循環里,總共有三件事:
- 調用了
dataloader的__iter__()方法, 產生了一個DataLoaderIter - 反復調用
DataLoaderIter的__next__()來得到batch, 具體操作就是, 多次調用dataset的__getitem__()方法 (如果num_worker> 0 就多線程調用 ),然后用collate_fn來把它們打包成batch. 中間還會涉及到shuffle, 以及sample的方法等, 這里就不多說了. - 當數據讀完后,
__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
DataContainerandMMDataParallelto 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 決定。
