Unverified Commit d7067e44 authored by Wenwei Zhang's avatar Wenwei Zhang Committed by GitHub
Browse files

Bump version to v1.1.0rc2

Bump to v1.1.0rc2
parents 28fe73d2 fb0e57e5
......@@ -15,6 +15,7 @@ defines how to process the annotations and a data pipeline defines all the steps
A pipeline consists of a sequence of operations. Each operation takes a dict as input and also output a dict for the next transform.
We present a classical pipeline in the following figure. The blue blocks are pipeline operations. With the pipeline going on, each operator can add new keys (marked as green) to the result dict or update the existing keys (marked as orange).
![](../../../resources/data_pipeline.png)
The operations are categorized into data loading, pre-processing, formatting and test-time augmentation.
......
......@@ -78,7 +78,7 @@ mmdetection3d
### KITTI
Download KITTI 3D detection data [HERE](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d). Prepare KITTI data splits by running
Download KITTI 3D detection data [HERE](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d). Prepare KITTI data splits by running:
```bash
mkdir ./data/kitti/ && mkdir ./data/kitti/ImageSets
......@@ -90,31 +90,49 @@ wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/sec
wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/second/data/ImageSets/trainval.txt --no-check-certificate --content-disposition -O ./data/kitti/ImageSets/trainval.txt
```
Then generate info files by running
Then generate info files by running:
```
```bash
python tools/create_data.py kitti --root-path ./data/kitti --out-dir ./data/kitti --extra-tag kitti
```
In an environment using slurm, users may run the following command instead
In an environment using slurm, users may run the following command instead:
```
```bash
sh tools/create_data.sh <partition> kitti
```
### Waymo
Download Waymo open dataset V1.2 [HERE](https://waymo.com/open/download/) and its data split [HERE](https://drive.google.com/drive/folders/18BVuF_RYJF0NjZpt8SnfzANiakoRMf0o?usp=sharing). Then put tfrecord files into corresponding folders in `data/waymo/waymo_format/` and put the data split txt files into `data/waymo/kitti_format/ImageSets`. Download ground truth bin file for validation set [HERE](https://console.cloud.google.com/storage/browser/waymo_open_dataset_v_1_2_0/validation/ground_truth_objects) and put it into `data/waymo/waymo_format/`. A tip is that you can use `gsutil` to download the large-scale dataset with commands. You can take this [tool](https://github.com/RalphMao/Waymo-Dataset-Tool) as an example for more details. Subsequently, prepare waymo data by running
Download Waymo open dataset V1.2 [HERE](https://waymo.com/open/download/) and its data split [HERE](https://drive.google.com/drive/folders/18BVuF_RYJF0NjZpt8SnfzANiakoRMf0o?usp=sharing). Then put `.tfrecord` files into corresponding folders in `data/waymo/waymo_format/` and put the data split `.txt` files into `data/waymo/kitti_format/ImageSets`. Download ground truth `.bin` file for validation set [HERE](https://console.cloud.google.com/storage/browser/waymo_open_dataset_v_1_2_0/validation/ground_truth_objects) and put it into `data/waymo/waymo_format/`. A tip is that you can use `gsutil` to download the large-scale dataset with commands. You can take this [tool](https://github.com/RalphMao/Waymo-Dataset-Tool) as an example for more details. Subsequently, prepare waymo data by running:
```bash
python tools/create_data.py waymo --root-path ./data/waymo/ --out-dir ./data/waymo/ --workers 128 --extra-tag waymo
```
Note that if your local disk does not have enough space for saving converted data, you can change the `out-dir` to anywhere else. Just remember to create folders and prepare data there in advance and link them back to `data/waymo/kitti_format` after the data conversion.
Note that:
- If your local disk does not have enough space for saving converted data, you can change the `--out-dir` to anywhere else. Just remember to create folders and prepare data there in advance and link them back to `data/waymo/kitti_format` after the data conversion.
- If you want faster evaluation on Waymo, you can download the preprocessed [metainfo](https://download.openmmlab.com/mmdetection3d/data/waymo/idx2metainfo.pkl) containing `contextname` and `timestamp` to the directory `data/waymo/waymo_format/`. Then, the dataset config is modified like the following:
```python
val_evaluator = dict(
type='WaymoMetric',
ann_file='./data/waymo/kitti_format/waymo_infos_val.pkl',
waymo_bin_file='./data/waymo/waymo_format/gt.bin',
data_root='./data/waymo/waymo_format',
file_client_args=file_client_args,
convert_kitti_format=True,
idx2metainfo='data/waymo/waymo_format/idx2metainfo.pkl'
)
```
Now, this trick is only used for LiDAR-based detection methods.
### NuScenes
Download nuScenes V1.0 full dataset data [HERE](https://www.nuscenes.org/download). Prepare nuscenes data by running
Download nuScenes V1.0 full dataset data [HERE](https://www.nuscenes.org/download). Prepare nuscenes data by running:
```bash
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes
......@@ -122,22 +140,22 @@ python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./dat
### Lyft
Download Lyft 3D detection data [HERE](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/data). Prepare Lyft data by running
Download Lyft 3D detection data [HERE](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/data). Prepare Lyft data by running:
```bash
python tools/create_data.py lyft --root-path ./data/lyft --out-dir ./data/lyft --extra-tag lyft --version v1.01
python tools/dataset_converters/lyft_data_fixer.py --version v1.01 --root-folder ./data/lyft
```
Note that we follow the original folder names for clear organization. Please rename the raw folders as shown above. Also note that the second command serves the purpose of fixing a corrupted lidar data file. Please refer to the discussion [here](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000) for more details.
Note that we follow the original folder names for clear organization. Please rename the raw folders as shown above. Also note that the second command serves the purpose of fixing a corrupted lidar data file. Please refer to the [discussion](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000) for more details.
### S3DIS, ScanNet and SUN RGB-D
To prepare S3DIS data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/s3dis/README.md/).
To prepare S3DIS data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/s3dis/README.md).
To prepare ScanNet data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/scannet/README.md/).
To prepare ScanNet data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/scannet/README.md).
To prepare SUN RGB-D data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/sunrgbd/README.md/).
To prepare SUN RGB-D data, please see its [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/sunrgbd/README.md).
### Customized Datasets
......@@ -148,15 +166,15 @@ For using custom datasets, please refer to [Customize Datasets](https://github.c
If you have used v1.0.0rc1-v1.0.0rc4 mmdetection3d to create data infos before, and now you want to use the newest v1.1.0 mmdetection3d, you need to update the data infos file.
```bash
python tools/dataset_converters/update_infos_to_v2.py --dataset ${DATA_SET} --pkl ${PKL_PATH} --out-dir ${OUT_DIR}
python tools/dataset_converters/update_infos_to_v2.py --dataset ${DATA_SET} --pkl-path ${PKL_PATH} --out-dir ${OUT_DIR}
```
- `dataset` : Name of dataset.
- `pkl` : Specify the data infos pkl file path.
- `out-dir` : Output direction of the data infos pkl file.
- `--dataset` : Name of dataset.
- `--pkl-path` : Specify the data infos pkl file path.
- `--out-dir` : Output direction of the data infos pkl file.
Example
Example:
```bash
python tools/dataset_converters/update_infos_to_v2.py --dataset kitti --pkl ./data/kitti/kitti_infos_trainval.pkl --out-dir ./data/kitti
python tools/dataset_converters/update_infos_to_v2.py --dataset kitti --pkl-path ./data/kitti/kitti_infos_trainval.pkl --out-dir ./data/kitti
```
.. toctree::
:maxdepth: 3
1_exist_data_model.md
2_new_data_model.md
backends_support.md
config.md
coord_sys_tutorial.md
data_pipeline.md
data_prepare.md
demo.md
dataset_prepare.md
inference.md
index.rst
model_deployment.md
train_test.md
useful_tools.md
visualization.md
......@@ -2,7 +2,7 @@
## Introduction
We provide scripts for multi-modality/single-modality (LiDAR-based/vision-based), indoor/outdoor 3D detection and 3D semantic segmentation demos. The pre-trained models can be downloaded from [model zoo](https://github.com/open-mmlab/mmdetection3d/blob/master/docs/en/model_zoo.md/). We provide pre-processed sample data from KITTI, SUN RGB-D, nuScenes and ScanNet dataset. You can use any other data following our pre-processing steps.
We provide scripts for multi-modality/single-modality (LiDAR-based/vision-based), indoor/outdoor 3D detection and 3D semantic segmentation demos. The pre-trained models can be downloaded from [model zoo](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/en/model_zoo.md). We provide pre-processed sample data from KITTI, SUN RGB-D, nuScenes and ScanNet dataset. You can use any other data following our pre-processing steps.
## Testing
......
......@@ -44,7 +44,7 @@ The arguments:
- `type`: The name of the corresponding metric, usually associated with the dataset.
- `ann_file`: The path of annotation file.
- `pklfile_prefix`: An optional argument. The filename of the output results in pickle format. If not specified, the results will not be saved to a file.
- `submission_prefix`: An optional argument. The the results will not be saved to a file then you can upload it to do the official evaluation.
- `submission_prefix`: An optional argument. The results will be saved to a file then you can upload it to do the official evaluation.
Examples:
......@@ -70,7 +70,7 @@ Assume that you have already downloaded the checkpoints to the directory `checkp
```shell
python tools/test.py configs/votenet/votenet_8xb8_scannet-3d.py \
checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth \
checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth
```
4. Test SECOND on KITTI with 8 GPUs, and evaluate the mAP.
......@@ -243,17 +243,17 @@ If you use launch training jobs with Slurm, there are two ways to specify the po
In `config1.py`,
```python
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29500)
)
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29500)
)
```
In `config2.py`,
```python
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29500)
)
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29501)
)
```
Then you can launch two jobs with `config1.py` and `config2.py`.
......
......@@ -6,7 +6,7 @@ We provide lots of useful tools under `tools/` directory.
You can plot loss/mAP curves given a training log file. Run `pip install seaborn` first to install the dependency.
![loss curve image](../../resources/loss_curve.png)
![loss curve image](../../../resources/loss_curve.png)
```shell
python tools/analysis_tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}] [--mode ${MODE}] [--interval ${INTERVAL}]
......
......@@ -16,9 +16,11 @@ We support drawing point cloud on the image by using `draw_points_on_image`.
```python
import mmcv
import numpy as np
from mmengine import load
from mmdet3d.visualization import Det3DLocalVisualizer
import numpy as np
info_file = load('demo/data/kitti/000008.pkl')
points = np.fromfile('demo/data/kitti/000008.bin', dtype=np.float32)
points = points.reshape(-1, 4)[:, :3]
......@@ -40,6 +42,7 @@ We support drawing 3D boxes on point cloud by using `draw_bboxes_3d`.
```python
import torch
from mmdet3d.visualization import Det3DLocalVisualizer
from mmdet3d.structures import LiDARInstance3DBoxes
......@@ -66,8 +69,10 @@ We support drawing projected 3D boxes on image by using `draw_proj_bboxes_3d`.
import mmcv
import numpy as np
from mmengine import load
from mmdet3d.visualization import Det3DLocalVisualizer
from mmdet3d.structures import CameraInstance3DBoxes
info_file = load('demo/data/kitti/000008.pkl')
cam2img = np.array(info_file['data_list'][0]['images']['CAM2']['cam2img'], dtype=np.float32)
bboxes_3d = []
......@@ -91,11 +96,12 @@ visualizer.show()
### Drawing BEV Boxes
We support drawing projected 3D boxes on image by using `draw_proj_bboxes_3d`.
We support drawing BEV boxes by using `draw_bev_bboxes`.
```python
import numpy as np
from mmengine import load
from mmdet3d.visualization import Det3DLocalVisualizer
from mmdet3d.structures import CameraInstance3DBoxes
......@@ -122,6 +128,7 @@ We support draw segmentation mask via per-point colorization by using `draw_seg_
```python
import torch
from mmdet3d.visualization import Det3DLocalVisualizer
points = np.fromfile('tests/data/s3dis/points/Area_1_office_2.bin', dtype=np.float32)
......@@ -136,7 +143,7 @@ visualizer.show()
## Results
To see the prediction results of trained models, you can run the following command
To see the prediction results of trained models, you can run the following command:
```bash
python tools/test.py ${CONFIG_FILE} ${CKPT_PATH} --show --show-dir ${SHOW_DIR}
......@@ -144,9 +151,9 @@ python tools/test.py ${CONFIG_FILE} ${CKPT_PATH} --show --show-dir ${SHOW_DIR}
After running this command, plotted results including input data and the output of networks visualized on the input will be saved in `${SHOW_DIR}`.
After running this command, you will obtain the input data, the output of networks and ground-truth labels visualized on the input (e.g. `***_gt.png` and `***_pred.png` in multi-modality detection task and vision-based detection task) in `${SHOW_DIR}`. When `show` is enabled, [Open3D](http://www.open3d.org/) will be used to visualize the results online. If you are running test in remote server without GUI, the online visualization is not supported, you can download the `results.pkl` from the remote server, and visualize the prediction results offline in your local machine.
After running this command, you will obtain the input data, the output of networks and ground-truth labels visualized on the input (e.g. `***_gt.png` and `***_pred.png` in multi-modality detection task and vision-based detection task) in `${SHOW_DIR}`. When `show` is enabled, [Open3D](http://www.open3d.org/) will be used to visualize the results online. If you are running test in remote server without GUI, the online visualization is not supported. You can download the `results.pkl` from the remote server, and visualize the prediction results offline in your local machine.
To visualize the results with `Open3D` backend offline, you can run the following command
To visualize the results with `Open3D` backend offline, you can run the following command:
```bash
python tools/misc/visualize_results.py ${CONFIG_FILE} --result ${RESULTS_PATH} --show-dir ${SHOW_DIR}
......@@ -158,7 +165,7 @@ This allows the inference and results generation to be done in remote server and
## Dataset
We also provide scripts to visualize the dataset without inference. You can use `tools/misc/browse_dataset.py` to show loaded data and ground-truth online and save them on the disk. Currently we support single-modality 3D detection and 3D segmentation on all the datasets, multi-modality 3D detection on KITTI and SUN RGB-D, as well as monocular 3D detection on nuScenes. To browse the KITTI dataset, you can run the following command
We also provide scripts to visualize the dataset without inference. You can use `tools/misc/browse_dataset.py` to show loaded data and ground-truth online and save them on the disk. Currently we support single-modality 3D detection and 3D segmentation on all the datasets, multi-modality 3D detection on KITTI and SUN RGB-D, as well as monocular 3D detection on nuScenes. To browse the KITTI dataset, you can run the following command:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py --task det --output-dir ${OUTPUT_DIR}
......@@ -169,10 +176,10 @@ python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py -
To verify the data consistency and the effect of data augmentation, you can also add `--aug` flag to visualize the data after data augmentation using the command as below:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py --task det --aug --output-dir ${OUTPUT_DIR}
python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py --task lidar_det --aug --output-dir ${OUTPUT_DIR}
```
If you also want to show 2D images with 3D bounding boxes projected onto them, you need to find a config that supports multi-modality data loading, and then change the `--task` args to `multi_modality-det`. An example is showed below
If you also want to show 2D images with 3D bounding boxes projected onto them, you need to find a config that supports multi-modality data loading, and then change the `--task` args to `multi-modality_det`. An example is showed below:
```shell
python tools/misc/browse_dataset.py configs/mvxnet/dv_mvx-fpn_second_secfpn_adamw_2x8_80e_kitti-3d-3class.py --task multi-modality_det --output-dir ${OUTPUT_DIR}
......@@ -180,15 +187,15 @@ python tools/misc/browse_dataset.py configs/mvxnet/dv_mvx-fpn_second_secfpn_adam
![](../../../resources/browse_dataset_multi_modality.png)
You can simply browse different datasets using different configs, e.g. visualizing the ScanNet dataset in 3D semantic segmentation task
You can simply browse different datasets using different configs, e.g. visualizing the ScanNet dataset in 3D semantic segmentation task:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/scannet_seg-3d-20class.py --task seg --output-dir ${OUTPUT_DIR} --online
python tools/misc/browse_dataset.py configs/_base_/datasets/scannet_seg-3d-20class.py --task lidar_seg --output-dir ${OUTPUT_DIR} --online
```
![](../../../resources/browse_dataset_seg.png)
And browsing the nuScenes dataset in monocular 3D detection task
And browsing the nuScenes dataset in monocular 3D detection task:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/nus-mono3d.py --task mono_det --output-dir ${OUTPUT_DIR} --online
......
# 教程 2: 自定义数据集
# 自定义数据集
## 支持新的数据格式
在本节中,您将了解如何使用自定义数据集训练和测试预训练模型。
为了支持新的数据格式,可以通过将新数据转换为现有的数据形式,或者直接将新数据转换为能够被模型直接调用的中间格式。此外,可以通过数据离线转换的方式(在调用脚本进行训练之前完成)或者通过数据在线转换的格式(调用新的数据集并在训练过程中进行数据转换)。在 MMDetection3D 中,对于那些不便于在线读取的数据,我们建议通过离线转换的方法将其转换为 KTIIT 数据集的格式,因此只需要在转换后修改配置文件中的数据标注文件的路径和标注数据所包含类别;对于那些与现有数据格式相似的新数据集,如 Lyft 数据集和 nuScenes 数据集,我们建议直接调用数据转换器和现有的数据集类别信息,在这个过程中,可以考虑通过继承的方式来减少实施数据转换的负担。
基本步骤如下:
### 将新数据的格式转换为现有数据的格式
1. 准备数据
2. 准备配置文件
3. 在自定义数据集上训练,测试和推理模型
对于那些不便于在线读取的数据,最简单的方法是将新数据集的格式转换为现有数据集的格式。
## 数据准备
通常来说,我们需要一个数据转换器来重新组织原始数据的格式,并将对应的标注格式转换 KITTI 数据集的风格;当现有数据集与新数据集存在差异时,可以通过定义一个从现有数据集类继承而来的新数据集类来处理具体的差异;最后,用户需要进一步修改配置文件来调用新的数据集。可以参考如何通过将 Waymo 数据集转换为 KITTI 数据集的风格并进一步训练模型的[例子](https://mmdetection3d.readthedocs.io/zh_CN/latest/2_new_data_model.html)
理想情况下我们可以重新组织自定义的原始数据并将标注格式转换 KITTI 风格。但是,考虑到对于自定义数据集而言,一些校准文件和 KITTI 格式的 3D 标注难以获得,我们在文档中介绍基本的数据格式
### 将新数据集的格式转换为一种当前可支持的中间格式
### 基本数据格式
如果不想采用将标注格式转为为现有格式的方式,也可以通过以下的方式来完成新数据集的转换。
实际上,我们将所支持的所有数据集都转换成 pickle 文件的格式,这些文件整理了所有应用于模型训练和推理的有用的信息。
#### 点云格式
数据集的标注信息是通过一个字典列表来描述的,每个字典包含对应数据帧的标注信息。
下面展示了一个基础例子(应用在 KITTI 数据集上),每一帧包含了几项关键字,如 `image``point_cloud``calib``annos` 等。只要能够根据这些信息来直接读取到数据,其原始数据的组织方式就可以不同于现有的数据组织方式。通过这种设计,我们提供一种可替代的方案来自定义数据集。
目前,我们只支持 `.bin` 格式的点云用于训练和推理。在训练自己的数据集之前,需要将其他格式的点云文件转换成 `.bin` 文件。常见的点云数据格式包括 `.pcd``.las`,我们列出一些开源工具作为参考。
```python
1. `.pcd` 转换成 `.bin`:https://github.com/DanielPollithy/pypcd
[
{'image': {'image_idx': 0, 'image_path': 'training/image_2/000000.png', 'image_shape': array([ 370, 1224], dtype=int32)},
'point_cloud': {'num_features': 4, 'velodyne_path': 'training/velodyne/000000.bin'},
'calib': {'P0': array([[707.0493, 0. , 604.0814, 0. ],
[ 0. , 707.0493, 180.5066, 0. ],
[ 0. , 0. , 1. , 0. ],
[ 0. , 0. , 0. , 1. ]]),
'P1': array([[ 707.0493, 0. , 604.0814, -379.7842],
[ 0. , 707.0493, 180.5066, 0. ],
[ 0. , 0. , 1. , 0. ],
[ 0. , 0. , 0. , 1. ]]),
'P2': array([[ 7.070493e+02, 0.000000e+00, 6.040814e+02, 4.575831e+01],
[ 0.000000e+00, 7.070493e+02, 1.805066e+02, -3.454157e-01],
[ 0.000000e+00, 0.000000e+00, 1.000000e+00, 4.981016e-03],
[ 0.000000e+00, 0.000000e+00, 0.000000e+00, 1.000000e+00]]),
'P3': array([[ 7.070493e+02, 0.000000e+00, 6.040814e+02, -3.341081e+02],
[ 0.000000e+00, 7.070493e+02, 1.805066e+02, 2.330660e+00],
[ 0.000000e+00, 0.000000e+00, 1.000000e+00, 3.201153e-03],
[ 0.000000e+00, 0.000000e+00, 0.000000e+00, 1.000000e+00]]),
'R0_rect': array([[ 0.9999128 , 0.01009263, -0.00851193, 0. ],
[-0.01012729, 0.9999406 , -0.00403767, 0. ],
[ 0.00847068, 0.00412352, 0.9999556 , 0. ],
[ 0. , 0. , 0. , 1. ]]),
'Tr_velo_to_cam': array([[ 0.00692796, -0.9999722 , -0.00275783, -0.02457729],
[-0.00116298, 0.00274984, -0.9999955 , -0.06127237],
[ 0.9999753 , 0.00693114, -0.0011439 , -0.3321029 ],
[ 0. , 0. , 0. , 1. ]]),
'Tr_imu_to_velo': array([[ 9.999976e-01, 7.553071e-04, -2.035826e-03, -8.086759e-01],
[-7.854027e-04, 9.998898e-01, -1.482298e-02, 3.195559e-01],
[ 2.024406e-03, 1.482454e-02, 9.998881e-01, -7.997231e-01],
[ 0.000000e+00, 0.000000e+00, 0.000000e+00, 1.000000e+00]])},
'annos': {'name': array(['Pedestrian'], dtype='<U10'), 'truncated': array([0.]), 'occluded': array([0]), 'alpha': array([-0.2]), 'bbox': array([[712.4 , 143. , 810.73, 307.92]]), 'dimensions': array([[1.2 , 1.89, 0.48]]), 'location': array([[1.84, 1.47, 8.41]]), 'rotation_y': array([0.01]), 'score': array([0.]), 'index': array([0], dtype=int32), 'group_ids': array([0], dtype=int32), 'difficulty': array([0], dtype=int32), 'num_points_in_gt': array([377], dtype=int32)}}
...
]
```
- 您可以通过以下指令安装 `pypcd`
在此之上,用户可以通过继承 `Custom3DDataset` 来实现新的数据集类,并重载相关的方法,如 [KITTI 数据集](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/kitti_dataset.py)[ScanNet 数据集](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/scannet_dataset.py)所示。
```bash
pip install git+https://github.com/DanielPollithy/pypcd.git
```
### 自定义数据集的例子
- 您可以使用以下脚本读取 `.pcd` 文件,将其转换成 `.bin` 格式并保存。
我们在这里提供了一个自定义数据集的例子:
```python
import numpy as np
from pypcd import pypcd
假设已经将标注信息重新组织成一个 pickle 文件格式的字典列表,比如 ScanNet。
标注框的标注信息会被存储在 `annotation.pkl` 文件中,其格式如下所示:
pcd_data = pypcd.PointCloud.from_path('point_cloud_data.pcd')
points = np.zeros([pcd_data.width, 4], dtype=np.float32)
points[:, 0] = pcd_data.pc_data['x'].copy()
points[:, 1] = pcd_data.pc_data['y'].copy()
points[:, 2] = pcd_data.pc_data['z'].copy()
points[:, 3] = pcd_data.pc_data['intensity'].copy().astype(np.float32)
with open('point_cloud_data.bin', 'wb') as f:
f.write(points.tobytes())
```
```
{'point_cloud': {'num_features': 6, 'lidar_idx': 'scene0000_00'}, 'pts_path': 'points/scene0000_00.bin',
'pts_instance_mask_path': 'instance_mask/scene0000_00.bin', 'pts_semantic_mask_path': 'semantic_mask/scene0000_00.bin',
'annos': {'gt_num': 27, 'name': array(['window', 'window', 'table', 'counter', 'curtain', 'curtain',
'desk', 'cabinet', 'sink', 'garbagebin', 'garbagebin',
'garbagebin', 'sofa', 'refrigerator', 'table', 'table', 'toilet',
'bed', 'cabinet', 'cabinet', 'cabinet', 'cabinet', 'cabinet',
'cabinet', 'door', 'door', 'door'], dtype='<U12'),
'location': array([[ 1.48129511, 3.52074146, 1.85652947],
[ 2.90395617, -3.48033905, 1.52682471]]),
'dimensions': array([[1.74445975, 0.23195696, 0.57235193],
[0.66077662, 0.17072392, 0.67153597]]),
'gt_boxes_upright_depth': array([
[ 1.48129511, 3.52074146, 1.85652947, 1.74445975, 0.23195696,
0.57235193],
[ 2.90395617, -3.48033905, 1.52682471, 0.66077662, 0.17072392,
0.67153597]]),
'index': array([ 0, 1 ], dtype=int32),
'class': array([ 6, 6 ])}}
```
2. `.las` 转换成 `.bin`:常见的转换流程为 `.las -> .pcd -> .bin``.las -> .pcd` 的转换可以用该[工具](https://github.com/Hitachi-Automotive-And-Industry-Lab/semantic-segmentation-editor)
我们在 `mmdet3d/datasets/my_dataset.py` 中创建了一个新的数据集类来进行数据的加载,如下所示:
#### 标签格式
```python
import numpy as np
from os import path as osp
最基本的信息:每个场景的 3D 边界框和类别标签应该包含在标注 `.txt` 文件中。每一行代表特定场景的一个 3D 框,如下所示:
from mmdet3d.core import show_result
from mmdet3d.core.bbox import DepthInstance3DBoxes
from mmdet.datasets import DATASETS
from .custom_3d import Custom3DDataset
```
# 格式:[x, y, z, dx, dy, dz, yaw, category_name]
1.23 1.42 0.23 3.96 1.65 1.55 1.56 Car
3.51 2.15 0.42 1.05 0.87 1.86 1.23 Pedestrian
...
```
**注意**:对于自定义数据集评估目前我们只支持 KITTI 评估方法。
@DATASETS.register_module()
class MyDataset(Custom3DDataset):
CLASSES = ('cabinet', 'bed', 'chair', 'sofa', 'table', 'door', 'window',
'bookshelf', 'picture', 'counter', 'desk', 'curtain',
'refrigerator', 'showercurtrain', 'toilet', 'sink', 'bathtub',
'garbagebin')
def __init__(self,
data_root,
ann_file,
pipeline=None,
classes=None,
modality=None,
box_type_3d='Depth',
filter_empty_gt=True,
test_mode=False):
super().__init__(
data_root=data_root,
ann_file=ann_file,
pipeline=pipeline,
classes=classes,
modality=modality,
box_type_3d=box_type_3d,
filter_empty_gt=filter_empty_gt,
test_mode=test_mode)
def get_ann_info(self, index):
# 通过下标来获取标注信息,evalhook 也能够通过此接口来获取标注信息
info = self.data_infos[index]
if info['annos']['gt_num'] != 0:
gt_bboxes_3d = info['annos']['gt_boxes_upright_depth'].astype(
np.float32) # k, 6
gt_labels_3d = info['annos']['class'].astype(np.int64)
else:
gt_bboxes_3d = np.zeros((0, 6), dtype=np.float32)
gt_labels_3d = np.zeros((0, ), dtype=np.int64)
# 转换为目标标注框的结构
gt_bboxes_3d = DepthInstance3DBoxes(
gt_bboxes_3d,
box_dim=gt_bboxes_3d.shape[-1],
with_yaw=False,
origin=(0.5, 0.5, 0.5)).convert_to(self.box_mode_3d)
pts_instance_mask_path = osp.join(self.data_root,
info['pts_instance_mask_path'])
pts_semantic_mask_path = osp.join(self.data_root,
info['pts_semantic_mask_path'])
anns_results = dict(
gt_bboxes_3d=gt_bboxes_3d,
gt_labels_3d=gt_labels_3d,
pts_instance_mask_path=pts_instance_mask_path,
pts_semantic_mask_path=pts_semantic_mask_path)
return anns_results
3D 框应存储在统一的 3D 坐标系中。
```
#### 校准格式
接着,可以对配置文件进行修改来调用 `MyDataset` 数据集类,如下所示:
对于每个激光雷达收集的点云数据,通常会进行融合并转换到特定的激光雷达坐标系。因此,校准信息文件中应该包含每个相机的内参矩阵和激光雷达到每个相机的外参转换矩阵,并保存在校准 `.txt` 文件中,其中 `Px` 表示 `camera_x` 的内参矩阵,`lidar2camx` 表示 `lidar``camera_x` 的外参转换矩阵。
```python
dataset_A_train = dict(
type='MyDataset',
ann_file = 'annotation.pkl',
pipeline=train_pipeline
)
```
P0
P1
P2
P3
P4
...
lidar2cam0
lidar2cam1
lidar2cam2
lidar2cam3
lidar2cam4
...
```
### 使用数据集包装器来自定义数据集
### 原始数据结构
与 MMDetection 类似,MMDetection3D 也提供了许多数据集包装器来统合数据集或者修改数据集的分布,并应用到模型的训练中。
目前 MMDetection3D 支持3种数据集包装器
#### 基于激光雷达的 3D 检测
- `RepeatDataset`:简单地重复整个数据集
- `ClassBalancedDataset`:以类别平衡的方式重复数据集
- `ConcatDataset`:拼接多个数据集
基于激光雷达的 3D 目标检测原始数据通常组织成如下格式,其中 `ImageSets` 包含划分文件,指明哪些文件数据属于训练/验证集,`points` 包含存储成 `.bin` 格式的点云数据,`labels` 包含 3D 检测的标签文件。
### 重复数据集
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── custom
│ │ ├── ImageSets
│ │ │ ├── train.txt
│ │ │ ├── val.txt
│ │ ├── points
│ │ │ ├── 000000.bin
│ │ │ ├── 000001.bin
│ │ │ ├── ...
│ │ ├── labels
│ │ │ ├── 000000.txt
│ │ │ ├── 000001.txt
│ │ │ ├── ...
```
我们使用 `RepeatDataset` 包装器来进行数据集重复的设置,例如,假定当前需要重复的数据集为 `Dataset_A`,则配置文件应设置成如下所示:
## 基于视觉的 3D 检测
```python
dataset_A_train = dict(
type='RepeatDataset',
times=N,
dataset=dict( # 这是 Dataset_A 的原始配置文件
type='Dataset_A',
...
pipeline=train_pipeline
)
)
基于视觉的 3D 目标检测原始数据通常组织成如下格式,其中 `ImageSets` 包含划分文件,指明哪些文件数据属于训练/验证集,`images` 包含来自不同相机的图像,例如 `camera_x` 获得的图像应放在 `images/images_x` 下,`calibs` 包含校准信息文件,其中存储了每个相机的内参矩阵,`labels` 包含 3D 检测的标签文件。
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── custom
│ │ ├── ImageSets
│ │ │ ├── train.txt
│ │ │ ├── val.txt
│ │ ├── calibs
│ │ │ ├── 000000.txt
│ │ │ ├── 000001.txt
│ │ │ ├── ...
│ │ ├── images
│ │ │ ├── images_0
│ │ │ │ ├── 000000.png
│ │ │ │ ├── 000001.png
│ │ │ │ ├── ...
│ │ │ ├── images_1
│ │ │ ├── images_2
│ │ │ ├── ...
│ │ ├── labels
│ │ │ ├── 000000.txt
│ │ │ ├── 000001.txt
│ │ │ ├── ...
```
### 类别平衡数据集
#### 多模态 3D 检测
我们使用 `ClassBalancedDataset` 包装器能够基于类别出现的频率进行数据集重复的设置,进行重复的数据集需要实例化函数 `self.get_cat_ids(idx)`,以支持 `ClassBalancedDataset` 包装器的正常调用。例如,假定需要以 `oversample_thr=1e-3` 的设置来定义 `Dataset_A` 的重复,则对应的配置文件如下所示:
多模态 3D 目标检测原始数据通常组织成如下格式。不同于基于视觉的 3D 目标检测,`calibs` 里的校准信息文件存储了每个相机的内参矩阵和外参矩阵。
```python
dataset_A_train = dict(
type='ClassBalancedDataset',
oversample_thr=1e-3,
dataset=dict( # 这是 Dataset_A 的原始配置文件
type='Dataset_A',
...
pipeline=train_pipeline
)
)
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── custom
│ │ ├── ImageSets
│ │ │ ├── train.txt
│ │ │ ├── val.txt
│ │ ├── calibs
│ │ │ ├── 000000.txt
│ │ │ ├── 000001.txt
│ │ │ ├── ...
│ │ ├── points
│ │ │ ├── 000000.bin
│ │ │ ├── 000001.bin
│ │ │ ├── ...
│ │ ├── images
│ │ │ ├── images_0
│ │ │ │ ├── 000000.png
│ │ │ │ ├── 000001.png
│ │ │ │ ├── ...
│ │ │ ├── images_1
│ │ │ ├── images_2
│ │ │ ├── ...
│ │ ├── labels
│ │ │ ├── 000000.txt
│ │ │ ├── 000001.txt
│ │ │ ├── ...
```
请参考 [源码](https://github.com/open-mmlab/mmdetection/blob/master/mmdet/datasets/dataset_wrappers.py) 获取更多细节。
#### 基于激光雷达的 3D 语义分割
### 拼接数据集
基于激光雷达的 3D 语义分割原始数据通常组织成如下格式,其中 `ImageSets` 包含划分文件,指明哪些文件数据属于训练/验证集,`points` 包含点云数据,`semantic_mask` 包含逐点级标签。
我们提供3种方式来实现数据集的拼接。
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── custom
│ │ ├── ImageSets
│ │ │ ├── train.txt
│ │ │ ├── val.txt
│ │ ├── points
│ │ │ ├── 000000.bin
│ │ │ ├── 000001.bin
│ │ │ ├── ...
│ │ ├── semantic_mask
│ │ │ ├── 000000.bin
│ │ │ ├── 000001.bin
│ │ │ ├── ...
```
1. 如果待拼接的数据集的类别相同,标注文件的不同,此时可通过下面的方式来实现数据集的拼接:
### 数据转换
```python
dataset_A_train = dict(
type='Dataset_A',
ann_file = ['anno_file_1', 'anno_file_2'],
pipeline=train_pipeline
)
```
按照我们的说明准备好原始数据后,您可以直接使用以下命令生成训练/验证信息文件。
如果拼接数据集用于测试或者评估,那么这种拼接方式能够对每个数据集进行分开地测试或者评估,若希望对拼接数据集进行整体的测试或者评估,此时需要设置 `separate_eval=False`,如下所示:
```
python tools/create_data.py base --root-path ./data/custom --out-dir ./data/custom
```
```python
dataset_A_train = dict(
type='Dataset_A',
ann_file = ['anno_file_1', 'anno_file_2'],
separate_eval=False,
pipeline=train_pipeline
)
```
## 自定义数据集示例
2. 如果待拼接的数据集完全不相同,此时可通过拼接不同数据集的配置的方式实现数据集的拼接,如下所示:
在完成数据准备后,我们可以在 `mmdet3d/datasets/my_dataset.py` 中创建一个新的数据集来加载数据。
```python
dataset_A_train = dict()
dataset_B_train = dict()
```python
import mmengine
from mmdet3d.det3d_dataset import Det3DDataset
from mmdet3d.registry import DATASETS
data = dict(
imgs_per_gpu=2,
workers_per_gpu=2,
train = [
dataset_A_train,
dataset_B_train
],
val = dataset_A_val,
test = dataset_A_test
)
```
如果拼接数据集用于测试或者评估,那么这种拼接方式能够对每个数据集进行分开地测试或者评估。
@DATASETS.register_module()
class MyDataset(Det3DDataset):
# 替换成自定义 pkl 信息文件里的所有类别
METAINFO = {
'classes': ('Pedestrian', 'Cyclist', 'Car')
}
def parse_ann_info(self, info):
"""Process the `instances` in data info to `ann_info`
Args:
info (dict): Info dict.
Returns:
dict | None: Processed `ann_info`
"""
ann_info = super().parse_ann_info(info)
if ann_info is None:
ann_info = dict()
# 空实例
ann_info['gt_bboxes_3d'] = np.zeros((0, 7), dtype=np.float32)
ann_info['gt_labels_3d'] = np.zeros(0, dtype=np.int64)
# 过滤掉没有在训练中使用的类别
ann_info = self._remove_dontcare(ann_info)
gt_bboxes_3d = LiDARInstance3DBoxes(ann_info['gt_bboxes_3d'])
ann_info['gt_bboxes_3d'] = gt_bboxes_3d
return ann_info
```
3. 可以通过显示地定义 `ConcatDataset` 来实现数据集的拼接,如下所示
数据预处理后,用户可以通过两个步骤来训练自定义数据集
```python
dataset_A_val = dict()
dataset_B_val = dict()
1. 修改配置文件来使用自定义数据集。
2. 验证自定义数据集的正确性。
data = dict(
imgs_per_gpu=2,
workers_per_gpu=2,
train=dataset_A_train,
val=dict(
type='ConcatDataset',
datasets=[dataset_A_val, dataset_B_val],
separate_eval=False))
```
这里我们以在自定义数据集上训练 PointPillars 为例:
其中,`separate_eval=False` 表示将所有的数据集作为一个整体进行评估。
### 准备配置
**注意:**
这里我们演示一个纯点云训练的配置示例:
1. 当使用选项 `separate_eval=False` 时,拼接的数据集需要在评估的过程中调用 `self.data_infos`,由于 COCO 数据集在评估过程中并未完全依赖于 `self.data_infos`来获取数据信息,因此 COCO 数据集无法使用 `separate_eval=False` 选项。此外,我们暂未对将不同类型的数据集进行结合并作为整体进行评估的过程进行测试,因此我们暂时不建议使用上述方法对不同类型的数据集进行整体的评估。
2. 我们暂时不支持对 `ClassBalancedDataset``RepeatDataset` 进行评估,因此也不支持由这两种类型的数据集进行拼接的数据集的评估。
#### 准备数据集配置
复杂的例子:将 `Dataset_A``Dataset_B` 分别重复 N 次和 M 次,然后将重复数据集进行拼接,如下所示
`configs/_base_/datasets/custom.py`
```python
dataset_A_train = dict(
type='RepeatDataset',
times=N,
# 数据集设置
dataset_type = 'MyDataset'
data_root = 'data/custom/'
class_names = ['Pedestrian', 'Cyclist', 'Car'] # 替换成自己的数据集类别
point_cloud_range = [0, -40, -3, 70.4, 40, 1] # 根据你的数据集进行调整
input_modality = dict(use_lidar=True, use_camera=False)
metainfo = dict(classes=class_names)
train_pipeline = [
dict(
type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=4, # 替换成你的点云数据维度
use_dim=4), # 替换成在训练和推理时实际使用的维度
dict(
type='LoadAnnotations3D',
with_bbox_3d=True,
with_label_3d=True),
dict(
type='ObjectNoise',
num_try=100,
translation_std=[1.0, 1.0, 0.5],
global_rot_range=[0.0, 0.0],
rot_range=[-0.78539816, 0.78539816]),
dict(type='RandomFlip3D', flip_ratio_bev_horizontal=0.5),
dict(
type='GlobalRotScaleTrans',
rot_range=[-0.78539816, 0.78539816],
scale_ratio_range=[0.95, 1.05]),
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='PointShuffle'),
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
test_pipeline = [
dict(
type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=4, # 替换成你的点云数据维度
use_dim=4),
dict(type='Pack3DDetInputs', keys=['points'])
]
# 为可视化阶段的数据和 GT 加载构造流水线
eval_pipeline = [
dict(type='LoadPointsFromFile', coord_type='LIDAR', load_dim=4, use_dim=4),
dict(type='Pack3DDetInputs', keys=['points']),
]
train_dataloader = dict(
batch_size=6,
num_workers=4,
persistent_workers=True,
sampler=dict(type='DefaultSampler', shuffle=True),
dataset=dict(
type='Dataset_A',
...
pipeline=train_pipeline
)
)
dataset_A_val = dict(
...
pipeline=test_pipeline
)
dataset_A_test = dict(
...
pipeline=test_pipeline
)
dataset_B_train = dict(
type='RepeatDataset',
times=M,
type='RepeatDataset',
times=2,
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='custom_infos_train.pkl', # 指定你的训练 pkl 信息
data_prefix=dict(pts='points'),
pipeline=train_pipeline,
modality=input_modality,
test_mode=False,
metainfo=metainfo,
box_type_3d='LiDAR')))
val_dataloader = dict(
batch_size=1,
num_workers=1,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type='Dataset_B',
...
pipeline=train_pipeline
)
)
data = dict(
imgs_per_gpu=2,
workers_per_gpu=2,
train = [
dataset_A_train,
dataset_B_train
],
val = dataset_A_val,
test = dataset_A_test
)
type=dataset_type,
data_root=data_root,
data_prefix=dict(pts='points'),
ann_file='custom_infos_val.pkl', # 指定你的验证 pkl 信息
pipeline=test_pipeline,
modality=input_modality,
test_mode=True,
metainfo=metainfo,
box_type_3d='LiDAR'))
val_evaluator = dict(
type='KittiMetric',
ann_file=data_root + 'custom_infos_val.pkl', # 指定你的验证 pkl 信息
metric='bbox')
```
## 修改数据集的类别
#### 准备模型配置
我们可以对现有的数据集的类别名称进行修改,从而实现全部标注的子集标注的训练。
例如,如果想要对现有数据集中的三个类别进行训练,可以对现有数据集的类别进行如下的修改,此时数据集将会自动过滤其他类别对应的真实标注框:
对于基于体素化的检测器如 SECOND,PointPillars 及 CenterPoint,点云范围(point cloud range)和体素大小(voxel size)应该根据你的数据集做调整。理论上,`voxel_size``point_cloud_range` 的设置是相关联的。设置较小的 `voxel_size` 将增加体素数以及相应的内存消耗。此外,需要注意以下问题:
```python
classes = ('person', 'bicycle', 'car')
data = dict(
train=dict(classes=classes),
val=dict(classes=classes),
test=dict(classes=classes))
```
如果将 `point_cloud_range``voxel_size` 分别设置成 `[0, -40, -3, 70.4, 40, 1]``[0.05, 0.05, 0.1]`,则中间特征图的形状为 `[(1-(-3))/0.1+1, (40-(-40))/0.05, (70.4-0)/0.05]=[41, 1600, 1408]`。更改 `point_cloud_range` 时,请记得依据 `voxel_size` 更改 `middle_encoder` 里中间特征图的形状。
MMDetection V2.0 也支持从文件中读取数据集的类别,更加符合真实的应用场景。
例如,假定 `classes.txt` 包含如下所示的类别名称:
关于 `anchor_range` 的设置,一般需要根据数据集做调整。需要注意的是,`z` 值需要根据点云的位置做相应调整,具体请参考此 [issue](https://github.com/open-mmlab/mmdetection3d/issues/986)
关于 `anchor_size` 的设置,通常需要计算整个训练集中目标的长、宽、高的平均值作为 `anchor_size`,以获得最好的结果。
`configs/_base_/models/pointpillars_hv_secfpn_custom.py`
```python
voxel_size = [0.16, 0.16, 4] # 根据你的数据集做调整
point_cloud_range = [0, -39.68, -3, 69.12, 39.68, 1] # 根据你的数据集做调整
model = dict(
type='VoxelNet',
data_preprocessor=dict(
type='Det3DDataPreprocessor',
voxel=True,
voxel_layer=dict(
max_num_points=32,
point_cloud_range=point_cloud_range,
voxel_size=voxel_size,
max_voxels=(16000, 40000))),
voxel_encoder=dict(
type='PillarFeatureNet',
in_channels=4,
feat_channels=[64],
with_distance=False,
voxel_size=voxel_size,
point_cloud_range=point_cloud_range),
# `output_shape` 需要根据 `point_cloud_range` 和 `voxel_size` 做相应调整
middle_encoder=dict(
type='PointPillarsScatter', in_channels=64, output_shape=[496, 432]),
backbone=dict(
type='SECOND',
in_channels=64,
layer_nums=[3, 5, 5],
layer_strides=[2, 2, 2],
out_channels=[64, 128, 256]),
neck=dict(
type='SECONDFPN',
in_channels=[64, 128, 256],
upsample_strides=[1, 2, 4],
out_channels=[128, 128, 128]),
bbox_head=dict(
type='Anchor3DHead',
num_classes=3,
in_channels=384,
feat_channels=384,
use_direction_classifier=True,
assign_per_class=True,
# 根据你的数据集调整 `ranges` 和 `sizes`
anchor_generator=dict(
type='AlignedAnchor3DRangeGenerator',
ranges=[
[0, -39.68, -0.6, 69.12, 39.68, -0.6],
[0, -39.68, -0.6, 69.12, 39.68, -0.6],
[0, -39.68, -1.78, 69.12, 39.68, -1.78],
],
sizes=[[0.8, 0.6, 1.73], [1.76, 0.6, 1.73], [3.9, 1.6, 1.56]],
rotations=[0, 1.57],
reshape_out=False),
diff_rad_by_sin=True,
bbox_coder=dict(type='DeltaXYZWLHRBBoxCoder'),
loss_cls=dict(
type='mmdet.FocalLoss',
use_sigmoid=True,
gamma=2.0,
alpha=0.25,
loss_weight=1.0),
loss_bbox=dict(
type='mmdet.SmoothL1Loss', beta=1.0 / 9.0, loss_weight=2.0),
loss_dir=dict(
type='mmdet.CrossEntropyLoss', use_sigmoid=False,
loss_weight=0.2)),
# 模型训练和测试设置
train_cfg=dict(
assigner=[
dict( # for Pedestrian
type='Max3DIoUAssigner',
iou_calculator=dict(type='mmdet3d.BboxOverlapsNearest3D'),
pos_iou_thr=0.5,
neg_iou_thr=0.35,
min_pos_iou=0.35,
ignore_iof_thr=-1),
dict( # for Cyclist
type='Max3DIoUAssigner',
iou_calculator=dict(type='mmdet3d.BboxOverlapsNearest3D'),
pos_iou_thr=0.5,
neg_iou_thr=0.35,
min_pos_iou=0.35,
ignore_iof_thr=-1),
dict( # for Car
type='Max3DIoUAssigner',
iou_calculator=dict(type='mmdet3d.BboxOverlapsNearest3D'),
pos_iou_thr=0.6,
neg_iou_thr=0.45,
min_pos_iou=0.45,
ignore_iof_thr=-1),
],
allowed_border=0,
pos_weight=-1,
debug=False),
test_cfg=dict(
use_rotate_nms=True,
nms_across_levels=False,
nms_thr=0.01,
score_thr=0.1,
min_bbox_size=0,
nms_pre=100,
max_num=50))
```
person
bicycle
car
```
用户能够将类别文件的路径名写入到配置文件中的类别信息中,此时数据集将会自动地加载该类别文件并将其转换成列表:
#### 准备整体配置
我们将上诉的所有配置组合在 `configs/pointpillars/pointpillars_hv_secfpn_8xb6_custom.py` 文件中:
```python
classes = 'path/to/classes.txt'
data = dict(
train=dict(classes=classes),
val=dict(classes=classes),
test=dict(classes=classes))
_base_ = [
'../_base_/models/pointpillars_hv_secfpn_custom.py',
'../_base_/datasets/custom.py',
'../_base_/schedules/cyclic-40e.py', '../_base_/default_runtime.py'
]
```
**注意** (与 MMDetection 相关):
#### 可视化数据集(可选)
- 在 MMDetection v2.5.0 之前,一旦设置了上述的 classes,数据集将会自动的过滤没有真实标注框的图像,然而却无法通过调整配置文件的方式来取消该行为,这会引起一定的疑惑:当没有设置 classes 的情况下,只有当选项中同时出现 `filter_empty_gt=True``test_mode=False` 时才会对数据集中没有真实标注框的图像进行过滤。在 MMDetection v2.5.0 之后,我们对图像过滤过程和类别修改过程进行分离,例如:不管配置文件中是否 classes 进行设置,数据集只会在设置 `filter_empty_gt=True``test_mode=False` 时对没有真实标注框的图像进行过滤。 因此,设置 classes 仅会影响用于训练的类别标注信息,用户可以自行决定是否需要对没有真实标注框的图像进行过滤。
- 因为数据集的中间格式仅包含标注框的标签信息,并不包含类别名,因此在使用 `CustomDataset` 时,用户只能够通过离线的方式来过滤没有真实标注框的图像,而无法通过配置文件来实现过滤。
- 设置数据集类别和数据集过滤的特征将在之后进行重构,使得对应的特征更加便于使用。
为了验证准备的数据和配置是否正确,我们建议在训练和验证前使用 `tools/misc/browse_dataest.py` 脚本可视化数据集和标注,更多细节请参考[可视化](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/user_guides/visualization.md)文档。
## 评估
准备好数据和配置之后,你可以遵循我们的文档直接运行训练/测试脚本。
**注意**:我们为自定义数据集提供了 KITTI 风格的评估实现方法。在数据集配置中需要包含如下内容:
```python
val_evaluator = dict(
type='KittiMetric',
ann_file=data_root + 'custom_infos_val.pkl', # 指定你的验证 pkl 信息
metric='bbox')
```
# 教程 4: 自定义模型
# 自定义模型
我们通常把模型的各个组成成分分成6种类型:
我们通常把模型的各个组成成分分成 6 种类型:
- 编码器(encoder):包括 voxel layer、voxel encoder 和 middle encoder 等进入 backbone 前所使用的基于 voxel 的方法,如 HardVFE 和 PointPillarsScatter。
- 骨干网络(backbone):通常采用 FCN 网络来提取特征图,如 ResNet 和 SECOND。
......@@ -22,10 +22,10 @@
```python
import torch.nn as nn
from ..builder import VOXEL_ENCODERS
from mmdet3d.registry import MODELS
@VOXEL_ENCODERS.register_module()
@MODELS.register_module()
class HardVFE(nn.Module):
def __init__(self, arg1, arg2):
......@@ -74,10 +74,10 @@ model = dict(
```python
import torch.nn as nn
from ..builder import BACKBONES
from mmdet3d.registry import MODELS
@BACKBONES.register_module()
@MODELS.register_module()
class SECOND(BaseModule):
def __init__(self, arg1, arg2):
......@@ -122,9 +122,9 @@ model = dict(
创建一个新文件 `mmdet3d/models/necks/second_fpn.py`
```python
from ..builder import NECKS
from mmdet3d.registry import MODELS
@NECKS.register
@MODELS.register_module()
class SECONDFPN(BaseModule):
def __init__(self,
......@@ -183,10 +183,10 @@ PartA2 RoI Head 实现一个新的 bbox head ,并用于目标检测的任务
为了实现一个新的 bbox head,通常需要在其中实现三个功能,如下所示,有时该模块还需要实现其他相关的功能,如 `loss``get_targets`
```python
from mmdet.models.builder import HEADS
from .bbox_head import BBoxHead
from mmdet3d.registry import MODELS
from mmengine.model import BaseModule
@HEADS.register_module()
@MODELS.register_module()
class PartA2BboxHead(BaseModule):
"""PartA2 RoI head."""
......@@ -218,71 +218,63 @@ class PartA2BboxHead(BaseModule):
super(PartA2BboxHead, self).__init__(init_cfg=init_cfg)
def forward(self, seg_feats, part_feats):
```
其次,如果有必要的话,用户还需要实现一个新的 RoI Head,此处我们从 `Base3DRoIHead` 中继承得到一个新类 `PartAggregationROIHead`,此时我们就能发现 `Base3DRoIHead` 已经实现了下面的功能:
```python
from abc import ABCMeta, abstractmethod
from torch import nn as nn
from mmdet3d.registry import MODELS, TASK_UTILS
from mmdet.models.roi_heads import BaseRoIHead
@HEADS.register_module()
class Base3DRoIHead(BaseModule, metaclass=ABCMeta):
class Base3DRoIHead(BaseRoIHead):
"""Base class for 3d RoIHeads."""
def __init__(self,
bbox_head=None,
mask_roi_extractor=None,
bbox_roi_extractor=None,
mask_head=None,
mask_roi_extractor=None,
train_cfg=None,
test_cfg=None,
init_cfg=None):
super(Base3DRoIHead, self).__init__(
bbox_head=bbox_head,
bbox_roi_extractor=bbox_roi_extractor,
mask_head=mask_head,
mask_roi_extractor=mask_roi_extractor,
train_cfg=train_cfg,
test_cfg=test_cfg,
init_cfg=init_cfg)
@property
def with_bbox(self):
@property
def with_mask(self):
@abstractmethod
def init_weights(self, pretrained):
@abstractmethod
def init_bbox_head(self):
def init_bbox_head(self, bbox_roi_extractor: dict,
bbox_head: dict) -> None:
"""Initialize box head and box roi extractor.
@abstractmethod
def init_mask_head(self):
Args:
bbox_roi_extractor (dict or ConfigDict): Config of box
roi extractor.
bbox_head (dict or ConfigDict): Config of box in box head.
"""
self.bbox_roi_extractor = MODELS.build(bbox_roi_extractor)
self.bbox_head = MODELS.build(bbox_head)
@abstractmethod
def init_assigner_sampler(self):
"""Initialize assigner and sampler."""
self.bbox_assigner = None
self.bbox_sampler = None
if self.train_cfg:
if isinstance(self.train_cfg.assigner, dict):
self.bbox_assigner = TASK_UTILS.build(self.train_cfg.assigner)
elif isinstance(self.train_cfg.assigner, list):
self.bbox_assigner = [
TASK_UTILS.build(res) for res in self.train_cfg.assigner
]
self.bbox_sampler = TASK_UTILS.build(self.train_cfg.sampler)
@abstractmethod
def forward_train(self,
x,
img_metas,
proposal_list,
gt_bboxes,
gt_labels,
gt_bboxes_ignore=None,
**kwargs):
def simple_test(self,
x,
proposal_list,
img_metas,
proposals=None,
rescale=False,
**kwargs):
"""Test without augmentation."""
pass
def aug_test(self, x, proposal_list, img_metas, rescale=False, **kwargs):
"""Test with augmentations.
If rescale is False, then returned bboxes and masks will fit the scale
of imgs[0].
"""
def init_mask_head(self):
"""Initialize mask head, skip since ``PartAggregationROIHead`` does not
have one."""
pass
```
......@@ -290,87 +282,194 @@ class Base3DRoIHead(BaseModule, metaclass=ABCMeta):
接着将会对 bbox_forward 的逻辑进行修改,同时,bbox_forward 还会继承来自 `Base3DRoIHead` 的其他逻辑,在 `mmdet3d/models/roi_heads/part_aggregation_roi_head.py` 中,我们实现了新的 RoI Head,如下所示:
```python
from typing import Dict, List, Tuple
from mmcv import ConfigDict
from torch import Tensor
from torch.nn import functional as F
from mmdet3d.core import AssignResult
from mmdet3d.core.bbox import bbox3d2result, bbox3d2roi
from mmdet.core import build_assigner, build_sampler
from mmdet.models import HEADS
from ..builder import build_head, build_roi_extractor
from mmdet3d.registry import MODELS
from mmdet3d.structures import bbox3d2roi
from mmdet3d.utils import InstanceList
from mmdet.models.task_modules import AssignResult, SamplingResult
from ...structures.det3d_data_sample import SampleList
from .base_3droi_head import Base3DRoIHead
@HEADS.register_module()
@MODELS.register_module()
class PartAggregationROIHead(Base3DRoIHead):
"""Part aggregation roi head for PartA2.
Args:
semantic_head (ConfigDict): Config of semantic head.
num_classes (int): The number of classes.
seg_roi_extractor (ConfigDict): Config of seg_roi_extractor.
part_roi_extractor (ConfigDict): Config of part_roi_extractor.
bbox_roi_extractor (ConfigDict): Config of part_roi_extractor.
bbox_head (ConfigDict): Config of bbox_head.
train_cfg (ConfigDict): Training config.
test_cfg (ConfigDict): Testing config.
"""
def __init__(self,
semantic_head,
num_classes=3,
seg_roi_extractor=None,
part_roi_extractor=None,
bbox_head=None,
train_cfg=None,
test_cfg=None,
init_cfg=None):
semantic_head: dict,
num_classes: int = 3,
seg_roi_extractor: dict = None,
bbox_head: dict = None,
bbox_roi_extractor: dict = None,
train_cfg: dict = None,
test_cfg: dict = None,
init_cfg: dict = None) -> None:
super(PartAggregationROIHead, self).__init__(
bbox_head=bbox_head,
bbox_roi_extractor=bbox_roi_extractor,
train_cfg=train_cfg,
test_cfg=test_cfg,
init_cfg=init_cfg)
self.num_classes = num_classes
assert semantic_head is not None
self.semantic_head = build_head(semantic_head)
self.init_seg_head(seg_roi_extractor, semantic_head)
if seg_roi_extractor is not None:
self.seg_roi_extractor = build_roi_extractor(seg_roi_extractor)
if part_roi_extractor is not None:
self.part_roi_extractor = build_roi_extractor(part_roi_extractor)
def init_seg_head(self, seg_roi_extractor: dict,
semantic_head: dict) -> None:
"""Initialize semantic head and seg roi extractor.
self.init_assigner_sampler()
Args:
seg_roi_extractor (dict): Config of seg
roi extractor.
semantic_head (dict): Config of semantic head.
"""
self.semantic_head = MODELS.build(semantic_head)
self.seg_roi_extractor = MODELS.build(seg_roi_extractor)
@property
def with_semantic(self):
"""bool: whether the head has semantic branch"""
return hasattr(self,
'semantic_head') and self.semantic_head is not None
def predict(self,
feats_dict: Dict,
rpn_results_list: InstanceList,
batch_data_samples: SampleList,
rescale: bool = False,
**kwargs) -> InstanceList:
"""Perform forward propagation of the roi head and predict detection
results on the features of the upstream network.
def _bbox_forward(self, seg_feats, part_feats, voxels_dict, rois):
"""Forward function of roi_extractor and bbox_head used in both
training and testing.
Args:
seg_feats (torch.Tensor): Point-wise semantic features.
part_feats (torch.Tensor): Point-wise part prediction features.
voxels_dict (dict): Contains information of voxels.
rois (Tensor): Roi boxes.
feats_dict (dict): Contains features from the first stage.
rpn_results_list (List[:obj:`InstancesData`]): Detection results
of rpn head.
batch_data_samples (List[:obj:`Det3DDataSample`]): The Data
samples. It usually includes information such as
`gt_instance_3d`, `gt_panoptic_seg_3d` and `gt_sem_seg_3d`.
rescale (bool): If True, return boxes in original image space.
Defaults to False.
Returns:
dict: Contains predictions of bbox_head and
features of roi_extractor.
list[:obj:`InstanceData`]: Detection results of each sample
after the post process.
Each item usually contains following keys.
- scores_3d (Tensor): Classification scores, has a shape
(num_instances, )
- labels_3d (Tensor): Labels of bboxes, has a shape
(num_instances, ).
- bboxes_3d (BaseInstance3DBoxes): Prediction of bboxes,
contains a tensor with shape (num_instances, C), where
C >= 7.
"""
pooled_seg_feats = self.seg_roi_extractor(seg_feats,
voxels_dict['voxel_centers'],
voxels_dict['coors'][..., 0],
rois)
pooled_part_feats = self.part_roi_extractor(
part_feats, voxels_dict['voxel_centers'],
voxels_dict['coors'][..., 0], rois)
cls_score, bbox_pred = self.bbox_head(pooled_seg_feats,
pooled_part_feats)
bbox_results = dict(
cls_score=cls_score,
bbox_pred=bbox_pred,
pooled_seg_feats=pooled_seg_feats,
pooled_part_feats=pooled_part_feats)
return bbox_results
assert self.with_bbox, 'Bbox head must be implemented in PartA2.'
assert self.with_semantic, 'Semantic head must be implemented' \
' in PartA2.'
batch_input_metas = [
data_samples.metainfo for data_samples in batch_data_samples
]
voxels_dict = feats_dict.pop('voxels_dict')
# TODO: Split predict semantic and bbox
results_list = self.predict_bbox(feats_dict, voxels_dict,
batch_input_metas, rpn_results_list,
self.test_cfg)
return results_list
def predict_bbox(self, feats_dict: Dict, voxel_dict: Dict,
batch_input_metas: List[dict],
rpn_results_list: InstanceList,
test_cfg: ConfigDict) -> InstanceList:
"""Perform forward propagation of the bbox head and predict detection
results on the features of the upstream network.
Args:
feats_dict (dict): Contains features from the first stage.
voxel_dict (dict): Contains information of voxels.
batch_input_metas (list[dict], Optional): Batch image meta info.
Defaults to None.
rpn_results_list (List[:obj:`InstancesData`]): Detection results
of rpn head.
test_cfg (Config): Test config.
Returns:
list[:obj:`InstanceData`]: Detection results of each sample
after the post process.
Each item usually contains following keys.
- scores_3d (Tensor): Classification scores, has a shape
(num_instances, )
- labels_3d (Tensor): Labels of bboxes, has a shape
(num_instances, ).
- bboxes_3d (BaseInstance3DBoxes): Prediction of bboxes,
contains a tensor with shape (num_instances, C), where
C >= 7.
"""
...
def loss(self, feats_dict: Dict, rpn_results_list: InstanceList,
batch_data_samples: SampleList, **kwargs) -> dict:
"""Perform forward propagation and loss calculation of the detection
roi on the features of the upstream network.
Args:
feats_dict (dict): Contains features from the first stage.
rpn_results_list (List[:obj:`InstancesData`]): Detection results
of rpn head.
batch_data_samples (List[:obj:`Det3DDataSample`]): The Data
samples. It usually includes information such as
`gt_instance_3d`, `gt_panoptic_seg_3d` and `gt_sem_seg_3d`.
Returns:
dict[str, Tensor]: A dictionary of loss components
"""
assert len(rpn_results_list) == len(batch_data_samples)
losses = dict()
batch_gt_instances_3d = []
batch_gt_instances_ignore = []
voxels_dict = feats_dict.pop('voxels_dict')
for data_sample in batch_data_samples:
batch_gt_instances_3d.append(data_sample.gt_instances_3d)
if 'ignored_instances' in data_sample:
batch_gt_instances_ignore.append(data_sample.ignored_instances)
else:
batch_gt_instances_ignore.append(None)
if self.with_semantic:
semantic_results = self._semantic_forward_train(
feats_dict, voxels_dict, batch_gt_instances_3d)
losses.update(semantic_results.pop('loss_semantic'))
sample_results = self._assign_and_sample(rpn_results_list,
batch_gt_instances_3d)
if self.with_bbox:
feats_dict.update(semantic_results)
bbox_results = self._bbox_forward_train(feats_dict, voxels_dict,
sample_results)
losses.update(bbox_results['loss_bbox'])
return losses
```
此处我们省略了与其他功能相关的细节请参考 [此处](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/models/roi_heads/part_aggregation_roi_head.py) 获取更多细节。
此处我们省略相关函数的更多细节请参考[代码](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/models/roi_heads/part_aggregation_roi_head.py)了解更多细节。
最后,用户需要在 `mmdet3d/models/bbox_heads/__init__.py``mmdet3d/models/roi_heads/__init__.py` 中添加模块,使得对应的注册器能够发现并加载该模块
最后,用户需要在 `mmdet3d/models/bbox_heads/__init__.py``mmdet3d/models/roi_heads/__init__.py` 中添加模块以确保相应的注册器能够找到并加载它们
此外,用户也可以添加以下的代码到配置文件中,从而实现相同的目标。
......@@ -379,7 +478,7 @@ custom_imports=dict(
imports=['mmdet3d.models.roi_heads.part_aggregation_roi_head', 'mmdet3d.models.roi_heads.bbox_heads.parta2_bbox_head'])
```
PartAggregationROIHead 的配置文件如下所示
PartAggregationROIHead 的配置文件如下所示
```python
model = dict(
......@@ -394,14 +493,16 @@ model = dict(
seg_score_thr=0.3,
num_classes=3,
loss_seg=dict(
type='FocalLoss',
type='mmdet.FocalLoss',
use_sigmoid=True,
reduction='sum',
gamma=2.0,
alpha=0.25,
loss_weight=1.0),
loss_part=dict(
type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)),
type='mmdet.CrossEntropyLoss',
use_sigmoid=True,
loss_weight=1.0)),
seg_roi_extractor=dict(
type='Single3DRoIAwareExtractor',
roi_layer=dict(
......@@ -409,7 +510,7 @@ model = dict(
out_size=14,
max_pts_per_voxel=128,
mode='max')),
part_roi_extractor=dict(
bbox_roi_extractor=dict(
type='Single3DRoIAwareExtractor',
roi_layer=dict(
type='RoIAwarePool3d',
......@@ -433,15 +534,15 @@ model = dict(
roi_feat_size=14,
with_corner_loss=True,
loss_bbox=dict(
type='SmoothL1Loss',
type='mmdet.SmoothL1Loss',
beta=1.0 / 9.0,
reduction='sum',
loss_weight=1.0),
loss_cls=dict(
type='CrossEntropyLoss',
type='mmdet.CrossEntropyLoss',
use_sigmoid=True,
reduction='sum',
loss_weight=1.0)))
loss_weight=1.0))),
...
)
```
......@@ -459,8 +560,8 @@ PartA2 Head 的第二阶段主要使用新建的 `PartAggregationROIHead` 和 `P
import torch
import torch.nn as nn
from ..builder import LOSSES
from .utils import weighted_loss
from mmdet3d.registry import MODELS
from mmdet.models.losses.utils import weighted_loss
@weighted_loss
def my_loss(pred, target):
......@@ -468,7 +569,7 @@ def my_loss(pred, target):
loss = torch.abs(pred - target)
return loss
@LOSSES.register_module()
@MODELS.register_module()
class MyLoss(nn.Module):
def __init__(self, reduction='mean', loss_weight=1.0):
......
# 教程 5: 自定义运行时配置
# 自定义运行时配置
## 自定义优化器设置
优化器相关的配置是由 `optim_wrapper` 管理的,其通常有三个字段:`optimizer``paramwise_cfg``clip_grad`。更多细节请参考 [OptimWrapper](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html)。如下所示,使用 `AdamW` 作为`优化器`,骨干网络的学习率降低 10 倍,并添加了梯度裁剪。
```python
optim_wrapper = dict(
type='OptimWrapper',
# 优化器
optimizer=dict(
type='AdamW',
lr=0.0001,
weight_decay=0.05,
eps=1e-8,
betas=(0.9, 0.999)),
# 参数级学习率及权重衰减系数设置
paramwise_cfg=dict(
custom_keys={
'backbone': dict(lr_mult=0.1, decay_mult=1.0),
},
norm_decay_mult=0.0),
# 梯度裁剪
clip_grad=dict(max_norm=0.01, norm_type=2))
```
### 自定义 PyTorch 支持的优化器
我们已经支持使用所有 PyTorch 实现的优化器,且唯一需要修改的地方就是改变配置文件中的 `optimizer` 字段。
举个例子,如果您想使用 `ADAM` (注意到这样可能会使性能大幅下降),您可以这样修改:
我们已经支持使用所有 PyTorch 实现的优化器,且唯一需要修改的地方就是改变配置文件中的 `optim_wrapper` 字段中的 `optimizer` 字段。例如,如果您想使用 `ADAM`(注意到这样可能会使性能大幅下降),您可以这样修改:
```python
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
optim_wrapper = dict(
type='OptimWrapper',
optimizer=dict(type='Adam', lr=0.0003, weight_decay=0.0001))
```
为了修改模型的学习率,用户只需要修改优化器配置中的 `lr` 字段。用户可以根据 PyTorch 的 [API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) 直接设置参数。
为了修改模型的学习率,用户只需要修改 `optimizer` 中的 `lr` 字段。用户可以根据 PyTorch 的 [API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim)直接设置参数。
### 自定义并实现优化器
......@@ -19,239 +44,265 @@ optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
一个自定义优化器可以按照如下过程定义:
假设您想要添加一个叫 `MyOptimizer` 的,拥有参数 `a``b``c` 的优化器,您需要创建一个叫做 `mmdet3d/core/optimizer` 的目录。
接下来,应该在目录下某个文件中实现新的优化器,比如 `mmdet3d/core/optimizer/my_optimizer.py`
假设您想要添加一个叫 `MyOptimizer` 的,拥有参数 `a``b``c` 的优化器,您需要创建一个叫做 `mmdet3d/engine/optimizers` 的目录。接下来,应该在目录下某个文件中实现新的优化器,比如 `mmdet3d/engine/optimizers/my_optimizer.py`
```python
from mmcv.runner.optimizer import OPTIMIZERS
from mmdet3d.registry import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c)
def __init__(self, a, b, c):
```
#### 2. 将优化器添加到注册器
为了找到上述定义的优化器模块,该模块首先需要被引入主命名空间。有两种方法实现之:
- 新建 `mmdet3d/core/optimizer/__init__.py` 文件用于引入。
新定义的模块应该在 `mmdet3d/core/optimizer/__init__.py` 中被引入,使得注册器可以找到新模块并注册之:
为了找到上述定义的优化器模块,该模块首先需要被引入主命名空间。有两种实现方法:
```python
from .my_optimizer import MyOptimizer
__all__ = ['MyOptimizer']
- 修改 `mmdet3d/engine/optimizers/__init__.py` 导入该模块。
```
您也需要通过添加如下语句在 `mmdet3d/core/__init__.py` 中引入 `optimizer`
新定义的模块应该在 `mmdet3d/engine/optimizers/__init__.py` 中被导入,从而被找到并且被添加到注册器中:
```python
from .optimizer import *
```
```python
from .my_optimizer import MyOptimizer
```
或者在配置中使用 `custom_imports` 来人工入新优化器:
- 在配置中使用 `custom_imports` 来人工入新优化器:
```python
custom_imports = dict(imports=['mmdet3d.core.optimizer.my_optimizer'], allow_failed_imports=False)
```
```python
custom_imports = dict(imports=['mmdet3d.engine.optimizers.my_optimizer'], allow_failed_imports=False)
```
模块 `mmdet3d.core.optimizer.my_optimizer` 会在程序伊始被引入,且 `MyOptimizer` 类在那时会自动被注册。
注意到只有包含 `MyOptimizer` 类的包应该被引入。
`mmdet3d.core.optimizer.my_optimizer.MyOptimizer` **不能** 被直接引入。
模块 `mmdet3d.engine.optimizers.my_optimizer` 会在程序开始被导入,且 `MyOptimizer` 类在那时会自动被注册。注意到应该只有包含 `MyOptimizer` 类的包被导入。`mmdet3d.engine.optimizers.my_optimizer.MyOptimizer`**不能**被直接导入。
事实上,用户可以在这种入的方法中使用完全不同的文件目录结构,只要保证根目录能在 `PYTHONPATH` 中被定位。
事实上,用户可以在这种入的方法中使用完全不同的文件目录结构,只要保证根目录能在 `PYTHONPATH` 中被定位。
#### 3. 在配置文件中指定优化器
接下来您可以在配置文件的 `optimizer` 字段中使用 `MyOptimizer`
在配置文件中,优化器在 `optimizer` 字段中以如下方式定义:
接下来您可以在配置文件的 `optimizer` 字段中使用 `MyOptimizer`。在配置文件中,优化器在 `optimizer` 字段中以如下方式定义:
```python
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optim_wrapper = dict(
type='OptimWrapper',
optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001))
```
为了使用您自己的优化器,该字段可以改为:
```python
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
optim_wrapper = dict(
type='OptimWrapper',
optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value))
```
### 自定义优化器构造器
### 自定义优化器封装构造器
部分模型可能会拥有一些参数专属的优化器设置,比如 BatchNorm 层的权重衰减 (weight decay)。
用户可以通过自定义优化器的构造器来对那些细粒度的参数进行调优。
部分模型可能会拥有一些参数专属的优化器设置,比如 BatchNorm 层的权重衰减 (weight decay)。用户可以通过自定义优化器封装构造器来对那些细粒度的参数进行调优。
```python
from mmcv.utils import build_from_cfg
from mmengine.optim import DefaultOptiWrapperConstructor
from mmcv.runner.optimizer import OPTIMIZER_BUILDERS, OPTIMIZERS
from mmdet.utils import get_root_logger
from mmdet3d.registry import OPTIM_WRAPPER_CONSTRUCTORS
from .my_optimizer import MyOptimizer
@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor(object):
@OPTIM_WRAPPER_CONSTRUCTORS.register_module()
class MyOptimizerWrapperConstructor(DefaultOptimWrapperConstructor):
def __init__(self, optimizer_cfg, paramwise_cfg=None):
def __init__(self,
optim_wrapper_cfg: dict,
paramwise_cfg: Optional[dict] = None):
def __call__(self, model):
def __call__(self, model: nn.Module) -> OptimWrapper:
return my_optimizer
return optim_wrapper
```
默认优化器构造器在[这里](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/optimizer/default_constructor.py#L11)实现。这部分代码也可以用作新优化器构造器的模版。
默认优化器封装构造器在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/optimizer/default_constructor.py#L18)实现。这部分代码也可以用作新优化器封装构造器的模版。
### 额外的设置
没有在优化器部分实现的技巧应该通过优化器构造器或者钩子来实现 (比如逐参数的学习率设置)。我们列举了一些常用的可以稳定训练过程或者加速训练的设置。我们欢迎提供更多类似设置的 PR 和 issue。
没有在优化器部分实现的技巧应该通过优化器封装构造器或者钩子来实现(比如逐参数的学习率设置)。我们列举了一些常用的可以稳定训练过程或者加速训练的设置。我们欢迎提供更多类似设置的 PR 和 issue。
- __使用梯度裁剪 (gradient clip) 来稳定训练过程__:
一些模型依赖梯度裁剪技术来裁剪训练中的梯度,以稳定训练过程。举例如下:
```python
optimizer_config = dict(
_delete_=True, grad_clip=dict(max_norm=35, norm_type=2))
optim_wrapper = dict(
_delete_=True, clip_grad=dict(max_norm=35, norm_type=2))
```
如果您的配置继承了一个已经设置了 `optimizer_config` 的基础配置,那么您可能需要 `_delete_=True` 字段来覆盖基础配置中无用的设置。详见配置文件的[说明文档](https://mmdetection.readthedocs.io/zh_CN/latest/tutorials/config.html)
如果您的配置继承了一个已经设置了 `optim_wrapper` 的基础配置,那么您可能需要 `_delete_=True` 字段来覆盖基础配置中无用的设置。更多细节请参考[说明文档](https://mmdetection3d.readthedocs.io/en/latest/tutorials/config.html)
- __使用动量规划器 (momentum scheduler) 来加速模型收敛__:
- __使用动量调度器 (momentum scheduler) 来加速模型收敛__:
我们支持用动量规划器来根据学习率更改模型的动量,这样可以使模型更快地收敛。
动量规划器通常和学习率规划器一起使用,比如说,如下配置文件在 3D 检测中被用于加速模型收敛。
更多细节详见 [CyclicLrUpdater](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/hooks/lr_updater.py#L358)[CyclicMomentumUpdater](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/hooks/momentum_updater.py#L225) 的实现。
我们支持用动量调度器来根据学习率更改模型的动量,这样可以使模型更快地收敛。动量调度器通常和学习率调度器一起使用,例如,如下配置文件在 [3D 检测](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/schedules/cyclic_20e.py)中被用于加速模型收敛。更多细节请参考 [CosineAnnealingLR](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L43)[CosineAnnealingMomentum](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/momentum_scheduler.py#L71) 的实现方法。
```python
lr_config = dict(
policy='cyclic',
target_ratio=(10, 1e-4),
cyclic_times=1,
step_ratio_up=0.4,
)
momentum_config = dict(
policy='cyclic',
target_ratio=(0.85 / 0.95, 1),
cyclic_times=1,
step_ratio_up=0.4,
)
param_scheduler = [
# 学习率调度器
# 在前 8 个 epoch,学习率从 0 升到 lr * 10
# 在接下来 12 个 epoch,学习率从 lr * 10 降到 lr * 1e-4
dict(
type='CosineAnnealingLR',
T_max=8,
eta_min=lr * 10,
begin=0,
end=8,
by_epoch=True,
convert_to_iter_based=True),
dict(
type='CosineAnnealingLR',
T_max=12,
eta_min=lr * 1e-4,
begin=8,
end=20,
by_epoch=True,
convert_to_iter_based=True),
# 动量调度器
# 在前 8 个 epoch,动量从 0 升到 0.85 / 0.95
# 在接下来 12 个 epoch,动量从 0.85 / 0.95 升到 1
dict(
type='CosineAnnealingMomentum',
T_max=8,
eta_min=0.85 / 0.95,
begin=0,
end=8,
by_epoch=True,
convert_to_iter_based=True),
dict(
type='CosineAnnealingMomentum',
T_max=12,
eta_min=1,
begin=8,
end=20,
by_epoch=True,
convert_to_iter_based=True)
]
```
## 自定义训练规程
## 自定义训练调度
默认情况,我们使用阶梯式学习率衰减的 1 倍训练规程。这会调用 `MMCV` 中的 [`StepLRHook`](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/hooks/lr_updater.py#L167)
我们在[这里](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/hooks/lr_updater.py)支持很多其他学习率规划方案,比如`余弦退火``多项式衰减`规程。下面是一些样例:
默认情况下我们使用阶梯式学习率衰减的 1 倍训练调度。这会调用 MMEngine 中的 [`MultiStepLR`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py#L139)。我们在[这里](https://github.com/open-mmlab/mmengine/blob/main/mmengine/optim/scheduler/lr_scheduler.py)支持了很多其他学习率调度,比如`余弦退火``多项式衰减`调度。下面是一些样例:
- 多项式衰减规程:
- 多项式衰减调度:
```python
lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
param_scheduler = [
dict(
type='PolyLR',
power=0.9,
eta_min=1e-4,
begin=0,
end=8,
by_epoch=True)]
```
- 余弦退火规程:
- 余弦退火调度:
```python
lr_config = dict(
policy='CosineAnnealing',
warmup='linear',
warmup_iters=1000,
warmup_ratio=1.0 / 10,
min_lr_ratio=1e-5)
param_scheduler = [
dict(
type='CosineAnnealingLR',
T_max=8,
eta_min=lr * 1e-5,
begin=0,
end=8,
by_epoch=True)]
```
## 自定义工作流
## 自定义训练循环控制器
工作流是一个(阶段,epoch 数)的列表,用于指定不同阶段运行顺序和运行的 epoch 数。
默认情况它被设置为:
默认情况下,我们在 `train_cfg` 中使用 `EpochBasedTrainLoop`,并在每一个训练 epoch 完成后进行一次验证,如下所示:
```python
workflow = [('train', 1)]
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1)
```
这意味着,工作流包括训练 1 个 epoch。
有时候用户可能想要检查一些模型在验证集上的评估指标(比如损失、准确率)。
在这种情况中,我们可以将工作流设置如下:
事实上,[`IterBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L183%5D)[`EpochBasedTrainLoop`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/runner/loops.py#L18) 都支持动态间隔验证,如下所示:
```python
[('train', 1), ('val', 1)]
# 在第 365001 次迭代之前,我们每隔 5000 次迭代验证一次
# 在第 365000 次迭代之后,我们每隔 368750 次迭代验证一次
# 这意味着我们在训练结束后进行验证。
interval = 5000
max_iters = 368750
dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)]
train_cfg = dict(
type='IterBasedTrainLoop',
max_iters=max_iters,
val_interval=interval,
dynamic_intervals=dynamic_intervals)
```
这样,就是交替地运行 1 个 epoch 进行训练,1 个 epoch 进行验证。
**请注意**:
1. 模型参数在验证期间不会被更新。
2. 配置文件中,`runner` 里的 `max_epochs` 字段只控制训练 epoch 的数量,而不会影响验证工作流。
3. `[('train', 1), ('val', 1)]``[('train', 1)]` 工作流不会改变 `EvalHook` 的行为,这是因为 `EvalHook``after_train_epoch` 调用,且验证工作流只会影响通过 `after_val_epoch` 调用的钩子。因此,`[('train', 1), ('val', 1)]``[('train', 1)]` 的唯一区别就是执行器 (runner) 会在每个训练 epoch 之后在验证集上计算损失。
## 自定义钩子
### 自定义并实现钩子
#### 1. 实现一个新钩子
存在一些情况下用户可能需要实现新钩子。在版本 v2.3.0 之后,MMDetection 支持自定义训练过程中的钩子 (#3395)。因此用户可以直接在 mmdet 中,或者在其基于 mmdet 的代码库中实现钩子并通过更改训练配置来使用钩子。
在 v2.3.0 之前,用户需要更改代码以使得训练开始之前钩子已经注册完毕。
这里我们给出一个,在 mmdet3d 中创建并使用新钩子的例子。
MMEngine 提供了一些实用的[钩子](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/hook.md),但有些场合用户可能需要实现一个新的钩子。在 v1.1.0rc0 之后,MMDetection3D 在训练时支持基于 MMEngine 自定义钩子。因此用户可以直接在 mmdet3d 或者基于 mmdet3d 的代码库中实现钩子并通过更改训练配置来使用钩子。这里我们给出一个在 mmdet3d 中创建并使用新钩子的例子。
```python
from mmcv.runner import HOOKS, Hook
from mmengine.hooks import Hook
from mmdet3d.registry import HOOKS
@HOOKS.register_module()
class MyHook(Hook):
def __init__(self, a, b):
pass
def before_run(self, runner):
pass
def before_run(self, runner) -> None:
def after_run(self, runner):
pass
def after_run(self, runner) -> None:
def before_epoch(self, runner):
pass
def before_train(self, runner) -> None:
def after_epoch(self, runner):
pass
def after_train(self, runner) -> None:
def before_iter(self, runner):
pass
def before_train_epoch(self, runner) -> None:
def after_iter(self, runner):
pass
```
def after_train_epoch(self, runner) -> None:
取决于钩子的功能,用户需要指定钩子在每个训练阶段时的行为,具体包括如下阶段:`before_run``after_run``before_epoch``after_epoch``before_iter`,和 `after_iter`
def before_train_iter(self,
runner,
batch_idx: int,
data_batch: DATA_BATCH = None) -> None:
#### 2. 注册新钩子
def after_train_iter(self,
runner,
batch_idx: int,
data_batch: DATA_BATCH = None,
outputs: Optional[dict] = None) -> None:
```
接下来我们需要引入 `MyHook`。假设新钩子位于文件 `mmdet3d/core/utils/my_hook.py` 中,有两种方法可以实现之:
用户需要根据钩子的功能指定钩子在每个训练阶段时的行为,具体包括如下阶段:`before_run``after_run``before_train``after_train``before_train_epoch``after_train_epoch``before_train_iter`,和 `after_train_iter`。有更多的位点可以插入钩子,详情可参考 [base hook class](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/hook.py#L9)
- 更改 `mmdet3d/core/utils/__init__.py` 来引入之:
#### 2. 注册新钩子
新定义的模块应在 `mmdet3d/core/utils/__init__.py` 中引入,以使得注册器可以找到新模块并注册之
接下来我们需要导入 `MyHook`。假设新钩子位于文件 `mmdet3d/engine/hooks/my_hook.py` 中,有两种实现方法
```python
from .my_hook import MyHook
- 修改 `mmdet3d/engine/hooks/__init__.py` 导入该模块:
__all__ = [..., 'MyHook']
新定义的模块应该在 `mmdet3d/engine/hooks/__init__.py` 中被导入,从而被找到并且被添加到注册器中:
```
```python
from .my_hook import MyHook
```
或者在配置中使用 `custom_imports` 来人为地引入之
- 在配置中使用 `custom_imports` 来人为地导入新钩子:
```python
custom_imports = dict(imports=['mmdet3d.core.utils.my_hook'], allow_failed_imports=False)
```
```python
custom_imports = dict(imports=['mmdet3d.engine.hooks.my_hook'], allow_failed_imports=False)
```
#### 3. 更改配置文件
......@@ -261,7 +312,7 @@ custom_hooks = [
]
```
您可以将字段 `priority` 设置为 `'NORMAL'` 或者 `'HIGHEST'`来设置钩子的优先级,如下所示:
您可以将字段 `priority` 设置为 `'NORMAL'` 或者 `'HIGHEST'` 来设置钩子的优先级,如下所示:
```python
custom_hooks = [
......@@ -269,62 +320,41 @@ custom_hooks = [
]
```
默认情况,注册阶段钩子的优先级被设置`NORMAL`
默认情况,注册阶段钩子的优先级为 `NORMAL`
### 使用 MMCV 中实现的钩子
### 使用 MMEngine 中实现的钩子
如果钩子已经在 MMCV 中被实现了,您可以直接通过更改配置文件来使用该钩子:
```python
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]
```
如果钩子已经在 MMEngine 中被实现了,您可以直接通过更改配置文件来使用该钩子:
### 更改默认的运行时钩子
有一些常用的钩子并没有通过 `custom_hooks` 注册,它们是:
- 日志配置 (log_config)
- 检查点配置 (checkpoint_config)
- 评估 (evaluation)
- 学习率配置 (lr_config)
- 优化器配置 (optimizer_config)
- 动量配置 (momentum_config)
有一些常用的钩子通过 `default_hooks` 注册,它们是:
在这些钩子中,只有日志钩子拥有 `VERY_LOW` 的优先级,其他钩子的优先级均为 `NORMAL`
上述教程已经涉及了如何更改 `optimizer_config``momentum_config`,和 `lr_config`
下面我们展示如何在 `log_config``checkpoint_config`,和 `evaluation` 上做文章。
#### 检查点配置
MMCV 执行器会使用 `checkpoint_config` 来初始化 [`CheckpointHook`](https://github.com/open-mmlab/mmcv/blob/v1.3.7/mmcv/runner/hooks/checkpoint.py#L9)
```python
checkpoint_config = dict(interval=1)
```
- `IterTimerHook`:该钩子用来记录加载数据的时间 'data_time' 和模型训练一步的时间 'time' 。
- `LoggerHook`:该钩子用来从`执行器(Runner)`的不同组件收集日志并将其写入终端,Json 文件,tensorboard 和 wandb 等。
- `ParamSchedulerHook`:该钩子用来更新优化器中的一些超参数,例如学习率和动量。
- `CheckpointHook`:该钩子用来定期地保存检查点。
- `DistSamplerSeedHook`:该钩子用来设置采样和批采样的种子。
用户可以设置 `max_keep_ckpts` 来保存一定少量的检查点,或者用 `save_optimizer` 来决定是否保存优化器的状态。更多参数的细节详见[这里](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.CheckpointHook)
`IterTimerHook``ParamSchedulerHook``DistSamplerSeedHook` 都很简单,通常不需要修改,因此此处我们将介绍如何使用 `LoggerHook``CheckpointHook``Det3DVisualizationHook`
#### 日志配置
#### CheckpointHook
`log_config` 将多个日志钩子封装在一起,并允许设置日志记录间隔。现在 MMCV 支持 `WandbLoggerHook``MlflowLoggerHook`,和 `TensorboardLoggerHook`
更详细的使用方法请移步 [MMCV 文档](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.LoggerHook)
除了定期地保存检查点,[`CheckpointHook`](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19) 提供了其它的可选项例如 `max_keep_ckpts``save_optimizer` 等。用户可以设置 `max_keep_ckpts` 只保存少量的检查点或者通过 `save_optimizer` 决定是否保存优化器的状态。参数的更多细节参考[此处](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/checkpoint_hook.py#L19)
```python
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
dict(type='TensorboardLoggerHook')
])
default_hooks = dict(
checkpoint=dict(
type='CheckpointHook',
interval=1,
max_keep_ckpts=3,
save_optimizer=True))
```
#### 评估配置
#### LoggerHook
`evaluation` 的配置会被用于初始化 [`EvalHook`](https://github.com/open-mmlab/mmdetection/blob/v2.13.0/mmdet/core/evaluation/eval_hooks.py#L9)
除了 `interval` 字段,其他参数,比如 `metric`,会被传递给 `dataset.evaluate()`
`LoggerHook` 允许设置日志记录间隔。详细介绍可参考[文档](https://github.com/open-mmlab/mmengine/blob/main/mmengine/hooks/logger_hook.py#L18)
```python
evaluation = dict(interval=1, metric='bbox')
default_hooks = dict(logger=dict(type='LoggerHook', interval=50))
```
......@@ -2,11 +2,9 @@
本页提供了有关在 MMDetection3D 中使用 KITTI 数据集的具体教程。
**注意**:此教程目前仅适用于基于雷达和多模态的 3D 目标检测的相关方法,与基于单目图像的 3D 目标检测相关的内容会在之后进行补充。
## 数据准备
您可以在[这里](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d)下载 KITTI 3D 检测数据并解压缩所有 zip 文件。此外,您可以在[这里](https://download.openmmlab.com/mmdetection3d/data/train_planes.zip)下载道路平面信息,其在训练过程中作为一个可选项,用来提高模型的性能。道路平面信息由 [AVOD](https://github.com/kujason/avod) 生成,你可以在[这里](https://github.com/kujason/avod/issues/19)查看更多细节
您可以在[这里](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d)下载 KITTI 3D 检测数据并解压缩所有 zip 文件。此外,您可以在[这里](https://download.openmmlab.com/mmdetection3d/data/train_planes.zip)下载道路平面信息,其在训练过程中作为一个可选项,用来提高模型的性能。道路平面信息由 [AVOD](https://github.com/kujason/avod) 生成,更多细节请参考[此处](https://github.com/kujason/avod/issues/19)
像准备数据集的一般方法一样,建议将数据集根目录链接到 `$MMDETECTION3D/data`
......@@ -39,7 +37,7 @@ mmdetection3d
```bash
mkdir ./data/kitti/ && mkdir ./data/kitti/ImageSets
# Download data split
# 下载数据划分
wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/second/data/ImageSets/test.txt --no-check-certificate --content-disposition -O ./data/kitti/ImageSets/test.txt
wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/second/data/ImageSets/train.txt --no-check-certificate --content-disposition -O ./data/kitti/ImageSets/train.txt
wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/second/data/ImageSets/val.txt --no-check-certificate --content-disposition -O ./data/kitti/ImageSets/val.txt
......@@ -48,7 +46,7 @@ wget -c https://raw.githubusercontent.com/traveller59/second.pytorch/master/sec
python tools/create_data.py kitti --root-path ./data/kitti --out-dir ./data/kitti --extra-tag kitti --with-plane
```
需要注意的是,如果您的本地磁盘没有充足的存储空间来存储转换后的数据,您可以通过改变 `out-dir` 来指定其他任意的存储路径。如果您没有准备 `planes` 数据,您需要移除 `--with-plane` 标志。
需要注意的是,如果您的本地磁盘没有充足的存储空间来存储转换后的数据,您可以通过改变 `--out-dir` 来指定其他任意的存储路径。如果您没有准备 `planes` 数据,您需要移除 `--with-plane` 标志。
处理后的文件夹结构应该如下:
......@@ -78,39 +76,40 @@ kitti
├── kitti_dbinfos_train.pkl
├── kitti_infos_test.pkl
├── kitti_infos_trainval.pkl
├── kitti_infos_train_mono3d.coco.json
├── kitti_infos_trainval_mono3d.coco.json
├── kitti_infos_test_mono3d.coco.json
├── kitti_infos_val_mono3d.coco.json
```
其中的各项文件的含义如下所示:
- `kitti_gt_database/xxxxx.bin`: 训练数据集中包含在 3D 标注框中的点云数据
- `kitti_infos_train.pkl`:训练数据集的信息,其中每一帧的信息包含下面的内容:
- info\['point_cloud'\]: {'num_features': 4, 'velodyne_path': velodyne_path}.
- info\['annos'\]: {
- 位置:其中 x,y,z 为相机参考坐标系下的目标的底部中心(单位为米),是一个尺寸为 Nx3 的数组
- 维度: 目标的高、宽、长(单位为米),是一个尺寸为 Nx3 的数组
- 旋转角:相机坐标系下目标绕着 Y 轴的旋转角 ry,其取值范围为 \[-pi..pi\] ,是一个尺寸为 N 的数组
- 名称:标准框所包含的目标的名称,是一个尺寸为 N 的数组
- 困难度:kitti 官方所定义的困难度,包括 简单,适中,困难
- 组别标识符:用于多部件的目标
}
- (optional) info\['calib'\]: {
- P0:校对后的 camera0 投影矩阵,是一个 3x4 数组
- P1:校对后的 camera1 投影矩阵,是一个 3x4 数组
- P2:校对后的 camera2 投影矩阵,是一个 3x4 数组
- P3:校对后的 camera3 投影矩阵,是一个 3x4 数组
- R0_rect:校准旋转矩阵,是一个 4x4 数组
- Tr_velo_to_cam:从 Velodyne 坐标到相机坐标的变换矩阵,是一个 4x4 数组
- Tr_imu_to_velo:从 IMU 坐标到 Velodyne 坐标的变换矩阵,是一个 4x4 数组
}
- (optional) info\['image'\]:{'image_idx': idx, 'image_path': image_path, 'image_shape', image_shape}.
**注意**:其中的 info\['annos'\] 中的数据均位于相机参考坐标系中,更多的细节请参考[此处](http://www.cvlibs.net/publications/Geiger2013IJRR.pdf)
获取 kitti_infos_xxx.pkl 和 kitti_infos_xxx_mono3d.coco.json 的核心函数分别为 [get_kitti_image_info](https://github.com/open-mmlab/mmdetection3d/blob/7873c8f62b99314f35079f369d1dab8d63f8a3ce/tools/data_converter/kitti_data_utils.py#L140)[get_2d_boxes](https://github.com/open-mmlab/mmdetection3d/blob/7873c8f62b99314f35079f369d1dab8d63f8a3ce/tools/data_converter/kitti_converter.py#L378).
- `kitti_gt_database/xxxxx.bin`:训练数据集中包含在 3D 标注框中的点云数据。
- `kitti_infos_train.pkl`:训练数据集,该字典包含了两个键值:`metainfo``data_list``metainfo` 包含数据集的基本信息,例如 `categories`, `dataset``info_version``data_list` 是由字典组成的列表,每个字典(以下简称 `info`)包含了单个样本的所有详细信息。
- info\['sample_idx'\]:该样本在整个数据集的索引。
- info\['images'\]:多个相机捕获的图像信息。是一个字典,包含 5 个键值:`CAM0`, `CAM1`, `CAM2`, `CAM3`, `R0_rect`
- info\['images'\]\['R0_rect'\]:校准旋转矩阵,是一个 4x4 数组。
- info\['images'\]\['CAM2'\]:包含 `CAM2` 相机传感器的信息。
- info\['images'\]\['CAM2'\]\['img_path'\]:图像的文件名。
- info\['images'\]\['CAM2'\]\['height'\]:图像的高。
- info\['images'\]\['CAM2'\]\['width'\]:图像的宽。
- info\['images'\]\['CAM2'\]\['cam2img'\]:相机到图像的变换矩阵,是一个 4x4 数组。
- info\['images'\]\['CAM2'\]\['lidar2cam'\]:激光雷达到相机的变换矩阵,是一个 4x4 数组。
- info\['images'\]\['CAM2'\]\['lidar2img'\]:激光雷达到图像的变换矩阵,是一个 4x4 数组。
- info\['lidar_points'\]:是一个字典,包含了激光雷达点相关的信息。
- info\['lidar_points'\]\['lidar_path'\]:激光雷达点云数据的文件名。
- info\['lidar_points'\]\['num_pts_feats'\]:点的特征维度。
- info\['lidar_points'\]\['Tr_velo_to_cam'\]:Velodyne 坐标到相机坐标的变换矩阵,是一个 4x4 数组。
- info\['lidar_points'\]\['Tr_imu_to_velo'\]:IMU 坐标到 Velodyne 坐标的变换矩阵,是一个 4x4 数组。
- info\['instances'\]:是一个字典组成的列表。每个字典包含单个实例的所有标注信息。对于其中的第 i 个实例,我们有:
- info\['instances'\]\[i\]\['bbox'\]:长度为 4 的列表,以 (x1, y1, x2, y2) 的顺序表示实例的 2D 边界框。
- info\['instances'\]\[i\]\['bbox_3d'\]:长度为 7 的列表,以 (x, y, z, l, h, w, yaw) 的顺序表示实例的 3D 边界框。
- info\['instances'\]\[i\]\['bbox_label'\]:是一个整数,表示实例的 2D 标签,-1 代表忽略。
- info\['instances'\]\[i\]\['bbox_label_3d'\]:是一个整数,表示实例的 3D 标签,-1 代表忽略。
- info\['instances'\]\[i\]\['depth'\]:3D 边界框投影到相关图像平面的中心点的深度。
- info\['instances'\]\[i\]\['num_lidar_pts'\]:3D 边界框内的激光雷达点数。
- info\['instances'\]\[i\]\['center_2d'\]:3D 边界框投影的 2D 中心。
- info\['instances'\]\[i\]\['difficulty'\]:KITTI 官方定义的困难度,包括简单、适中、困难。
- info\['instances'\]\[i\]\['truncated'\]:从 0(非截断)到 1(截断)的浮点数,其中截断指的是离开检测图像边界的检测目标。
- info\['instances'\]\[i\]\['occluded'\]:整数 (0,1,2,3) 表示目标的遮挡状态:0 = 完全可见,1 = 部分遮挡,2 = 大面积遮挡,3 = 未知。
- info\['instances'\]\[i\]\['group_ids'\]:用于多部分的物体。
- info\['plane'\](可选):地平面信息。
更多细节请参考 [kitti_converter.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/kitti_converter.py)[update_infos_to_v2.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/update_infos_to_v2.py)
## 训练流程
......@@ -122,13 +121,9 @@ train_pipeline = [
type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=4, # x, y, z, intensity
use_dim=4, # x, y, z, intensity
file_client_args=file_client_args),
use_dim=4),
dict(
type='LoadAnnotations3D',
with_bbox_3d=True,
with_label_3d=True,
file_client_args=file_client_args),
type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(type='ObjectSample', db_sampler=db_sampler),
dict(
type='ObjectNoise',
......@@ -144,8 +139,9 @@ train_pipeline = [
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='PointShuffle'),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
```
......@@ -159,12 +155,12 @@ train_pipeline = [
使用 8 个 GPU 以及 KITTI 指标评估的 PointPillars 的示例如下:
```shell
bash tools/dist_test.sh configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py work_dirs/hv_pointpillars_secfpn_6x8_160e_kitti-3d-3class/latest.pth 8 --eval bbox
bash tools/dist_test.sh configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py work_dirs/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class/latest.pth 8
```
## 度量指标
KITTI 官方使用全类平均精度(mAP)和平均方向相似度(AOS)来评估 3D 目标检测的性能,请参考[官方网站](http://www.cvlibs.net/datasets/kitti/eval_3dobject.php)[论文](http://www.cvlibs.net/publications/Geiger2012CVPR.pdf)获取更多细节
KITTI 官方使用全类平均精度(mAP)和平均方向相似度(AOS)来评估 3D 目标检测的性能,更多细节请参考[官方网站](http://www.cvlibs.net/datasets/kitti/eval_3dobject.php)[论文](http://www.cvlibs.net/publications/Geiger2012CVPR.pdf)
MMDetection3D 采用相同的方法在 KITTI 数据集上进行评估,下面展示了一个评估结果的例子:
......@@ -185,10 +181,26 @@ aos AP:97.70, 89.11, 87.38
使用 8 个 GPU 在 KITTI 上测试 PointPillars 并生成对排行榜的提交的示例如下:
```shell
mkdir -p results/kitti-3class
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py work_dirs/hv_pointpillars_secfpn_6x8_160e_kitti-3d-3class/latest.pth 8 --out results/kitti-3class/results_eval.pkl --format-only --eval-options 'pklfile_prefix=results/kitti-3class/kitti_results' 'submission_prefix=results/kitti-3class/kitti_results'
```
在生成 `results/kitti-3class/kitti_results/xxxxx.txt` 后,您可以提交这些文件到 KITTI 官方网站进行基准测试,请参考 [KITTI 官方网站](<(http://www.cvlibs.net/datasets/kitti/index.php)>)获取更多细节。
- 首先,你需要在你的配置文件中修改 `test_dataloader``test_evaluator` 字典,如下所示:
```python
data_root = 'data/kitti/'
test_dataloader = dict(
dataset=dict(
ann_file='kitti_infos_test.pkl',
load_eval_anns=False,
data_prefix=dict(pts='testing/velodyne_reduced')))
test_evaluator = dict(
ann_file=data_root + 'kitti_infos_test.pkl',
format_only=True,
pklfile_prefix='results/kitti-3class/kitti_results',
submission_prefix='results/kitti-3class/kitti_results')
```
- 接下来,你可以运行如下测试脚本。
```shell
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py work_dirs/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class/latest.pth 8
```
在生成 `results/kitti-3class/kitti_results/xxxxx.txt` 后,您可以提交这些文件到 KITTI 官方网站进行基准测试,更多细节请参考 [KITTI 官方网站](http://www.cvlibs.net/datasets/kitti/index.php)
......@@ -33,14 +33,11 @@ mmdetection3d
│ │ ├── sample_submission.csv
```
其中 `v1.01-train``v1.01-test` 包含与 nuScenes 数据集相同的元文件,`.txt` 文件包含数据划分的信息。
Lyft 不提供训练集和验证集的官方划分方案,因此 MMDetection3D 对不同场景下的不同类别的目标数量进行分析,并提供了一个数据集划分方案。
`sample_submission.csv` 是用于提交到 Kaggle 评估服务器的基本文件。
需要注意的是,我们遵循了 Lyft 最初的文件夹命名以实现更清楚的文件组织。请将下载下来的原始文件夹重命名按照上述组织结构重新命名。
其中 `v1.01-train``v1.01-test` 包含与 nuScenes 数据集相同的元文件,`.txt` 文件包含数据划分的信息。Lyft 不提供训练集和验证集的官方划分方案,因此 MMDetection3D 对不同场景下的不同类别的目标数量进行分析,并提供了一个数据集划分方案。`sample_submission.csv` 是用于提交到 Kaggle 评估服务器的基本文件。需要注意的是,我们遵循了 Lyft 最初的文件夹命名以实现更清楚的文件组织。请将下载下来的原始文件夹按照上述组织结构重新命名。
## 数据准备
组织 Lyft 数据集的方式和组织 nuScenes 的方式相同,首先会生成几乎具有相同结构的 .pkl 和 .json 文件,接着需要重点关注这两个数据集之间的不同点,请参考 [nuScenes 教程](https://github.com/open-mmlab/mmdetection3d/blob/master/docs_zh-CN/datasets/nuscenes_det.md)获取更加详细的数据集信息文件结构的说明
组织 Lyft 数据集的方式和组织 nuScenes 的方式相同,首先会生成几乎具有相同结构的 `.pkl` 文件,接着需要重点关注这两个数据集之间的不同点,更多关于数据集信息文件结构的说明请参考 [nuScenes 教程](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/advanced_guides/datasets/nuscenes_det.md)
请通过运行下面的命令来生成 Lyft 的数据集信息文件:
......@@ -49,7 +46,7 @@ python tools/create_data.py lyft --root-path ./data/lyft --out-dir ./data/lyft -
python tools/data_converter/lyft_data_fixer.py --version v1.01 --root-folder ./data/lyft
```
请注意,上面的第二行命令用于修复损坏的 lidar 数据文件,请参考[此处](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000)获取更多细节
请注意,上面的第二行命令用于修复损坏的 lidar 数据文件,更多细节请参考此处[讨论](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000)
处理后的文件夹结构应该如下:
......@@ -77,38 +74,48 @@ mmdetection3d
│ │ ├── lyft_infos_train.pkl
│ │ ├── lyft_infos_val.pkl
│ │ ├── lyft_infos_test.pkl
│ │ ├── lyft_infos_train_mono3d.coco.json
│ │ ├── lyft_infos_val_mono3d.coco.json
│ │ ├── lyft_infos_test_mono3d.coco.json
```
其中,.pkl 文件通常适用于涉及到点云的相关方法,coco 类型的 .json 文件更加适用于涉及到基于图像的相关方法,如基于图像的 2D 和 3D 目标检测。
不同于 nuScenes 数据集,这里仅能使用 json 文件进行 2D 检测相关的实验,未来将会进一步支持基于图像的 3D 检测。
- `lyft_infos_train.pkl`:训练数据集信息,该字典包含两个关键字:`metainfo``data_list``metainfo` 包含数据集的基本信息,例如 `categories`, `dataset``info_version``data_list` 是由字典组成的列表,每个字典(以下简称 `info`)包含了单个样本的所有详细信息。
- info\['sample_idx'\]:样本在整个数据集的索引。
- info\['token'\]:样本数据标记。
- info\['timestamp'\]:样本数据时间戳。
- info\['lidar_points'\]:是一个字典,包含了所有与激光雷达点相关的信息。
- info\['lidar_points'\]\['lidar_path'\]:激光雷达点云数据的文件名。
- info\['lidar_points'\]\['num_pts_feats'\]:点的特征维度。
- info\['lidar_points'\]\['lidar2ego'\]:该激光雷达传感器到自车的变换矩阵。(4x4 列表)
- info\['lidar_points'\]\['ego2global'\]:自车到全局坐标的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]:是一个列表,包含了扫描信息(没有标注的中间帧)。
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\['data_path'\]:第 i 次扫描的激光雷达数据的文件路径。
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\[lidar2ego''\]:当前激光雷达传感器到自车在第 i 次扫描的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\['ego2global'\]:自车在第 i 次扫描到全局坐标的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['lidar2sensor'\]:从当前帧主激光雷达到第 i 帧扫描激光雷达的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['timestamp'\]:扫描数据的时间戳。
- info\['lidar_sweeps'\]\[i\]\['sample_data_token'\]:扫描样本数据标记。
- info\['images'\]:是一个字典,包含与每个相机对应的六个键值:`'CAM_FRONT'`, `'CAM_FRONT_RIGHT'`, `'CAM_FRONT_LEFT'`, `'CAM_BACK'`, `'CAM_BACK_LEFT'`, `'CAM_BACK_RIGHT'`。每个字典包含了对应相机的所有数据信息。
- info\['images'\]\['CAM_XXX'\]\['img_path'\]:图像的文件名。
- info\['images'\]\['CAM_XXX'\]\['cam2img'\]:当 3D 点投影到图像平面时需要的内参信息相关的变换矩阵。(3x3 列表)
- info\['images'\]\['CAM_XXX'\]\['sample_data_token'\]:图像样本数据标记。
- info\['images'\]\['CAM_XXX'\]\['timestamp'\]:图像的时间戳。
- info\['images'\]\['CAM_XXX'\]\['cam2ego'\]:该相机传感器到自车的变换矩阵。(4x4 列表)
- info\['images'\]\['CAM_XXX'\]\['lidar2cam'\]:激光雷达传感器到该相机的变换矩阵。(4x4 列表)
- info\['instances'\]:是一个字典组成的列表。每个字典包含单个实例的所有标注信息。对于其中的第 i 个实例,我们有:
- info\['instances'\]\[i\]\['bbox_3d'\]:长度为 7 的列表,以 (x, y, z, l, w, h, yaw) 的顺序表示实例在激光雷达坐标系下的 3D 边界框。
- info\['instances'\]\[i\]\['bbox_label_3d'\]:整数从 0 开始表示实例的标签,其中 -1 代表忽略该类别。
- info\['instances'\]\[i\]\['bbox_3d_isvalid'\]:每个包围框是否有效。一般情况下,我们只将包含至少一个激光雷达或雷达点的 3D 框作为有效框。
接下来将详细介绍 Lyft 数据集和 nuScenes 数据集之间的数据集信息文件中的不同点:
- `lyft_database/xxxxx.bin` 文件不存在:由于真实标注框的采样对实验的影响可以忽略不计,在 Lyft 数据集中不会提取该目录和相关的 `.bin` 文件。
- `lyft_infos_train.pkl`:包含训练数据集信息,每一帧包含两个关键字:`metadata``infos`
`metadata` 包含数据集自身的基础信息,如 `{'version': 'v1.01-train'}`,然而 `infos` 包含和 nuScenes 数据集相似的数据集详细信息,但是并不包含一下几点:
- info\['sweeps'\]:扫描信息.
- info\['sweeps'\]\[i\]\['type'\]:扫描信息的数据类型,如 `'lidar'`
Lyft 数据集中的一些样例具有不同的 LiDAR 设置,然而为了数据分布的一致性,这里将一直采用顶部的 LiDAR 设备所采集的数据点信息。
- info\['gt_names'\]:在 Lyft 数据集中有 9 个类别,相比于 nuScenes 数据集,不同类别的标注不平衡问题更加突出。
- info\['gt_velocity'\] 不存在:Lyft 数据集中不存在速度评估信息。
- info\['num_lidar_pts'\]:默认值设置为 -1。
- info\['num_radar_pts'\]:默认值设置为 0。
- info\['valid_flag'\] 不存在:这个标志信息因无效的 `num_lidar_pts``num_radar_pts` 的存在而存在。
- `nuscenes_infos_train_mono3d.coco.json`:包含 coco 类型的训练数据集相关的信息。这个文件仅包含 2D 相关的信息,不包含 3D 目标检测所需要的信息,如相机内参。
- info\['images'\]:包含所有图像信息的列表。
- 仅包含 `'file_name'`, `'id'`, `'width'`, `'height'`
- info\['annotations'\]:包含所有标注信息的列表。
- 仅包含 `'file_name'``'image_id'``'area'``'category_name'``'category_id'``'bbox'``'is_crowd'``'segmentation'``'id'`,其中 `'is_crowd'``'segmentation'` 默认设置为 `0``[]`
Lyft 数据集中不包含属性标注信息。
这里仅介绍存储在训练数据文件的数据记录信息,在测试数据集也采用上述的数据记录方式。
获取 `lyft_infos_xxx.pkl` 的核心函数是 [\_fill_trainval_infos](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/lyft_converter.py#L93)
请参考 [lyft_converter.py](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/lyft_converter.py) 获取更多细节。
- `lyft_infos_train.pkl`
- info\['instances'\]\[i\]\['velocity'\] 不存在:Lyft 数据集中不存在速度评估信息。
- info\['instances'\]\[i\]\['num_lidar_pts'\] 及 info\['instances'\]\[i\]\['num_radar_pts'\] 不存在。
这里仅介绍存储在训练数据文件的数据记录信息。这同样适用于验证集和测试集(没有实例)。
更多关于 `lyft_infos_xxx.pkl` 的结构信息请参考 [lyft_converter.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/lyft_converter.py)
## 训练流程
......@@ -122,12 +129,10 @@ train_pipeline = [
type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=5,
use_dim=5,
file_client_args=file_client_args),
use_dim=5),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
file_client_args=file_client_args),
sweeps_num=10),
dict(type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(
type='GlobalRotScaleTrans',
......@@ -138,29 +143,25 @@ train_pipeline = [
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='PointShuffle'),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
```
与 nuScenes 相似,在 Lyft 上进行训练的模型也需要 `LoadPointsFromMultiSweeps` 步骤来从连续帧中加载点云数据。
另外,考虑到 Lyft 中所收集的激光雷达点的强度是无效的,因此将 `LoadPointsFromMultiSweeps` 中的 `use_dim` 默认值设置为 `[0, 1, 2, 4]`,其中前三个维度表示点的坐标,最后一个维度表示时间戳的差异。
与 nuScenes 相似,在 Lyft 上进行训练的模型也需要 `LoadPointsFromMultiSweeps` 步骤来从连续帧中加载点云数据。另外,考虑到 Lyft 中所收集的激光雷达点的强度是无效的,因此将 `LoadPointsFromMultiSweeps` 中的 `use_dim` 默认值设置为 `[0, 1, 2, 4]`,其中前三个维度表示点的坐标,最后一个维度表示时间戳的差异。
## 评估
使用 8 个 GPU 以及 Lyft 指标评估的 PointPillars 的示例如下:
```shell
bash ./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb2-2x_lyft-3d.py checkpoints/hv_pointpillars_fpn_sbn-all_2x8_2x_lyft-3d_20210517_202818-fc6904c3.pth 8 --eval bbox
bash ./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb2-2x_lyft-3d.py checkpoints/hv_pointpillars_fpn_sbn-all_2x8_2x_lyft-3d_20210517_202818-fc6904c3.pth 8
```
## 度量指标
Lyft 提出了一个更加严格的用以评估所预测的 3D 检测框的度量指标。
判断一个预测框是否是正类的基本评判标准和 KITTI 一样,如基于 3D 交并比进行评估,然而,Lyft 采用与 COCO 相似的方式来计算平均精度 -- 计算 3D 交并比在 0.5-0.95 之间的不同阈值下的平均精度。
实际上,重叠部分大于 0.7 的 3D 交并比是一项对于 3D 检测方法比较严格的标准,因此整体的性能似乎会偏低。
相比于其他数据集,Lyft 上不同类别的标注不平衡是导致最终结果偏低的另一个重要原因。
请参考[官方网址](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/overview/evaluation)获取更多关于度量指标的定义的细节。
Lyft 提出了一个更加严格的用以评估所预测的 3D 检测框的度量指标。判断一个预测框是否是正类的基本评判标准和 KITTI 一样,如基于 3D 交并比进行评估,然而,Lyft 采用与 COCO 相似的方式来计算平均精度 -- 计算 3D 交并比在 0.5-0.95 之间的不同阈值下的平均精度。实际上,重叠部分大于 0.7 的 3D 交并比是一项对于 3D 检测方法比较严格的标准,因此整体的性能似乎会偏低。相比于其他数据集,Lyft 上不同类别的标注不平衡是导致最终结果偏低的另一个重要原因。更多关于度量指标的定义请参考[官方网址](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/overview/evaluation)
这里将采用官方方法对 Lyft 进行评估,下面展示了一个评估结果的例子:
......@@ -186,9 +187,9 @@ Lyft 提出了一个更加严格的用以评估所预测的 3D 检测框的度
使用 8 个 GPU 在 Lyft 上测试 PointPillars 并生成对排行榜的提交的示例如下:
```shell
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb2-2x_lyft-3d.py work_dirs/pp-lyft/latest.pth 8 --out work_dirs/pp-lyft/results_challenge.pkl --format-only --eval-options 'jsonfile_prefix=work_dirs/pp-lyft/results_challenge' 'csv_savepath=results/pp-lyft/results_challenge.csv'
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb2-2x_lyft-3d.py work_dirs/pp-lyft/latest.pth 8 --cfg-options test_evaluator.jsonfile_prefix=work_dirs/pp-lyft/results_challenge test_evaluator.csv_savepath=results/pp-lyft/results_challenge.csv
```
在生成 `work_dirs/pp-lyft/results_challenge.csv`,您可以将生成的文件提交到 Kaggle 评估服务器,请参考[官方网址](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles)获取更多细节。
同时还可以使用可视化工具将预测结果进行可视化,请参考[可视化文档](https://mmdetection3d.readthedocs.io/zh_CN/latest/useful_tools.html#visualization)获取更多细节
同时还可以使用可视化工具将预测结果进行可视化,更多细节请参考[可视化文档](https://mmdetection3d.readthedocs.io/zh_CN/latest/useful_tools.html#visualization)
......@@ -26,14 +26,13 @@ mmdetection3d
## 数据准备
我们通常需要通过特定样式来使用 .pkl 或 .json 文件组织有用的数据信息,例如用于组织图像及其标注的 coco 样式。
要为 nuScenes 准备这些文件,请运行以下命令:
我们通常需要通过特定样式来使用 `.pkl` 文件组织有用的数据信息。要为 nuScenes 准备这些文件,请运行以下命令:
```bash
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes
```
处理后的文件夹结构应该如下
处理后的文件夹结构应该如下
```
mmdetection3d
......@@ -53,74 +52,56 @@ mmdetection3d
│ │ ├── nuscenes_infos_val.pkl
│ │ ├── nuscenes_infos_test.pkl
│ │ ├── nuscenes_dbinfos_train.pkl
│ │ ├── nuscenes_infos_train_mono3d.coco.json
│ │ ├── nuscenes_infos_trainval_mono3d.coco.json
│ │ ├── nuscenes_infos_val_mono3d.coco.json
│ │ ├── nuscenes_infos_test_mono3d.coco.json
```
这里,.pkl 文件一般用于涉及点云的方法,coco 风格的 .json 文件更适合基于图像的方法,例如基于图像的 2D 和 3D 检测。
接下来,我们将详细说明这些信息文件中记录的细节。
- `nuscenes_database/xxxxx.bin`:训练数据集的每个 3D 包围框中包含的点云数据。
- `nuscenes_infos_train.pkl`:训练数据集信息,每帧信息有两个键值: `metadata``infos` `metadata` 包含数据集本身的基本信息,例如 `{'version': 'v1.0-trainval'}`,而 `infos` 包含详细信息如下:
- info\['lidar_path'\]:激光雷达点云数据的文件路径
- `nuscenes_infos_train.pkl`:训练数据集,该字典包含了两个键值:`metainfo``data_list``metainfo` 包含数据集的基本信息,例如 `categories`, `dataset``info_version``data_list` 是由字典组成的列表,每个字典(以下简称 `info`)包含了单个样本的所有详细信息
- info\['sample_idx'\]:样本在整个数据集的索引
- info\['token'\]:样本数据标记。
- info\['sweeps'\]:扫描信息(nuScenes 中的 `sweeps` 是指没有标注的中间帧,而 `samples` 是指那些带有标注的关键帧)。
- info\['sweeps'\]\[i\]\['data_path'\]:第 i 次扫描的数据路径。
- info\['sweeps'\]\[i\]\['type'\]:扫描数据类型,例如“激光雷达”。
- info\['sweeps'\]\[i\]\['sample_data_token'\]:扫描样本数据标记。
- info\['sweeps'\]\[i\]\['sensor2ego_translation'\]:从当前传感器(用于收集扫描数据)到自车(包含感知周围环境传感器的车辆,车辆坐标系固连在自车上)的转换(1x3 列表)。
- info\['sweeps'\]\[i\]\['sensor2ego_rotation'\]:从当前传感器(用于收集扫描数据)到自车的旋转(四元数格式的 1x4 列表)。
- info\['sweeps'\]\[i\]\['ego2global_translation'\]:从自车到全局坐标的转换(1x3 列表)。
- info\['sweeps'\]\[i\]\['ego2global_rotation'\]:从自车到全局坐标的旋转(四元数格式的 1x4 列表)。
- info\['sweeps'\]\[i\]\['timestamp'\]:扫描数据的时间戳。
- info\['sweeps'\]\[i\]\['sensor2lidar_translation'\]:从当前传感器(用于收集扫描数据)到激光雷达的转换(1x3 列表)。
- info\['sweeps'\]\[i\]\['sensor2lidar_rotation'\]:从当前传感器(用于收集扫描数据)到激光雷达的旋转(四元数格式的 1x4 列表)。
- info\['cams'\]:相机校准信息。它包含与每个摄像头对应的六个键值: `'CAM_FRONT'`, `'CAM_FRONT_RIGHT'`, `'CAM_FRONT_LEFT'`, `'CAM_BACK'`, `'CAM_BACK_LEFT'`, `'CAM_BACK_RIGHT'`
每个字典包含每个扫描数据按照上述方式的详细信息(每个信息的关键字与上述相同)。除此之外,每个相机还包含了一个键值 `'cam_intrinsic'` 用来保存 3D 点投影到图像平面上需要的内参信息。
- info\['lidar2ego_translation'\]:从激光雷达到自车的转换(1x3 列表)。
- info\['lidar2ego_rotation'\]:从激光雷达到自车的旋转(四元数格式的 1x4 列表)。
- info\['ego2global_translation'\]:从自车到全局坐标的转换(1x3 列表)。
- info\['ego2global_rotation'\]:从自我车辆到全局坐标的旋转(四元数格式的 1x4 列表)。
- info\['timestamp'\]:样本数据的时间戳。
- info\['gt_boxes'\]:7 个自由度的 3D 包围框,一个 Nx7 数组。
- info\['gt_names'\]:3D 包围框的类别,一个 1xN 数组。
- info\['gt_velocity'\]:3D 包围框的速度(由于不准确,没有垂直测量),一个 Nx2 数组。
- info\['num_lidar_pts'\]:每个 3D 包围框中包含的激光雷达点数。
- info\['num_radar_pts'\]:每个 3D 包围框中包含的雷达点数。
- info\['valid_flag'\]:每个包围框是否有效。一般情况下,我们只将包含至少一个激光雷达或雷达点的 3D 框作为有效框。
- `nuscenes_infos_train_mono3d.coco.json`:训练数据集 coco 风格的信息。该文件将基于图像的数据组织为三类(键值):`'categories'`, `'images'`, `'annotations'`
- info\['categories'\]:包含所有类别名称的列表。每个元素都遵循字典格式并由两个键值组成:`'id'``'name'`
- info\['images'\]:包含所有图像信息的列表。
- info\['images'\]\[i\]\['file_name'\]:第 i 张图像的文件名。
- info\['images'\]\[i\]\['id'\]:第 i 张图像的样本数据标记。
- info\['images'\]\[i\]\['token'\]:与该帧对应的样本标记。
- info\['images'\]\[i\]\['cam2ego_rotation'\]:从相机到自车的旋转(四元数格式的 1x4 列表)。
- info\['images'\]\[i\]\['cam2ego_translation'\]:从相机到自车的转换(1x3 列表)。
- info\['images'\]\[i\]\['ego2global_rotation''\]:从自车到全局坐标的旋转(四元数格式的 1x4 列表)。
- info\['images'\]\[i\]\['ego2global_translation'\]:从自车到全局坐标的转换(1x3 列表)。
- info\['images'\]\[i\]\['cam_intrinsic'\]: 相机内参矩阵(3x3 列表)。
- info\['images'\]\[i\]\['width'\]:图片宽度, nuScenes 中默认为 1600。
- info\['images'\]\[i\]\['height'\]:图像高度, nuScenes 中默认为 900。
- info\['annotations'\]: 包含所有标注信息的列表。
- info\['annotations'\]\[i\]\['file_name'\]:对应图像的文件名。
- info\['annotations'\]\[i\]\['image_id'\]:对应图像的图像 ID (标记)。
- info\['annotations'\]\[i\]\['area'\]:2D 包围框的面积。
- info\['annotations'\]\[i\]\['category_name'\]:类别名称。
- info\['annotations'\]\[i\]\['category_id'\]:类别 id。
- info\['annotations'\]\[i\]\['bbox'\]:2D 包围框标注(3D 投影框的外部矩形),1x4 列表跟随 \[x1, y1, x2-x1, y2-y1\]。x1/y1 是沿图像水平/垂直方向的最小坐标。
- info\['annotations'\]\[i\]\['iscrowd'\]:该区域是否拥挤。默认为 0。
- info\['annotations'\]\[i\]\['bbox_cam3d'\]:3D 包围框(重力)中心位置(3)、大小(3)、(全局)偏航角(1)、1x7 列表。
- info\['annotations'\]\[i\]\['velo_cam3d'\]:3D 包围框的速度(由于不准确,没有垂直测量),一个 Nx2 数组。
- info\['annotations'\]\[i\]\['center2d'\]:包含 2.5D 信息的投影 3D 中心:图像上的投影中心位置(2)和深度(1),1x3 列表。
- info\['annotations'\]\[i\]\['attribute_name'\]:属性名称。
- info\['annotations'\]\[i\]\['attribute_id'\]:属性 ID。
我们为属性分类维护了一个属性集合和映射。更多的细节请参考[这里](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/nuscenes_mono_dataset.py#L53)
- info\['annotations'\]\[i\]\['id'\]:标注 ID。默认为 `i`
这里我们只解释训练信息文件中记录的数据。这同样适用于验证和测试集。
获取 `nuscenes_infos_xxx.pkl``nuscenes_infos_xxx_mono3d.coco.json` 的核心函数分别为 [\_fill_trainval_infos](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/nuscenes_converter.py#L143)[get_2d_boxes](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/nuscenes_converter.py#L397)。更多细节请参考 [nuscenes_converter.py](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/nuscenes_converter.py)
- info\['timestamp'\]:样本数据时间戳。
- info\['lidar_points'\]:是一个字典,包含了所有与激光雷达点相关的信息。
- info\['lidar_points'\]\['lidar_path'\]:激光雷达点云数据的文件名。
- info\['lidar_points'\]\['num_pts_feats'\]:点的特征维度。
- info\['lidar_points'\]\['lidar2ego'\]:该激光雷达传感器到自车的变换矩阵。(4x4 列表)
- info\['lidar_points'\]\['ego2global'\]:自车到全局坐标的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]:是一个列表,包含了扫描信息(没有标注的中间帧)。
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\['data_path'\]:第 i 次扫描的激光雷达数据的文件路径。
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\[lidar2ego''\]:当前激光雷达传感器到自车的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['lidar_points'\]\['ego2global'\]:自车到全局坐标的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['lidar2sensor'\]:从主激光雷达传感器到当前传感器(用于收集扫描数据)的变换矩阵。(4x4 列表)
- info\['lidar_sweeps'\]\[i\]\['timestamp'\]:扫描数据的时间戳。
- info\['lidar_sweeps'\]\[i\]\['sample_data_token'\]:扫描样本数据标记。
- info\['images'\]:是一个字典,包含与每个相机对应的六个键值:`'CAM_FRONT'`, `'CAM_FRONT_RIGHT'`, `'CAM_FRONT_LEFT'`, `'CAM_BACK'`, `'CAM_BACK_LEFT'`, `'CAM_BACK_RIGHT'`。每个字典包含了对应相机的所有数据信息。
- info\['images'\]\['CAM_XXX'\]\['img_path'\]:图像的文件名。
- info\['images'\]\['CAM_XXX'\]\['cam2img'\]:当 3D 点投影到图像平面时需要的内参信息相关的变换矩阵。(3x3 列表)
- info\['images'\]\['CAM_XXX'\]\['sample_data_token'\]:图像样本数据标记。
- info\['images'\]\['CAM_XXX'\]\['timestamp'\]:图像的时间戳。
- info\['images'\]\['CAM_XXX'\]\['cam2ego'\]:该相机传感器到自车的变换矩阵。(4x4 列表)
- info\['images'\]\['CAM_XXX'\]\['lidar2cam'\]:激光雷达传感器到该相机的变换矩阵。(4x4 列表)
- info\['instances'\]:是一个字典组成的列表。每个字典包含单个实例的所有标注信息。对于其中的第 i 个实例,我们有:
- info\['instances'\]\[i\]\['bbox_3d'\]:长度为 7 的列表,以 (x, y, z, l, w, h, yaw) 的顺序表示实例的 3D 边界框。
- info\['instances'\]\[i\]\['bbox_label_3d'\]:整数表示实例的标签,-1 代表忽略。
- info\['instances'\]\[i\]\['velocity'\]:3D 边界框的速度(由于不正确,没有垂直测量),大小为 (2, ) 的列表。
- info\['instances'\]\[i\]\['num_lidar_pts'\]:每个 3D 边界框内包含的激光雷达点数。
- info\['instances'\]\[i\]\['num_radar_pts'\]:每个 3D 边界框内包含的雷达点数。
- info\['instances'\]\[i\]\['bbox_3d_isvalid'\]:每个包围框是否有效。一般情况下,我们只将包含至少一个激光雷达或雷达点的 3D 框作为有效框。
- info\['cam_instances'\]:是一个字典,包含以下键值:`'CAM_FRONT'`, `'CAM_FRONT_RIGHT'`, `'CAM_FRONT_LEFT'`, `'CAM_BACK'`, `'CAM_BACK_LEFT'`, `'CAM_BACK_RIGHT'`。对于基于视觉的 3D 目标检测任务,我们将整个场景的 3D 标注划分至它们所属于的相应相机中。对于其中的第 i 个实例,我们有:
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['bbox_label'\]:实例标签。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['bbox_label_3d'\]:实例标签。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['bbox'\]:2D 边界框标注(3D 框投影的矩形框),顺序为 \[x1, y1, x2, y2\] 的列表。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['center_2d'\]:3D 框投影到图像上的中心点,大小为 (2, ) 的列表。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['depth'\]:3D 框投影中心的深度。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['velocity'\]:3D 边界框的速度(由于不正确,没有垂直测量),大小为 (2, ) 的列表。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['attr_label'\]:实例的属性标签。我们为属性分类维护了一个属性集合和映射。
- info\['cam_instances'\]\['CAM_XXX'\]\[i\]\['bbox_3d'\]:长度为 7 的列表,以 (x, y, z, l, h, w, yaw) 的顺序表示实例的 3D 边界框。
注意:
1. `instances``cam_instances``bbox_3d` 的区别。`bbox_3d` 都被转换到 MMDet3D 定义的坐标系下,`instances` 中的 `bbox_3d` 是在激光雷达坐标系下,而 `cam_instances` 是在相机坐标系下。注意它们 3D 框中表示的不同('l, w, h' 和 'l, h, w')。
2. 这里我们只解释训练信息文件中记录的数据。这同样适用于验证集和测试集(测试集的 `.pkl` 文件中不包含 `instances` 以及 `cam_instances`)。
获取 `nuscenes_infos_xxx.pkl` 的核心函数为 [\_fill_trainval_infos](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/nuscenes_converter.py#L146)。更多细节请参考 [nuscenes_converter.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/nuscenes_converter.py)
## 训练流程
......@@ -134,12 +115,10 @@ train_pipeline = [
type='LoadPointsFromFile',
coord_type='LIDAR',
load_dim=5,
use_dim=5,
file_client_args=file_client_args),
use_dim=5),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
file_client_args=file_client_args),
sweeps_num=10),
dict(type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(
type='GlobalRotScaleTrans',
......@@ -151,15 +130,13 @@ train_pipeline = [
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectNameFilter', classes=class_names),
dict(type='PointShuffle'),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
```
与一般情况相比,nuScenes 有一个特定的 `'LoadPointsFromMultiSweeps'` 流水线来从连续帧加载点云。这是此设置中使用的常见做法。
更多细节请参考 nuScenes [原始论文](https://arxiv.org/abs/1903.11027)
`'LoadPointsFromMultiSweeps'` 中的默认 `use_dim``[0, 1, 2, 4]`,其中前 3 个维度是指点坐标,最后一个是指时间戳差异。
由于在拼接来自不同帧的点时使用点云的强度信息会产生噪声,因此默认情况下不使用点云的强度信息。
与一般情况相比,nuScenes 有一个特定的 `'LoadPointsFromMultiSweeps'` 流水线来从连续帧加载点云。这是此设置中使用的常见做法。更多细节请参考 nuScenes [原始论文](https://arxiv.org/abs/1903.11027)`'LoadPointsFromMultiSweeps'` 中的默认 `use_dim``[0, 1, 2, 4]`,其中前 3 个维度是指点坐标,最后一个是指时间戳差异。由于在拼接来自不同帧的点时使用点云的强度信息会产生噪声,因此默认情况下不使用点云的强度信息。
### 基于视觉的方法
......@@ -176,16 +153,13 @@ train_pipeline = [
with_bbox_3d=True,
with_label_3d=True,
with_bbox_depth=True),
dict(type='Resize', img_scale=(1600, 900), keep_ratio=True),
dict(type='mmdet.Resize', img_scale=(1600, 900), keep_ratio=True),
dict(type='RandomFlip3D', flip_ratio_bev_horizontal=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(
type='Collect3D',
type='Pack3DDetInputs',
keys=[
'img', 'gt_bboxes', 'gt_labels', 'attr_labels', 'gt_bboxes_3d',
'gt_labels_3d', 'centers2d', 'depths'
'img', 'gt_bboxes', 'gt_bboxes_labels', 'attr_labels', 'gt_bboxes_3d',
'gt_labels_3d', 'centers_2d', 'depths'
]),
]
```
......@@ -194,22 +168,19 @@ train_pipeline = [
- 它使用单目流水线加载图像,其中包括额外的必需信息,如相机内参矩阵。
- 它需要加载 3D 标注。
- 一些数据增强技术需要调整,例如`RandomFlip3D`
目前我们不支持更多的增强方法,因为如何迁移和应用其他技术仍在探索中。
- 一些数据增强技术需要调整,例如`RandomFlip3D`。目前我们不支持更多的增强方法,因为如何迁移和应用其他技术仍在探索中。
## 评估
使用 8 个 GPU 以及 nuScenes 指标评估的 PointPillars 的示例如下
```shell
bash ./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb4-2x_nus-3d.py checkpoints/hv_pointpillars_fpn_sbn-all_4x8_2x_nus-3d_20200620_230405-2fa62f3d.pth 8 --eval bbox
bash ./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb4-2x_nus-3d.py checkpoints/hv_pointpillars_fpn_sbn-all_4x8_2x_nus-3d_20200620_230405-2fa62f3d.pth 8
```
## 指标
NuScenes 提出了一个综合指标,即 nuScenes 检测分数(NDS),以评估不同的方法并设置基准测试。
它由平均精度(mAP)、平均平移误差(ATE)、平均尺度误差(ASE)、平均方向误差(AOE)、平均速度误差(AVE)和平均属性误差(AAE)组成。
更多细节请参考其[官方网站](https://www.nuscenes.org/object-detection?externalData=all&mapData=all&modalities=Any)
NuScenes 提出了一个综合指标,即 nuScenes 检测分数(NDS),以评估不同的方法并设置基准测试。它由平均精度(mAP)、平均平移误差(ATE)、平均尺度误差(ASE)、平均方向误差(AOE)、平均速度误差(AVE)和平均属性误差(AAE)组成。更多细节请参考其[官方网站](https://www.nuscenes.org/object-detection?externalData=all&mapData=all&modalities=Any)
我们也采用这种方法对 nuScenes 进行评估。打印的评估结果示例如下:
......@@ -241,11 +212,13 @@ barrier 0.466 0.581 0.269 0.169 nan nan
使用 8 个 GPU 在 nuScenes 上测试 PointPillars 并生成对排行榜的提交的示例如下
你需要在对应的配置文件中的 `test_evaluator` 里修改 `jsonfile_prefix`。举个例子,添加 `test_evaluator = dict(type='NuScenesMetric', jsonfile_prefix='work_dirs/pp-nus/results_eval.json')` 或在测试命令后使用 `--cfg-options "test_evaluator.jsonfile_prefix=work_dirs/pp-nus/results_eval.json)`
```shell
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb4-2x_nus-3d.py work_dirs/hv_pointpillars_secfpn_6x8_160e_kitti-3d-3class/latest.pth 8 --out work_dirs/pp-nus/results_eval.pkl --format-only --eval-options 'jsonfile_prefix=work_dirs/pp-nus/results_eval'
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_fpn_sbn-all_8xb4-2x_nus-3d.py work_dirs/pp-nus/latest.pth 8 --cfg-options 'test_evaluator.jsonfile_prefix=work_dirs/pp-nus/results_eval'
```
请注意,在[这里](https://github.com/open-mmlab/mmdetection3d/blob/master/configs/_base_/datasets/nus-3d.py#L132)测试信息应更改为测试集而不是验证集。
请注意,在[这里](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/datasets/nus-3d.py#L132)测试信息应更改为测试集而不是验证集。
生成 `work_dirs/pp-nus/results_eval.json` 后,您可以压缩并提交给 nuScenes 基准测试。更多信息请参考 [nuScenes 官方网站](https://www.nuscenes.org/object-detection?externalData=all&mapData=all&modalities=Any)
......
......@@ -179,9 +179,7 @@ train_pipeline = [
with_mask_3d=False,
with_seg_3d=True),
dict(
type='PointSegClassMapping',
valid_cat_ids=tuple(range(len(class_names))),
max_cat_id=13),
type='PointSegClassMapping'),
dict(
type='IndoorPatchPointSample',
num_points=num_points,
......@@ -202,8 +200,7 @@ train_pipeline = [
jitter_std=[0.01, 0.01, 0.01],
clip_range=[-0.05, 0.05]),
dict(type='RandomDropPointsColor', drop_ratio=0.2),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'pts_semantic_mask'])
dict(type='Pack3DDetInputs', keys=['points', 'pts_semantic_mask'])
]
```
......@@ -219,7 +216,7 @@ train_pipeline = [
通常我们使用平均交并比 (mean Intersection over Union, mIoU) 作为 ScanNet 语义分割任务的度量指标。
具体而言,我们先计算所有类别的 IoU,然后取平均值作为 mIoU。
更多实现细节请参考 [seg_eval.py](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/evaluation/seg_eval.py)
更多实现细节请参考 [seg_eval.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/evaluation/functional/seg_eval.py)
正如在 `提取 S3DIS 数据` 一节中所提及的,S3DIS 通常在 5 个区域上进行训练,然后在余下的 1 个区域上进行测试。但是在其他论文中,也有不同的划分方式。
为了便于灵活划分训练和测试的子集,我们首先定义子数据集 (sub-dataset) 来表示每一个区域,然后根据区域划分对其进行合并,以得到完整的训练集。
......@@ -232,31 +229,42 @@ class_names = ('ceiling', 'floor', 'wall', 'beam', 'column', 'window', 'door',
'table', 'chair', 'sofa', 'bookcase', 'board', 'clutter')
train_area = [1, 2, 3, 4, 6]
test_area = 5
data = dict(
train=dict(
train_dataloader = dict(
batch_size=8,
num_workers=4,
persistent_workers=True,
sampler=dict(type='DefaultSampler', shuffle=True),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_files=[
data_root + f's3dis_infos_Area_{i}.pkl' for i in train_area
],
ann_files=[f's3dis_infos_Area_{i}.pkl' for i in train_area],
metainfo=metainfo,
data_prefix=data_prefix,
pipeline=train_pipeline,
classes=class_names,
test_mode=False,
modality=input_modality,
ignore_index=len(class_names),
scene_idxs=[
data_root + f'seg_info/Area_{i}_resampled_scene_idxs.npy'
for i in train_area
]),
val=dict(
f'seg_info/Area_{i}_resampled_scene_idxs.npy' for i in train_area
],
test_mode=False))
test_dataloader = dict(
batch_size=1,
num_workers=1,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_files=data_root + f's3dis_infos_Area_{test_area}.pkl',
ann_files=f's3dis_infos_Area_{test_area}.pkl',
metainfo=metainfo,
data_prefix=data_prefix,
pipeline=test_pipeline,
classes=class_names,
test_mode=True,
modality=input_modality,
ignore_index=len(class_names),
scene_idxs=data_root +
f'seg_info/Area_{test_area}_resampled_scene_idxs.npy'))
scene_idxs=f'seg_info/Area_{test_area}_resampled_scene_idxs.npy',
test_mode=True))
val_dataloader = test_dataloader
```
可以看到,我们通过将多个相应路径构成的列表 (list) 输入 `ann_files``scene_idxs` 以实现训练测试集的划分。
......
# 3D 目标检测 Scannet 数据集
# 3D 目标检测 ScanNet 数据集
## 数据集准备
请参考 ScanNet 的[指南](https://github.com/open-mmlab/mmdetection3d/blob/master/data/scannet/README.md/)以查看总体流程。
请参考 ScanNet 的[指南](https://github.com/open-mmlab/mmdetection3d/blob/master/data/scannet/README.md)以查看总体流程。
### 提取 ScanNet 点云数据
......@@ -32,10 +32,10 @@ mmdetection3d
`scans` 文件夹下总共有 1201 个训练样本文件夹和 312 个验证样本文件夹,其中存有未处理的点云数据和相关的标注。比如说,在文件夹 `scene0001_01` 下文件是这样组织的:
- `scene0001_01_vh_clean_2.ply`: 存有每个顶点坐标和颜色的网格文件。网格的顶点被直接用作未处理的点云数据。
- `scene0001_01.aggregation.json`: 包含物体 ID、分割部分 ID、标签的标注文件。
- `scene0001_01_vh_clean_2.0.010000.segs.json`: 包含分割部分 ID 和顶点的分割标注文件。
- `scene0001_01.txt`: 包括对齐矩阵等的元文件。
- `scene0001_01_vh_clean_2.ply`存有每个顶点坐标和颜色的网格文件。网格的顶点被直接用作未处理的点云数据。
- `scene0001_01.aggregation.json`包含物体 ID、分割部分 ID、标签的标注文件。
- `scene0001_01_vh_clean_2.0.010000.segs.json`包含分割部分 ID 和顶点的分割标注文件。
- `scene0001_01.txt`包括对齐矩阵等的元文件。
- `scene0001_01_vh_clean_2.labels.ply`:包含每个顶点类别的标注文件。
通过运行 `python batch_load_scannet_data.py` 来提取 ScanNet 数据。主要步骤包括:
......@@ -227,21 +227,15 @@ scannet
- `semantic_mask/xxxxx.bin`:每个点的语义标签,值的范围为:\[1, 40\], 也就是 `nyu40id` 的标准。请注意:在训练流程 `PointSegClassMapping` 中,`nyu40id` 的 ID 会被映射到训练 ID。
- `posed_images/scenexxxx_xx``.jpg` 图像的集合,还包含 `.txt` 格式的 4x4 相机姿态和单个 `.txt` 格式的相机内参矩阵文件。
- `scannet_infos_train.pkl`:训练集的数据信息,每个场景的具体信息如下:
- info\['point_cloud'\]`{'num_features': 6, 'lidar_idx': sample_idx}`,其中 `sample_idx` 为该场景的索引。
- info\['pts_path'\]`points/xxxxx.bin` 的路径。
- info\['pts_instance_mask_path'\]`instance_mask/xxxxx.bin` 的路径。
- info\['pts_semantic_mask_path'\]`semantic_mask/xxxxx.bin` 的路径。
- info\['annos'\]:每个场景的标注。
- annotations\['gt_num'\]:真实物体 (ground truth) 的数量。
- annotations\['name'\]:所有真实物体的语义类别名称,比如 `chair`(椅子)。
- annotations\['location'\]:depth 坐标系下与坐标轴平行的三维包围框的重力中心 (gravity center),形状为 \[K, 3\],其中 K 是真实物体的数量。
- annotations\['dimensions'\]:depth 坐标系下与坐标轴平行的三维包围框的大小,形状为 \[K, 3\]
- annotations\['gt_boxes_upright_depth'\]:depth 坐标系下与坐标轴平行的三维包围框 `(x, y, z, x_size, y_size, z_size, yaw)`,形状为 \[K, 6\]
- annotations\['unaligned_location'\]:depth 坐标系下与坐标轴不平行(对齐前)的三维包围框的重力中心。
- annotations\['unaligned_dimensions'\]:depth 坐标系下与坐标轴不平行的三维包围框的大小。
- annotations\['unaligned_gt_boxes_upright_depth'\]:depth 坐标系下与坐标轴不平行的三维包围框。
- annotations\['index'\]:所有真实物体的索引,范围为 \[0, K)。
- annotations\['class'\]:所有真实物体类别的标号,范围为 \[0, 18),形状为 \[K, \]
- info\['lidar_points'\]:字典包含与激光雷达点相关的信息。
- info\['lidar_points'\]\['lidar_path'\]:激光雷达点云数据的文件名。
- info\['lidar_points'\]\['num_pts_feats'\]:点的特征维度。
- info\['lidar_points'\]\['axis_align_matrix'\]:用于对齐坐标轴的变换矩阵。
- info\['pts_semantic_mask_path'\]:语义分割标注的文件名。
- info\['pts_instance_mask_path'\]:实例分割标注的文件名。
- info\['instances'\]:字典组成的列表,每个字典包含一个实例的所有标注信息。对于其中的第 i 个实例,我们有:
- info\['instances'\]\[i\]\['bbox_3d'\]:长度为 6 的列表,以 (x, y, z, l, w, h) 的顺序表示深度坐标系下与坐标轴平行的 3D 边界框。
- info\[instances\]\[i\]\['bbox_label_3d'\]:3D 边界框的标签。
- `scannet_infos_val.pkl`:验证集上的数据信息,与 `scannet_infos_train.pkl` 格式完全一致。
- `scannet_infos_test.pkl`:测试集上的数据信息,与 `scannet_infos_train.pkl` 格式几乎完全一致,除了缺少标注。
......@@ -264,8 +258,7 @@ train_pipeline = [
with_mask_3d=True,
with_seg_3d=True),
dict(type='GlobalAlignment', rotation_axis=2),
dict(
type='PointSegClassMapping'),
dict(type='PointSegClassMapping'),
dict(type='PointSample', num_points=40000),
dict(
type='RandomFlip3D',
......@@ -277,9 +270,8 @@ train_pipeline = [
rot_range=[-0.087266, 0.087266],
scale_ratio_range=[1.0, 1.0],
shift_height=True),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(
type='Collect3D',
type='Pack3DDetInputs',
keys=[
'points', 'gt_bboxes_3d', 'gt_labels_3d', 'pts_semantic_mask',
'pts_instance_mask'
......@@ -292,10 +284,10 @@ train_pipeline = [
- 数据增强:
- `PointSample`:下采样输入点云。
- `RandomFlip3D`:随机左右或前后翻转点云。
- `GlobalRotScaleTrans`: 旋转输入点云,对于 ScanNet 角度通常落入 \[-5, 5\] (度)的范围;并放缩输入点云,对于 ScanNet 比例通常为 1.0(即不做缩放);最后平移输入点云,对于 ScanNet 通常位移量为 0(即不做位移)。
- `GlobalRotScaleTrans`旋转输入点云,对于 ScanNet 角度通常落入 \[-5, 5\](度)的范围;并放缩输入点云,对于 ScanNet 比例通常为 1.0(即不做缩放);最后平移输入点云,对于 ScanNet 通常位移量为 0(即不做位移)。
## 评估指标
通常 mAP(全类平均精度)被用于 ScanNet 的检测任务的评估,比如 `mAP@0.25``mAP@0.5`。具体来说,评估时一个通用的计算 3D 物体检测多个类别的精度和召回率的函数被调用,可以参考 [indoor_eval](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3D/core/evaluation/indoor_eval.py)
通常使用 mAP(全类平均精度)来评估 ScanNet 的检测任务的性能,比如 `mAP@0.25``mAP@0.5`。具体来说,评估时调用一个通用的计算 3D 物体检测多个类别的精度和召回率的函数。更多细节请参考 [indoor_eval](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/evaluation/functional/indoor_eval.py)
与在章节`提取 ScanNet 数据` 中介绍的那样,所有真实物体的三维包围框是与坐标轴平行的,也就是说旋转角为 0。因此,预测包围框的网络接受的包围框旋转角监督也是 0,且在后处理阶段我们使用适用于与坐标轴平行的包围框的非极大值抑制 (NMS) ,该过程不会考虑包围框的旋转。
与在章节`提取 ScanNet 数据`中介绍的那样,所有真实物体的三维包围框是与坐标轴平行的,也就是说旋转角为 0。因此,预测包围框的网络接受的包围框旋转角监督也是 0,且在后处理阶段我们使用适用于与坐标轴平行的包围框的非极大值抑制NMS,该过程不会考虑包围框的旋转。
......@@ -2,7 +2,7 @@
## 数据集的准备
ScanNet 3D 语义分割数据集的准备和 3D 检测任务的准备很相似,请查看[此文档](https://github.com/open-mmlab/mmdetection3d/blob/master/docs_zh-CN/datasets/scannet_det.md#dataset-preparation)以获取更多细节。
ScanNet 3D 语义分割数据集的准备和 3D 检测任务的准备很相似,请查看[此文档](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/advanced_guides/datasets/scannet_det.md#%E6%95%B0%E6%8D%AE%E9%9B%86%E5%87%86%E5%A4%87)以获取更多细节。
以下我们只罗列部分 3D 语义分割特有的处理步骤和数据信息。
### 提取 ScanNet 数据
......@@ -102,8 +102,7 @@ train_pipeline = [
enlarge_size=0.2,
min_unique_num=None),
dict(type='NormalizePointsColor', color_mean=None),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'pts_semantic_mask'])
dict(type='Pack3DDetInputs', keys=['points', 'pts_semantic_mask'])
]
```
......@@ -115,7 +114,7 @@ train_pipeline = [
通常我们使用平均交并比 (mean Intersection over Union, mIoU) 作为 ScanNet 语义分割任务的度量指标。
具体而言,我们先计算所有类别的 IoU,然后取平均值作为 mIoU。
更多实现细节请参考 [seg_eval.py](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/evaluation/seg_eval.py)
更多实现细节请参考 [seg_eval.py](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/evaluation/functional/seg_eval.py)
## 在测试集上测试并提交结果
......
......@@ -2,7 +2,7 @@
## 数据集的准备
对于数据集准备的整体流程,请参考 SUN RGB-D 的[指南](https://github.com/open-mmlab/mmdetection3d/blob/master/data/sunrgbd/README.md/)
对于数据集准备的整体流程,请参考 SUN RGB-D 的[指南](https://github.com/open-mmlab/mmdetection3d/blob/master/data/sunrgbd/README.md)
### 下载 SUN RGB-D 数据与工具包
......@@ -133,92 +133,7 @@ python tools/create_data.py sunrgbd --root-path ./data/sunrgbd \
bash tools/create_data.sh <job_name> sunrgbd
```
之前提到的点云数据就会被处理并以 `.bin` 格式重新存储。与此同时,`.pkl` 文件也被生成,用于存储数据标注和元信息。这一步处理中,用于生成 `.pkl` 文件的核心函数 `process_single_scene` 如下:
```python
def process_single_scene(sample_idx):
print(f'{self.split} sample_idx: {sample_idx}')
# 将深度图转换为点云并降采样点云
pc_upright_depth = self.get_depth(sample_idx)
pc_upright_depth_subsampled = random_sampling(
pc_upright_depth, self.num_points)
info = dict()
pc_info = {'num_features': 6, 'lidar_idx': sample_idx}
info['point_cloud'] = pc_info
# 将点云保存为 `.bin` 格式
mmengine.mkdir_or_exist(osp.join(self.root_dir, 'points'))
pc_upright_depth_subsampled.tofile(
osp.join(self.root_dir, 'points', f'{sample_idx:06d}.bin'))
# 存储点云存储路径
info['pts_path'] = osp.join('points', f'{sample_idx:06d}.bin')
# 存储图像存储路径以及其元信息
img_path = osp.join('image', f'{sample_idx:06d}.jpg')
image_info = {
'image_idx': sample_idx,
'image_shape': self.get_image_shape(sample_idx),
'image_path': img_path
}
info['image'] = image_info
# 保存标定信息
K, Rt = self.get_calibration(sample_idx)
calib_info = {'K': K, 'Rt': Rt}
info['calib'] = calib_info
# 保存所有数据标注
if has_label:
obj_list = self.get_label_objects(sample_idx)
annotations = {}
annotations['gt_num'] = len([
obj.classname for obj in obj_list
if obj.classname in self.cat2label.keys()
])
if annotations['gt_num'] != 0:
# 类别名称
annotations['name'] = np.array([
obj.classname for obj in obj_list
if obj.classname in self.cat2label.keys()
])
# 二维图像包围框
annotations['bbox'] = np.concatenate([
obj.box2d.reshape(1, 4) for obj in obj_list
if obj.classname in self.cat2label.keys()
], axis=0)
# depth 坐标系下的三维包围框中心坐标
annotations['location'] = np.concatenate([
obj.centroid.reshape(1, 3) for obj in obj_list
if obj.classname in self.cat2label.keys()
], axis=0)
# depth 坐标系下的三维包围框大小
annotations['dimensions'] = 2 * np.array([
[obj.l, obj.h, obj.w] for obj in obj_list
if obj.classname in self.cat2label.keys()
])
# depth 坐标系下的三维包围框旋转角
annotations['rotation_y'] = np.array([
obj.heading_angle for obj in obj_list
if obj.classname in self.cat2label.keys()
])
annotations['index'] = np.arange(
len(obj_list), dtype=np.int32)
# 类别标签(数字)
annotations['class'] = np.array([
self.cat2label[obj.classname] for obj in obj_list
if obj.classname in self.cat2label.keys()
])
# depth 坐标系下的三维包围框
annotations['gt_boxes_upright_depth'] = np.stack(
[
obj.box3d for obj in obj_list
if obj.classname in self.cat2label.keys()
], axis=0) # (K,8)
info['annos'] = annotations
return info
```
之前提到的点云数据就会被处理并以 `.bin` 格式重新存储。与此同时,`.pkl` 文件也被生成,用于存储数据标注和元信息。
如上数据处理后,文件目录结构应如下:
......@@ -236,24 +151,21 @@ sunrgbd
├── sunrgbd_infos_val.pkl
```
- `points/0xxxxx.bin`:降采样后的点云数据。
- `points/xxxxxx.bin`:降采样后的点云数据。
- `sunrgbd_infos_train.pkl`:训练集数据信息(标注与元信息),每个场景所含数据信息具体如下:
- info\['point_cloud'\]`{'num_features': 6, 'lidar_idx': sample_idx}`,其中 `sample_idx` 为该场景的索引。
- info\['pts_path'\]`points/0xxxxx.bin` 的路径。
- info\['image'\]:图像路径与元信息:
- image\['image_idx'\]:图像索引。
- image\['image_shape'\]:图像张量的形状(即其尺寸)。
- image\['image_path'\]:图像路径。
- info\['annos'\]:每个场景的标注:
- annotations\['gt_num'\]:真实物体 (ground truth) 的数量。
- annotations\['name'\]:所有真实物体的语义类别名称,比如 `chair`(椅子)。
- annotations\['location'\]:depth 坐标系下三维包围框的重力中心 (gravity center),形状为 \[K, 3\],其中 K 是真实物体的数量。
- annotations\['dimensions'\]:depth 坐标系下三维包围框的大小,形状为 \[K, 3\]
- annotations\['rotation_y'\]:depth 坐标系下三维包围框的旋转角,形状为 \[K, \]
- annotations\['gt_boxes_upright_depth'\]:depth 坐标系下三维包围框 `(x, y, z, x_size, y_size, z_size, yaw)`,形状为 \[K, 7\]
- annotations\['bbox'\]:二维包围框 `(x, y, x_size, y_size)`,形状为 \[K, 4\]
- annotations\['index'\]:所有真实物体的索引,范围为 \[0, K)。
- annotations\['class'\]:所有真实物体类别的标号,范围为 \[0, 10),形状为 \[K, \]
- info\['lidar_points'\]:字典包含了与激光雷达点相关的信息。
- info\['lidar_points'\]\['num_pts_feats'\]:点的特征维度。
- info\['lidar_points'\]\['lidar_path'\]:激光雷达点云数据的文件名。
- info\['images'\]:字典包含了与图像数据相关的信息。
- info\['images'\]\['CAM0'\]\['img_path'\]:图像的文件名。
- info\['images'\]\['CAM0'\]\['depth2img'\]:深度到图像的变换矩阵,形状为 (4, 4)。
- info\['images'\]\['CAM0'\]\['height'\]:图像的高。
- info\['images'\]\['CAM0'\]\['width'\]:图像的宽。
- info\['instances'\]:由字典组成的列表,包含了该帧的所有标注信息。每个字典与单个实例的标注相关。对于其中的第 i 个实例,我们有:
- info\['instances'\]\[i\]\['bbox_3d'\]:长度为 7 的列表,表示深度坐标系下的 3D 边界框。
- info\['instances'\]\[i\]\['bbox'\]:长度为 4 的列表,以 (x1, y1, x2, y2) 的顺序表示实例的 2D 边界框。
- info\['instances'\]\[i\]\['bbox_label_3d'\]:整数表示实例的 3D 标签,-1 表示忽略该类别。
- info\['instances'\]\[i\]\['bbox_label'\]:整数表示实例的 2D 标签,-1 表示忽略该类别。
- `sunrgbd_infos_val.pkl`:验证集上的数据信息,与 `sunrgbd_infos_train.pkl` 格式完全一致。
## 训练流程
......@@ -280,15 +192,16 @@ train_pipeline = [
scale_ratio_range=[0.85, 1.15],
shift_height=True),
dict(type='PointSample', num_points=20000),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
```
点云上的数据增强
- `RandomFlip3D`:随机左右或前后翻转输入点云。
- `GlobalRotScaleTrans`:旋转输入点云,对于 SUN RGB-D 角度通常落入 \[-30, 30\] (度)的范围;并放缩输入点云,对于 SUN RGB-D 比例通常落入 \[0.85, 1.15\] 的范围;最后平移输入点云,对于 SUN RGB-D 通常位移量为 0(即不做位移)。
- `GlobalRotScaleTrans`:旋转输入点云,对于 SUN RGB-D 角度通常落入 \[-30, 30\](度)的范围;并放缩输入点云,对于 SUN RGB-D 比例通常落入 \[0.85, 1.15\] 的范围;最后平移输入点云,对于 SUN RGB-D 通常位移量为 0(即不做位移)。
- `PointSample`:降采样输入点云。
SUN RGB-D 上多模态(点云和图像)3D 物体检测的典型流程如下:
......@@ -304,9 +217,8 @@ train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations3D'),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='Resize', img_scale=(1333, 600), keep_ratio=True),
dict(type='Resize', scale=(1333, 600), keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.0),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(
type='RandomFlip3D',
......@@ -318,28 +230,21 @@ train_pipeline = [
rot_range=[-0.523599, 0.523599],
scale_ratio_range=[0.85, 1.15],
shift_height=True),
dict(type='PointSample', num_points=20000),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(
type='Collect3D',
keys=[
'img', 'gt_bboxes', 'gt_labels', 'points', 'gt_bboxes_3d',
'gt_labels_3d'
])
type='Pack3DDetInputs',
keys=['points', 'gt_bboxes_3d', 'gt_labels_3d','img', 'gt_bboxes', 'gt_bboxes_labels'])
]
```
图像上的数据增强/归一化
图像上的数据增强
- `Resize`: 改变输入图像的大小, `keep_ratio=True` 意味着图像的比例不改变。
- `Normalize`: 归一化图像的 RGB 通道。
- `RandomFlip`: 随机地翻折图像。
- `Pad`: 扩大图像,默认情况下用零填充图像的边缘。
- `Resize`:改变输入图像的大小,`keep_ratio=True` 意味着图像的比例不改变。
- `RandomFlip`:随机地翻折图像。
图像增强和归一化函数的实现取自 [MMDetection](https://github.com/open-mmlab/mmdetection/tree/master/mmdet/datasets/pipelines)
图像增强的实现取自 [MMDetection](https://github.com/open-mmlab/mmdetection/tree/dev-3.x/mmdet/datasets/transforms)
## 度量指标
与 ScanNet 一样,通常 mAP(全类平均精度)被用于 SUN RGB-D 的检测任务的评估,比如 `mAP@0.25``mAP@0.5`。具体来说,评估时一个通用的计算 3D 物体检测多个类别的精度和召回率的函数被调用,可以参考 [`indoor_eval.py`](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/evaluation/indoor_eval.py)
与 ScanNet 一样,通常使用 mAP(全类平均精度)来评估 SUN RGB-D 的检测任务的性能,比如 `mAP@0.25``mAP@0.5`。具体来说,评估时调用一个通用的计算 3D 物体检测多个类别的精度和召回率的函数。更多细节请参考 [`indoor_eval.py`](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/evaluation/functional/indoor_eval.py)
因为 SUN RGB-D 包含有图像数据,所以图像上的物体检测也是可行的。举个例子,在 ImVoteNet 中,我们首先训练了一个图像检测器,并且也使用 mAP 指标,如 `mAP@0.5`,来评估其表现。我们使用 [MMDetection](https://github.com/open-mmlab/mmdetection) 库中的 `eval_map` 函数来计算 mAP。
......@@ -101,11 +101,12 @@ mmdetection3d
## 评估
为了在 Waymo 数据集上进行检测性能评估,请按照[此处指示](https://github.com/waymo-research/waymo-open-dataset/blob/master/docs/quick_start.md/)构建用于计算评估指标的二进制文件 `compute_detection_metrics_main`,并将它置于 `mmdet3d/core/evaluation/waymo_utils/` 下。您基本上可以按照下方命令安装 `bazel`,然后构建二进制文件:
为了在 Waymo 数据集上进行检测性能评估,请按照[此处指示](https://github.com/waymo-research/waymo-open-dataset/blob/master/docs/quick_start.md)构建用于计算评估指标的二进制文件 `compute_detection_metrics_main`,并将它置于 `mmdet3d/core/evaluation/waymo_utils/` 下。您基本上可以按照下方命令安装 `bazel`,然后构建二进制文件:
```shell
# download the code and enter the base directory
git clone https://github.com/waymo-research/waymo-open-dataset.git waymo-od
# git clone https://github.com/Abyssaledge/waymo-open-dataset-master waymo-od # if you want to use faster multi-thread version.
cd waymo-od
git checkout remotes/origin/master
......@@ -122,19 +123,16 @@ sudo apt install build-essential
bazel clean
bazel build waymo_open_dataset/metrics/tools/compute_detection_metrics_main
cp bazel-bin/waymo_open_dataset/metrics/tools/compute_detection_metrics_main ../mmdetection3d/mmdet3d/core/evaluation/waymo_utils/
cp bazel-bin/waymo_open_dataset/metrics/tools/compute_detection_metrics_main ../mmdetection3d/mmdet3d/evaluation/functional/waymo_utils/
```
接下来,您就可以在 Waymo 上评估您的模型了。如下示例是使用 8 个图形处理器 (GPU) 在 Waymo 上用 Waymo 评价指标评估 PointPillars 模型的情景:
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/pointpillars/hv_pointpillars_secfpn_sbn-2x16_2x_waymo-3d-car.py \
checkpoints/hv_pointpillars_secfpn_sbn-2x16_2x_waymo-3d-car_latest.pth --out results/waymo-car/results_eval.pkl \
--eval waymo --eval-options 'pklfile_prefix=results/waymo-car/kitti_results' \
'submission_prefix=results/waymo-car/kitti_results'
./tools/dist_test.sh configs/pointpillars/pointpillars_hv_secfpn_sbn-all_16xb2-2x_waymo-3d-car.py checkpoints/hv_pointpillars_secfpn_sbn-2x16_2x_waymo-3d-car_latest.pth
```
如果需要生成 bin 文件,应在 `--eval-options`给出 `pklfile_prefix`。对于评价指标, `waymo` 是我们推荐的官方评估原型。目前,`kitti` 这一评估选项是从 KITTI 迁移而来的,且每个难度下的评估结果和 KITTI 数据集中定义得到的不尽相同——目前大多数物体被标记为难度 0(日后会修复)。`kitti` 评估选项的不稳定来源于很大的计算量,转换的数据中遮挡 (occlusion) 和截断 (truncation) 的缺失,难度的不同定义方式,以及不同的平均精度 (Average Precision) 计算方式
如果需要生成 bin 文件,需要在配置文件的 `test_evaluator`指定 `pklfile_prefix`,因此你可以在命令后添加 `--cfg-options "test_evaluator.pklfile_prefix=xxxx"`
**注意**:
......@@ -148,25 +146,20 @@ cp bazel-bin/waymo_open_dataset/metrics/tools/compute_detection_metrics_main ../
如下是一个使用 8 个图形处理器在 Waymo 上测试 PointPillars,生成 bin 文件并提交结果到官方榜单的例子:
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/pointpillars/hv_pointpillars_secfpn_sbn-2x16_2x_waymo-3d-car.py \
checkpoints/hv_pointpillars_secfpn_sbn-2x16_2x_waymo-3d-car_latest.pth --out results/waymo-car/results_eval.pkl \
--format-only --eval-options 'pklfile_prefix=results/waymo-car/kitti_results' \
'submission_prefix=results/waymo-car/kitti_results'
```
如果你想生成 bin 文件并提交到服务器中,在运行测试指令前你需要在配置文件的 `test_evaluator` 中指定 `submission_prefix`
在生成 bin 文件后,您可以简单地构建二进制文件 `create_submission`,并按照[指示](https://github.com/waymo-research/waymo-open-dataset/blob/master/docs/quick_start.md/) 创建一个提交文件。下面是一些示例:
在生成 bin 文件后,您可以简单地构建二进制文件 `create_submission`,并按照[指示](https://github.com/waymo-research/waymo-open-dataset/blob/master/docs/quick_start.md/)创建一个提交文件。下面是一些示例:
```shell
cd ../waymo-od/
bazel build waymo_open_dataset/metrics/tools/create_submission
cp bazel-bin/waymo_open_dataset/metrics/tools/create_submission ../mmdetection3d/mmdet3d/core/evaluation/waymo_utils/
vim waymo_open_dataset/metrics/tools/submission.txtpb # set the metadata information
cp waymo_open_dataset/metrics/tools/submission.txtpb ../mmdetection3d/mmdet3d/core/evaluation/waymo_utils/
cp waymo_open_dataset/metrics/tools/submission.txtpb ../mmdetection3d/mmdet3d/evaluation/functional/waymo_utils/
cd ../mmdetection3d
# suppose the result bin is in `results/waymo-car/submission`
mmdet3d/core/evaluation/waymo_utils/create_submission --input_filenames='results/waymo-car/kitti_results_test.bin' --output_filename='results/waymo-car/submission/model' --submission_filename='mmdet3d/core/evaluation/waymo_utils/submission.txtpb'
mmdet3d/core/evaluation/waymo_utils/create_submission --input_filenames='results/waymo-car/kitti_results_test.bin' --output_filename='results/waymo-car/submission/model' --submission_filename='mmdet3d/evaluation/functional/waymo_utils/submission.txtpb'
tar cvf results/waymo-car/submission/my_model.tar results/waymo-car/submission/my_model/
gzip results/waymo-car/submission/my_model.tar
......
# 依赖
MMDetection3D 可以安装在 Linux, MacOS, (实验性支持 Windows) 的平台上,它具体需要下列安装包:
在本节中,我们将展示如何使用 PyTorch 准备环境。MMDetection3D 支持在 Linux,Windows(实验性支持),MacOS 上运行,它具体需要下列安装包
- Python 3.6+
- PyTorch 1.3+
- CUDA 9.2+ (如果从源码编译 PyTorch, CUDA 9.0 也是兼容的。)
- PyTorch 1.6+
- CUDA 9.2+如果从源码编译 PyTorchCUDA 9.0 也是兼容的
- GCC 5+
- [MMCV](https://mmcv.readthedocs.io/en/latest/#installation)
- [MMEngine](https://mmengine.readthedocs.io/zh_CN/latest/#installation)
- [MMCV](https://mmcv.readthedocs.io/zh_CN/2.x/#installation)
```{note}
如果你已经装了 pytorch, 可以跳过这一部分,然后转到[下一节](#安装). 如果没有,可以参照以下步骤安装环境
如果您对 PyTorch 有经验并且已经安装了它,您可以直接跳转到[下一节](#安装)。否则,您可以按照下述步骤进行准备
```
**步骤 0.** 安装 MiniConda [官网](https://docs.conda.io/en/latest/miniconda.html).
**步骤 0.** [官方网站](https://docs.conda.io/en/latest/miniconda.html)下载并安装 Miniconda。
**步骤 1.** 使用 conda 新建虚拟环境,并进入该虚拟环境.
**步骤 1.** 创建并激活一个 conda 环境。
```shell
# 鉴于 waymo-open-dataset-tf-2-6-0 要求 python>=3.7, 我们推荐安装 python3.8
# 如果您想要安装 python3.6, 之后确保安装 waymo-open-dataset-tf-2-x-0 (x<=4)
# 鉴于 waymo-open-dataset-tf-2-6-0 要求 python>=3.7我们推荐安装 python=3.8
# 如果您想要安装 python<3.7,之后确保安装 waymo-open-dataset-tf-2-x-0 (x<=4)
conda create --name openmmlab python=3.8 -y
conda activate openmmlab
```
**步骤 2.** 基于 [PyTorch 官](https://pytorch.org/)安装 PyTorch 和 torchvision,例如:
**步骤 2.** 基于 [PyTorch 官方说明](https://pytorch.org/get-started/locally/)安装 PyTorch,例如:
GPU 环境下
GPU 平台上:
```shell
conda install pytorch torchvision -c pytorch
```
CPU 环境下
CPU 平台上:
```shell
conda install pytorch torchvision cpuonly -c pytorch
......@@ -39,73 +40,67 @@ conda install pytorch torchvision cpuonly -c pytorch
# 安装
我们建议用户参照我们的最佳实践 MMDetection3D。不过,整个过程也是可定制化的,具体可参照[自定义安装章节](#customize-installation)
我们推荐用户参照我们的最佳实践安装 MMDetection3D。不过,整个过程也是可定制化的,更多信息请参考[自定义安装](#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%AE%89%E8%A3%85)章节。
## 最佳实践
如果你已经成功安装 CUDA 11.0,那么你可以使用这个快速安装命令进行 MMDetection3D 的安装。 否则,则参考下一小节的详细安装流程
假设您已经安装 CUDA 11.0,此处提供了一个完整的脚本来使用 conda 快速安装 MMDetection3D。否则,您需要参考下一小节的详细安装说明
```shell
pip install openmim
mim install mmcv-full
mim install mmdet
mim install mmsegmentation
git clone https://github.com/open-mmlab/mmdetection3d.git
pip install -U openmim
mim install mmengine
mim install 'mmcv>=2.0.0rc0'
mim install 'mmdet>=3.0.0rc0'
git clone https://github.com/open-mmlab/mmdetection3d.git -b dev-1.x
cd mmdetection3d
pip install -e .
```
**步骤 0. 通过[MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv).**
**步骤 1. 安装 [MMDetection](https://github.com/open-mmlab/mmdetection).**
```shell
pip install mmdet
```
同时,如果你想修改这部分的代码,也可以通过以下命令从源码编译 MMDetection:
**步骤 0.** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMEngine](https://github.com/open-mmlab/mmengine)[MMCV](https://github.com/open-mmlab/mmcv)
```shell
git clone https://github.com/open-mmlab/mmdetection.git
cd mmdetection
git checkout v2.24.0 # switch to v2.24.0 branch
pip install -r requirements/build.txt
pip install -v -e . # or "python setup.py develop"
pip install -U openmim
mim install mmengine
mim install 'mmcv>=2.0.0rc0'
```
**步骤 2. 安装 [MMSegmentation](https://github.com/open-mmlab/mmsegmentation).**
**步骤 1.** 安装 [MMDetection](https://github.com/open-mmlab/mmdetection)
```shell
pip install mmsegmentation
mim install 'mmdet>=3.0.0rc0'
```
同时,如果想修改这部分的代码,也可以通过以下命令从源码编译 MMSegmentation:
此外,如果想修改这部分的代码,也可以从源码编译 MMDetection:
```shell
git clone https://github.com/open-mmlab/mmsegmentation.git
cd mmsegmentation
git checkout v0.20.0 # switch to v0.20.0 branch
pip install -e . # or "python setup.py develop"
git clone https://github.com/open-mmlab/mmdetection.git -b dev-3.x
# "-b dev-3.x" 表示切换到 `dev-3.x` 分支。
cd mmdetection
pip install -v -e .
# "-v" 指详细说明,或更多的输出
# "-e" 表示以可编辑的模式安装项目
# 因此本地对代码做的任何修改都会生效,而无需重新安装。
```
**步骤 3. 克隆 MMDetection3D 代码仓库.**
**步骤 2.** 克隆 MMDetection3D 代码仓库
```shell
git clone https://github.com/open-mmlab/mmdetection3d.git
git clone https://github.com/open-mmlab/mmdetection3d.git -b dev-1.x
# "-b dev-1.x" 表示切换到 `dev-1.x` 分支。
cd mmdetection3d
```
**步骤 4. 安装依赖包和 MMDetection3D.**
**步骤 3.** 安装依赖包和 MMDetection3D
```shell
pip install -v -e . # or "python setup.py develop"
pip install -v -e . # 或者 "python setup.py develop"
```
**注意:**
注意:
1. Git 的 commit id 在步骤 d 将会被写入到版本号当中,例 0.6.0+2e7045c 。版本号将保存在训练的模型里。推荐在每一次执行步骤 d 时,从 github 上获取最新的更新。如果基于 C++/CUDA 代码被修改了,请执行以下步骤;
1. Git 的 commit id 在步骤 3 将会被写入到版本号当中,例`0.6.0+2e7045c`。版本号将保存在训练的模型里。我们推荐您每次从 github 上获取更新后都执行一次步骤 3。如果修改了 C++/CUDA 代码,那么执行该步骤是必要的。
> 重要: 如果你重装了不同版本的 CUDA 或者 PyTorch mmdet,请务必移除 `./build` 文件。
> 重要:如果您使用了不同版本的 CUDA/PyTorch 重新安装 mmdet3d,需要移除 `./build` 文件
```shell
pip uninstall mmdet3d
......@@ -113,53 +108,51 @@ pip install -v -e . # or "python setup.py develop"
find . -name "*.so" | xargs rm
```
2. 按照上述说明,MMDetection3D 安装在 `dev` 模式下,因此在本地对代码做的任何修改都会生效,无需重新安装
2. 按照上述说明,MMDetection3D 安装在 `dev` 模式下,因此在本地对代码做的任何修改都会生效,无需重新安装(除非您提交了 commits 并且想要更新版本号)。
3. 如果希望使用 `opencv-python-headless` 而不是 `opencv-python` 可以在安装 MMCV 之前安装
3. 如果希望使用 `opencv-python-headless` 而不是 `opencv-python`可以在安装 MMCV 之前安装它。
4. 一些安装依赖是可以选择的。例如只需要安装最低运行要求的版本,则可以使用 `pip install -v -e .` 命令。如果希望使用可选择的像 `albumentations``imagecorruptions` 这种依赖项,可以使用 `pip install -r requirements/optional.txt ` 进行手动安装,或者在使用 `pip` 时指定所需的附加功能(例如 `pip install -v -e .[optional]`),支持附加功能的有效键值包括 `all``tests``build` 以及 `optional`
4. 一些安装依赖是可选的。简单地运行 `pip install -v -e .` 将会安装最低运行要求的版本。如果想要使用一些可选依赖项,例如 `albumentations``imagecorruptions`,可以使用 `pip install -r requirements/optional.txt` 进行手动安装,或者在使用 `pip` 时指定所需的附加功能(例如 `pip install -v -e .[optional]`),支持附加功能的有效键值包括 `all``tests``build` 以及 `optional`
我们已经支持 spconv2.0. 如果用户已经安装 spconv 2.0, 代码会默认使用 spconv 2.0。它可以比原生 mmcv spconv 使用更少的内存。 用户可以使用下列的命令来安装 spconv 2.0.
我们已经支持 `spconv 2.0`如果用户已经安装 `spconv 2.0`,代码会默认使用 `spconv 2.0`,它会比原生 `mmcv spconv` 使用更少的 GPU 内存。用户可以使用下列的命令来安装 `spconv 2.0`
```bash
pip install cumm-cuxxx
pip install spconv-cuxxx
```
xxx 表示 CUDA 版本。
`xxx` 表示环境中的 CUDA 版本。
例如, 使用 CUDA 10.2, 对应命令是 `pip install cumm-cu102 && pip install spconv-cu102`.
例如使用 CUDA 10.2对应命令是 `pip install cumm-cu102 && pip install spconv-cu102`
支持的 CUDA 版本包括 10.2, 11.1, 11.3, and 11.4. 用户可以通过源码编译来在这些版本上安装. 具体细节请参考 [spconv v2.x](https://github.com/traveller59/spconv).
支持的 CUDA 版本包括 10.211.111.3 11.4用户可以通过源码编译来安装。更多细节请参考[spconv v2.x](https://github.com/traveller59/spconv)
我们同时也支持 Minkowski Engine 作为稀疏卷的后端. 如果需要,可以参照 [安装指南](https://github.com/NVIDIA/MinkowskiEngine#installation) 或使用 `pip`:
我们也支持 `Minkowski Engine` 作为稀疏卷的后端如果需要,请参考[安装指南](https://github.com/NVIDIA/MinkowskiEngine#installation)使用 `pip` 来安装:
```shell
conda install openblas-devel -c anaconda
pip install -U git+https://github.com/NVIDIA/MinkowskiEngine -v --no-deps --install-option="--blas_include_dirs=/opt/conda/include" --install-option="--blas=openblas"
```
5. 我们的代码目前不能在只有 CPU 的环境(CUDA 不可用)下编译运行
5. 我们的代码目前不能在只有 CPU 的环境(CUDA 不可用)下编译。
# 验证
## 验证
## 通过点云样例程序来验证
### 使用点云样例来验证
我们提供了一些样例脚本去测试单个样本预训练的模型可以从[模型库](model_zoo.md)中下载. 运行如下命令可以去测试点云场景下一个单模态的 3D 检测算法
我们提供了一些样例脚本去测试单个样本预训练的模型可以从[模型库](model_zoo.md)中下载运行如下命令可以去测试点云场景下一个单模态的 3D 检测算法
```shell
python demo/pcd_demo.py ${PCD_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--score-thr ${SCORE_THR}] [--out-dir ${OUT_DIR}]
```
:
如:
```shell
python demo/pcd_demo.py demo/data/kitti/kitti_000008.bin configs/second/hv_second_secfpn_6x8_80e_kitti-3d-car.py checkpoints/hv_second_secfpn_6x8_80e_kitti-3d-car_20200620_230238-393f000c.pth
python demo/pcd_demo.py demo/data/kitti/000008.bin configs/second/second_hv_secfpn_8xb6-80e_kitti-3d-car.py checkpoints/second_hv_secfpn_8xb6-80e_kitti-3d-car_20200620_230238-393f000c.pth
```
如果你想输入一个 `ply` 格式的文件,你可以使用如下函数将它转换为 `bin` 的文件格式。然后就可以使用转化成 `bin` 格式的文件去运行样例程序。
请注意在使用此脚本前,你需要先安装 `pandas``plyfile`。 这个函数也可使用在数据预处理当中,为了能够直接训练 `ply data`
如果您想输入一个 `.ply` 文件,您可以使用如下函数将它转换成 `.bin` 格式。然后您可以使用转化的 `.bin` 文件来运行样例。请注意在使用此脚本之前,您需要安装 `pandas``plyfile`。这个函数也可以用于训练 `ply 数据`时作为数据预处理来使用。
```python
import numpy as np
......@@ -167,97 +160,85 @@ import pandas as pd
from plyfile import PlyData
def convert_ply(input_path, output_path):
plydata = PlyData.read(input_path) # read file
data = plydata.elements[0].data # read data
data_pd = pd.DataFrame(data) # convert to DataFrame
data_np = np.zeros(data_pd.shape, dtype=np.float) # initialize array to store data
property_names = data[0].dtype.names # read names of properties
plydata = PlyData.read(input_path) # 读取文件
data = plydata.elements[0].data # 读取数据
data_pd = pd.DataFrame(data) # 转换成 DataFrame
data_np = np.zeros(data_pd.shape, dtype=np.float) # 初始化数组来存储数据
property_names = data[0].dtype.names # 读取属性名称
for i, name in enumerate(
property_names): # read data by property
property_names): # 通过属性读取数据
data_np[:, i] = data_pd[name]
data_np.astype(np.float32).tofile(output_path)
```
:
如:
```python
convert_ply('./test.ply', './test.bin')
```
如果有其他格式的点云文件 (例:`off`, `obj`), 你可以使用 `trimesh` 将它们转化成 `ply`.
如果有其他格式的点云数据(`.off``.obj` 等),您可以使用 `trimesh` 将它们转化成 `.ply`
```python
import trimesh
def to_ply(input_path, output_path, original_type):
mesh = trimesh.load(input_path, file_type=original_type) # read file
mesh.export(output_path, file_type='ply') # convert to ply
mesh = trimesh.load(input_path, file_type=original_type) # 读取文件
mesh.export(output_path, file_type='ply') # 转换成 ply
```
:
如:
```python
to_ply('./test.obj', './test.ply', 'obj')
```
更多的关于单/多模态和室内/室外的 3D 检测的样例可以在[](demo.md)找到.
更多的关于单/多模态和室内/室外的 3D 检测的样例可以在[](user_guides/inference.md)找到
## 测试点云的高级接口
### 同步接口
## 自定义安装
这里有一个例子去说明如何构建模型以及测试给出的点云:
### CUDA 版本
```python
from mmdet3d.apis import init_model, inference_detector
在安装 PyTorch 时,您需要指定 CUDA 的版本。如果您不清楚应该选择哪一个,请遵循我们的建议:
config_file = 'configs/votenet/votenet_8xb8_scannet-3d.py'
checkpoint_file = 'checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth'
- 对于 Ampere 架构的 NVIDIA GPU,例如 GeForce 30 系列以及 NVIDIA A100,CUDA 11 是必需的。
- 对于较早的 NVIDIA GPU,CUDA 11 是向后兼容的,但 CUDA 10.2 提供更好的兼容性,并且更轻量。
# 从配置文件和预训练的模型文件中构建模型
model = init_model(config_file, checkpoint_file, device='cuda:0')
请确保 GPU 驱动版本满足最低的版本需求。更多信息请参考此[表格](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions)
# 测试单个文件并可视化结果
point_cloud = 'test.bin'
result, data = inference_detector(model, point_cloud)
# 可视化结果并且将结果保存到 'results' 文件夹
model.show_results(data, result, out_dir='results')
```{note}
如果您遵循我们的最佳实践,您只需要安装 CUDA 运行库,这是因为不需要在本地编译 CUDA 代码。但如果您希望从源码编译 MMCV,或者开发其他 CUDA 算子,那么您需要从 NVIDIA 的[官网](https://developer.nvidia.com/cuda-downloads)安装完整的 CUDA 工具链,并且该版本应该与 PyTorch 的 CUDA 版本相匹配,比如在 `conda install` 指令里指定 cudatoolkit 版本。
```
## 自定义安装
### CUDA 版本
当安装 PyTorch 的时候,你需要去指定 CUDA 的版本。如果你不清楚如何选择 CUDA 的版本,可以参考我们如下的建议:
### 不通过 MIM 安装 MMEngine
- 对于 Ampere 的 NVIDIA GPU, 比如 GeForce 30 series 和 NVIDIA A100, CUDA 11 是必须的。
- 对于老款的 NVIDIA GPUs, CUDA 11 是可编译的,但是 CUDA 10.2 提供更好的可编译性,并且更轻量。
如果想要使用 pip 而不是 MIM 安装 MMEngine,请参考 [MMEngine 安装指南](https://mmengine.readthedocs.io/zh_CN/latest/get_started/installation.html)
请确保GPU 驱动版本大于最低需求。这个[表格](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions) 提供更多的信息。
例如,您可以通过以下指令安装 MMEngine:
```{note}
如果你参照最佳实践,你只需要安装 CUDA runtime libraries。 这是因为没有代码需要在本地通过 CUDA 编译。然而如果你需要编译MMCV源码,或者编译其他 CUDA 代码,你需要基于 NVIDIA [website](https://developer.nvidia.com/cuda-downloads) 安装完整的 CUDA toolkit,并且要保证它的版本跟 PyTorch 匹配。比如在 'conda install` 里对应的 cudatoolkit 版本。
```shell
pip install mmengine
```
### 不通过MIM 安装MMCV
### 不通过 MIM 安装 MMCV
MMCV 包含一些 C++ 和 CUDA 扩展,因此以复杂的方式依赖于 PyTorch。 MIM 会自动解决此类依赖关系并使安装更容易。但是,这不是必的。
MMCV 包含 C++ 和 CUDA 拓展,因此其对 PyTorch 的依赖更复杂。MIM 会自动解决此类依赖关系并使安装更容易。但这不是必的。
如果想要使用 pip 而不是 MIM 安装 MMCV, 请参考 [MMCV 安装指南](https://mmcv.readthedocs.io/en/latest/get_started/installation.html). 这需要根据 PyTorch 版本及其 CUDA 版本手动指定 find-url
如果想要使用 pip 而不是 MIM 安装 MMCV请参考 [MMCV 安装指南](https://mmcv.readthedocs.io/zh_CN/2.x/get_started/installation.html)这需要用指定 url 的形式手动指定对应的 PyTorch CUDA 版本。
例如, 下面的脚本安装 的 mmcv-full 是对应的 PyTorch 1.10.x 和 CUDA 11.3.
例如,下述指令将会安装基于 PyTorch 1.10.x 和 CUDA 11.3 编译的 MMCV:
```shell
pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html
pip install mmcv -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html
```
### 通过Docker 安装
### 通过 Docker 使用 MMDetection3D
我们提供了 [Dockerfile](https://github.com/open-mmlab/mmdetection3d/blob/master/docker/Dockerfile)建立一个镜像。
我们提供了 [Dockerfile](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docker/Dockerfile) 来一个镜像。
```shell
# 基于 PyTorch 1.6, CUDA 10.1 生成 docker 的镜像
docker build -t mmdetection3d docker/
# 基于 PyTorch 1.6CUDA 10.1 构建镜像
docker build -t mmdetection3d -f docker/Dockerfile .
```
运行命令:
......@@ -266,35 +247,33 @@ docker build -t mmdetection3d docker/
docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmdetection3d/data mmdetection3d
```
## 从零开始的安装脚本
### 从零开始的安装脚本
以下是一个基于 conda 安装 MMdetection3D 的脚本
以下是一个基于 conda 安装 MMDetection3D 的完整脚本
```shell
# 鉴于 waymo-open-dataset-tf-2-6-0 要求 python>=3.7, 我们推荐安装 python3.8
# 如果您想要安装 python3.6, 之后确保安装 waymo-open-dataset-tf-2-x-0 (x<=4)
conda create -n open-mmlab python=3.8 -y
conda activate open-mmlab
# 鉴于 waymo-open-dataset-tf-2-6-0 要求 python>=3.7我们推荐安装 python=3.8
# 如果您想要安装 python<3.7,之后确保安装 waymo-open-dataset-tf-2-x-0 (x<=4)
conda create -n openmmlab python=3.8 -y
conda activate openmmlab
# install latest PyTorch prebuilt with the default prebuilt CUDA version (usually the latest)
# 使用默认的预编译 CUDA 版本(通常是最新的)安装最新的 PyTorch
conda install -c pytorch pytorch torchvision -y
# install mmcv
pip install mmcv-full
# 安装 mmengine 和 mmcv
pip install -U openmim
mim install mmengine
mim install 'mmcv>=2.0.0rc0'
# install mmdetection
pip install git+https://github.com/open-mmlab/mmdetection.git
# 安装 mmdetection
mim install 'mmdet>=3.0.0rc0'
# install mmsegmentation
pip install git+https://github.com/open-mmlab/mmsegmentation.git
# install mmdetection3d
git clone https://github.com/open-mmlab/mmdetection3d.git
# 安装 mmdetection3d
git clone https://github.com/open-mmlab/mmdetection3d.git -b dev-1.x
cd mmdetection3d
pip install -v -e .
pip install -e .
```
## 故障排除
如果在安装过程中遇到什么问题,可以先参考 [FAQ](faq.md) 页面.
如果没有找到对应的解决方案,你也可以在 Github [提一个 issue](https://github.com/open-mmlab/mmdetection3d/issues/new/choose)
如果在安装过程中遇到一些问题,请先参考 [FAQ](notes/faq.md) 页面。如果没有找到对应的解决方案,您也可以在 GitHub [提一个 issue](https://github.com/open-mmlab/mmdetection3d/issues/new/choose)
# 变更日志
# v1.1 变更日志
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment