1. 从原始点云到3D框:自动驾驶感知的核心命题
做自动驾驶感知这几年,我最大的体会是:摄像头能告诉你"前面有个东西",但只有激光雷达点云能告诉你"那个东西离我到底几米、有多宽、朝向哪边"。这个差别在高速跟车、窄路会车、夜间无路灯场景下,直接决定刹车时机是提前0.3秒还是晚0.5秒。而基于点云的3D目标检测和分类,就是把这一堆散乱的三维坐标点,变成带类别标签、带朝向、带尺寸的三维边界框的完整过程。
这篇文章我想系统梳理一下这个方向的主流方法脉络,从点云的数据特性讲起,到检测范式的演进,再到分类方法、数据集、评估指标,以及我自己在复现和调参过程中踩过的坑。适合刚进入自动驾驶感知方向的工程师、做点云相关课题的研究生,以及想从2D检测转向3D检测的开发者。读完你应该能建立起一个完整的技术地图,知道每种方法的适用边界在哪里,而不是盲目追最新的论文。
先说清楚一个前提:这里的"点云"主要指LiDAR点云,也就是激光雷达扫描得到的稀疏三维点集。它和深度相机转出来的点云、双目重建的点云在密度、噪声分布、扫描模式上都有本质差异,方法选型时不能混为一谈。
2. 点云数据特性与检测任务的核心难点
2.1 点云到底"长"什么样
一帧典型的64线机械旋转激光雷达点云,大概有10万到12万个点。每个点至少包含三维坐标(x, y, z),通常还有反射强度intensity,有些还有时间戳和ring index。听起来点很多,但撒在几十米到上百米的空间里,密度衰减非常快——近处1平方米可能有几百个点,50米外可能只有几个点。
这种非均匀稀疏性是点云检测的第一个大麻烦。图像是规整的2D网格,每个像素都有值;点云是散乱的三维集合,没有天然的网格结构。你没法直接套用卷积神经网络,因为卷积要求输入在局部是规则排列的。
第二个特性是旋转不变性的缺失。同一辆车,从不同角度看,点云分布完全不同。车头对着雷达和车侧对着雷达,点的分布模式差异巨大。而图像里同一辆车从不同角度看,至少还有纹理和轮廓的连续性可以利用。
第三个是遮挡与截断。激光雷达只能看到物体朝向自己的那一面,一辆车你永远只能扫到它的前半面或者侧面,背面是空的。这意味着基于完整形状的分类思路在点云上根本行不通。
2.2 检测任务要输出什么
3D目标检测的输出通常是一个7维向量:中心点坐标(x, y, z)、尺寸(长l、宽w、高h)、偏航角θ(yaw)。有些方法还会输出速度(vx, vy)用于跟踪,或者俯仰角、翻滚角,但绝大多数自动驾驶场景下,车辆基本在平面上运动,所以只预测yaw就够了。
分类任务则是在检测框的基础上,判断这个框里是什么:轿车、卡车、行人、骑行者、交通锥……在KITTI数据集里通常分三类(Car、Pedestrian、Cyclist),在nuScenes里分得更细,有23类。
注意:检测和分类在点云领域经常是耦合的。很多方法先做类别无关的候选框生成,再对每个框做分类和回归精修。也有端到端直接输出带类别的框。理解这个区别对后面选方法很关键。
2.3 为什么不能直接把图像的方法搬过来
我见过不少刚入门的同学,第一反应是把点云投影成鸟瞰图(BEV)或者前视图,然后套YOLO或者Faster R-CNN。这个思路本身没错,早期确实有人这么做,但它有个致命问题:投影会丢失信息。
把点云压成BEV图,每个像素格子里的高度信息被压缩了。你可以用最大高度、平均高度、高度方差等统计量来编码,但一辆车和一根电线杆在BEV上可能占同样的面积,高度统计量也接近,区分度就下来了。前视图更糟,前后遮挡完全无法表达。
所以后来大家才转向直接在3D空间做检测,或者用体素化保留三维结构。这个演进逻辑,是理解整个领域的关键线索。
3. 3D目标检测方法的主流范式拆解
3.1 基于体素的方法:把点云变成3D网格
体素化的思路很直观:把三维空间切成一个个小立方体(比如0.05m×0.05m×0.1m),每个立方体里的点用某种方式编码成一个特征向量。这样点云就变成了规则的3D张量,可以套3D卷积了。
VoxelNet是这个方向的開山之作。它用VFE(Voxel Feature Encoding)层把每个体素内的点聚合成一个特征,然后用3D卷积提取空间特征,最后接RPN(Region Proposal Network)出检测框。思路清晰,但3D卷积计算量巨大,推理速度上不去。
SECOND做了关键改进:用稀疏卷积替代稠密3D卷积。因为点云体素化后,99%以上的体素是空的,稠密卷积在空体素上浪费了大量计算。稀疏卷积只对非空体素做运算,速度提升非常明显。我实测下来,SECOND在KITTI上能跑到40+FPS,基本满足实时性要求。
PointPillars走得更远,它干脆不做3D体素,而是把空间切成一个个柱体(pillar),在高度方向不切分。每个柱体内的点用一个简化的PointNet提取特征,然后压成2D的伪图像,后面用2D卷积做检测。这个设计非常巧妙,既保留了3D信息,又把计算量降到了2D卷积的水平。工业界很多量产方案都用PointPillars或者它的变体,因为速度和精度的平衡做得好。
| 方法 | 体素化方式 | 核心卷积 | KITTI Car 3D AP | 推理速度 |
|---|---|---|---|---|
| VoxelNet | 3D体素 | 3D稠密卷积 | 约65% | 慢 |
| SECOND | 3D体素 | 3D稀疏卷积 | 约78% | 快 |
| PointPillars | 柱体 | 2D卷积 | 约75% | 很快 |
实操心得:体素大小是个关键参数。体素太大,小目标(行人、锥桶)的特征被稀释,召回率下降;体素太小,非空体素数量暴增,显存吃不消。我一般从(0.1, 0.1, 0.2)开始试,根据目标尺寸分布调整。检测行人为主就把体素调小,检测卡车为主可以适当放大。
3.2 基于点的方法:直接在原始点上做文章
体素化不可避免会引入量化误差,而且手工设计体素编码方式也有信息损失。于是有人想:能不能直接在原始点云上做检测?
PointNet和**PointNet++**是点云深度学习的基石。PointNet用对称函数(max pooling)解决了点的无序性问题,PointNet++引入层次化特征提取,能在不同尺度上聚合局部特征。但这两个主要是做分类和分割的,直接做检测还需要额外的框回归头。
PointRCNN是两阶段的点云检测方法。第一阶段用PointNet++做前景点分割,把属于目标的点挑出来,每个前景点生成一个候选框;第二阶段对这些候选框做精修和分类。这个思路很像图像里的Faster R-CNN,但操作对象是原始点。精度很高,但速度偏慢。
3DSSD针对PointRCNN速度慢的问题做了优化,去掉了上采样层和精修阶段,用单阶段的方式做检测,速度提升明显,精度略有下降但可接受。
基于点的方法最大的优势是保留了原始几何信息,没有量化损失。但缺点是点云的无序性和稀疏性让卷积操作很难高效实现,推理速度通常不如体素方法。
3.3 点体素融合:取长补短
既然体素方法快、点方法准,那能不能结合起来?PV-RCNN就是干这个的。它先用体素卷积提取特征,生成候选框,然后把候选框内的原始点采样出来,用PointNet做精细的特征提取和框精修。体素分支负责高效召回,点分支负责精确回归,两者互补。
SA-SSD是另一个思路,它在体素网络的基础上加了一个辅助任务:预测每个体素点到目标中心的偏移量。这个辅助任务让网络学到更结构化的特征,检测精度提升明显,而且推理时辅助分支可以砍掉,不增加部署成本。
这类融合方法在KITTI榜单上长期霸榜,但工程复杂度也高,调参难度大。如果你是做产品落地,我建议先从PointPillars这种简单可靠的方案入手,有精度需求再考虑融合方案。
3.4 基于Transformer的方法:新范式的冲击
2020年之后,Transformer开始渗透到点云检测领域。DETR3D把2D检测里的DETR范式搬到3D,用一组可学习的query,通过注意力机制从多视角特征里采样,直接输出3D框。PETR系列进一步引入了位置编码的3D感知设计,让query能感知三维空间位置。
这类方法的优势是端到端、无需NMS后处理,流程简洁。但训练收敛慢,对数据量要求高,而且注意力机制的计算复杂度随点云规模增长快。目前在nuScenes等大规模数据集上表现不错,但在实际车端部署还有距离。
我个人的判断是:Transformer方法代表了方向,但短期内体素和点方法仍然是工程落地的主力。做研究可以追新范式,做产品要优先考虑成熟度和可维护性。
4. 点云分类方法的关键技术点
4.1 分类和检测的关系
点云分类在自动驾驶里通常不是独立任务,而是检测流程的一部分。检测框出来后,需要对框内的点云片段做类别判断。但也有一些场景是纯分类任务,比如路侧感知单元对经过的车辆做车型分类,或者对点云场景做语义分割(每个点属于什么类别)。
从技术上讲,分类比检测简单,因为不需要回归位置和尺寸。但点云分类有它自己的难点:类内差异大、类间差异小。同样是轿车,三厢和两厢的点云分布不同;轿车和SUV的点云轮廓又很接近。再加上遮挡和稀疏,分类器很容易混淆。
4.2 基于多视图的分类
早期方法(如MVCNN)把3D物体从多个角度投影成2D图像,用CNN提取特征后融合。这个思路在ModelNet40等CAD模型数据集上效果好,但用在真实LiDAR点云上问题很大:真实点云只有单视角,你没法从背面投影,因为背面根本没有点。
所以多视图方法在自动驾驶场景下基本被淘汰了,除非你有多帧累积的点云,能拼出相对完整的物体形状。
4.3 基于体素的分类
VoxNet是最早的体素分类网络之一,把物体体素化成32×32×32的网格,用3D卷积分类。简单直接,但分辨率受限,细粒度分类效果一般。
3D ShapeNets用深度信念网络做体素分类,更早,现在基本只作为历史参考。
体素分类的问题是:分辨率越高,计算量指数增长;分辨率低了,细节丢失。在自动驾驶场景下,远处目标的点云本来就稀疏,体素化后几乎没什么信息了。
4.4 基于点的分类
PointNet在分类任务上的表现是里程碑式的。它直接吃原始点云,用共享MLP提取逐点特征,再用max pooling聚合全局特征,最后接全连接分类。在ModelNet40上达到89%的准确率,而且对点的排列顺序不变,对刚体变换也有一定鲁棒性。
**PointNet++**通过层次化采样和分组,能捕捉局部结构,分类精度进一步提升到91%以上。DGCNN引入EdgeConv,动态构建局部图,在分类和分割上都表现优秀。
在自动驾驶点云分类中,我通常用PointNet++的简化版做框内点云分类。输入是检测框内的点(通常采样到512或1024个点),输出是类别概率。实测下来,对Car、Pedestrian、Cyclist三分类,准确率能到95%以上,但加上Truck、Bus、Motorcycle等细分类别后,准确率会掉到85%左右,主要混淆发生在Car和Van、Truck和Bus之间。
实操心得:做点云分类时,点的采样策略很关键。FPS(最远点采样)比随机采样更能保留形状信息,但计算慢。实际部署时我常用随机采样加体素下采样的组合,先体素下采样去掉冗余,再随机采样固定点数,速度和效果的平衡比较好。
4.5 分类中的特征增强技巧
单纯用坐标做分类,信息量有限。我一般会加几类额外特征:
- 反射强度:不同材质的反射率不同,金属车身反射强,行人衣物反射弱,这个特征对分类很有帮助。
- 点密度:单位体积内的点数,反映物体的表面材质和距离。
- 高度统计:最大高度、高度方差、高度直方图,对区分轿车和SUV、行人和骑行者很有效。
- 法向量:局部表面的朝向,对区分平面(车门)和曲面(车顶)有用。
这些特征拼接起来,分类准确率通常能提升3到5个百分点。但要注意特征归一化,不同量纲的特征直接拼接会让网络偏向数值大的特征。
5. 数据集、评估指标与实操流程
5.1 主流数据集怎么选
KITTI是点云检测最经典的数据集,7481帧训练、7518帧测试,标注了三类目标。它的点云是64线雷达采集的,场景主要是城市道路和高速公路。优点是标注质量高、社区活跃、榜单透明;缺点是规模小、类别少、场景单一。做算法验证和论文对比,KITTI是标配。
nuScenes是更大规模的数据集,1000个场景,每场景20秒,32线雷达,标注23类目标,还有360度环视图像。它的评估指标更严格,用了mAP和NDS(nuScenes Detection Score),对速度误差、朝向误差、属性误差都有惩罚。做量产相关的研究,nuScenes更有参考价值。
Waymo Open Dataset规模更大,1150个场景,64线雷达,标注质量极高。但获取需要申请,而且计算资源要求高。
Argoverse和Lyft L5也是可选的数据集,各有侧重。Argoverse有高精地图和运动预测标注,Lyft L5的场景多样性好。
| 数据集 | 帧数/场景数 | 雷达线数 | 类别数 | 特点 |
|---|---|---|---|---|
| KITTI | 14999帧 | 64线 | 3类 | 经典、规模小 |
| nuScenes | 1000场景 | 32线 | 23类 | 大规模、多模态 |
| Waymo | 1150场景 | 64线 | 4类 | 高质量、需申请 |
| Argoverse | 113场景 | 32线 | 15类 | 含地图和预测 |
注意:不同数据集的坐标系定义、标注格式、评估协议都不一样。跨数据集训练时一定要做坐标对齐和类别映射,否则结果没法比。我见过有人直接把KITTI训练的模型放到nuScenes上测,mAP掉了一半,很大一部分原因就是坐标系和类别定义没对齐。
5.2 评估指标到底怎么看
3D检测的核心指标是3D AP(Average Precision),计算方式和2D检测类似,但IoU是在3D空间算的。KITTI的评估协议里,Car类的IoU阈值是0.7,Pedestrian和Cyclist是0.5。这个阈值设定是有讲究的:车辆形状规整,0.7的IoU能保证框的贴合度;行人和骑行者形状不规则,0.5更合理。
KITTI还分了三个难度等级:Easy、Moderate、Hard,根据目标的高度、遮挡程度、截断程度划分。看论文结果时,Moderate的3D AP是最常被引用的指标,因为它最接近实际场景的难度分布。
nuScenes的指标更复杂:mAP是各类别AP的平均,但匹配时用的是中心点距离而不是IoU(因为不同类别的尺寸差异大,IoU不公平)。NDS是mAP、mATE(平移误差)、mASE(尺度误差)、mAOE(朝向误差)、mAVE(速度误差)、mAAE(属性误差)的加权组合。这个指标体系更全面,但也更难优化。
5.3 从数据到部署的完整流程
我以KITTI数据集、PointPillars方法为例,走一遍完整流程:
第一步:数据准备。下载KITTI 3D Object Detection数据集,解压后得到velodyne点云、label标注、calib标定文件、image图像。用官方提供的工具把标注转成训练需要的格式(中心点、尺寸、朝向)。
第二步:点云预处理。把点云限制在检测范围内(比如x方向[0, 70.4]米,y方向[-40, 40]米,z方向[-3, 1]米),超出范围的点裁掉。然后做数据增强:随机旋转、随机平移、随机翻转、全局缩放。KITTI数据量小,增强是必须的,否则很容易过拟合。
第三步:体素化/柱体化。PointPillars把点云切成柱体,每个柱体最多保留一定数量的点(比如32个),用PointNet提取特征,得到(C, H, W)的伪图像。
第四步:网络搭建。主干网络用2D CNN(比如ResNet-18的变体)提取特征,然后接检测头,分别预测类别、框回归、朝向分类。朝向通常用两个bin的分类加残差回归,避免角度回归的周期性问题。
第五步:损失函数。分类用Focal Loss解决正负样本不平衡,回归用Smooth L1 Loss,朝向用bin分类的交叉熵加残差回归的Smooth L1。
第六步:训练与调参。Adam优化器,初始学习率1e-3,余弦退火,训练80到100个epoch。Batch size根据显存调整,一般8到16。关键调参点:正负样本匹配的IoU阈值、Focal Loss的alpha和gamma、数据增强的强度。
第七步:推理与后处理。网络输出大量候选框,先按类别置信度过滤,再做NMS(非极大值抑制),最后输出最终的3D框。NMS的IoU阈值一般设0.1到0.3,因为3D框在BEV上的重叠比2D框更常见。
第八步:评估与可视化。用官方评估工具算3D AP和BEV AP,用Open3D或CloudCompare可视化检测结果,检查漏检和误检的模式。
# PointPillars柱体化核心逻辑示意 def points_to_pillars(points, point_cloud_range, voxel_size): # points: (N, 4) [x, y, z, intensity] # 计算每个点所属的柱体索引 pillar_x = ((points[:, 0] - point_cloud_range[0]) / voxel_size[0]).astype(np.int32) pillar_y = ((points[:, 1] - point_cloud_range[1]) / voxel_size[1]).astype(np.int32) # 过滤超出范围的点 mask = (pillar_x >= 0) & (pillar_x < grid_size[0]) & \ (pillar_y >= 0) & (pillar_y < grid_size[1]) # 每个柱体最多保留max_points个点 # 用PointNet提取柱体特征 # 散射回2D伪图像 return pseudo_image实操心得:训练PointPillars时,数据增强的强度对最终精度影响很大。我试过只用随机翻转和轻微旋转,Moderate 3D AP只有72%左右;加上全局缩放、随机平移、GT采样增强后,能到76%以上。但增强太猛也会导致训练不稳定,建议逐步增加增强强度,观察验证集指标变化。
6. 常见问题与排查技巧实录
6.1 训练不收敛怎么办
这是最常见的问题。点云检测网络的损失函数通常由分类损失和回归损失组成,两者量级差异大,容易导致某一项主导梯度。
排查顺序:先看分类损失是否下降。如果分类损失不降,检查正负样本匹配逻辑,可能是正样本太少或者匹配阈值设得太高。再看回归损失,如果回归损失震荡,检查学习率是否太大,或者框的编码方式是否有问题。
我遇到过一次,训练了20个epoch,分类损失正常下降,但回归损失一直在0.5左右震荡。后来发现是朝向角的编码有问题:我用的是直接回归角度值,但角度有周期性,网络在0和2π附近来回跳。改成bin分类加残差回归后,问题解决。
6.2 验证集精度远低于训练集
典型的过拟合。KITTI只有7481帧训练数据,如果不做数据增强,过拟合几乎是必然的。
解决手段:加强数据增强(特别是GT采样增强,从其他帧复制目标粘贴到当前帧)、加Dropout、加权重衰减、减小模型容量。我一般还会用早停策略,验证集连续10个epoch不提升就停。
还有一个容易被忽略的点:数据泄露。KITTI的训练集和验证集如果按连续帧划分,相邻帧的点云几乎一样,验证集精度会虚高。正确做法是按场景划分,确保验证集的场景在训练集中没出现过。
6.3 小目标检测效果差
行人、锥桶这类小目标,点云本来就少,体素化后更容易被淹没。我试过几种改进:
- 降低体素尺寸,但显存吃不消。
- 在损失函数里给小目标更高的权重。
- 用多尺度特征融合,在浅层特征图上做小目标检测。
- 用点分支补充体素分支,PV-RCNN就是这个思路。
实测下来,多尺度特征融合性价比最高,改动小,提升明显。在PointPillars的FPN结构里,把浅层特征图也接入检测头,行人的AP能提升5个点左右。
6.4 推理速度不达标
车端部署通常要求10FPS以上,也就是每帧100毫秒以内。如果模型推理慢,先看瓶颈在哪:是体素化耗时,还是网络前向耗时,还是NMS耗时。
体素化用CUDA实现通常很快,几毫秒搞定。网络前向是大头,可以换更轻的主干网络(比如用MobileNet替换ResNet),或者做通道剪枝。NMS在候选框多的时候也耗时,可以用CUDA版的NMS,或者减少候选框数量。
我做过一次优化:把PointPillars的主干从ResNet-18换成自定义的轻量网络,通道数减半,推理速度从25FPS提升到45FPS,精度只掉了1.5个点。这个 trade-off 在量产场景下完全可以接受。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 分类损失不降 | 正负样本失衡 | 统计正负样本比例 | 调Focal Loss参数、调匹配阈值 |
| 回归损失震荡 | 学习率过大/角度编码问题 | 检查学习率曲线、角度分布 | 降学习率、改bin分类 |
| 验证精度低 | 过拟合/数据泄露 | 对比训练验证曲线、检查划分 | 加增强、按场景划分 |
| 小目标漏检 | 体素化信息丢失 | 可视化小目标点云 | 多尺度融合、调损失权重 |
| 推理慢 | 主干网络重/NMS慢 | 分模块计时 | 换轻量主干、CUDA NMS |
| 朝向预测不准 | 角度周期性 | 可视化朝向误差分布 | bin分类+残差回归 |
6.6 几个容易踩的坑
坑一:坐标系搞混。KITTI的雷达坐标系是x向前、y向左、z向上,但有些代码库用的是x向前、y向右、z向上。坐标系搞反了,训练出来的框全是镜像的。我建议在数据加载后先可视化几帧,确认坐标系方向。
坑二:标注格式转换错误。KITTI的标注是(l, w, h, x, y, z, yaw),但有些代码库期望的是(w, l, h, ...)或者(h, w, l, ...)。顺序搞错,框的尺寸就全乱了。转换后一定要用官方工具验证一遍。
坑三:忽略类别不平衡。KITTI里Car占绝大多数,Pedestrian和Cyclist很少。如果不做类别平衡,网络会倾向于把所有东西都预测成Car。我通常用类别加权的损失函数,或者对稀有类别做过采样。
坑四:NMS阈值设太大。3D框在BEV上的重叠比2D框严重,NMS阈值设0.5的话,相邻的两辆车可能被合并成一个。我一般设0.1到0.2,具体根据场景调整。
坑五:忘了做点云范围裁剪。KITTI的点云范围很大,但标注只在特定范围内。如果不裁剪,网络会学到很多无用的背景点,浪费计算资源还影响精度。
7. 方法选型与工程落地的个人建议
做了几个量产项目后,我对方法选型有一些比较务实的看法。如果你刚入门,想快速跑通一个baseline,PointPillars是最佳起点:代码成熟、社区活跃、速度精度平衡好、部署友好。跑通之后,再根据需求往两个方向走:要精度就上PV-RCNN或SA-SSD,要速度就做轻量化。
如果是做研究发论文,那肯定要追最新的范式,Transformer-based的方法、多模态融合、自监督预训练都是热点。但要注意,论文里的SOTA和实际能落地的方案之间往往有巨大鸿沟。我见过太多论文方法在KITTI上刷到80%+的AP,但推理速度只有2FPS,根本没法上车。
数据方面,我的建议是:KITTI用来验证算法正确性,nuScenes用来验证泛化能力,自有数据用来验证落地可行性。三个层次缺一不可。很多问题只有在自有数据上才会暴露,比如特定车型的点云分布、特定场景的遮挡模式、传感器的标定误差。
最后说一个我踩过的大坑:不要忽视标定。LiDAR和IMU的标定、LiDAR和相机的标定,如果外参有偏差,点云和图像对不齐,多模态融合就是灾难。我遇到过一次,LiDAR-IMU外参的旋转矩阵差了0.5度,导致点云在地图里整体倾斜,检测框的位置系统性偏移。标定这件事,宁可多花时间做精确,也不要凑合。
点云3D检测这个方向,方法迭代很快,但核心的工程问题——数据质量、标定精度、部署效率——这些年其实没怎么变。把基础打牢,追新方法才有意义。