OpenPCDet实战:从环境配置到PointPillars训练全流程解析
做自动驾驶、机器人或者任何需要从激光雷达点云里认物体的任务3D目标检测都是绕不开的一步。点云这个数据形态和图像差别很大——它是稀疏的、无序的、密度不均匀的传统2D检测里那套卷积方法不能直接搬过来用。OpenPCDet就是为这个场景而生的一个开源工具箱它把PointPillars、SECOND、PV-RCNN、CenterPoint这些主流3D检测算法统一进了同一个模块化代码库从数据预处理、模型训练到评估和可视化全链路都给你安排好了。这篇文章不打算讲太多理论就说我实际动手时的完整路径怎么把环境配好、怎么安装、怎么准备KITTI数据、怎么跑通一次训练、最后怎么看效果外加我踩过的一些坑。不管你是刚入门的学生还是想快速评估3D检测的工程师按这个流程走一遍基本能少折腾两个星期。1. 先弄明白OpenPCDet在3D检测生态中的位置1.1 为什么我最终选了OpenPCDet在OpenPCDet出现之前复现一篇3D检测论文的体验是相当痛苦的。每个仓库的数据处理方式、模块命名、训练技巧都不一样A作者的代码里用voxelizationB作者的代码里可能叫voxelize你想把两个模型的backbone拼在一起得先花大量时间做适配。OpenPCDet做的事情是把多个主流算法的公共部分抽取出来重新组织成一套统一框架。在OpenPCDet里切换算法很多时候只需要换一个config文件不需要重写训练循环。我当时在几个框架之间犹豫过简单说说对比感受mmdetection3d功能全室内室外都支持但模块抽象层数比较多出了问题想顺藤摸瓜找到底层实现需要翻不少代码。Det3D早期很流行的开源方案支持SECOND、PointPillars等算法可惜后来维护节奏放慢了新卡新驱动下编译容易出问题。OpenPCDet代码结构清晰模型组件比如backbone、voxelization、dense head、roi head之间边界分明改一个模块不会牵连别处。社区活跃新算法跟进也快环境问题和BUG基本都能在issue里找到解决办法。如果要用一句话给它定位我觉得它是介于“研究原型代码”和“工业部署代码”之间的一个工程化程度较高的训练验证平台。你可以快速跑通一个baseline也可以在自己的数据集上做二次开发。对于我个人来说选它最重要的理由就一个想要换算法做对比实验时改动的成本低到可以忽略。1.2 它支持的算法到底是怎么组织起来的OpenPCDet里的模型大致分几个流派。最主流的是基于体素的比如SECOND、Voxel R-CNN、PV-RCNN。它们先把点云量化为体素网格用3D稀疏卷积提取特征然后在鸟瞰图BEV或者原始点云上做区域提议和框体细化。另一种是PointPillars这种它不是真正的3D卷积而是把点云在竖直方向聚合形成伪图像后再用2D卷积处理所以速度非常快在嵌入式设备上也有实用价值。基于点的方法OpenPCDet也提供了一部分支持但整体上体素流派更完备生态也更成熟。所谓体素化可以想象成把空间划分成很多小立方体格子每个格子里装着若干点。算法要从这些不规则分布的格子中提取特征而稀疏卷积正是为“格子大部分是空的”这种情况做优化的普通3D卷积就像把整个考场每张桌子都检查一遍哪怕座位空着也要算稀疏卷积只对有人的桌子做计算省下的计算量非常可观。理解了这一点你就明白为什么OpenPCDet和spconv这个库绑得那么紧——spconv就是稀疏卷积的底层实现。从数据流角度看一帧点云进入模型后大致经过这样的路径先voxelization变成体素网格再经过稀疏卷积Stage提取体素特征然后把特征映射到BEV平面形成伪图像特征接着由dense head输出3D框的中心、尺寸、朝向和类别置信度如果是两阶段模型还会进入roi head做二次refine。这个流程在你后面看config文件时非常有帮助因为每个字段都对应这条流水线上的一个环节。2. 安装成功的关键不在OpenPCDet而在版本匹配2.1 CUDA、PyTorch、spconv三者的版本矩阵说句实在话OpenPCDet本身的安装只有两三条命令绝大多数安装失败都发生在它依赖的底层库上。先给出一组我实测可行的版本组合环境组合A稳妥组合B较新系统Ubuntu 20.04Ubuntu 22.04Python3.83.9CUDA Toolkit11.311.8PyTorch1.10.1cu1132.0.1cu118spconv2.1.22.3.6GCC7.5.0以上9.4.0以上先澄清两个容易混淆的概念。nvidia-smi显示的CUDA Version是驱动支持的最高CUDA版本它不等于你当前运行环境实际使用的CUDA Toolkit版本。实际编译程序时用的可能是conda环境里安装的cudatoolkit也可能是系统/usr/local/cuda-xx下的cuda。只要驱动的版本不低于你需要的CUDA Toolkit版本一般不会有大问题。比如驱动是470那CUDA 11.x基本都能用。为什么版本绑定这么紧因为spconv是调用CUDA的C扩展它和PyTorch的二进制接口是强绑定的。PyTorch 1.x和2.x的ABI不兼容spconv针对不同PyTorch版本编译出来的产物也不能混用。很多人换完PyTorch版本后旧的spconv直接报undefined symbol就是这个原因。2.2 推荐的环境搭建顺序我个人的经验是先装PyTorch再装spconv最后装OpenPCDet顺序不能乱。如果你倒着来或者一次性把依赖全塞进去出问题后很难定位。从头开始的话建议用conda建一个干净环境conda create -n openpcdet python3.8 conda activate openpcdet然后装PyTorch。去PyTorch官网选择对应的CUDA版本安装命令例如pip install torch1.10.1cu113 torchvision0.11.2cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html装完立刻验证一下CUDA是否可用python -c import torch; print(torch.__version__, torch.cuda.is_available())这一步如果打印出True后面大概率顺利如果是False先解决CUDA问题再继续。不要在一个坏地基上盖房子不然你永远不知道报错到底是哪一层引出来的。3. 从源码编译到import通过OpenPCDet安装全流程3.1 源码编译到底做了什么环境准备好之后安装OpenPCDet主体的流程如下git clone https://github.com/open-mmlab/OpenPCDet.git cd OpenPCDet pip install -r requirements.txt python setup.py developpip install -r requirements.txt会装上一堆基础依赖比如easydict、tensorboardX、numpy这些。但这里有个容易忽略的坑OpenPCDet的requirements.txt里很可能包含了torch和torchvision。如果你已经装好了PyTorch直接执行这一句pip可能会尝试重新安装默认版本的torch把你原来配好的环境覆盖掉。我自己第一次安装时就栽在这个点上——跑完setup.py develop后才发现torch被换成了CPU版本半天白折腾。正确做法是打开requirements.txt把torch和torchvision这两行注释掉然后再执行安装。如果只是图省事也可以手动安装除torch外的几个包pip install easydict numpy scikit-image tensorboardX接下来是python setup.py develop。这个命令会把C/CUDA扩展编译成可加载的.so文件并建立一个软链接让import pcdet直接指向当前源码目录。这样你修改Python代码后不用重新安装改动即时生效特别适合做研究改模型。有人问为什么不直接用pip install .因为那相当于把编译好的包复制到site-packages里后续你改源码还得重新安装非常不方便所以官方推荐develop模式。编译时会调用本机的PyTorch头文件和CUDA Toolkit。如果环境里有多个CUDA版本要注意PATH和LD_LIBRARY_PATH的指向export PATH/usr/local/cuda-11.3/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-11.3/lib64:$LD_LIBRARY_PATH3.2 安装spconv的正确姿势spconv是OpenPCDet的核心依赖也是最容易出问题的环节。spconv有1.x和2.x两个大版本OpenPCDet当前主线适配的是2.x。最简单的方式是直接装预编译包# 假设PyTorch 1.10 CUDA 11.3 pip install spconv-cu113如果是PyTorch 2.0 CUDA 11.8一般可以用pip install spconv-cu118装完验证python -c import spconv; print(spconv.__version__)如果pip找不到对应的包或者你想源码编译可以这样git clone -b v2.1.2 https://github.com/traveller59/spconv.git cd spconv pip install .源码编译需要较长时间可能十几分钟到半小时。编译前确认gcc版本足够新建议gcc 7以上否则会报C标准相关的错误。我当时第一次编译spconv时用的老机器上gcc 5直接报了一堆-stdc14相关的错误换了gcc 7才通过。3.3 验证安装是否成功全部装完之后进入OpenPCDet目录运行python -c import pcdet; print(pcdet.__version__)如果没有任何报错说明安装已经成功了。你也可以顺手跑一下python tools/demo.py --help看看命令行工具是否正常。这一步如果报import spconv失败回头检查上一节的版本矩阵十有八九还是版本不匹配。如果编译时报堆栈异常建议把报错日志完整读一遍重点看是哪个.cpp文件出错。通常在C层面暴露的问题环境原因居多OpenPCDet代码本身一般没问题。提示安装阶段最大的成本是时间。每次环境变更后重跑一次python -c import pcdet确认状态能帮你把“环境坏了”和“代码错了”快速区分开。4. 把KITTI数据集变成模型能吃的训练数据4.1 下载与目录组织要快速跑通训练最合适的公开数据是KITTI。虽然这个数据集已经有年头了但作为3D检测的入门benchmark规模适中、标注格式简单用来跑通OpenPCDet的流程完全够用。KITTI的3D object detection数据需要下载这几部分RGB图像大约12GBVelodyne激光点云大约29GB相机标定文件大约16MB标签文件大约5MB下载完成后在OpenPCDet项目里推荐组织成这样的目录结构data/kitti/ ├── ImageSets/ │ ├── train.txt │ ├── val.txt │ ├── test.txt │ └── trainval.txt ├── training/ │ ├── image_2/ │ ├── label_2/ │ ├── velodyne/ │ └── calib/ └── testing/ ├── image_2/ ├── velodyne/ └── calib/ImageSets里的txt文件可以从官方devkit里的划分生成也可以复用网上整理好的现成文件。每个txt文件的内容是一行一个id比如000000、000001这些id对应的点云和标签文件要在training或testing目录下能找到。这里有个新手容易犯的错把训练集和测试集的文件全部混在一个目录里。OpenPCDet是通过train.txt和val.txt来区分训练和验证的目录结构必须严格对应否则生成pkl阶段就会报找不到文件。我自己第一次就吃了这个亏点云和图像全部丢到一个大目录里最后只能重新组织文件结构。4.2 create_kitti_infos和gt_database的作用数据文件放好后需要做两步预处理。第一步是生成kitti_infos相关的pkl文件里面保存了每一帧的点云路径、图像路径、标定参数、3D框标注信息等。在新版OpenPCDet中命令是python -m pcdet.datasets.kitti.kitti_dataset create_kitti_infos tools/cfgs/dataset_configs/kitti_dataset.yaml执行之后data/kitti/下会生成kitti_infos_train.pkl、kitti_infos_val.pkl、kitti_infos_trainval.pkl等文件。这些pkl相当于一个索引文件训练时DataLoader通过它快速找到对应数据不用每次都去遍历原始目录。第二步是生成gt_database。这一步会把训练集中所有被标注的3D框内的点云裁剪出来存到一个数据库里。训练时数据增广模块会从这个数据库中随机取一些真实目标插入到当前场景的空闲区域从而增加训练样本的多样性。它就像做视频剪辑时从素材库拖一段人物抠像贴到新的背景里是一种非常有效的增广手段。python -m pcdet.datasets.kitti.kitti_dataset create_gt_database tools/cfgs/dataset_configs/kitti_dataset.yaml生成后会出现gt_database目录和kitti_dbinfos_train.pkl。如果漏了这一步训练启动时DataLoader会直接报错错误信息指向gt_sampling_processor。我当时第一次跑就是漏了这一步排查了很久才发现是预处理没做全。所以建议不管时间多紧都完整跑完这两步再进入训练。5. 训练一个PointPillars模型从config到正式训练5.1 config文件的关键字段解析OpenPCDet最大的特点是换模型就是换config。以PointPillars为例对应的配置文件在tools/cfgs/kitti_models/pointpillar.yaml。打开这个文件从上到下大致分这几种模块CLASS_NAMES检测类别KITTI上是Car、Pedestrian、Cyclist。DATA_CONFIG数据路径、样本划分、数据增广开关。MODEL模型结构包含Voxelization、Backbone3D、MapToBEV、Backbone2D、DenseHead这些子模块。OPTIMIZATION学习率、batch size、训练轮数等。新手最需要关注的是Voxelization里的参数VOXEL_SIZE: [0.16, 0.16, 4.0] MAX_POINTS_PER_VOXEL: 32 MAX_NUMBER_OF_VOXELS: [16000, 40000]VOXEL_SIZE前两个数是水平方向的x/y分辨率单位是米。0.16意味着地面大约16厘米见方的一个格子。格子越小空间分辨率越高但计算量和显存占用也会上升。PointPillars能在速度和精度上取得平衡这个0.16是很关键的设计。第三个值是z方向的高度设为4.0是因为KITTI激光雷达在垂直方向覆盖范围有限把整个高度作为一个体素维度可以在不损失太多信息的情况下降低计算量。MAX_POINTS_PER_VOXEL表示每个体素里最多保留多少个点超出会被随机采样。MAX_NUMBER_OF_VOXELS中16000对应训练集每帧最多保留的非空体素数40000对应测试超出会采样裁剪。如果这个值设得太小远处的点被丢得太多影响小目标检测设得太大显存占用会上升。官方的默认值在KITTI上是经过验证的一般不用动。5.2 训练启动、监控与断点续训训练命令很简单cd tools python train.py --cfg_file cfgs/kitti_models/pointpillar.yaml --batch_size 8 --epochs 80 --extra_tag my_first_try--extra_tag是训练run的名字所有输出都会放在output/kitti_models/pointpillar/my_first_try下。为什么要强调这个参数因为OpenPCDet的断点续训逻辑是按路径判断的。你在同一个tag下重新运行如果已有checkpoint它会自动从最近的epoch继续训练。如果你想从头开一个新实验务必换一个tag或者清空对应目录否则它会默默接着上一个run训练结果和预期完全不同。batch size要根据显卡显存来定。PointPillars在batch size等于16时大约需要11GB显存如果你的显卡是8GB建议设成4或者6。我自己在KITTI上测试过小batch size训练80个epoch最终mAP差距并不大但显存不够直接OOM程序崩溃那是真的一点进展都没有。所以第一优先级是保证程序能稳定跑完而不是一味追求大batch。训练过程中日志文件在output/kitti_models/pointpillar/my_first_try/log_train.txt也可以用tensorboard实时看曲线tensorboard --logdir output/kitti_models/pointpillar/my_first_try正常情况下前几个epoch loss会快速下降从十几降到一以下后面就平缓了。如果loss一直不降或者直接变成NaN去第7节看排查思路。训练完成后output/kitti_models/pointpillar/my_first_try/ckpt/下会保存checkpoint_epoch_XX.pth这类权重文件以及一个last_checkpoint.pth评估和推理都要用到它们。6. 评估和可视化demo判断模型好不好用6.1 test.py评估流程详解训练结束后先在验证集上看一下效果cd tools python test.py --cfg_file cfgs/kitti_models/pointpillar.yaml --extra_tag my_first_try --eval_all--eval_all表示逐个评估所有保存的checkpoint然后从日志中选出最佳epoch的结果。也可以指定某个具体权重python test.py --cfg_file cfgs/kitti_models/pointpillar.yaml --ckpt output/kitti_models/pointpillar/my_first_try/ckpt/checkpoint_epoch_80.pth评估结果会打印一张表按类别和IoU阈值分别计算mAP。KITTI上Car的IoU阈值是0.7Pedestrian和Cyclist是0.5。结果分成easy、moderate、hard三档难度对应目标在图像中的遮挡和截断程度。PointPillars在moderate档Car上的mAP通常能做到80左右如果追求更高精度可以换PV-RCNN或Voxel R-CNN但训练时间会成倍增加。这里提醒一句mAP并不是一个可以跨实验随便对比的绝对值它跟数据划分、IoU阈值、召回计算方式都有关系。OpenPCDet里评估流程已经统一了你只需要确保不要拿不同数据划分下的结果做对比就行。6.2 demo.py单帧推理与可视化想直观看到检测效果用demo.py做单帧推理最方便。假设你有一个KITTI格式的激光雷达bin文件比如data/kitti/testing/velodyne/000123.binpython demo.py --cfg_file cfgs/kitti_models/pointpillar.yaml --ckpt output/kitti_models/pointpillar/my_first_try/ckpt/checkpoint_epoch_80.pth --data_path ../data/kitti/testing/velodyne/000123.bin程序会加载模型完成推理把3D框画在鸟瞰图上并以图片形式保存到output/demo目录。你也可以拿自己的点云数据来测但要注意训练用的点云坐标系是KITTI激光雷达坐标系如果你的数据来自不同雷达、不同安装方式需要先对齐坐标系否则模型预测结果会完全不对。demo.py通常会打印每个检测框的类别、置信度、3D中心点和尺寸。如果背景框太多就把置信度阈值调高如果目标漏检严重就把阈值调低。这个参数在config里一般也有默认值实际使用时你可以根据自己的场景灵活调整。7. 实测中常踩的坑与排查路径7.1 spconv编译失败怎么定位最典型的情况是执行pip install spconv或编译OpenPCDet时报错信息里出现一堆.cpp和.cu文件最后落到error: failed to compile CUDA code或者gcc: internal compiler error。这种情况基本是环境不匹配不要急着去改OpenPCDet的源码。按下面几步排查确认nvcc --version输出的CUDA版本和torch.version.cuda一致。确认gcc版本。spconv 2.x要求gcc 7以上Ubuntu 18.04自带的gcc 7没问题Ubuntu 16.04上的gcc 5就会报错。确认没有在PATH中混入不兼容的CUDA。多个CUDA切换时在.bashrc里只保留你当前环境要用的一条PATH和LD_LIBRARY_PATH。如果编译时间太长或者老是失败可以优先考虑用预编译的wheel包本质上是跳过源码编译直接拿二进制能省十几分钟而且不容易出错。7.2 显存不足与NaN loss的处理显存溢出是最容易定位的问题报错一般是CUDA out of memory。处理方法减小--batch_size从8改到4甚至2。减小MAX_NUMBER_OF_VOXELS比如从16000改到12000但这会损失一定精度。num_workers不要设太高一般4到8就好太高反而会因为数据加载CPU竞争导致训练变慢。如果只是想快速验证流程可以加--epochs 1跑通一次别一上来就训满80个epoch。NaN loss处理起来比较麻烦。最常见原因是学习率过大。OpenPCDet默认初始学习率0.001是在特定batch size下设计的如果你单卡batch size很小建议调低一点比如0.0005。另外训练数据里如果存在异常标注比如3D框尺寸为0或者坐标全为0也可能导致loss回传出现NaN。这时候需要检查pkl里的标注维度是否合理。还有个新版本容易踩的坑PyTorch 2.0之后如果开了混合精度训练可能和OpenPCDet的某些自定义算子不兼容表现为前几个iter正常跑几十步后loss突然变成NaN。遇到这种情况先关掉混合精度用纯FP32训练稳定之后再考虑加速方案。7.3 NumPy与Python版本引发的兼容性问题很多人在新环境里装PyTorch 2.x时会连带装到NumPy 1.24以上而NumPy 1.24移除了np.bool这样的旧接口。OpenPCDet部分历史代码里如果还在用np.bool会直接报AttributeError: module numpy has no attribute bool。解决办法是降级pip install numpy1.23.5这个版本在所有主流PyTorch版本下都能兼容。类似地如果遇到collections.Mapping报错通常是Python 3.10以上把collections.abc路径改了属于某个旧依赖的问题对症升级或者打补丁就行。我自己的习惯是新建环境时固定基础依赖版本单独写一个requirements-lock.txt记录下来。这样即使过了几个月别人拿着你的环境配置也能复现出相同结果省去很多不必要的联调时间。