☰
MMDetection3D 动态体素化(Dynamic Voxelization)原理与 KITTI 实战配置全解析
2026/10/8 14:18:57 网站建设 项目流程
  • 人工智能
  • 计算机视觉
  • 深度学习
  • 自动驾驶

【免费下载链接】mmdetection3d

OpenMMLab's next-generation platform for general 3D object detection.

项目地址:https://gitcode.com/gh_mirrors/mm/mmdetection3d
点击查看免费下载

动态体素化(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 总结了动态体素化相对既有体素化方法的四大优点:

  1. 无需预先分配固定大小的张量:传统方法要按max_points × max_voxels预留连续显存,动态方法按实际点数分配;
  2. 克服随机点/体素丢弃带来的信息损失:硬体素化在点数超出上限或体素数超出上限时会截断点/体素,动态体素化不设固定上限;
  3. 产生确定性的体素嵌入与更稳定的检测结果:不再依赖随机截断,训练与推理结果可复现;
  4. 建立点与体素的双向关系:既知道"点属于哪个体素",也能反查"体素包含哪些点",为跨视图特征融合提供了天然基础。

MVF 基于该机制让每个点学习融合不同视图的上下文信息,且整体框架在点云上操作,可自然扩展到其他基于 LiDAR 点云的方法。

二、仓库实现概览与文件索引

本仓库对动态体素化的实现贯穿数据预处理、检测器与体素编码器三层:

层次文件关键组件
数据预处理mmdet3d/models/data_preprocessors/voxelize.py_Voxelization、DynamicScatter3D、dynamic_scatter_3d
数据预处理mmdet3d/models/data_preprocessors/data_preprocessor.pyDet3DDataPreprocessor(voxel_type='hard'/'dynamic')
检测器mmdet3d/models/detectors/dynamic_voxelnet.pyDynamicVoxelNet
体素编码器mmdet3d/models/voxel_encoders/voxel_encoder.pyDynamicSimpleVFE、DynamicVFE
体素编码器mmdet3d/models/voxel_encoders/pillar_encoder.pyDynamicPillarFeatureNet

入口配置位于 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 结果表):

  1. second_dv_secfpn_8xb6-80e_kitti-3d-car.py(DV-SECOND,Car 单类)
  2. second_dv_secfpn_8xb2-cosine-80e_kitti-3d-3class.py(DV-SECOND,3 类)
  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.pyCarcyclic 80e5.578.83
SECOND(DV)second_dv_secfpn_8xb2-cosine-80e_kitti-3d-3class.py3 Classcosine 80e5.565.27
PointPillars(DV)pointpillars_dv_secfpn_8xb6-160e_kitti-3d-car.pyCarcyclic 80e4.777.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.

项目地址:https://gitcode.com/gh_mirrors/mm/mmdetection3d
点击查看免费下载

相关推荐

上一篇:深入解析 MLflow 仓库 upload-media Skill:本地媒体一键上传 GitHub user-attachments 并嵌入 PR 评论
下一篇:ToolJet 在 AWS EKS 上的 Kubernetes 部署实战:从 Deployment、AWS Load Balancer 到 PostgREST 与 LTS 升级

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询