- 人工智能
- 计算机视觉
- 深度学习
- 自动驾驶
【免费下载链接】mmdetection3d
OpenMMLab's next-generation platform for general 3D object detection.
动态体素化(Dynamic Voxelization)是 OpenMMLab 的 mmdetection3d 仓库中一项重要的点云预处理技术,源自论文End-to-End Multi-View Fusion for 3D Object Detection in LiDAR Point Clouds(Yin Zhou 等人,2019)。本文以 configs/dynamic_voxelization/README.md 为主线,结合仓库内 DynamicVoxelNet 检测器、体素化算子 与三个 KITTI 实战配置,讲清动态体素化的动机、核心算子原理、配置写法与复现方法。读完本文,你将能理解它与硬体素化的差异,并直接基于 KITTI 配置训练/测试 DV-SECOND 与 DV-PointPillars 模型。
一、为什么要做动态体素化:论文动机与四大优势
传统 3D 检测通常将点云在鸟瞰图(BEV)下进行体素化,物体在该视角下保持物理尺寸、天然可分。但 BEV 点云稀疏、点密度差异极大,导致远距离或小目标(行人、交通标志等)难以检测;而透视视图能提供密集观测,更有利于此类目标的特征编码。MVF(Multi-View Fusion)算法试图协同两种视图,引入动态体素化作为跨视图特征融合的基础。
原 README 的 Abstract 总结了动态体素化相对既有体素化方法的四大优点:
- 无需预先分配固定大小的张量:传统方法要按
max_points × max_voxels预留连续显存,动态方法按实际点数分配; - 克服随机点/体素丢弃带来的信息损失:硬体素化在点数超出上限或体素数超出上限时会截断点/体素,动态体素化不设固定上限;
- 产生确定性的体素嵌入与更稳定的检测结果:不再依赖随机截断,训练与推理结果可复现;
- 建立点与体素的双向关系:既知道"点属于哪个体素",也能反查"体素包含哪些点",为跨视图特征融合提供了天然基础。
MVF 基于该机制让每个点学习融合不同视图的上下文信息,且整体框架在点云上操作,可自然扩展到其他基于 LiDAR 点云的方法。
二、仓库实现概览与文件索引
本仓库对动态体素化的实现贯穿数据预处理、检测器与体素编码器三层:
| 层次 | 文件 | 关键组件 |
|---|---|---|
| 数据预处理 | mmdet3d/models/data_preprocessors/voxelize.py | _Voxelization、DynamicScatter3D、dynamic_scatter_3d |
| 数据预处理 | mmdet3d/models/data_preprocessors/data_preprocessor.py | Det3DDataPreprocessor(voxel_type='hard'/'dynamic') |
| 检测器 | mmdet3d/models/detectors/dynamic_voxelnet.py | DynamicVoxelNet |
| 体素编码器 | mmdet3d/models/voxel_encoders/voxel_encoder.py | DynamicSimpleVFE、DynamicVFE |
| 体素编码器 | mmdet3d/models/voxel_encoders/pillar_encoder.py | DynamicPillarFeatureNet |
入口配置位于 configs/dynamic_voxelization/ 目录,模型元数据记录在 configs/dynamic_voxelization/metafile.yml。
三、硬体素化 vs 动态体素化:核心差异
动态体素化的切换核心在数据预处理器的voxel_type参数。查看 data_preprocessor.py 可知Det3DDataPreprocessor支持voxel_type='hard'(默认)与voxel_type='dynamic'两种模式。
以 SECOND 的基准配置 configs/base/models/second_hv_secfpn_kitti.py 为例,硬体素化的典型设置为:
data_preprocessor=dict( type='Det3DDataPreprocessor', voxel=True, voxel_layer=dict( max_num_points=5, # 每个体素最多 5 个点,超出即截断 point_cloud_range=[0, -40, -3, 70.4, 40, 1], voxel_size=[0.05, 0.05, 0.1], max_voxels=(16000, 40000))), # 训练/测试时体素数量上限,超出即丢弃 voxel_encoder=dict(type='HardSimpleVFE'),而动态体素化配置则将max_num_points与max_voxels置为-1。从 voxelize.py 中_Voxelization.forward的实现可以确认这一约定:当max_points == -1或max_voxels == -1时,算子只调用dynamic_voxelize_forward计算每个点的体素坐标(coors),不再分配[M, max_points, n_dim]的固定张量;否则走hard_voxelize_forward的截断路径。
两种模式对比:
| 维度 | 硬体素化 | 动态体素化 |
|---|---|---|
| 每体素点数上限 | 固定(如 5/32/35) | 无限制(max_num_points=-1) |
| 体素数量上限 | 固定(如(16000, 40000)) | 无限制(max_voxels=(-1, -1)) |
| 内存分配 | 预先分配固定大张量 | 按需计算坐标,随后的 scatter 按实际体素数分配 |
| 点→体素关系 | 单向(截断后无法反查) | 双向(point2voxel_map可反查) |
| 结果确定性 | 随机截断可能引入抖动 | 确定性嵌入、结果稳定 |
四、KITTI 实战配置逐行解析
仓库提供了三个动态体素化 KITTI 配置(对应 README 结果表):
- second_dv_secfpn_8xb6-80e_kitti-3d-car.py(DV-SECOND,Car 单类)
- second_dv_secfpn_8xb2-cosine-80e_kitti-3d-3class.py(DV-SECOND,3 类)
- pointpillars_dv_secfpn_8xb6-160e_kitti-3d-car.py(DV-PointPillars,Car 单类)
三个配置都只重写模型头部的"体素化 + 体素编码器"部分,其余(middle encoder、SECOND backbone、SECONDFPN neck、Anchor3DHead 等)继承自对应基准配置,改动极小,这正是动态体素化"即插即用"的体现。
4.1 DV-SECOND(Car 单类,80 epoch)
_base_ = '../second/second_hv_secfpn_8xb6-80e_kitti-3d-car.py' point_cloud_range = [0, -40, -3, 70.4, 40, 1] voxel_size = [0.05, 0.05, 0.1] model = dict( type='DynamicVoxelNet', data_preprocessor=dict( voxel_type='dynamic', # 关键开关:切换到动态体素化 voxel_layer=dict( _delete_=True, # 删除基类里的硬体素化 voxel_layer max_num_points=-1, # 每体素点数不设上限 point_cloud_range=point_cloud_range, voxel_size=voxel_size, max_voxels=(-1, -1))), # 体素数量不设上限 voxel_encoder=dict( _delete_=True, # 用动态 VFE 替换 HardSimpleVFE type='DynamicSimpleVFE', voxel_size=voxel_size, point_cloud_range=point_cloud_range))要点说明:
_delete_=True:MMEngine 配置继承语法,用于删除_base_中继承来的voxel_layer与voxel_encoder配置项,避免与动态版本冲突;voxel_type='dynamic'对应 Det3DDataPreprocessor 的voxel_type参数;DynamicSimpleVFE见 voxel_encoder.py,内部持有一个DynamicScatter,对点数不固定(N个点)的输入按体素坐标做平均聚合,输出[M, C]的体素特征与[M, 3]的体素坐标,与HardSimpleVFE接收(N, M, C)稠密张量的接口完全不同;- 继承的
middle_encoder=dict(type='SparseEncoder', in_channels=4, sparse_shape=[41, 1600, 1408])保持 4 通道输入、[0.05, 0.05, 0.1]对应的稀疏形状。
4.2 DV-SECOND(KITTI 3 类,cosine 80 epoch)
该配置通过组合基类实现:
_base_ = [ '../_base_/models/second_hv_secfpn_kitti.py', '../_base_/datasets/kitti-3d-3class.py', '../_base_/schedules/cosine.py', '../_base_/default_runtime.py' ] point_cloud_range = [0, -40, -3, 70.4, 40, 1] voxel_size = [0.05, 0.05, 0.1] model = dict( type='DynamicVoxelNet', data_preprocessor=dict( voxel_type='dynamic', voxel_layer=dict( _delete_=True, max_num_points=-1, point_cloud_range=point_cloud_range, voxel_size=voxel_size, max_voxels=(-1, -1))), voxel_encoder=dict( _delete_=True, type='DynamicSimpleVFE', voxel_size=voxel_size, point_cloud_range=point_cloud_range))与 4.1 的唯一结构区别在于:调度器使用_base_/schedules/cosine.py(cosine 学习率衰减),数据集为 configs/base/datasets/kitti-3d-3class.py(Car/Pedestrian/Cyclist 三类,bbox_head 使用 3 类 anchor,见 second_hv_secfpn_kitti.py 中的num_classes=3与三组 anchor ranges/sizes)。
4.3 DV-PointPillars(Car 单类,160 epoch)
_base_ = '../pointpillars/pointpillars_hv_secfpn_8xb6-160e_kitti-3d-car.py' voxel_size = [0.16, 0.16, 4] # 柱体(pillar)尺寸,z 方向覆盖整个高度 point_cloud_range = [0, -39.68, -3, 69.12, 39.68, 1] model = dict( type='DynamicVoxelNet', data_preprocessor=dict( voxel_type='dynamic', voxel_layer=dict( max_num_points=-1, point_cloud_range=point_cloud_range, voxel_size=voxel_size, max_voxels=(-1, -1))), voxel_encoder=dict( type='DynamicPillarFeatureNet', in_channels=4, # x, y, z, r 四维输入 feat_channels=[64], # 单层 PFNLayer,输出 64 维 with_distance=False, voxel_size=voxel_size, point_cloud_range=point_cloud_range))要点说明:
- PointPillars 使用柱状体素(
voxel_size=[0.16, 0.16, 4],z 方向 4m 覆盖整列); DynamicPillarFeatureNet定义于 pillar_encoder.py,继承自PillarFeatureNet,核心差异是用DynamicScatter处理点数不固定的体素:pfn_scatter按mode('max'/'avg')聚合点特征得到体素特征,cluster_scatter用于计算点到聚类中心的偏移;- 该配置未使用
_delete_,因为基类 pointpillars_hv_secfpn_8xb6-160e_kitti-3d-car.py 中对应配置项被整体覆盖,两种写法均可,注意区分。
五、源码级原理:DynamicVoxelNet 与 scatter 算子
5.1 检测器:DynamicVoxelNet
DynamicVoxelNet定义在 dynamic_voxelnet.py,继承自VoxelNet,仅重写了extract_feat:
def extract_feat(self, batch_inputs_dict: dict) -> Tuple[Tensor]: voxel_dict = batch_inputs_dict['voxels'] voxel_features, feature_coors = self.voxel_encoder( voxel_dict['voxels'], voxel_dict['coors']) batch_size = voxel_dict['coors'][-1, 0].item() + 1 x = self.middle_encoder(voxel_features, feature_coors, batch_size) x = self.backbone(x) if self.with_neck: x = self.neck(x) return x与前向过程对应的数据流为:点云 →Det3DDataPreprocessor动态体素化得到voxels(点特征)与coors(每个点的体素坐标)→DynamicSimpleVFE/DynamicPillarFeatureNetscatter 成体素特征 →SparseEncoder(middle encoder)→SECONDbackbone →SECONDFPNneck →Anchor3DHead输出检测框。
5.2 体素化算子与 scatter 算子
voxelize.py 是动态体素化的算子层,通过 mmcv 的 ext_loader 加载了四个 CUDA 扩展:
ext_module = ext_loader.load_ext('_ext', [ 'dynamic_voxelize_forward', 'hard_voxelize_forward', 'dynamic_point_to_voxel_forward', 'dynamic_point_to_voxel_backward' ])dynamic_voxelize_forward:对每个点计算其体素坐标coors,输出形状[N, 3],不产生体素张量(voxelize.py);dynamic_point_to_voxel_forward:将[N, C]点特征按坐标归约(reduce_type支持'max'/'sum'/'mean')为[M, C]体素特征,同时返回point2voxel_map与voxel_points_count(voxelize.py);dynamic_point_to_voxel_backward:配套的反向传播算子,将体素梯度按point2voxel_map散射回每个点,保证整个流程可端到端训练(voxelize.py)。
值得注意的是_DynamicScatter注释明确说明"与 mmcv 实现不同,这里允许返回point2voxel_map",这正是论文"点与体素双向关系"这一优点的代码级落地:有了point2voxel_map,下游模块(如多视图融合层)可以把体素级特征再映射回点级,实现逐点融合。DynamicScatter3D模块在coors.size(-1) == 3时走单样本路径,否则按 batch 逐样本 scatter 后拼接(voxelize.py)。
5.3 支持融合与点级输出的 DynamicVFE
除DynamicSimpleVFE外,仓库还提供功能更完整的DynamicVFE(voxel_encoder.py),其构造函数包含fusion_layer(用于多模态检测器的跨视图融合层配置)、with_voxel_center、with_cluster_center、return_point_feats(是否返回逐点特征)等参数,mode参数控制池化方式('max'/'avg')。这一设计直接呼应论文中"每个点学习融合不同视图上下文信息"的目标,是 MVF 算法落地更完整形态的编码器,可作为在动态体素化基础上扩展多模态融合的参考入口。
六、训练、测试与复现方法
配置就绪后,按仓库通用流程执行。训练:
# 单机多卡分布式训练(8x V100 与 metafile 记录的资源一致) bash tools/dist_train.sh configs/dynamic_voxelization/second_dv_secfpn_8xb6-80e_kitti-3d-car.py 8 # 或单卡训练 python tools/train.py configs/dynamic_voxelization/second_dv_secfpn_8xb6-80e_kitti-3d-car.py测试:
python tools/test.py configs/dynamic_voxelization/second_dv_secfpn_8xb6-80e_kitti-3d-car.py \ <checkpoint路径> --eval bbox前提说明:需要先按 docs/zh_cn/user_guides/dataset_prepare.md(英文版见 docs/en/user_guides/dataset_prepare.md)准备 KITTI 数据集并生成 pkl 信息文件;_base_中的 KITTI 数据配置(configs/base/datasets/kitti-3d-car.py、configs/base/datasets/kitti-3d-3class.py)指定了数据路径、测试模式与评估指标,请按实际数据目录调整。
七、KITTI 上的复现结果
下表完整收录 README 与 metafile.yml 中登记的结果与配置对应关系:
| 模型 | 配置 | 类别 | 学习率调度 | 显存(GB) | mAP |
|---|---|---|---|---|---|
| SECOND(DV) | second_dv_secfpn_8xb6-80e_kitti-3d-car.py | Car | cyclic 80e | 5.5 | 78.83 |
| SECOND(DV) | second_dv_secfpn_8xb2-cosine-80e_kitti-3d-3class.py | 3 Class | cosine 80e | 5.5 | 65.27 |
| PointPillars(DV) | pointpillars_dv_secfpn_8xb6-160e_kitti-3d-car.py | Car | cyclic 80e | 4.7 | 77.76 |
几点说明:
- 权重下载地址与训练日志链接记录在 metafile.yml 及仓库根目录的 model-index.yml 中(对应
Weights字段),需要权重时请前往该元数据文件核对获取; - 原 README 结果表中 PointPillars 行的 Lr schd 标注为 cyclic 80e,但其配置文件名与继承基类均为 160 epoch(见 pointpillars_hv_secfpn_8xb6-160e_kitti-3d-car.py),复现时以配置文件实际 epoch 数为准;
- 模型目录内各模型同样在 model-index.yml 中有结构化登记,供模型库工具自动检索。
八、总结与引用
动态体素化在本仓库中被实现为一条完整的"预处理算子 → 检测器 → 体素编码器"链路,核心改动集中在数据预处理器(voxel_type='dynamic')与两类动态编码器(DynamicSimpleVFE/DynamicPillarFeatureNet),对 SECOND、PointPillars 等主流框架均可低侵入替换。其"免预分配、无损点云、确定性输出、点体素双向映射"四大特性,使其尤其适合远距离/小目标场景以及需要逐点跨视图特征融合的多模态方法。
若在论文中使用本工作,可引用原文献:
@article{zhou2019endtoend, title={End-to-End Multi-View Fusion for 3D Object Detection in LiDAR Point Clouds}, author={Yin Zhou and Pei Sun and Yu Zhang and Dragomir Anguelov and Jiyang Gao and Tom Ouyang and James Guo and Jiquan Ngiam and Vijay Vasudevan}, year={2019}, eprint={1910.06528}, archivePrefix={arXiv}, primaryClass={cs.CV} }- 人工智能
- 计算机视觉
- 深度学习
- 自动驾驶
【免费下载链接】mmdetection3d
OpenMMLab's next-generation platform for general 3D object detection.
相关推荐
MMDetection3D 中的 PointRCNN:两阶段纯点云 3D 目标检测原理、配置解析与 KITTI 实战
MMDetection3D 中的 PointRCNN:两阶段纯点云 3D 目标检测原理、配置解析与 KITTI 实战 PointRCNN 是首个直接从原始点云进
人工智能计算机视觉深度学习自动驾驶Redis 生产环境部署方案实战:doocs/advanced-java 中的集群架构、容量规划与高可用设计
Redis 生产环境部署方案实战:doocs/advanced java 中的集群架构、容量规划与高可用设计 生产环境中的 Redis 到底该怎么部署?本篇文章
人工智能计算机视觉深度学习自动驾驶Front-End-Checklist 无障碍指南:为 ARIA command 元素提供可访问名称(Accessible Names)
Front End Checklist 无障碍指南:为 ARIA command 元素提供可访问名称(Accessible Names) 本文基于 Front
人工智能计算机视觉深度学习自动驾驶
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考