Commit eb1107e4 authored by raojy's avatar raojy
Browse files

fix_mmdetection

parent 7aa442d5
Pipeline #3461 canceled with stages
# 学习配置文件
MMDetection3D 和其他 OpenMMLab 仓库使用 [MMEngine 的配置文件系统](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)。它具有模块化和继承性设计,以便于进行各种实验。
## 配置文件的内容
MMDetection3D 采用模块化设计,所有功能的模块可以通过配置文件进行配置。以 PointPillars 为例,我们将根据不同的功能模块介绍配置文件的各个字段。
### 模型配置
在 MMDetection3D 的配置中,我们使用 `model` 字段来配置检测算法的组件。除了 `voxel_encoder``backbone` 等神经网络组件外,还需要 `data_preprocessor``train_cfg``test_cfg``data_preprocessor` 负责对数据加载器(dataloader)输出的每一批数据进行预处理。模型配置中的 `train_cfg``test_cfg` 用于设置训练和测试组件的超参数。
```python
model = dict(
type='VoxelNet',
data_preprocessor=dict(
type='Det3DDataPreprocessor',
voxel=True,
voxel_layer=dict(
max_num_points=32,
point_cloud_range=[0, -39.68, -3, 69.12, 39.68, 1],
voxel_size=[0.16, 0.16, 4],
max_voxels=(16000, 40000))),
voxel_encoder=dict(
type='PillarFeatureNet',
in_channels=4,
feat_channels=[64],
with_distance=False,
voxel_size=[0.16, 0.16, 4],
point_cloud_range=[0, -39.68, -3, 69.12, 39.68, 1]),
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,
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=0.1111111111111111,
loss_weight=2.0),
loss_dir=dict(
type='mmdet.CrossEntropyLoss', use_sigmoid=False,
loss_weight=0.2)),
train_cfg=dict(
assigner=[
dict(
type='Max3DIoUAssigner',
iou_calculator=dict(type='BboxOverlapsNearest3D'),
pos_iou_thr=0.5,
neg_iou_thr=0.35,
min_pos_iou=0.35,
ignore_iof_thr=-1),
dict(
type='Max3DIoUAssigner',
iou_calculator=dict(type='BboxOverlapsNearest3D'),
pos_iou_thr=0.5,
neg_iou_thr=0.35,
min_pos_iou=0.35,
ignore_iof_thr=-1),
dict(
type='Max3DIoUAssigner',
iou_calculator=dict(type='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))
```
### 数据集和评测器配置
在使用[执行器(Runner)](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/runner.html)进行训练、测试和验证时,我们需要配置[数据加载器](https://pytorch.org/docs/stable/data.html?highlight=data%20loader#torch.utils.data.DataLoader)。构建数据加载器需要设置数据集和数据处理流程。由于这部分的配置较为复杂,我们使用中间变量来简化数据加载器配置的编写。
```python
dataset_type = 'KittiDataset'
data_root = 'data/kitti/'
class_names = ['Pedestrian', 'Cyclist', 'Car']
point_cloud_range = [0, -39.68, -3, 69.12, 39.68, 1]
input_modality = dict(use_lidar=True, use_camera=False)
metainfo = dict(classes=class_names)
db_sampler = dict(
data_root=data_root,
info_path=data_root + 'kitti_dbinfos_train.pkl',
rate=1.0,
prepare=dict(
filter_by_difficulty=[-1],
filter_by_min_points=dict(Car=5, Pedestrian=5, Cyclist=5)),
classes=class_names,
sample_groups=dict(Car=15, Pedestrian=15, Cyclist=15),
points_loader=dict(
type='LoadPointsFromFile', coord_type='LIDAR', load_dim=4, use_dim=4))
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='ObjectSample', db_sampler=db_sampler, use_ground_plane=True),
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_labels_3d', 'gt_bboxes_3d'])
]
test_pipeline = [
dict(type='LoadPointsFromFile', coord_type='LIDAR', load_dim=4, use_dim=4),
dict(
type='MultiScaleFlipAug3D',
img_scale=(1333, 800),
pts_scale_ratio=1,
flip=False,
transforms=[
dict(
type='GlobalRotScaleTrans',
rot_range=[0, 0],
scale_ratio_range=[1., 1.],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D'),
dict(
type='PointsRangeFilter', point_cloud_range=point_cloud_range)
]),
dict(type='Pack3DDetInputs', keys=['points'])
]
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='RepeatDataset',
times=2,
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='kitti_infos_train.pkl',
data_prefix=dict(pts='training/velodyne_reduced'),
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_type,
data_root=data_root,
data_prefix=dict(pts='training/velodyne_reduced'),
ann_file='kitti_infos_val.pkl',
pipeline=test_pipeline,
modality=input_modality,
test_mode=True,
metainfo=metainfo,
box_type_3d='LiDAR'))
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,
data_prefix=dict(pts='training/velodyne_reduced'),
ann_file='kitti_infos_val.pkl',
pipeline=test_pipeline,
modality=input_modality,
test_mode=True,
metainfo=metainfo,
box_type_3d='LiDAR'))
```
[评测器](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/evaluation.html)用于计算训练模型在验证和测试数据集上的指标。评测器的配置由一个或一组评价指标配置组成:
```python
val_evaluator = dict(
type='KittiMetric',
ann_file=data_root + 'kitti_infos_val.pkl',
metric='bbox')
test_evaluator = val_evaluator
```
由于测试数据集没有标注文件,因此 MMDetection3D 中的 test_dataloader 和 test_evaluator 配置通常等于 val。如果您想要保存在测试数据集上的检测结果,则可以像这样编写配置:
```python
# 在测试集上推理,
# 并将检测结果转换格式以用于提交结果
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,
data_prefix=dict(pts='testing/velodyne_reduced'),
ann_file='kitti_infos_test.pkl',
load_eval_anns=False,
pipeline=test_pipeline,
modality=input_modality,
test_mode=True,
metainfo=metainfo,
box_type_3d='LiDAR'))
test_evaluator = dict(
type='KittiMetric',
ann_file=data_root + 'kitti_infos_test.pkl',
metric='bbox',
format_only=True,
submission_prefix='results/kitti-3class/kitti_results')
```
### 训练和测试配置
MMEngine 的执行器使用循环(Loop)来控制训练,验证和测试过程。用户可以使用这些字段设置最大训练轮次和验证间隔:
```python
train_cfg = dict(
type='EpochBasedTrainLoop',
max_epochs=80,
val_interval=2)
val_cfg = dict(type='ValLoop')
test_cfg = dict(type='TestLoop')
```
### 优化配置
`optim_wrapper` 是配置优化相关设置的字段。优化器封装不仅提供了优化器的功能,还支持梯度裁剪、混合精度训练等功能。更多内容请看[优化器封装教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html)
```python
optim_wrapper = dict( # 优化器封装配置
type='OptimWrapper', # 优化器封装类型,切换到 AmpOptimWrapper 启动混合精度训练
optimizer=dict( # 优化器配置。支持 PyTorch 的各种优化器,请参考 https://pytorch.org/docs/stable/optim.html#algorithms
type='AdamW', lr=0.001, betas=(0.95, 0.99), weight_decay=0.01),
clip_grad=dict(max_norm=35, norm_type=2)) # 梯度裁剪选项。设置为 None 禁用梯度裁剪。使用方法请见 https://mmengine.readthedocs.io/zh_CN/latest/tutorials/optim_wrapper.html
```
`param_scheduler` 是配置调整优化器超参数(例如学习率和动量)的字段。用户可以组合多个调度器来创建所需要的参数调整策略。更多信息请参考[参数调度器教程](https://mmengine.readthedocs.io/zh_CN/latest/tutorials/param_scheduler.html)[参数调度器 API 文档](https://mmengine.readthedocs.io/zh_CN/latest/api/optim.html#scheduler)
```python
param_scheduler = [
dict(
type='CosineAnnealingLR',
T_max=32,
eta_min=0.01,
begin=0,
end=32,
by_epoch=True,
convert_to_iter_based=True),
dict(
type='CosineAnnealingLR',
T_max=48,
eta_min=1.0000000000000001e-07,
begin=32,
end=80,
by_epoch=True,
convert_to_iter_based=True),
dict(
type='CosineAnnealingMomentum',
T_max=32,
eta_min=0.8947368421052632,
begin=0,
end=32,
by_epoch=True,
convert_to_iter_based=True),
dict(
type='CosineAnnealingMomentum',
T_max=48,
eta_min=1,
begin=32,
end=80,
by_epoch=True,
convert_to_iter_based=True),
]
```
### 钩子配置
用户可以在训练、验证和测试循环上添加钩子,从而在运行期间插入一些操作。有两种不同的钩子字段,一种是 `default_hooks`,另一种是 `custom_hooks`
`default_hooks` 是一个钩子配置字典,并且这些钩子是运行时所需要的。它们具有默认优先级,是不需要修改的。如果未设置,执行器将使用默认值。如果要禁用默认钩子,用户可以将其配置设置为 `None`
```python
default_hooks = dict(
timer=dict(type='IterTimerHook'),
logger=dict(type='LoggerHook', interval=50),
param_scheduler=dict(type='ParamSchedulerHook'),
checkpoint=dict(type='CheckpointHook', interval=-1),
sampler_seed=dict(type='DistSamplerSeedHook'),
visualization=dict(type='Det3DVisualizationHook'))
```
`custom_hooks` 是一个由其他钩子配置组成的列表。用户可以开发自己的钩子并将其插入到该字段中。
```python
custom_hooks = []
```
### 运行配置
```python
default_scope = 'mmdet3d' # 寻找模块的默认注册器域。请参考 https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/registry.html
env_cfg = dict(
cudnn_benchmark=False, # 是否启用 cudnn benchmark
mp_cfg=dict( # 多进程配置
mp_start_method='fork', # 使用 fork 来启动多进程。'fork' 通常比 'spawn' 更快,但可能不安全。请参考 https://github.com/pytorch/pytorch/issues/1355
opencv_num_threads=0), # 关闭 opencv 的多进程以避免系统超负荷
dist_cfg=dict(backend='nccl')) # 分布式配置
vis_backends = [dict(type='LocalVisBackend')] # 可视化后端。请参考 https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/visualization.html
visualizer = dict(
type='Det3DLocalVisualizer', vis_backends=vis_backends, name='visualizer')
log_processor = dict(
type='LogProcessor', # 日志处理器用于处理运行时日志
window_size=50, # 日志数值的平滑窗口
by_epoch=True) # 是否使用 epoch 格式的日志。需要与训练循环的类型保持一致
log_level = 'INFO' # 日志等级
load_from = None # 从给定路径加载模型检查点作为预训练模型。这不会恢复训练。
resume = False # 是否从 `load_from` 中定义的检查点恢复。如果 `load_from` 为 None,它将恢复 `work_dir` 中的最近检查点。
```
## 配置文件继承
`configs/_base_` 文件夹下有 4 个基本组件类型,分别是:数据集(dataset),模型(model),训练策略(schedule)和运行时的默认设置(default runtime)。许多方法,如 SECOND、PointPillars、PartA2、VoteNet 都能够很容易地构建出来。由 `_base_` 下的组件组成的配置,被我们称为 _原始配置(primitive)_。
对于同一个文件夹下的所有配置,推荐**只有一个**对应的 _原始配置_ 文件。所有其他的配置文件都应该继承自这个 _原始配置_ 文件。这样就能保证配置文件的最大继承深度为 3。
为了便于理解,我们建议贡献者继承现有方法。例如,如果在 PointPillars 的基础上做了一些修改,用户可以首先通过指定 `_base_ = '../pointpillars/pointpillars_hv_fpn_sbn-all_8xb4-2x_nus-3d.py'` 来继承基础的 PointPillars 结构,然后修改配置文件中的必要参数以完成继承。
如果您在构建一个与任何现有方法都不共享的全新方法,那么可以在 `configs` 文件夹下创建一个新的例如 `xxx_rcnn` 文件夹。
更多细节请参考 [MMEngine 配置文件教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)
通过设置 `_base_` 字段,我们可以设置当前配置文件继承自哪些文件。
`_base_` 为文件路径字符串时,表示继承一个配置文件的内容。
```python
_base_ = './pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py'
```
`_base_` 是多个文件路径组成的列表式,表示继承多个文件。
```python
_base_ = [
'../_base_/models/pointpillars_hv_secfpn_kitti.py',
'../_base_/datasets/kitti-3d-3class.py',
'../_base_/schedules/cyclic-40e.py', '../_base_/default_runtime.py'
]
```
如果需要检测配置文件,可以通过运行 `python tools/misc/print_config.py /PATH/TO/CONFIG` 来查看完整的配置。
### 忽略基础配置文件里的部分字段
有时,您也许会设置 `_delete_=True` 去忽略基础配置文件里的一些字段。您可以参考 [MMEngine 配置文件教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html) 来获得一些简单的指导。
在 MMDetection3D 里,例如,修改以下 PointPillars 配置中的颈部网络:
```python
model = dict(
type='MVXFasterRCNN',
data_preprocessor=dict(voxel_layer=dict(...)),
pts_voxel_encoder=dict(...),
pts_middle_encoder=dict(...),
pts_backbone=dict(...),
pts_neck=dict(
type='FPN',
norm_cfg=dict(type='naiveSyncBN2d', eps=1e-3, momentum=0.01),
act_cfg=dict(type='ReLU'),
in_channels=[64, 128, 256],
out_channels=256,
start_level=0,
num_outs=3),
pts_bbox_head=dict(...))
```
`FPN``SECONDFPN` 使用不同的关键字来构建:
```python
_base_ = '../_base_/models/pointpillars_hv_fpn_nus.py'
model = dict(
pts_neck=dict(
_delete_=True,
type='SECONDFPN',
norm_cfg=dict(type='naiveSyncBN2d', eps=1e-3, momentum=0.01),
in_channels=[64, 128, 256],
upsample_strides=[1, 2, 4],
out_channels=[128, 128, 128]),
pts_bbox_head=dict(...))
```
`_delete_=True` 将使用新的键去替换 `pts_neck` 字段内所有旧的键。
### 在配置文件里使用中间变量
配置文件里会使用一些中间变量,例如数据集里的 `train_pipeline`/`test_pipeline`。需要注意的是,当修改子配置文件中的中间变量时,用户需要再次将中间变量传递到对应的字段中。例如,我们想使用多尺度策略训练并测试 PointPillars,`train_pipeline`/`test_pipeline` 是我们想要修改的中间变量。
```python
_base_ = './nus-3d.py'
train_pipeline = [
dict(
type='LoadPointsFromFile',
load_dim=5,
use_dim=5,
backend_args=backend_args),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
backend_args=backend_args),
dict(type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(
type='GlobalRotScaleTrans',
rot_range=[-0.3925, 0.3925],
scale_ratio_range=[0.95, 1.05],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D', flip_ratio_bev_horizontal=0.5),
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectNameFilter', classes=class_names),
dict(type='PointShuffle'),
dict(
type='Pack3DDetInputs',
keys=['points', 'gt_labels_3d', 'gt_bboxes_3d'])
]
test_pipeline = [
dict(
type='LoadPointsFromFile',
load_dim=5,
use_dim=5,
backend_args=backend_args),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
backend_args=backend_args),
dict(
type='MultiScaleFlipAug3D',
img_scale=(1333, 800),
pts_scale_ratio=[0.95, 1.0, 1.05],
flip=False,
transforms=[
dict(
type='GlobalRotScaleTrans',
rot_range=[0, 0],
scale_ratio_range=[1., 1.],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D'),
dict(
type='PointsRangeFilter', point_cloud_range=point_cloud_range)
]),
dict(type='Pack3DDetInputs', keys=['points'])
]
train_dataloader = dict(dataset=dict(pipeline=train_pipeline))
val_dataloader = dict(dataset=dict(pipeline=test_pipeline))
test_dataloader = dict(dataset=dict(pipeline=test_pipeline))
```
我们首先定义新的 `train_pipeline`/`test_pipeline`,然后传递到数据加载器字段中。
### 复用 \_base\_ 文件中的变量
如果用户希望复用 base 文件中的变量,则可以通过使用 `{{_base_.xxx}}` 获取对应变量的拷贝。例如:
```python
_base_ = './pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py'
a = {{_base_.model}} # 变量 `a` 等于 `_base_` 中定义的 `model`
```
## 通过脚本参数修改配置
当使用 `tools/train.py` 或者 `tools/test.py` 提交工作时,您可以通过指定 `--cfg-options` 来修改配置文件。
- 更新配置字典的键值
可以按照原始配置文件中字典的键值顺序指定配置选项。例如,使用 `--cfg-options model.backbone.norm_eval=False` 将模型主干网络中的所有 BN 模块都改为 `train` 模式。
- 更新配置列表中的键值
在配置文件里,一些配置字典被包含在列表中,例如,训练流程 `train_dataloader.dataset.pipeline` 通常是一个列表,例如 `[dict(type='LoadPointsFromFile'), ...]`。如果您想要将训练流程中的 `'LoadPointsFromFile'` 改成 `'LoadPointsFromDict'`,您需要指定 `--cfg-options data.train.pipeline.0.type=LoadPointsFromDict`
- 更新列表/元组的值
如果要更新的值是列表或元组。例如,配置文件通常设置 `model.data_preprocessor.mean=[123.675, 116.28, 103.53]`。如果您想要改变这个均值,您需要指定 `--cfg-options model.data_preprocessor.mean="[127,127,127]"`。注意,引号 `"` 是支持列表/元组数据类型所必需的,并且在指定值的引号内**不允许**有空格。
## 配置文件名称风格
我们遵循以下样式来命名配置文件。建议贡献者遵循相同的风格。
```
{algorithm name}_{model component names [component1]_[component2]_[...]}_{training settings}_{training dataset information}_{testing dataset information}.py
```
文件名分为五个部分。所有部分和组件用 `_` 连接,每个部分或组件内的单词应该用 `-` 连接。
- `{algorithm name}`:算法的名称。它可以是检测器的名称,例如 `pointpillars``fcos3d` 等。
- `{model component names}`:算法中使用的组件名称,如 voxel_encoder、backbone、neck 等。例如 `second_secfpn_head-dcn-circlenms` 表示使用 SECOND 的 SparseEncoder,SECONDFPN,以及带有 DCN 和 circle NMS 的检测头。
- `{training settings}`:训练设置的信息,例如批量大小,数据增强,损失函数策略,调度器以及训练轮次/迭代。例如 `8xb4-tta-cyclic-20e` 表示使用 8 个 gpu,每个 gpu 有 4 个数据样本,测试增强,余弦退火学习率,训练 20 个 epoch。缩写介绍:
- `{gpu x batch_per_gpu}`:GPU 数和每个 GPU 的样本数。`bN` 表示每个 GPU 上的批量大小为 N。例如 `4xb4` 是 4 个 GPU,每个 GPU 有 4 个样本数的缩写。
- `{schedule}`:训练方案,可选项为 `schedule-2x``schedule-3x``cyclic-20e` 等。`schedule-2x``schedule-3x` 分别代表 24 epoch 和 36 epoch。`cyclic-20e` 表示 20 epoch。
- `{training dataset information}`:训练数据集名,例如 `kitti-3d-3class``nus-3d``s3dis-seg``scannet-seg``waymoD5-3d-car`。这里 `3d` 表示数据集用于 3D 目标检测,`seg` 表示数据集用于点云分割。
- `{testing dataset information}`(可选):当模型在一个数据集上训练,在另一个数据集上测试时的测试数据集名。如果没有注明,则表示训练和测试的数据集类型相同。
# 坐标系
## 概述
MMDetection3D 使用 3 种不同的坐标系。3D 目标检测领域中不同坐标系的存在是非常有必要的,因为对于各种 3D 数据采集设备来说,如激光雷达、深度相机等,使用的坐标系是不一致的,不同的 3D 数据集也遵循不同的数据格式。早期的工作,比如 SECOND、VoteNet 将原始数据转换为另一种格式,形成了一些后续工作也遵循的约定,使得不同坐标系之间的转换变得更加复杂。
尽管数据集和采集设备多种多样,但是通过总结 3D 目标检测的工作线,我们可以将坐标系大致分为三类:
- 相机坐标系 -- 大多数相机的坐标系,在该坐标系中 y 轴正方向指向地面,x 轴正方向指向右侧,z 轴正方向指向前方。
```
上 z 前
| ^
| /
| /
| /
|/
左 ------ 0 ------> x 右
|
|
|
|
v
y 下
```
- 激光雷达坐标系 -- 众多激光雷达的坐标系,在该坐标系中 z 轴负方向指向地面,x 轴正方向指向前方,y 轴正方向指向左侧。
```
z 上 x 前
^ ^
| /
| /
| /
|/
y 左 <------ 0 ------ 右
```
- 深度坐标系 -- VoteNet、H3DNet 等模型使用的坐标系,在该坐标系中 z 轴负方向指向地面,x 轴正方向指向右侧,y 轴正方向指向前方。
```
z 上 y 前
^ ^
| /
| /
| /
|/
左 ------ 0 ------> x 右
```
该教程中的坐标系定义实际上**不仅仅是定义三个轴**。对于形如 $(x, y, z, dx, dy, dz, r)$ 的框来说,我们的坐标系也定义了如何解释框的尺寸 $(dx, dy, dz)$ 和转向角 (yaw) 角度 $r$。
三个坐标系的图示如下:
![](https://raw.githubusercontent.com/open-mmlab/mmdetection3d/master/resources/coord_sys_all.png)
上面三张图是 3D 坐标系,下面三张图是鸟瞰图。
以后我们将坚持使用本教程中定义的三个坐标系。
## 转向角 (yaw) 的定义
请参考[维基百科](https://en.wikipedia.org/wiki/Euler_angles#Tait%E2%80%93Bryan_angles)了解转向角的标准定义。在目标检测中,我们选择一个轴作为重力轴,并在垂直于重力轴的平面 $\\Pi$ 上选取一个参考方向,那么参考方向的转向角为 0,在 $\\Pi$ 上的其他方向有非零的转向角,其角度取决于其与参考方向的角度。
目前,对于所有支持的数据集,标注不包括俯仰角 (pitch) 和滚动角 (roll),这意味着我们在预测框和计算框之间的重叠时只需考虑转向角 (yaw)。
在 MMDetection3D 中,所有坐标系都是右手坐标系,这意味着如果从重力轴的负方向(轴的正方向指向人眼)看,转向角 (yaw) 沿着逆时针方向增加。
下图显示,在右手坐标系中,如果我们设定 x 轴正方向为参考方向,那么 y 轴正方向的转向角 (yaw) 为 $\\frac{\\pi}{2}$。
```
z 上 y 前 (yaw=0.5*pi)
^ ^
| /
| /
| /
|/
左 (yaw=pi) ------ 0 ------> x 右 (yaw=0)
```
对于一个框来说,其转向角 (yaw) 的值等于其方向减去一个参考方向。在 MMDetection3D 的所有三个坐标系中,参考方向总是 x 轴的正方向,而如果一个框的转向角 (yaw) 为 0,则其方向被定义为与 x 轴平行。框的转向角 (yaw) 的定义如下图所示。
```
y 前
^ 框的方向 (yaw=0.5*pi)
/|\ ^
| /|\
| ____|____
| | | |
| | | |
__|____|____|____|______\ x 右
| | | | /
| | | |
| |____|____|
|
```
## 框尺寸的定义
框尺寸的定义与转向角 (yaw) 的定义是分不开的。在上一节中,我们提到如果一个框的转向角 (yaw) 为 0,它的方向就被定义为与 x 轴平行。那么自然地,一个框对应于 x 轴的尺寸应该是 $dx$。但是,这在某些数据集中并非总是如此(我们稍后会解决这个问题)。
下图展示了 x 轴和 $dx$,y 轴和 $dy$ 对应的含义。
```
y 前
^ 框的方向 (yaw=0.5*pi)
/|\ ^
| /|\
| ____|____
| | | |
| | | | dx
__|____|____|____|______\ x 右
| | | | /
| | | |
| |____|____|
| dy
```
注意框的方向总是和 $dx$ 边平行。
```
y 前
^ _________
/|\ | | |
| | | |
| | | | dy
| |____|____|____\ 框的方向 (yaw=0)
| | | | /
__|____|____|____|_________\ x 右
| | | | /
| |____|____|
| dx
|
```
## 与支持的数据集的原始坐标系的关系
### KITTI
KITTI 数据集的原始标注是在相机坐标系下的,详见 [get_label_anno](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/kitti_data_utils.py)。在 MMDetection3D 中,为了在 KITTI 数据集上训练基于激光雷达的模型,首先将数据从相机坐标系转换到激光雷达坐标,详见 [get_ann_info](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/datasets/kitti_dataset.py)。对于训练基于视觉的模型,数据保持在相机坐标系不变。
在 SECOND 中,框的激光雷达坐标系定义如下(鸟瞰图):
![](https://raw.githubusercontent.com/traveller59/second.pytorch/master/images/kittibox.png)
对于每个框来说,尺寸为 $(w, l, h)$,转向角 (yaw) 的参考方向为 y 轴正方向。更多细节请参考[代码库](https://github.com/traveller59/second.pytorch#concepts)
我们的激光雷达坐标系有两处改变:
- 转向角 (yaw) 被定义为右手而非左手,从而保持一致性;
- 框的尺寸为 $(l, w, h)$ 而非 $(w, l, h)$,由于在 KITTI 数据集中 $w$ 对应 $dy$,$l$ 对应 $dx$。
### Waymo
我们使用 Waymo 数据集的 KITTI 格式数据。因此,在我们的实现中 KITTI 和 Waymo 也共用相同的坐标系。
### NuScenes
NuScenes 提供了一个评估工具包,其中每个框都被包装成一个 `Box` 实例。`Box` 的坐标系不同于我们的激光雷达坐标系,在 `Box` 坐标系中,前两个表示框尺寸的元素分别对应 $(dy, dx)$ 或者 $(w, l)$,和我们的表示方法相反。更多细节请参考 NuScenes [教程](https://github.com/open-mmlab/mmdetection3d/blob/master/docs/zh_cn/datasets/nuscenes_det.md#notes)
读者可以参考 [NuScenes 开发工具](https://github.com/nutonomy/nuscenes-devkit/tree/master/python-sdk/nuscenes/eval/detection),了解 [NuScenes 框](https://github.com/nutonomy/nuscenes-devkit/blob/2c6a752319f23910d5f55cc995abc547a9e54142/python-sdk/nuscenes/utils/data_classes.py#L457) 的定义和 [NuScenes 评估](https://github.com/nutonomy/nuscenes-devkit/blob/master/python-sdk/nuscenes/eval/detection/evaluate.py)的过程。
### Lyft
就涉及坐标系而言,Lyft 和 NuScenes 共用相同的数据格式。
请参考[官方网站](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/data)获取更多信息。
### ScanNet
ScanNet 的原始数据不是点云而是网格,需要在我们的深度坐标系下进行采样得到点云数据。对于 ScanNet 检测任务,框的标注是轴对齐的,并且转向角 (yaw) 始终是 0。因此,我们的深度坐标系中转向角 (yaw) 的方向对 ScanNet 没有影响。
### SUN RGB-D
SUN RGB-D 的原始数据不是点云而是 RGB-D 图像。我们通过反投影,可以得到每张图像对应的点云,其在我们的深度坐标系下。但是,数据集的标注并不在我们的系统中,所以需要进行转换。
将原始标注转换为我们的深度坐标系下的标注的转换过程请参考 [sunrgbd_data_utils.py](https://github.com/open-mmlab/mmdetection3d/blob/master/tools/data_converter/sunrgbd_data_utils.py)
### S3DIS
在我们的实现中,S3DIS 与 ScanNet 共用相同的坐标系。然而 S3DIS 是一个仅限于分割任务的数据集,因此没有标注是坐标系敏感的。
## 例子
### 框(在不同坐标系间)的转换
以相机坐标系和激光雷达坐标系间的转换为例:
首先,对于点和框的中心点,坐标转换前后满足下列关系:
- $x\_{LiDAR}=z\_{camera}$
- $y\_{LiDAR}=-x\_{camera}$
- $z\_{LiDAR}=-y\_{camera}$
然后,框的尺寸转换前后满足下列关系:
- $dx\_{LiDAR}=dx\_{camera}$
- $dy\_{LiDAR}=dz\_{camera}$
- $dz\_{LiDAR}=dy\_{camera}$
最后,转向角 (yaw) 也应该被转换:
- $r\_{LiDAR}=-\\frac{\\pi}{2}-r\_{camera}$
详见[此处](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/bbox/structures/box_3d_mode.py)代码了解更多细节。
### 鸟瞰图
如果 3D 框是 $(x, y, z, dx, dy, dz, r)$,相机坐标系下框的鸟瞰图是 $(x, z, dx, dz, -r)$。转向角 (yaw) 符号取反是因为相机坐标系重力轴的正方向指向地面。
详见[此处](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/bbox/structures/cam_box3d.py)代码了解更多细节。
### 框的旋转
我们将各种框的旋转设定为绕着重力轴逆时针旋转。因此,为了旋转一个 3D 框,我们首先需要计算新的框的中心,然后将旋转角度添加到转向角 (yaw)。
详见[此处](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/bbox/structures/cam_box3d.py)代码了解更多细节。
## 常见问题
#### Q1: 与框相关的算子是否适用于所有坐标系类型?
否。例如,[用于 RoI-Aware Pooling 的算子](https://github.com/open-mmlab/mmcv/blob/master/mmcv/ops/roiaware_pool3d.py)只适用于深度坐标系和激光雷达坐标系下的框。由于如果从上方看,旋转是顺时针的,所以 KITTI 数据集[这里](https://github.com/open-mmlab/mmdetection3d/blob/master/mmdet3d/core/evaluation/kitti_utils.py)的评估函数仅适用于相机坐标系下的框。
对于每个和框相关的算子,我们注明了其所适用的框类型。
#### Q2: 在每个坐标系中,三个轴是否分别准确地指向右侧、前方和地面?
否。例如在 KITTI 中,从相机坐标系转换为激光雷达坐标系时,我们需要一个校准矩阵。
#### Q3: 框中转向角 (yaw) $2\\pi$ 的相位差如何影响评估?
对于交并比 (IoU) 计算,转向角 (yaw) 有 $2\\pi$ 的相位差的两个框是相同的,所以不会影响评估。
对于角度预测评估,例如 NuScenes 中的 NDS 指标和 KITTI 中的 AOS 指标,会先对预测框的角度进行标准化,因此 $2\\pi$ 的相位差不会改变结果。
#### Q4: 框中转向角 (yaw) $\\pi$ 的相位差如何影响评估?
对于交并比 (IoU) 计算,转向角 (yaw) 有 $\\pi$ 的相位差的两个框是相同的,所以不会影响评估。
然而,对于角度预测评估,这会导致完全相反的方向。
考虑一辆汽车,转向角 (yaw) 是汽车前部方向与 x 轴正方向之间的夹角。如果我们将该角度增加 $\\pi$,车前部将变成车后部。
对于某些类别,例如障碍物,前后没有区别,因此 $\\pi$ 的相位差不会对角度预测分数产生影响。
# 自定义数据预处理流程
## 数据预处理流程的设计
遵循一般惯例,我们使用 `Dataset``DataLoader` 来调用多个进程进行数据的加载。`Dataset` 将会返回与模型前向传播的参数所对应的数据项构成的字典。因为目标检测中的数据的尺寸可能无法保持一致(如点云中点的数量、真实标注框的尺寸等),我们在 MMCV 中引入一个 `DataContainer` 类型,来帮助收集和分发不同尺寸的数据。请参考[此处](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py)获取更多细节。
数据预处理流程和数据集之间是互相分离的两个部分,通常数据集定义了如何处理标注信息,而数据预处理流程定义了准备数据项字典的所有步骤。数据集预处理流程包含一系列的操作,每个操作将一个字典作为输入,并输出应用于下一个转换的一个新的字典。
我们将在下图中展示一个最经典的数据集预处理流程,其中蓝色框表示预处理流程中的各项操作。随着预处理的进行,每一个操作都会添加新的键值(图中标记为绿色)到输出字典中,或者更新当前存在的键值(图中标记为橙色)。
![](../../../resources/data_pipeline.png)
预处理流程中的各项操作主要分为数据加载、预处理、格式化、测试时的数据增强。
接下来将展示一个用于 PointPillars 模型的数据集预处理流程的例子。
```python
train_pipeline = [
dict(
type='LoadPointsFromFile',
load_dim=5,
use_dim=5,
backend_args=backend_args),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
backend_args=backend_args),
dict(type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(
type='GlobalRotScaleTrans',
rot_range=[-0.3925, 0.3925],
scale_ratio_range=[0.95, 1.05],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D', flip_ratio_bev_horizontal=0.5),
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
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'])
]
test_pipeline = [
dict(
type='LoadPointsFromFile',
load_dim=5,
use_dim=5,
backend_args=backend_args),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
backend_args=backend_args),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
pts_scale_ratio=1.0,
flip=False,
pcd_horizontal_flip=False,
pcd_vertical_flip=False,
transforms=[
dict(
type='GlobalRotScaleTrans',
rot_range=[0, 0],
scale_ratio_range=[1., 1.],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D'),
dict(
type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(
type='DefaultFormatBundle3D',
class_names=class_names,
with_label=False),
dict(type='Collect3D', keys=['points'])
])
]
```
对于每项操作,我们将列出相关的被添加/更新/移除的字典项。
### 数据加载
`LoadPointsFromFile`
- 添加:points
`LoadPointsFromMultiSweeps`
- 更新:points
`LoadAnnotations3D`
- 添加:gt_bboxes_3d, gt_labels_3d, gt_bboxes, gt_labels, pts_instance_mask, pts_semantic_mask, bbox3d_fields, pts_mask_fields, pts_seg_fields
### 预处理
`GlobalRotScaleTrans`
- 添加:pcd_trans, pcd_rotation, pcd_scale_factor
- 更新:points, \*bbox3d_fields
`RandomFlip3D`
- 添加:flip, pcd_horizontal_flip, pcd_vertical_flip
- 更新:points, \*bbox3d_fields
`PointsRangeFilter`
- 更新:points
`ObjectRangeFilter`
- 更新:gt_bboxes_3d, gt_labels_3d
`ObjectNameFilter`
- 更新:gt_bboxes_3d, gt_labels_3d
`PointShuffle`
- 更新:points
`PointsRangeFilter`
- 更新:points
### 格式化
`DefaultFormatBundle3D`
- 更新:points, gt_bboxes_3d, gt_labels_3d, gt_bboxes, gt_labels
`Collect3D`
- 添加:img_meta (由 `meta_keys` 指定的键值构成的 img_meta)
- 移除:所有除 `keys` 指定的键值以外的其他键值
### 测试时的数据增强
`MultiScaleFlipAug`
- 更新: scale, pcd_scale_factor, flip, flip_direction, pcd_horizontal_flip, pcd_vertical_flip (与这些指定的参数对应的增强后的数据列表)
## 扩展并使用自定义数据集预处理方法
1. 在任意文件中写入新的数据集预处理方法,如 `my_pipeline.py`,该预处理方法的输入和输出均为字典
```python
from mmdet.datasets import PIPELINES
@PIPELINES.register_module()
class MyTransform:
def __call__(self, results):
results['dummy'] = True
return results
```
2. 导入新的预处理方法类
```python
from .my_pipeline import MyTransform
```
3. 在配置文件中使用该数据集预处理方法
```python
train_pipeline = [
dict(
type='LoadPointsFromFile',
load_dim=5,
use_dim=5,
backend_args=backend_args),
dict(
type='LoadPointsFromMultiSweeps',
sweeps_num=10,
backend_args=backend_args),
dict(type='LoadAnnotations3D', with_bbox_3d=True, with_label_3d=True),
dict(
type='GlobalRotScaleTrans',
rot_range=[-0.3925, 0.3925],
scale_ratio_range=[0.95, 1.05],
translation_std=[0, 0, 0]),
dict(type='RandomFlip3D', flip_ratio_bev_horizontal=0.5),
dict(type='PointsRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectRangeFilter', point_cloud_range=point_cloud_range),
dict(type='ObjectNameFilter', classes=class_names),
dict(type='MyTransform'),
dict(type='PointShuffle'),
dict(type='DefaultFormatBundle3D', class_names=class_names),
dict(type='Collect3D', keys=['points', 'gt_bboxes_3d', 'gt_labels_3d'])
]
```
# 数据预处理
## 在数据预处理前
我们推荐用户将数据集的路径软链接到 `$MMDETECTION3D/data`。如果你的文件夹结构和以下所展示的结构不一致,你可能需要改变配置文件中相应的数据路径。
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── nuscenes
│ │ ├── maps
│ │ ├── samples
│ │ ├── sweeps
│ │ ├── v1.0-test
| | ├── v1.0-trainval
│ ├── kitti
│ │ ├── ImageSets
│ │ ├── testing
│ │ │ ├── calib
│ │ │ ├── image_2
│ │ │ ├── velodyne
│ │ ├── training
│ │ │ ├── calib
│ │ │ ├── image_2
│ │ │ ├── label_2
│ │ │ ├── velodyne
│ ├── waymo
│ │ ├── waymo_format
│ │ │ ├── training
│ │ │ ├── validation
│ │ │ ├── testing
│ │ │ ├── gt.bin
│ │ ├── kitti_format
│ │ │ ├── ImageSets
│ ├── lyft
│ │ ├── v1.01-train
│ │ │ ├── v1.01-train (训练数据)
│ │ │ ├── lidar (训练激光雷达)
│ │ │ ├── images (训练图片)
│ │ │ ├── maps (训练地图)
│ │ ├── v1.01-test
│ │ │ ├── v1.01-test (测试数据)
│ │ │ ├── lidar (测试激光雷达)
│ │ │ ├── images (测试图片)
│ │ │ ├── maps (测试地图)
│ │ ├── train.txt
│ │ ├── val.txt
│ │ ├── test.txt
│ │ ├── sample_submission.csv
│ ├── s3dis
│ │ ├── meta_data
│ │ ├── Stanford3dDataset_v1.2_Aligned_Version
│ │ ├── collect_indoor3d_data.py
│ │ ├── indoor3d_util.py
│ │ ├── README.md
│ ├── scannet
│ │ ├── meta_data
│ │ ├── scans
│ │ ├── scans_test
│ │ ├── batch_load_scannet_data.py
│ │ ├── load_scannet_data.py
│ │ ├── scannet_utils.py
│ │ ├── README.md
│ ├── sunrgbd
│ │ ├── OFFICIAL_SUNRGBD
│ │ ├── matlab
│ │ ├── sunrgbd_data.py
│ │ ├── sunrgbd_utils.py
│ │ ├── README.md
```
## 数据下载和预处理
### KITTI
[这里](http://www.cvlibs.net/datasets/kitti/eval_object.php?obj_benchmark=3d)下载 KITTI 的 3D 检测数据。通过运行以下指令对 KITTI 数据进行预处理:
```bash
mkdir ./data/kitti/ && mkdir ./data/kitti/ImageSets
# 下载数据划分文件
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
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
```
然后通过运行以下指令生成信息文件:
```bash
python tools/create_data.py kitti --root-path ./data/kitti --out-dir ./data/kitti --extra-tag kitti
```
在使用 slurm 的环境下,用户需要使用下面的指令:
```bash
sh tools/create_data.sh <partition> kitti
```
**小贴士**
- **现成的标注文件**:我们已经提供了离线处理好的 [KITTI 标注文件](#数据集标注文件列表)。您直接下载他们并放到 `data/kitti/` 目录下。然而,如果你想在点云检测方法中使用 `ObjectSample` 这一数据增强,你可以再额外使用以下命令来生成物体标注框数据库:
```bash
python tools/create_data.py kitti --root-path ./data/kitti --out-dir ./data/kitti --extra-tag kitti --only-gt-database
```
### Waymo
[这里](https://waymo.com/open/download/)下载 Waymo 公开数据集 1.4.1 版本,在[这里](https://drive.google.com/drive/folders/18BVuF_RYJF0NjZpt8SnfzANiakoRMf0o?usp=sharing)下载其数据划分文件。然后,将 `.tfrecord` 文件置于 `data/waymo/waymo_format/` 目录下的相应位置,并将数据划分的 `.txt` 文件置于 `data/waymo/kitti_format/ImageSets` 目录下。在[这里](https://console.cloud.google.com/storage/browser/waymo_open_dataset_v_1_2_0/validation/ground_truth_objects)下载验证集的真实标签(`.bin` 文件)并将其置于 `data/waymo/waymo_format/`。提示:你可以使用 `gsutil` 来用命令下载大规模的数据集。更多细节请参考此[工具](https://github.com/RalphMao/Waymo-Dataset-Tool)。完成以上各步后,可以通过运行以下指令对 Waymo 数据进行预处理:
```bash
# TF_CPP_MIN_LOG_LEVEL=3 will disable all logging output from TensorFlow.
# The number of `--workers` depends on the maximum number of cores in your CPU.
TF_CPP_MIN_LOG_LEVEL=3 python tools/create_data.py waymo --root-path ./data/waymo --out-dir ./data/waymo --workers 128 --extra-tag waymo --version v1.4
```
注意:
- 如果你的硬盘空间大小不足以存储转换后的数据,你可以将 `--out-dir` 参数设定为别的路径。你只需要记得在那个路径下创建文件夹并下载数据,然后在数据预处理完成后将其链接回 `data/waymo/kitti_format` 即可。
**小贴士**
- **现成的标注文件**: 我们已经提供了离线处理好的 [Waymo 标注文件](#数据集标注文件列表)。您直接下载他们并放到 `data/waymo/kitti_format/` 目录下。然而,您还是需要自己使用上面的脚本将 Waymo 的原始数据还需要转成 kitti 格式。
- **Waymo-mini**: 如果你只是为了验证某些方法或者 debug, 你可以使用我们提供的 [Waymo-mini](https://download.openmmlab.com/mmdetection3d/data/waymo_mmdet3d_after_1x4/waymo_mini.tar.gz)。它只包含原始数据集中训练集中的 2 个 segments 和 验证集中的 1 个 segment。您只需要下载并且解压到 `data/waymo_mini/`,即可使用它:
```bash
tar -xzvf waymo_mini.tar.gz -C ./data/waymo_mini
```
### NuScenes
[这里](https://www.nuscenes.org/download)下载 nuScenes 数据集 1.0 版本的完整数据文件。通过运行以下指令对 nuScenes 数据进行预处理:
```bash
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes
```
**小贴士**
- **现成的标注文件**:我们已经提供了离线处理好的 [NuScenes 标注文件](#数据集标注文件列表)。您直接下载他们并放到 `data/nuscenes/` 目录下。然而,如果你想在点云检测方法中使用 `ObjectSample` 这一数据增强,你可以再额外使用以下命令来生成物体标注框数据库:
```bash
python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes --only-gt-database
```
### Lyft
[这里](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/data)下载 Lyft 3D 检测数据。通过运行以下指令对 Lyft 数据进行预处理:
```bash
python tools/create_data.py lyft --root-path ./data/lyft --out-dir ./data/lyft --extra-tag lyft --version v1.01
python tools/data_converter/lyft_data_fixer.py --version v1.01 --root-folder ./data/lyft
```
注意,为了文件结构的清晰性,我们遵从了 Lyft 数据原先的文件夹名称。请按照上面展示出的文件结构对原始文件夹进行重命名。同样值得注意的是,第二行命令的目的是为了修复一个损坏的激光雷达数据文件。更多细节请参考[该讨论](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/discussion/110000)
### SemanticKITTI
[这里](http://semantic-kitti.org/dataset.html#download)下载 SemanticKITTI 数据集并解压所有文件。通过运行以下指令对 SemanticKITTI 数据进行预处理:
```bash
python ./tools/create_data.py semantickitti --root-path ./data/semantickitti --out-dir ./data/semantickitti --extra-tag semantickitti
```
**小贴士**
- **现成的标注文件**. 我们已经提供了离线处理好的 [SemanticKITTI 标注文件](#数据集标注文件列表)。您直接下载他们并放到 `data/semantickitti` 目录下。
### S3DIS、ScanNet 和 SUN RGB-D
请参考 S3DIS [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/s3dis/README.md) 文件以对其进行数据预处理。
请参考 ScanNet [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/scannet/README.md) 文件以对其进行数据预处理。
请参考 SUN RGB-D [README](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/data/sunrgbd/README.md) 文件以对其进行数据预处理。
**小贴士**:对于 S3DIS, ScanNet 和 SUN RGB-D 数据集,我们已经提供了离线处理好的 [标注文件](#数据集标注文件列表)。您可以直接下载他们并放到 `data/${DATASET}/` 目录下。然而,您还是需要自己利用我们的脚本来生成点云文件以及语义掩膜文件(如果该数据集有的话)。
### 自定义数据集
关于如何使用自定义数据集,请参考[自定义数据集](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/advanced_guides/customize_dataset.md)
### 更新数据信息
如果你之前已经使用 v1.0.0rc1-v1.0.0rc4 版的 mmdetection3d 创建数据信息,现在你想使用最新的 v1.1.0 版 mmdetection3d,你需要更新数据信息文件。
```bash
python tools/dataset_converters/update_infos_to_v2.py --dataset ${DATA_SET} --pkl-path ${PKL_PATH} --out-dir ${OUT_DIR}
```
- `--dataset`:数据集名。
- `--pkl-path`:指定数据信息 pkl 文件路径。
- `--out-dir`:输出数据信息 pkl 文件目录。
例如:
```bash
python tools/dataset_converters/update_infos_to_v2.py --dataset kitti --pkl-path ./data/kitti/kitti_infos_trainval.pkl --out-dir ./data/kitti
```
### 数据集标注文件列表
我们提供了离线生成好的数据集标注文件以供参考。为了方便,您也可以直接使用他们。
| 数据集 | 训练集标注文件 | 验证集标注文件 | 测试集标注文件 |
| :-------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| KITTI | [kitti_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/kitti/kitti_infos_train.pkl) | [kitti_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/kitti/kitti_infos_val.pkl) | [kitti_infos_test](https://download.openmmlab.com/mmdetection3d/data/kitti/kitti_infos_test.pkl) |
| NuScenes | [nuscenes_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/nuscenes/nuscenes_infos_train.pkl) [nuscenes_mini_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/nuscenes/nuscenes_mini_infos_train.pkl) | [nuscenes_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/nuscenes/nuscenes_infos_val.pkl) [nuscenes_mini_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/nuscenes/nuscenes_mini_infos_val.pkl) | |
| Waymo | [waymo_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/waymo_mmdet3d_after_1x4/waymo_infos_train.pkl) | [waymo_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/waymo_mmdet3d_after_1x4/waymo_infos_val.pkl) | [waymo_infos_test.pkl](https://download.openmmlab.com/mmdetection3d/data/waymo/waymo_infos_test.pkl) [waymo_infos_test_cam_only.pkl](https://download.openmmlab.com/mmdetection3d/data/waymo_mmdet3d_after_1x4/waymo_infos_test_cam_only.pkl) |
| [Waymo-mini](https://download.openmmlab.com/mmdetection3d/data/waymo_mmdet3d_after_1x4/waymo_mini.tar.gz) | | | |
| SUN RGB-D | [sunrgbd_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/sunrgbd/sunrgbd_infos_train.pkl) | [sunrgbd_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/sunrgbd/sunrgbd_infos_val.pkl) | |
| ScanNet | [scannet_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/scannet/scannet_infos_train.pkl) | [scannet_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/scannet/scannet_infos_val.pkl) | [scannet_infos_test.pkl](https://download.openmmlab.com/mmdetection3d/data/scannet/scannet_infos_test.pkl) |
| SemanticKitti | [semantickitti_infos_train.pkl](https://download.openmmlab.com/mmdetection3d/data/semantickitti/semantickitti_infos_train.pkl) | [semantickitti_infos_val.pkl](https://download.openmmlab.com/mmdetection3d/data/semantickitti/semantickitti_infos_val.pkl) | [semantickitti_infos_test.pkl](https://download.openmmlab.com/mmdetection3d/data/semantickitti/semantickitti_infos_test.pkl) |
训练和测试
**************
.. toctree::
:maxdepth: 1
config.md
coord_sys_tutorial.md
dataset_prepare.md
data_pipeline.md
train_test.md
inference.md
new_data_model.md
实用工具
************
.. toctree::
:maxdepth: 1
useful_tools.md
visualization.md
backends_support.md
model_deployment.md
# 推理
## 介绍
我们提供了多模态/单模态(基于激光雷达/图像)、室内/室外场景的 3D 检测和 3D 语义分割样例的脚本,预训练模型可以从 [Model Zoo](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/model_zoo.md) 下载。我们也提供了 KITTI、SUN RGB-D、nuScenes 和 ScanNet 数据集的预处理样本数据,你可以根据我们的预处理步骤使用任何其它数据。
## 测试
### 3D 检测
#### 点云样例
在点云数据上测试 3D 检测器,运行:
```shell
python demo/pcd_demo.py ${PCD_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--score-thr ${SCORE_THR}] [--out-dir ${OUT_DIR}] [--show]
```
点云和预测 3D 框的可视化结果会被保存在 `${OUT_DIR}/PCD_NAME`,它可以使用 [MeshLab](http://www.meshlab.net/) 打开。注意如果你设置了 `--show`,通过 [Open3D](http://www.open3d.org/) 可以在线显示预测结果。
在 KITTI 数据上测试 [PointPillars 模型](https://download.openmmlab.com/mmdetection3d/v1.0.0_models/pointpillars/hv_pointpillars_secfpn_6x8_160e_kitti-3d-car/hv_pointpillars_secfpn_6x8_160e_kitti-3d-car_20220331_134606-d42d15ed.pth)
```shell
python demo/pcd_demo.py demo/data/kitti/000008.bin configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-car.py ${CHECKPOINT_FILE} --show
```
在 SUN RGB-D 数据上测试 [VoteNet 模型](https://download.openmmlab.com/mmdetection3d/v1.0.0_models/votenet/votenet_16x8_sunrgbd-3d-10class/votenet_16x8_sunrgbd-3d-10class_20210820_162823-bf11f014.pth)
```shell
python demo/pcd_demo.py demo/data/sunrgbd/sunrgbd_000017.bin configs/votenet/votenet_8xb16_sunrgbd-3d.py ${CHECKPOINT_FILE} --show
```
#### 单目 3D 样例
在图像数据上测试单目 3D 检测器,运行:
```shell
python demo/mono_det_demo.py ${IMAGE_FILE} ${ANNOTATION_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--out-dir ${OUT_DIR}] [--show]
```
`ANNOTATION_FILE` 需要提供 3D 到 2D 的仿射矩阵(相机内参矩阵),可视化结果会被保存在 `${OUT_DIR}/PCD_NAME`,其中包括图像以及预测 3D 框在图像上的投影。
在 KITTI 数据上测试 [PGD 模型](https://download.openmmlab.com/mmdetection3d/v1.0.0_models/pgd/pgd_r101_caffe_fpn_gn-head_3x4_4x_kitti-mono3d/pgd_r101_caffe_fpn_gn-head_3x4_4x_kitti-mono3d_20211022_102608-8a97533b.pth)
```shell
python demo/mono_det_demo.py demo/data/kitti/000008.png demo/data/kitti/000008.pkl configs/pgd/pgd_r101-caffe_fpn_head-gn_4xb3-4x_kitti-mono3d.py ${CHECKPOINT_FILE} --show --cam-type CAM2 --score-thr 8
```
**注意**: PGD 方法的预测框分数并不是在 (0, 1) 之间
在 nuScenes 数据上测试 [FCOS3D 模型](https://download.openmmlab.com/mmdetection3d/v0.1.0_models/fcos3d/fcos3d_r101_caffe_fpn_gn-head_dcn_2x8_1x_nus-mono3d_finetune/fcos3d_r101_caffe_fpn_gn-head_dcn_2x8_1x_nus-mono3d_finetune_20210717_095645-8d806dc2.pth)
```shell
python demo/mono_det_demo.py demo/data/nuscenes/n015-2018-07-24-11-22-45+0800__CAM_BACK__1532402927637525.jpg demo/data/nuscenes/n015-2018-07-24-11-22-45+0800.pkl configs/fcos3d/fcos3d_r101-caffe-dcn_fpn_head-gn_8xb2-1x_nus-mono3d_finetune.py ${CHECKPOINT_FILE} --show --cam-type CAM_BACK
```
**注意**: 当对翻转图像可视化单目 3D 检测结果是,相机内参矩阵也应该相应修改。在 PR [#744](https://github.com/open-mmlab/mmdetection3d/pull/744) 中可以了解更多细节和示例。
#### 多模态样例
在多模态数据(通常是点云和图像)上测试 3D 检测器,运行:
```shell
python demo/multi_modality_demo.py ${PCD_FILE} ${IMAGE_FILE} ${ANNOTATION_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--score-thr ${SCORE_THR}] [--out-dir ${OUT_DIR}] [--show]
```
`ANNOTATION_FILE` 需要提供 3D 到 2D 的仿射矩阵,可视化结果会被保存在 `${OUT_DIR}/PCD_NAME`,其中包括点云、图像、预测的 3D 框以及它们在图像上的投影。
在 KITTI 数据上测试 [MVX-Net 模型](https://download.openmmlab.com/mmdetection3d/v1.1.0_models/mvxnet/mvxnet_fpn_dv_second_secfpn_8xb2-80e_kitti-3d-3class/mvxnet_fpn_dv_second_secfpn_8xb2-80e_kitti-3d-3class-8963258a.pth)
```shell
python demo/multi_modality_demo.py demo/data/kitti/000008.bin demo/data/kitti/000008.png demo/data/kitti/000008.pkl configs/mvxnet/mvxnet_fpn_dv_second_secfpn_8xb2-80e_kitti-3d-3class.py ${CHECKPOINT_FILE} --cam-type CAM2 --show
```
在 SUN RGB-D 数据上测试 [ImVoteNet 模型](https://download.openmmlab.com/mmdetection3d/v1.0.0_models/imvotenet/imvotenet_stage2_16x8_sunrgbd-3d-10class/imvotenet_stage2_16x8_sunrgbd-3d-10class_20210819_192851-1bcd1b97.pth)
```shell
python demo/multi_modality_demo.py demo/data/sunrgbd/000017.bin demo/data/sunrgbd/000017.jpg demo/data/sunrgbd/sunrgbd_000017_infos.pkl configs/imvotenet/imvotenet_stage2_8xb16_sunrgbd-3d.py ${CHECKPOINT_FILE} --cam-type CAM0 --show --score-thr 0.6
```
在 NuScenes 数据上测试 [BEVFusion 模型](https://drive.google.com/file/d/1QkvbYDk4G2d6SZoeJqish13qSyXA4lp3/view?usp=share_link)
```shell
python demo/multi_modality_demo.py demo/data/nuscenes/n015-2018-07-24-11-22-45+0800__LIDAR_TOP__1532402927647951.pcd.bin demo/data/nuscenes/ demo/data/nuscenes/n015-2018-07-24-11-22-45+0800.pkl projects/BEVFusion/configs/bevfusion_voxel0075_second_secfpn_8xb4-cyclic-20e_nus-3d.py ${CHECKPOINT_FILE} --cam-type all --score-thr 0.2 --show
```
### 3D 分割
在点云数据上测试 3D 分割器,运行:
```shell
python demo/pc_seg_demo.py ${PCD_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} [--device ${GPU_ID}] [--out-dir ${OUT_DIR}] [--show]
```
可视化结果会被保存在 `${OUT_DIR}/PCD_NAME`,其中包括点云以及预测的 3D 分割掩码。
在 ScanNet 数据上测试 [PointNet++ (SSG) 模型](https://download.openmmlab.com/mmdetection3d/v0.1.0_models/pointnet2/pointnet2_ssg_16x2_cosine_200e_scannet_seg-3d-20class/pointnet2_ssg_16x2_cosine_200e_scannet_seg-3d-20class_20210514_143644-ee73704a.pth)
```shell
python demo/pcd_seg_demo.py demo/data/scannet/scene0000_00.bin configs/pointnet2/pointnet2_ssg_2xb16-cosine-200e_scannet-seg.py ${CHECKPOINT_FILE} --show
```
# 模型部署(待更新)
MMDet3D 1.1 完全基于 [MMDeploy](https://mmdeploy.readthedocs.io/) 來部署模型。
我们将在下一个版本完善这个文档。
# 在自定义数据集上进行训练
本文将主要介绍如何使用自定义数据集来进行模型的训练和测试,以 Waymo 数据集作为示例来说明整个流程。
基本步骤如下所示:
1. 准备自定义数据集;
2. 准备配置文件;
3. 在自定义数据集上进行模型的训练、测试和推理。
## 准备自定义数据集
在 MMDetection3D 中有三种方式来自定义一个新的数据集:
1. 将新数据集的数据格式重新组织成已支持的数据集格式;
2. 将新数据集的数据格式重新组织成已支持的一种中间格式;
3. 从头开始创建一个新的数据集。
由于前两种方式比第三种方式更加容易,我们更加建议采用前两种方式来自定义数据集。
在本文中,我们给出示例将数据转换成 KITTI 数据集的数据格式,你可以参考此处将你的数据集重新组织成 KITTI 格式。关于标准格式的数据集,你可以参考[自定义数据集文档](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/docs/zh_cn/advanced_guides/customize_dataset.md)
**注意**:考虑到 Waymo 数据集的格式与现有的其他数据集的格式的差别较大,因此本文以该数据集为例来讲解如何自定义数据集,从而方便理解数据集自定义的过程。若需要创建的新数据集与现有的数据集的组织格式较为相似,如 Lyft 数据集和 nuScenes 数据集,采用对数据集的中间格式进行转换的方式(第二种方式)相比于采用对数据格式进行转换的方式(第一种方式)会更加简单易行。
### KITTI 数据集格式
应用于 3D 目标检测的 KITTI 原始数据集的组织方式通常如下所示,其中 `ImageSets` 包含数据集划分文件,用以划分训练集/验证集/测试集,`calib` 包含对于每个数据样本的标定信息,`image_2``velodyne` 分别包含图像数据和点云数据,`label_2` 包含与 3D 目标检测相关的标注文件。
```
mmdetection3d
├── mmdet3d
├── tools
├── configs
├── data
│ ├── kitti
│ │ ├── ImageSets
│ │ ├── testing
│ │ │ ├── calib
│ │ │ ├── image_2
│ │ │ ├── velodyne
│ │ ├── training
│ │ │ ├── calib
│ │ │ ├── image_2
│ │ │ ├── label_2
│ │ │ ├── velodyne
```
KITTI 官方提供的目标检测开发[工具包](https://s3.eu-central-1.amazonaws.com/avg-kitti/devkit_object.zip)详细描述了 KITTI 数据集的标注格式,例如,KITTI 标注格式包含了以下的标注信息:
```
# 值 名称 描述
----------------------------------------------------------------------------
1 类型 描述检测目标的类型:'Car','Van','Truck',
'Pedestrian','Person_sitting','Cyclist','Tram',
'Misc' 或 'DontCare'
1 截断程度  从 0(非截断)到 1(截断)的浮点数,其中截断指的是离开检测图像边界的检测目标
1 遮挡程度  用来表示遮挡状态的四种整数(0,1,2,3):
0 = 可见,1 = 部分遮挡
2 = 大面积遮挡,3 = 未知
1 观测角 观测目标的角度,取值范围为 [-pi..pi]
4 标注框 检测目标在图像中的二维标注框(以0为初始下标):包括每个检测目标的左上角和右下角的坐标
3 维度  检测目标的三维维度:高度、宽度、长度(以米为单位)
3 位置  相机坐标系下的三维位置 x,y,z(以米为单位)
1 y 旋转  相机坐标系下检测目标绕着Y轴的旋转角,取值范围为 [-pi..pi]
1 得分  仅在计算结果时使用,检测中表示置信度的浮点数,用于生成 p/r 曲线,在p/r 图中,越高的曲线表示结果越好。
```
假定我们使用 Waymo 数据集。
在下载好数据集后,我们需要实现一个函数用来将输入数据和标注文件转换成 KITTI 风格。然后我们可以通过继承 `KittiDataset` 实现 `WaymoDataset`,用来加载数据以及训练模型,通过继承 `KittiMetric` 实现 `WaymoMetric` 来做模型的评估。
具体来说,首先使用[数据转换器](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/tools/dataset_converters/waymo_converter.py)将 Waymo 数据集转换成 KITTI 数据集的格式,并定义 [Waymo 类](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/datasets/waymo_dataset.py)对转换的数据进行处理。此外需要添加 waymo [评估类](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/mmdet3d/evaluation/metrics/waymo_metric.py)来评估结果。因为我们将 Waymo 原始数据集进行预处理并重新组织成 KITTI 数据集的格式,因此可以比较容易通过继承 KittiDataset 类来实现 WaymoDataset 类。需要注意的是,由于 Waymo 数据集有相应的官方评估方法,我们需要进一步实现新的 Waymo 评估方法,更多关于评估方法参考[评估文档](https://github.com/open-mmlab/mmengine/blob/main/docs/en/tutorials/metric_and_evaluator.md)。最后,用户可以成功地转换数据并使用 `WaymoDataset` 训练以及 `WaymoMetric` 评估模型。
更多关于 Waymo 数据集预处理的中间结果的细节,请参照对应的[说明文档](https://mmdetection3d.readthedocs.io/zh_CN/latest/datasets/waymo_det.html)
## 准备配置文件
第二步是准备配置文件来帮助数据集的读取和使用,另外,为了在 3D 检测中获得不错的性能,调整超参数通常是必要的。
假设我们想要使用 PointPillars 模型在 Waymo 数据集上实现三类的 3D 目标检测:vehicle、cyclist、pedestrian,参照 KITTI 数据集[配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/datasets/kitti-3d-3class.py)、模型[配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/models/pointpillars_hv_secfpn_kitti.py)[整体配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py),我们需要准备[数据集配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/datasets/waymoD5-3d-3class.py)[模型配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/_base_/models/pointpillars_hv_secfpn_waymo.py),并将这两种文件进行结合得到[整体配置文件](https://github.com/open-mmlab/mmdetection3d/blob/dev-1.x/configs/pointpillars/pointpillars_hv_secfpn_sbn-all_16xb2-2x_waymoD5-3d-3class.py)
## 训练一个新的模型
为了使用一个新的配置文件来训练模型,可以通过下面的命令来实现:
```shell
python tools/train.py configs/pointpillars/pointpillars_hv_secfpn_sbn-all_16xb2-2x_waymoD5-3d-3class.py
```
更多的使用细节,请参考[案例 1](https://mmdetection3d.readthedocs.io/zh_CN/latest/1_exist_data_model.html)
## 测试和推理
为了测试已经训练好的模型的性能,可以通过下面的命令来实现:
```shell
python tools/test.py configs/pointpillars/pointpillars_hv_secfpn_sbn-all_16xb2-2x_waymoD5-3d-3class.py work_dirs/pointpillars_hv_secfpn_sbn-all_16xb2-2x_waymoD5-3d-3class/latest.pth
```
**注意**:为了使用 Waymo 数据集的评估方法,需要参考[说明文档](https://mmdetection3d.readthedocs.io/zh_CN/latest/datasets/waymo_det.html)并按照官方指导来准备与评估相关联的文件。
更多有关测试和推理的使用细节,请参考[案例 1](https://mmdetection3d.readthedocs.io/zh_CN/latest/1_exist_data_model.html)
# 在标注数据集上测试和训练
### 在标准数据集上测试已有模型
- 单显卡
- CPU
- 单节点多显卡
- 多节点
你可以通过以下命令来测试数据集:
```shell
# 单块显卡测试
python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] [--show] [--show-dir ${SHOW_DIR}]
# CPU:禁用显卡并运行单块 CPU 测试脚本(实验性)
export CUDA_VISIBLE_DEVICES=-1
python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}] [--show] [--show-dir ${SHOW_DIR}]
# 多块显卡测试
./tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [--out ${RESULT_FILE}] [--eval ${EVAL_METRICS}]
```
**注意**:
目前我们只支持 SMOKE 的 CPU 推理测试。
可选参数:
- `--show`:如果被指定,检测结果会在静默模式下被保存,用于调试和可视化,但只在单块 GPU 测试的情况下生效,和 `--show-dir` 搭配使用。
- `--show-dir`:如果被指定,检测结果会被保存在指定文件夹下的 `***_points.obj``***_pred.obj` 文件中,用于调试和可视化,但只在单块 GPU 测试的情况下生效,对于这个选项,图形化界面在你的环境中不是必需的。
所有和评估相关的参数在相应的数据集配置的 `test_evaluator` 中设置。例如 `test_evaluator = dict(type='KittiMetric', ann_file=data_root + 'kitti_infos_val.pkl', pklfile_prefix=None, submission_prefix=None)`
参数:
- `type`:相对应的评价指标名,通常和数据集相关联。
- `ann_file`:标注文件路径。
- `pklfile_prefix`:可选参数。输出结果保存成 pickle 格式的文件名。如果没有指定,结果将不会保存成文件。
- `submission_prefix`:可选参数。结果将被保存到文件中,然后你可以将它上传到官方评估服务器中。
示例:
假定你已经把模型权重文件下载到 `checkpoints/` 文件夹下,
1. 在 ScanNet 数据集上测试 VoteNet,保存模型,可视化预测结果
```shell
python tools/test.py configs/votenet/votenet_8xb8_scannet-3d.py \
checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth \
--show --show-dir ./data/scannet/show_results
```
2. 在 ScanNet 数据集上测试 VoteNet,保存模型,可视化预测结果,可视化真实标签,计算 mAP
```shell
python tools/test.py configs/votenet/votenet_8xb8_scannet-3d.py \
checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth \
--show --show-dir ./data/scannet/show_results
```
3. 在 ScanNet 数据集上测试 VoteNet(不保存测试结果),计算 mAP
```shell
python tools/test.py configs/votenet/votenet_8xb8_scannet-3d.py \
checkpoints/votenet_8x8_scannet-3d-18class_20200620_230238-2cea9c3a.pth
```
4. 使用 8 块显卡在 KITTI 数据集上测试 SECOND,计算 mAP
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/second/second_hv_secfpn_8xb6-80e_kitti-3d-3class.py \
checkpoints/hv_second_secfpn_6x8_80e_kitti-3d-3class_20200620_230238-9208083a.pth
```
5. 使用 8 块显卡在 nuScenes 数据集上测试 PointPillars,生成提交给官方评测服务器的 json 文件
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/pointpillars/pointpillars_hv_secfpn_sbn-all_8xb4-2x_nus-3d.py \
checkpoints/hv_pointpillars_fpn_sbn-all_4x8_2x_nus-3d_20200620_230405-2fa62f3d.pth \
--cfg-options 'test_evaluator.jsonfile_prefix=./pointpillars_nuscenes_results'
```
生成的结果会保存在 `./pointpillars_nuscenes_results` 目录。
6. 使用 8 块显卡在 KITTI 数据集上测试 SECOND,生成提交给官方评测服务器的 txt 文件
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/second/second_hv_secfpn_8xb6-80e_kitti-3d-3class.py \
checkpoints/hv_second_secfpn_6x8_80e_kitti-3d-3class_20200620_230238-9208083a.pth \
--cfg-options 'test_evaluator.pklfile_prefix=./second_kitti_results' 'submission_prefix=./second_kitti_results'
```
生成的结果会保存在 `./second_kitti_results` 目录。
7. 使用 8 块显卡在 Lyft 数据集上测试 PointPillars,生成提交给排行榜的 pkl 文件
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} configs/pointpillars/hv_pointpillars_fpn_sbn-2x8_2x_lyft-3d.py \
checkpoints/hv_pointpillars_fpn_sbn-2x8_2x_lyft-3d_latest.pth \
--cfg-options 'test_evaluator.jsonfile_prefix=results/pp_lyft/results_challenge' \
'test_evaluator.csv_savepath=results/pp_lyft/results_challenge.csv' \
'test_evaluator.pklfile_prefix=results/pp_lyft/results_challenge.pkl'
```
**注意**:为了生成 Lyft 数据集的提交结果,`--eval-options` 必须指定 `csv_savepath`。生成 csv 文件后,你可以使用[网站](https://www.kaggle.com/c/3d-object-detection-for-autonomous-vehicles/submit)上给出的 kaggle 命令提交结果。
注意在 [Lyft 数据集的配置文件](../../configs/_base_/datasets/lyft-3d.py)`test` 中的 `ann_file` 值为 `lyft_infos_test.pkl`,是没有标注的 Lyft 官方测试集。要在验证数据集上测试,请把它改为 `lyft_infos_val.pkl`
8. 使用 8 块显卡在 waymo 数据集上测试 PointPillars,使用 waymo 度量方法计算 mAP
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} 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 \
--cfg-options 'test_evaluator.pklfile_prefix=results/waymo-car/kitti_results' \
'test_evaluator.submission_prefix=results/waymo-car/kitti_results'
```
**注意**:对于 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 构建 `compute_detection_metrics_main` 时,有时会出现 `'round' is not a member of 'std'` 的错误,我们只需要把那个文件中 `round` 前的 `std::` 去掉。)二进制文件生成时需要在 `--eval-options` 中给定 `pklfile_prefix`。对于度量方法,`waymo` 是推荐的官方评估策略,目前 `kitti` 评估是依照 KITTI 而来的,每个难度的结果和 KITTI 的定义并不完全一致。目前大多数物体都被标记为0难度,会在未来修复。它的不稳定原因包括评估的计算大、转换后的数据缺乏遮挡和截断、难度的定义不同以及平均精度的计算方法不同。
9. 使用 8 块显卡在 waymo 数据集上测试 PointPillars,生成 bin 文件并提交到排行榜
```shell
./tools/slurm_test.sh ${PARTITION} ${JOB_NAME} 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 \
--cfg-options 'test_evaluator.pklfile_prefix=results/waymo-car/kitti_results' \
'test_evaluator.submission_prefix=results/waymo-car/kitti_results'
```
**注意**:生成 bin 文件后,你可以简单地构建二进制文件 `create_submission`,并根据[说明](https://github.com/waymo-research/waymo-open-dataset/blob/master/docs/quick_start.md/)创建提交的文件。要在验证服务器上评测验证数据集,你也可以用同样的方式生成提交的文件。
## 在标准数据集上训练预定义模型
MMDetection3D 分别用 `MMDistributedDataParallel` and `MMDataParallel` 实现了分布式训练和非分布式训练。
所有的输出(日志文件和模型权重文件)都会被保存到工作目录下,通过配置文件里的 `work_dir` 指定。
默认我们每过一个周期都在验证数据集上评测模型,你可以通过在训练配置里添加间隔参数来改变评测的时间间隔:
```python
train_cfg = dict(type='EpochBasedTrainLoop', val_interval=1) # 每12个周期评估一次模型
```
**重要**:配置文件中的默认学习率对应 8 块显卡,配置文件名里有具体的批量大小,比如 '2xb8' 表示一共 8 块显卡,每块显卡 2 个样本。
根据 [Linear Scaling Rule](https://arxiv.org/abs/1706.02677),当你使用不同数量的显卡或每块显卡有不同数量的图像时,需要依批量大小按比例调整学习率。如果用 4 块显卡、每块显卡 2 幅图像时学习率为 0.01,那么用 16 块显卡、每块显卡 4 幅图像时学习率应设为 0.08。然而,由于大多数模型使用 ADAM 而不是 SGD 进行优化,上述规则可能并不适用,用户需要自己调整学习率。
### 使用单块显卡进行训练
```shell
python tools/train.py ${CONFIG_FILE} [optional arguments]
```
如果你想在命令中指定工作目录,添加参数 `--work-dir ${YOUR_WORK_DIR}`
### 使用 CPU 进行训练 (实验性)
在 CPU 上训练的过程与单 GPU 训练一致。 我们只需要在训练过程之前禁用显卡。
```shell
export CUDA_VISIBLE_DEVICES=-1
```
之后运行单显卡训练脚本即可。
**注意**
目前,大多数点云相关算法都依赖于 3D CUDA 算子,无法在 CPU 上进行训练。 一些单目 3D 物体检测算法,例如 FCOS3D、SMOKE 可以在 CPU 上进行训练。我们不推荐用户使用 CPU 进行训练,这太过缓慢。我们支持这个功能是为了方便用户在没有显卡的机器上调试某些特定的方法。
### 使用多块显卡进行训练
```shell
./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments]
```
可选参数:
- `--cfg-options 'Key=value'`:覆盖使用的配置中的一些设定。
### 使用多个机器进行训练
如果要在 [slurm](https://slurm.schedmd.com/) 管理的集群上运行 MMDectection3D,你可以使用 `slurm_train.sh` 脚本(该脚本也支持单机训练)
```shell
[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR}
```
下面是一个使用 16 块显卡在 dev 分区上训练 Mask R-CNN 的示例:
```shell
GPUS=16 ./tools/slurm_train.sh dev pp_kitti_3class configs/pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-3class.py /nfs/xxxx/pp_kitti_3class
```
你可以查看 [slurm_train.sh](https://github.com/open-mmlab/mmdetection/blob/master/tools/slurm_train.sh) 来获取所有的参数和环境变量。
如果您想使用由 ethernet 连接起来的多台机器, 您可以使用以下命令:
在第一台机器上:
```shell
NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR ./tools/dist_train.sh $CONFIG $GPUS
```
在第二台机器上:
```shell
NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR ./tools/dist_train.sh $CONFIG $GPUS
```
但是,如果您不使用高速网路连接这几台机器的话,训练将会非常慢。
### 在单个机器上启动多个任务
如果你在单个机器上启动多个任务,比如,在具有8块显卡的机器上进行2个4块显卡训练的任务,你需要为每个任务指定不同的端口(默认为29500)以避免通信冲突。
如果你使用 `dist_train.sh` 启动训练任务,可以在命令中设置端口:
```shell
CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4
CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4
```
如果你使用 Slurm 启动训练任务,有两种方式指定端口:
1. 通过 `--cfg-options` 设置端口,这是更推荐的,因为它不改变原来的配置
```shell
CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR} --cfg-options 'env_cfg.dist_cfg.port=29500'
CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR} --cfg-options 'env_cfg.dist_cfg.port=29501'
```
2. 修改配置文件(通常在配置文件的倒数第6行)来设置不同的通信端口
`config1.py` 中,
```python
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29500)
)
```
`config2.py` 中,
```python
env_cfg = dict(
dist_cfg=dict(backend='nccl', port=29501)
)
```
然后,你可以使用 `config1.py` and `config2.py` 启动两个任务
```shell
CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR}
CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR}
```
我们在 `tools/` 文件夹路径下提供了许多有用的工具。
## 日志分析
给定一个训练的日志文件,您可以绘制出 loss/mAP 曲线。首先需要运行 `pip install seaborn` 安装依赖包。
![loss曲线图](../../../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}]
```
**注意**: 如果您想绘制的指标是在验证阶段计算得到的,您需要添加一个标志 `--mode eval` ,如果您每经过一个 `${INTERVAL}` 的间隔进行评估,您需要增加一个参数 `--interval ${INTERVAL}`
示例:
- 绘制出某次运行的分类 loss。
```shell
python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls
```
- 绘制出某次运行的分类和回归 loss,并且保存图片为 pdf 格式。
```shell
python tools/analysis_tools/analyze_logs.py plot_curve log.json --keys loss_cls loss_bbox --out losses.pdf
```
- 在同一张图片中比较两次运行的 bbox mAP。
```shell
# 根据 Car_3D_moderate_strict 在 KITTI 上评估 PartA2 和 second。
python tools/analysis_tools/analyze_logs.py plot_curve tools/logs/PartA2.log.json tools/logs/second.log.json --keys KITTI/Car_3D_moderate_strict --legend PartA2 second --mode eval --interval 1
# 根据 Car_3D_moderate_strict 在 KITTI 上分别对车和 3 类评估 PointPillars。
python tools/analysis_tools/analyze_logs.py plot_curve tools/logs/pp-3class.log.json tools/logs/pp.log.json --keys KITTI/Car_3D_moderate_strict --legend pp-3class pp --mode eval --interval 2
```
您也能计算平均训练速度。
```shell
python tools/analysis_tools/analyze_logs.py cal_train_time log.json [--include-outliers]
```
预期输出应该如下所示。
```
-----Analyze train time of work_dirs/some_exp/20190611_192040.log.json-----
slowest epoch 11, average time is 1.2024
fastest epoch 1, average time is 1.1909
time std over epochs is 0.0028
average iter time: 1.1959 s/iter
```
&#8195;
## 模型部署
**注意**:此工具仍然处于试验阶段,目前只有 SECOND 支持用 [`TorchServe`](https://pytorch.org/serve/) 部署,我们将会在未来支持更多的模型。
为了使用 [`TorchServe`](https://pytorch.org/serve/) 部署 `MMDetection3D` 模型,您可以遵循以下步骤:
### 1. 将模型从 MMDetection3D 转换到 TorchServe
```shell
python tools/deployment/mmdet3d2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \
--output-folder ${MODEL_STORE} \
--model-name ${MODEL_NAME}
```
**Note**: ${MODEL_STORE} 需要为文件夹的绝对路径。
### 2. 构建 `mmdet3d-serve` 镜像
```shell
docker build -t mmdet3d-serve:latest docker/serve/
```
### 3. 运行 `mmdet3d-serve`
查看官网文档来 [使用 docker 运行 TorchServe](https://github.com/pytorch/serve/blob/master/docker/README.md#running-torchserve-in-a-production-docker-environment)
为了在 GPU 上运行,您需要安装 [nvidia-docker](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html)。您可以忽略 `--gpus` 参数,从而在 CPU 上运行。
例子:
```shell
docker run --rm \
--cpus 8 \
--gpus device=0 \
-p8080:8080 -p8081:8081 -p8082:8082 \
--mount type=bind,source=$MODEL_STORE,target=/home/model-server/model-store \
mmdet3d-serve:latest
```
[阅读文档](https://github.com/pytorch/serve/blob/072f5d088cce9bb64b2a18af065886c9b01b317b/docs/rest_api.md/) 关于 Inference (8080), Management (8081) and Metrics (8082) 接口。
### 4. 测试部署
您可以使用 `test_torchserver.py` 进行部署, 同时比较 torchserver 和 pytorch 的结果。
```shell
python tools/deployment/test_torchserver.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME}
[--inference-addr ${INFERENCE_ADDR}] [--device ${DEVICE}] [--score-thr ${SCORE_THR}]
```
例子:
```shell
python tools/deployment/test_torchserver.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 second
```
&#8195;
# 模型复杂度
您可以使用 MMDetection 中的 `tools/analysis_tools/get_flops.py` 这个脚本文件,基于 [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) 计算一个给定模型的计算量 (FLOPS) 和参数量 (params)。
```shell
python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
```
您将会得到如下的结果:
```text
==============================
Input shape: (4000, 4)
Flops: 5.78 GFLOPs
Params: 953.83 k
==============================
```
**注意**:此工具仍然处于试验阶段,我们不能保证数值是绝对正确的。您可以将结果用于简单的比较,但在写技术文档报告或者论文之前您需要再次确认一下。
1. 计算量 (FLOPs) 和输入形状有关,但是参数量 (params) 则和输入形状无关。默认的输入形状为 (1, 40000, 4)。
2. 一些运算操作不计入计算量 (FLOPs),比如说像GN和定制的运算操作,详细细节请参考 [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py)。
3. 我们现在仅仅支持单模态输入(点云或者图片)的单阶段模型的计算量 (FLOPs) 计算,我们将会在未来支持两阶段和多模态模型的计算。
&#8195;
## 模型转换
### RegNet 模型转换到 MMDetection
`tools/model_converters/regnet2mmdet.py` 将 pycls 预训练 RegNet 模型中的键转换为 MMDetection 风格。
```shell
python tools/model_converters/regnet2mmdet.py ${SRC} ${DST} [-h]
```
### Detectron ResNet 转换到 Pytorch
MMDetection 中的 `tools/detectron2pytorch.py` 能够把原始的 detectron 中预训练的 ResNet 模型的键转换为 PyTorch 风格。
```shell
python tools/detectron2pytorch.py ${SRC} ${DST} ${DEPTH} [-h]
```
### 准备要发布的模型
`tools/model_converters/publish_model.py` 帮助用户准备他们用于发布的模型。
在您上传一个模型到云服务器 (AWS) 之前,您需要做以下几步:
1. 将模型权重转换为 CPU 张量
2. 删除记录优化器状态 (optimizer states) 的相关信息
3. 计算检查点 (checkpoint) 文件的哈希编码 (hash id) 并且把哈希编码加到文件名里
```shell
python tools/model_converters/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
```
例如,
```shell
python tools/model_converters/publish_model.py work_dirs/faster_rcnn/latest.pth faster_rcnn_r50_fpn_1x_20190801.pth
```
最终的输出文件名将会是 `faster_rcnn_r50_fpn_1x_20190801-{hash id}.pth`。
&#8195;
# 数据集转换
`tools/dataset_converters/` 包含转换数据集为其他格式的一些工具。其中大多数转换数据集为基于 pickle 的信息文件,比如 KITTI,nuscense 和 lyft。Waymo 转换器被用来重新组织 waymo 原始数据为 KITTI 风格。用户能够参考它们了解我们转换数据格式的方法。将它们修改为 nuImages 转换器等脚本也很方便。
为了转换 nuImages 数据集为 COCO 格式,请使用下面的指令:
```shell
python -u tools/dataset_converters/nuimage_converter.py --data-root ${DATA_ROOT} --version ${VERSIONS} \
--out-dir ${OUT_DIR} --nproc ${NUM_WORKERS} --extra-tag ${TAG}
```
- `--data-root`: 数据集的根目录,默认为 `./data/nuimages`。
- `--version`: 数据集的版本,默认为 `v1.0-mini`。要获取完整数据集,请使用 `--version v1.0-train v1.0-val v1.0-mini`。
- `--out-dir`: 注释和语义掩码的输出目录,默认为 `./data/nuimages/annotations/`。
- `--nproc`: 数据准备的进程数,默认为 `4`。由于图片是并行处理的,更大的进程数目能够减少准备时间。
- `--extra-tag`: 注释的额外标签,默认为 `nuimages`。这可用于将不同时间处理的不同注释分开以供研究。
更多的数据准备细节参考 [doc](https://mmdetection3d.readthedocs.io/zh_CN/latest/data_preparation.html),nuImages 数据集的细节参考 [README](https://github.com/open-mmlab/mmdetection3d/blob/main/configs/nuimages/README.md/)。
&#8195;
# 其他内容
## 打印完整的配置文件
`tools/misc/print_config.py` 逐字打印整个配置文件,展开所有的导入。
```shell
python tools/misc/print_config.py ${CONFIG} [-h] [--options ${OPTIONS [OPTIONS...]}]
```
# 可视化
MMDetection3D 提供了 `Det3DLocalVisualizer` 用来在训练及测试阶段可视化和存储模型的状态以及结果,其具有以下特性:
1. 支持多模态数据和多任务的基本绘图界面。
2. 支持多个后端(如 local,TensorBoard),将训练状态(如 `loss``lr`)或模型评估指标写入指定的一个或多个后端中。
3. 支持多模态数据真实标签的可视化,3D 检测结果的跨模态可视化。
## 基本绘制界面
继承自 `DetLocalVisualizer``Det3DLocalVisualizer` 提供了在 2D 图像上绘制常见目标的界面,例如绘制检测框、点、文本、线、圆、多边形、二进制掩码等。关于 2D 绘制的更多细节,请参考 MMDetection 中的[可视化文档](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/visualization.html)。这里我们介绍 3D 绘制界面。
### 在图像上绘制点云
通过使用 `draw_points_on_image`,我们支持在图像上绘制点云。
```python
import mmcv
import numpy as np
from mmengine import load
from mmdet3d.visualization import Det3DLocalVisualizer
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]
lidar2img = np.array(info_file['data_list'][0]['images']['CAM2']['lidar2img'], dtype=np.float32)
visualizer = Det3DLocalVisualizer()
img = mmcv.imread('demo/data/kitti/000008.png')
img = mmcv.imconvert(img, 'bgr', 'rgb')
visualizer.set_image(img)
visualizer.draw_points_on_image(points, lidar2img)
visualizer.show()
```
![points_on_image](../../../resources/points_on_image.png)
### 在点云上绘制 3D 框
通过使用 `draw_bboxes_3d`,我们支持在点云上绘制 3D 框。
```python
import torch
import numpy as np
from mmdet3d.visualization import Det3DLocalVisualizer
from mmdet3d.structures import LiDARInstance3DBoxes
points = np.fromfile('demo/data/kitti/000008.bin', dtype=np.float32)
points = points.reshape(-1, 4)
visualizer = Det3DLocalVisualizer()
# set point cloud in visualizer
visualizer.set_points(points)
bboxes_3d = LiDARInstance3DBoxes(
torch.tensor([[8.7314, -1.8559, -1.5997, 4.2000, 3.4800, 1.8900,
-1.5808]]))
# Draw 3D bboxes
visualizer.draw_bboxes_3d(bboxes_3d)
visualizer.show()
```
![mono3d](../../../resources/pcd.png)
### 在图像上绘制投影的 3D 框
通过使用 `draw_proj_bboxes_3d`,我们支持在图像上绘制投影的 3D 框。
```python
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 = []
for instance in info_file['data_list'][0]['instances']:
bboxes_3d.append(instance['bbox_3d'])
gt_bboxes_3d = np.array(bboxes_3d, dtype=np.float32)
gt_bboxes_3d = CameraInstance3DBoxes(gt_bboxes_3d)
input_meta = {'cam2img': cam2img}
visualizer = Det3DLocalVisualizer()
img = mmcv.imread('demo/data/kitti/000008.png')
img = mmcv.imconvert(img, 'bgr', 'rgb')
visualizer.set_image(img)
# project 3D bboxes to image
visualizer.draw_proj_bboxes_3d(gt_bboxes_3d, input_meta)
visualizer.show()
```
### 绘制 BEV 视角的框
通过使用 `draw_bev_bboxes`,我们支持绘制 BEV 视角下的框。
```python
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')
bboxes_3d = []
for instance in info_file['data_list'][0]['instances']:
bboxes_3d.append(instance['bbox_3d'])
gt_bboxes_3d = np.array(bboxes_3d, dtype=np.float32)
gt_bboxes_3d = CameraInstance3DBoxes(gt_bboxes_3d)
visualizer = Det3DLocalVisualizer()
# set bev image in visualizer
visualizer.set_bev_image()
# draw bev bboxes
visualizer.draw_bev_bboxes(gt_bboxes_3d, edge_colors='orange')
visualizer.show()
```
### 绘制 3D 分割掩码
通过使用 `draw_seg_mask`,我们支持通过逐点着色来绘制分割掩码。
```python
import numpy as np
from mmdet3d.visualization import Det3DLocalVisualizer
points = np.fromfile('demo/data/sunrgbd/000017.bin', dtype=np.float32)
points = points.reshape(-1, 3)
visualizer = Det3DLocalVisualizer()
mask = np.random.rand(points.shape[0], 3)
points_with_mask = np.concatenate((points, mask), axis=-1)
# Draw 3D points with mask
visualizer.set_points(points, pcd_mode=2, vis_mode='add')
visualizer.draw_seg_mask(points_with_mask)
visualizer.show()
```
## 结果
如果想要可视化训练模型的预测结果,你可以运行如下指令:
```bash
python tools/test.py ${CONFIG_FILE} ${CKPT_PATH} --show --show-dir ${SHOW_DIR}
```
运行该指令后,绘制的结果(包括输入数据和网络输出在输入上的可视化)将会被保存在 `${SHOW_DIR}` 中。
运行该指令后,你将在 `${SHOW_DIR}` 中获得输入数据,网络输出和真是标签在输入上的可视化(如在多模态检测任务和基于视觉的检测任务中的 `***_gt.png``***_pred.png`)。当启用 `show` 时,[Open3D](http://www.open3d.org/) 将会用于在线可视化结果。如果你是在没有 GUI 的远程服务器上测试时,在线可视化是不被支持的。你可以从远程服务器中下载 `results.pkl`,并在本地机器上离线可视化预测结果。
使用 `Open3D` 后端离线可视化结果,你可以运行如下指令:
```bash
python tools/misc/visualize_results.py ${CONFIG_FILE} --result ${RESULTS_PATH} --show-dir ${SHOW_DIR}
```
![](../../../resources/open3d_visual.gif)
这需要在远程服务器中能够推理并生成结果,然后用户在主机中使用 GUI 打开。
## 数据集
我们也提供了脚本来可视化数据集而无需推理。你可以使用 `tools/misc/browse_dataset.py` 来在线可视化加载的数据的真实标签,并保存在硬盘中。目前我们支持所有数据集的单模态 3D 检测和 3D 分割,KITTI 和 SUN RGB-D 的多模态 3D 检测,以及 nuScenes 的单目 3D 检测。如果想要浏览 KITTI 数据集,你可以运行如下指令:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py --task lidar_det --output-dir ${OUTPUT_DIR}
```
**注意**:一旦指定了 `--output-dir`,当在 open3d 窗口中按下 `_ESC_` 时,用户指定的视图图像将会被保存下来。如果你想要对点云进行缩放操作以观察更多细节, 你可以在命令中指定 `--show-interval=0`
为了验证数据的一致性和数据增强的效果,你可以加上 `--aug` 来可视化数据增强后的数据,指令如下所示:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/kitti-3d-3class.py --task det --aug --output-dir ${OUTPUT_DIR}
```
如果你想显示带有投影的 3D 边界框的 2D 图像,你需要一个支持多模态数据加载的配置文件,并将 `--task` 参数改为 `multi-modality_det`。示例如下:
```shell
python tools/misc/browse_dataset.py configs/mvxnet/mvxnet_fpn_dv_second_secfpn_8xb2-80e_kitti-3d-3class.py --task multi-modality_det --output-dir ${OUTPUT_DIR}
```
![](../../../resources/browse_dataset_multi_modality.png)
你可以使用不同的配置浏览不同的数据集,例如在 3D 语义分割任务中可视化 ScanNet 数据集:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/scannet-seg.py --task lidar_seg --output-dir ${OUTPUT_DIR}
```
![](../../../resources/browse_dataset_seg.png)
在单目 3D 检测任务中浏览 nuScenes 数据集:
```shell
python tools/misc/browse_dataset.py configs/_base_/datasets/nus-mono3d.py --task mono_det --output-dir ${OUTPUT_DIR}
```
![](../../../resources/browse_dataset_mono.png)
import torch
# 指向你刚刚发给我的纯 LiDAR 官方权重
ckpt_path = 'pth/bevfusion_lidar_voxel0075_second_secfpn_8xb4-cyclic-20e_nus-3d-2628f933.pth'
ckpt = torch.load(ckpt_path, map_location='cpu')
state_dict = ckpt['state_dict']
fixed_count = 0
for key in list(state_dict.keys()):
# 修复 3D 稀疏卷积维度 (16,3,3,3,16) -> (3,3,3,16,16)
if 'pts_middle_encoder' in key and state_dict[key].dim() == 5:
state_dict[key] = state_dict[key].permute(1, 2, 3, 4, 0).contiguous()
fixed_count += 1
ckpt['state_dict'] = state_dict
fixed_path = ckpt_path.replace('.pth', '_fixed.pth')
torch.save(ckpt, fixed_path)
print(f'✅ 纯 LiDAR 权重修复完成!已保存至 {fixed_path},共处理 {fixed_count} 个层。')
\ No newline at end of file
# dataset settings
dataset_type = 'ADE20KInstanceDataset'
data_root = 'data/ADEChallengeData2016/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/detection/ADEChallengeData2016/'
# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/detection/',
# 'data/': 's3://openmmlab/datasets/detection/'
# }))
backend_args = None
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(2560, 640), keep_ratio=True),
# If you don't have a gt annotation, delete the pipeline
dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
dict(
type='PackDetInputs',
meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
'scale_factor'))
]
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='ade20k_instance_val.json',
data_prefix=dict(img='images/validation'),
test_mode=True,
pipeline=test_pipeline,
backend_args=backend_args))
test_dataloader = val_dataloader
val_evaluator = dict(
type='CocoMetric',
ann_file=data_root + 'ade20k_instance_val.json',
metric=['bbox', 'segm'],
format_only=False,
backend_args=backend_args)
test_evaluator = val_evaluator
# dataset settings
dataset_type = 'ADE20KPanopticDataset'
data_root = 'data/ADEChallengeData2016/'
backend_args = None
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(2560, 640), keep_ratio=True),
dict(type='LoadPanopticAnnotations', backend_args=backend_args),
dict(
type='PackDetInputs',
meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
'scale_factor'))
]
val_dataloader = dict(
batch_size=1,
num_workers=0,
persistent_workers=False,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='ade20k_panoptic_val.json',
data_prefix=dict(img='images/validation/', seg='ade20k_panoptic_val/'),
test_mode=True,
pipeline=test_pipeline,
backend_args=backend_args))
test_dataloader = val_dataloader
val_evaluator = dict(
type='CocoPanopticMetric',
ann_file=data_root + 'ade20k_panoptic_val.json',
seg_prefix=data_root + 'ade20k_panoptic_val/',
backend_args=backend_args)
test_evaluator = val_evaluator
dataset_type = 'ADE20KSegDataset'
data_root = 'data/ADEChallengeData2016/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/detection/ADEChallengeData2016/'
# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/detection/',
# 'data/': 's3://openmmlab/datasets/detection/'
# }))
backend_args = None
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(2048, 512), keep_ratio=True),
dict(
type='LoadAnnotations',
with_bbox=False,
with_mask=False,
with_seg=True,
reduce_zero_label=True),
dict(
type='PackDetInputs', meta_keys=('img_path', 'ori_shape', 'img_shape'))
]
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
data_prefix=dict(
img_path='images/validation',
seg_map_path='annotations/validation'),
pipeline=test_pipeline))
test_dataloader = val_dataloader
val_evaluator = dict(type='SemSegMetric', iou_metrics=['mIoU'])
test_evaluator = val_evaluator
# dataset settings
dataset_type = 'CityscapesDataset'
data_root = 'data/cityscapes/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/segmentation/cityscapes/'
# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/segmentation/',
# 'data/': 's3://openmmlab/datasets/segmentation/'
# }))
backend_args = None
train_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='LoadAnnotations', with_bbox=True),
dict(
type='RandomResize',
scale=[(2048, 800), (2048, 1024)],
keep_ratio=True),
dict(type='RandomFlip', prob=0.5),
dict(type='PackDetInputs')
]
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(2048, 1024), keep_ratio=True),
# If you don't have a gt annotation, delete the pipeline
dict(type='LoadAnnotations', with_bbox=True),
dict(
type='PackDetInputs',
meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
'scale_factor'))
]
train_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
sampler=dict(type='DefaultSampler', shuffle=True),
batch_sampler=dict(type='AspectRatioBatchSampler'),
dataset=dict(
type='RepeatDataset',
times=8,
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instancesonly_filtered_gtFine_train.json',
data_prefix=dict(img='leftImg8bit/train/'),
filter_cfg=dict(filter_empty_gt=True, min_size=32),
pipeline=train_pipeline,
backend_args=backend_args)))
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instancesonly_filtered_gtFine_val.json',
data_prefix=dict(img='leftImg8bit/val/'),
test_mode=True,
filter_cfg=dict(filter_empty_gt=True, min_size=32),
pipeline=test_pipeline,
backend_args=backend_args))
test_dataloader = val_dataloader
val_evaluator = dict(
type='CocoMetric',
ann_file=data_root + 'annotations/instancesonly_filtered_gtFine_val.json',
metric='bbox',
backend_args=backend_args)
test_evaluator = val_evaluator
# dataset settings
dataset_type = 'CityscapesDataset'
data_root = 'data/cityscapes/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/segmentation/cityscapes/'
# Method 2: Use backend_args, file_client_args in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/segmentation/',
# 'data/': 's3://openmmlab/datasets/segmentation/'
# }))
backend_args = None
train_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
dict(
type='RandomResize',
scale=[(2048, 800), (2048, 1024)],
keep_ratio=True),
dict(type='RandomFlip', prob=0.5),
dict(type='PackDetInputs')
]
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(2048, 1024), keep_ratio=True),
# If you don't have a gt annotation, delete the pipeline
dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
dict(
type='PackDetInputs',
meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
'scale_factor'))
]
train_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
sampler=dict(type='DefaultSampler', shuffle=True),
batch_sampler=dict(type='AspectRatioBatchSampler'),
dataset=dict(
type='RepeatDataset',
times=8,
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instancesonly_filtered_gtFine_train.json',
data_prefix=dict(img='leftImg8bit/train/'),
filter_cfg=dict(filter_empty_gt=True, min_size=32),
pipeline=train_pipeline,
backend_args=backend_args)))
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instancesonly_filtered_gtFine_val.json',
data_prefix=dict(img='leftImg8bit/val/'),
test_mode=True,
filter_cfg=dict(filter_empty_gt=True, min_size=32),
pipeline=test_pipeline,
backend_args=backend_args))
test_dataloader = val_dataloader
val_evaluator = [
dict(
type='CocoMetric',
ann_file=data_root +
'annotations/instancesonly_filtered_gtFine_val.json',
metric=['bbox', 'segm'],
backend_args=backend_args),
dict(
type='CityScapesMetric',
seg_prefix=data_root + 'gtFine/val',
outfile_prefix='./work_dirs/cityscapes_metric/instance',
backend_args=backend_args)
]
test_evaluator = val_evaluator
# inference on test dataset and
# format the output results for submission.
# test_dataloader = dict(
# batch_size=1,
# num_workers=2,
# persistent_workers=True,
# drop_last=False,
# sampler=dict(type='DefaultSampler', shuffle=False),
# dataset=dict(
# type=dataset_type,
# data_root=data_root,
# ann_file='annotations/instancesonly_filtered_gtFine_test.json',
# data_prefix=dict(img='leftImg8bit/test/'),
# test_mode=True,
# filter_cfg=dict(filter_empty_gt=True, min_size=32),
# pipeline=test_pipeline))
# test_evaluator = dict(
# type='CityScapesMetric',
# format_only=True,
# outfile_prefix='./work_dirs/cityscapes_metric/test')
# data settings
dataset_type = 'CocoCaptionDataset'
data_root = 'data/coco/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/detection/coco/'
# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/detection/',
# 'data/': 's3://openmmlab/datasets/detection/'
# }))
backend_args = None
test_pipeline = [
dict(
type='LoadImageFromFile',
imdecode_backend='pillow',
backend_args=backend_args),
dict(
type='Resize',
scale=(224, 224),
interpolation='bicubic',
backend='pillow'),
dict(type='PackInputs', meta_keys=['image_id']),
]
# ann_file download from
# train dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_train.json # noqa
# val dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val.json # noqa
# test dataset: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test.json # noqa
# val evaluator: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_val_gt.json # noqa
# test evaluator: https://storage.googleapis.com/sfr-vision-language-research/datasets/coco_karpathy_test_gt.json # noqa
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/coco_karpathy_val.json',
pipeline=test_pipeline,
))
val_evaluator = dict(
type='COCOCaptionMetric',
ann_file=data_root + 'annotations/coco_karpathy_val_gt.json',
)
# # If you want standard test, please manually configure the test dataset
test_dataloader = val_dataloader
test_evaluator = val_evaluator
# dataset settings
dataset_type = 'CocoDataset'
data_root = 'data/coco/'
# Example to use different file client
# Method 1: simply set the data root and let the file I/O module
# automatically infer from prefix (not support LMDB and Memcache yet)
# data_root = 's3://openmmlab/datasets/detection/coco/'
# Method 2: Use `backend_args`, `file_client_args` in versions before 3.0.0rc6
# backend_args = dict(
# backend='petrel',
# path_mapping=dict({
# './data/': 's3://openmmlab/datasets/detection/',
# 'data/': 's3://openmmlab/datasets/detection/'
# }))
backend_args = None
train_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='Resize', scale=(1333, 800), keep_ratio=True),
dict(type='RandomFlip', prob=0.5),
dict(type='PackDetInputs')
]
test_pipeline = [
dict(type='LoadImageFromFile', backend_args=backend_args),
dict(type='Resize', scale=(1333, 800), keep_ratio=True),
# If you don't have a gt annotation, delete the pipeline
dict(type='LoadAnnotations', with_bbox=True),
dict(
type='PackDetInputs',
meta_keys=('img_id', 'img_path', 'ori_shape', 'img_shape',
'scale_factor'))
]
train_dataloader = dict(
batch_size=2,
num_workers=2,
persistent_workers=True,
sampler=dict(type='DefaultSampler', shuffle=True),
batch_sampler=dict(type='AspectRatioBatchSampler'),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instances_train2017.json',
data_prefix=dict(img='train2017/'),
filter_cfg=dict(filter_empty_gt=True, min_size=32),
pipeline=train_pipeline,
backend_args=backend_args))
val_dataloader = dict(
batch_size=1,
num_workers=2,
persistent_workers=True,
drop_last=False,
sampler=dict(type='DefaultSampler', shuffle=False),
dataset=dict(
type=dataset_type,
data_root=data_root,
ann_file='annotations/instances_val2017.json',
data_prefix=dict(img='val2017/'),
test_mode=True,
pipeline=test_pipeline,
backend_args=backend_args))
test_dataloader = val_dataloader
val_evaluator = dict(
type='CocoMetric',
ann_file=data_root + 'annotations/instances_val2017.json',
metric='bbox',
format_only=False,
backend_args=backend_args)
test_evaluator = val_evaluator
# inference on test dataset and
# format the output results for submission.
# test_dataloader = dict(
# batch_size=1,
# num_workers=2,
# persistent_workers=True,
# drop_last=False,
# sampler=dict(type='DefaultSampler', shuffle=False),
# dataset=dict(
# type=dataset_type,
# data_root=data_root,
# ann_file=data_root + 'annotations/image_info_test-dev2017.json',
# data_prefix=dict(img='test2017/'),
# test_mode=True,
# pipeline=test_pipeline))
# test_evaluator = dict(
# type='CocoMetric',
# metric='bbox',
# format_only=True,
# ann_file=data_root + 'annotations/image_info_test-dev2017.json',
# outfile_prefix='./work_dirs/coco_detection/test')
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