3D目标检测实战笔记:从点云体素化到检测头设计的核心要点
1. 为什么我要啃3D目标检测这块硬骨头第一次接触3D目标检测是在一个室内机器人抓取的小项目里当时天真地以为把2D检测模型往点云上一套就完事了结果被现实按在地上摩擦了整整两周。相机告诉我有物体但机械臂伸过去抓了个空——深度信息完全对不上坐标系转换错得离谱。那次之后我才老老实实坐下来把3D目标检测这个领域的综述文献从头到尾捋了一遍边看边记慢慢攒出了一份自己的笔记。这份笔记不是论文翻译也不是教科书式的概念罗列而是我在实际动手过程中反复回看、反复修正后沉淀下来的理解框架。3D目标检测要解决的问题很朴素在一个三维空间里找出感兴趣的物体在哪里、有多大、朝向哪个方向。听起来比2D检测只多了“深度”和“朝向”两个维度但实际难度完全不是一个量级。2D图像是规整的像素网格卷积核滑过去就行3D数据要么是稀疏无序的点云要么是多视角的融合信息处理起来处处是坑。它被广泛用在自动驾驶感知、机器人抓取、AR/VR场景理解、工业质检这些领域凡是需要机器“看懂”真实三维空间的场景基本都绕不开它。这份笔记适合谁看如果你已经了解2D目标检测的基本流程骨干网络、特征金字塔、锚框、非极大值抑制这些概念不陌生想系统性地进入3D检测领域那这份笔记能帮你少走很多弯路。如果你是从点云处理、SLAM或者机器人方向转过来的对深度学习检测框架不太熟也能从里面的数据表示和坐标系部分找到切入点。我会尽量把每个技术选择背后的“为什么”讲清楚而不是只告诉你“是什么”。2. 整体思路拆解3D检测到底难在哪2.1 数据表示的三种流派及其取舍逻辑搞3D检测第一件事就是决定用什么数据、怎么表示。目前主流的数据来源和表示方式大致分三类每一类背后都对应着一套完全不同的网络设计思路。点云表示是最直接的。激光雷达扫出来的就是一堆三维坐标点每个点可能还带反射强度。点云的好处是几何信息精确不受光照影响测距准。但它的毛病也很明显稀疏、无序、密度不均匀。近处密密麻麻几百个点远处可能就零星几个点而且点的排列顺序没有任何语义含义你把同一帧点云的点打乱顺序它描述的仍然是同一个场景。这就导致常规的卷积操作没法直接用因为卷积默认输入是有序网格。针对这个问题有的方案把点云体素化切成规则的小格子每个格子里做特征聚合然后套3D卷积有的方案直接用PointNet系列的方法逐点提取特征再做全局池化还有的投影到鸟瞰图视角把三维问题降成二维来处理。每种选择都有代价体素化会损失分辨率逐点方法计算量大鸟瞰图投影会丢失高度方向的细节。图像表示就是普通相机拍的RGB图。图像密集、纹理丰富、语义信息强2D检测的成熟工具链可以直接复用。但图像天生缺深度单目估计深度误差大双目受基线限制远处精度差。所以纯图像做3D检测通常需要额外的几何约束或者多视角信息来补深度。多模态融合是把点云和图像结合起来点云提供精确几何图像提供丰富纹理和语义。听起来很美但融合的时机和方式很讲究早期融合在输入层拼接实现简单但两种数据对齐精度要求高中期融合在特征层交互灵活但网络设计复杂晚期融合在检测结果层面合并鲁棒但信息利用不充分。实际工程里选哪种取决于你的传感器标定精度、算力预算和对实时性的要求。2.2 坐标系这件事比想象中重要得多我见过太多新手在这一步翻车。3D检测涉及至少四个坐标系世界坐标系、自车/机器人坐标系、传感器坐标系、像素坐标系。点云是在激光雷达坐标系下的图像是在相机坐标系下的检测框最终要输出到世界坐标系里做跟踪和规划。每两个坐标系之间都有一个刚体变换矩阵包含旋转和平移。这些矩阵通常来自标定文件但标定误差、时间同步误差、运动畸变都会让变换结果偏离真实值。提示在动手写任何检测代码之前先把你的坐标系变换链路画出来逐个验证每个变换矩阵的方向和单位。我当时的错误就是把旋转矩阵的转置当成了逆矩阵用因为旋转矩阵是正交矩阵转置确实等于逆但平移向量的变换顺序搞反了导致点云整体偏移了一个车身长度。实际项目中我习惯在数据加载阶段就把所有传感器数据统一变换到一个公共参考坐标系下后续网络只在这个坐标系里做检测。这样做的好处是网络设计不用关心传感器安装位置坏处是变换本身会引入插值误差和计算开销。另一种做法是网络在传感器坐标系下检测后处理阶段再变换到世界坐标系精度更高但每个传感器要单独训练。两种方案没有绝对优劣看你的传感器数量和标定稳定性。2.3 检测头设计的核心矛盾锚框 vs 无锚框2D检测里锚框anchor用得很普遍3D检测早期也沿用了这个思路但3D锚框的参数维度高得多中心点三个坐标、长宽高三个尺寸、绕垂直轴的偏航角有的方案还要预测俯仰角和滚转角。维度一高锚框的数量和计算量就爆炸。而且3D物体的尺寸分布和朝向分布在不同数据集上差异很大锚框的尺寸和角度需要针对数据集精心设计泛化性差。无锚框anchor-free方案在3D检测里越来越流行核心思路是直接预测每个前景点的中心偏移和尺寸省去了锚框匹配的麻烦。但无锚框也有自己的问题正负样本极度不平衡因为点云里绝大多数点都是背景中心点的定义在稀疏点云上不够稳定一个物体可能只有几十个点中心点偏移的监督信号很弱。实际选型时如果数据集物体尺寸变化大、朝向分布广无锚框更灵活如果物体类别少、尺寸相对固定精心设计的锚框反而收敛更快。3. 核心细节解析与实操要点3.1 点云体素化的参数怎么定体素化是点云3D检测里最常见的预处理步骤把连续的三维空间切成规则的小立方体每个立方体里聚合落入其中的点特征。体素大小这个参数直接决定了检测精度和计算量。体素越小分辨率越高小物体越容易被检出但体素数量呈立方级增长显存和计算时间扛不住。体素越大计算效率高但两个挨得近的物体会被合并到同一个体素里导致漏检。我的经验是先从数据集里统计物体尺寸分布。比如自动驾驶场景里小轿车长度大约4米出头行人宽度不到1米如果体素边长设成0.4米行人至少占两三个体素还能区分开设成0.8米行人就只剩一个体素了检测头很难判断朝向。室内机器人场景物体更小体素边长通常要降到0.05到0.1米。另一个约束是显存假设检测范围是长宽各100米、高10米体素边长0.2米那体素网格就是500×500×50一共一千两百多万个体素每个体素如果存几十维特征显存直接爆掉。所以实际工程里通常只对非空体素做稀疏卷积空体素不占计算资源这也是稀疏卷积在3D检测里成为标配的原因。体素内特征聚合的方式也有讲究。最简单的是取体素内所有点的坐标和反射强度的平均值实现快但丢失了点的分布信息。稍微复杂一点的是取体素内点的最大反射强度或者随机采样一个点。更精细的做法是用PointNet对体素内的点做一次小型特征提取保留更多几何细节但计算量会上升。我实测下来在物体尺寸中等、点云密度尚可的场景里平均值聚合加反射强度通道已经够用没必要上太复杂的体素内编码。3.2 稀疏卷积的输入输出对齐问题稀疏卷积是3D点云检测的骨干网络核心组件它只对非空体素做卷积运算大幅降低了计算量。但稀疏卷积有个容易踩的坑下采样过程中体素网格的坐标映射关系必须严格对齐。比如你做了一次步长为2的稀疏卷积下采样输出特征图的每个体素对应输入特征图上2×2×2的区域这个映射关系如果搞错后续上采样恢复分辨率时特征就会错位检测框位置整体偏移。注意不同深度学习框架的稀疏卷积实现坐标系的定义可能不一样。有的框架体素坐标从0开始有的从1开始有的框架下采样时取floor有的取round。换框架迁移模型时这一块一定要用简单的测试用例验证一遍拿一个已知坐标的体素输入看输出特征图对应位置是否正确。我在一个跨框架迁移的项目里就吃过这个亏。源框架的稀疏卷积下采样用的是向下取整目标框架用的是四舍五入大部分体素坐标是偶数时两者结果一样但遇到奇数坐标就偏了一个体素。检测框在近处偏移不明显远处偏移被放大导致远处物体的定位误差特别大。排查了两天才定位到这个问题教训就是稀疏卷积的坐标对齐必须写单元测试。3.3 检测头的回归目标设计3D检测头要回归的量比2D多不少中心点偏移、物体尺寸、朝向角。中心点偏移通常是在体素特征图上预测每个前景体素到物体真实中心的偏移量这个偏移量的范围要限制在体素尺寸以内否则说明这个体素不该被选为前景。尺寸回归一般预测物体长宽高相对于某个基准尺寸的对数比值这样数值范围更稳定训练更容易收敛。朝向角的处理最麻烦因为角度有周期性359度和1度实际只差2度但数值上差了358。直接回归角度值会让网络在周期边界处产生巨大误差。常见的处理方式有两种一种是把角度回归拆成正弦和余弦两个分量网络预测这两个连续值推理时用反正切恢复角度这样周期性天然被编码进去了另一种是把角度离散化成若干个区间每个区间做一个分类加残差回归。正弦余弦编码实现简单但存在一个模糊性正弦和余弦值确定后角度可能有两个解相差180度。对于对称物体比如某些货车车厢这无所谓但对于朝向敏感的物体比如行人就需要额外的特征来消除歧义。离散化分类的方法没有这个模糊性问题但区间数量和边界设计需要调参。我目前倾向于正弦余弦编码加一个额外的朝向分类分支来消歧实测在行人检测上比纯回归稳定不少。3.4 数据增强在3D场景里的特殊玩法2D检测的数据增强手段搬到3D里很多不能直接用。水平翻转在图像上很自然但在点云上翻转后物体的朝向角要相应变换而且激光雷达的扫描模式不是左右对称的翻转后的点云密度分布和真实采集的有差异。旋转增强也要小心绕垂直轴旋转是安全的因为激光雷达水平方向扫描是360度的旋转后密度分布基本不变但绕水平轴旋转就会让地面点云分布变得不真实网络可能学到错误的几何先验。真正在3D检测里效果好且安全的增强手段我总结下来主要是这几个全局绕垂直轴随机旋转、全局随机平移、点云随机丢弃模拟远处稀疏点、物体级复制粘贴从其他帧里抠出物体点云贴到当前帧的空白区域。物体级复制粘贴要特别注意碰撞检测贴进去的物体不能和已有物体重叠也不能悬空或者埋进地面否则网络会学到错误的尺寸和位置先验。我一般会检查粘贴后物体的最低点是否落在地面平面附近以及和最近物体的中心距离是否大于两者尺寸之和的一半。4. 实操过程与核心环节实现4.1 从原始点云到检测框的完整流水线拿一帧原始激光雷达点云作为输入到最终输出带类别和朝向的三维检测框中间要经过哪些步骤我按实际代码执行顺序捋一遍。第一步是点云预处理。原始点云通常包含地面点、噪声点和超出检测范围的远处点。地面点如果不剔除体素化后地面会占据大量体素浪费计算资源而且地面上的特征容易干扰物体检测。常用的地面剔除方法是随机采样一致性拟合地平面或者基于高度阈值的简单过滤。检测范围裁剪就是设定一个长方体的感兴趣区域区域外的点直接丢弃。这一步还会做点云降采样用体素网格滤波器把密度均匀化避免近处点太密远处太稀。第二步是体素化与特征编码。把裁剪后的点云按设定的体素尺寸划分网格每个非空体素内聚合点的坐标均值、反射强度均值有的方案还会加入体素内点的数量作为特征。体素坐标要转换成整数索引同时保留从体素索引到真实坐标的映射关系后面还原检测框位置时要用。第三步是骨干网络特征提取。稀疏卷积网络对体素特征做多次下采样和上采样提取多尺度特征。下采样扩大感受野让网络看到更大范围的上下文上采样恢复空间分辨率保证小物体不被漏掉。特征金字塔结构在这里同样适用不同尺度的特征图分别接检测头。第四步是检测头预测。每个尺度的特征图上对每个前景体素预测类别概率、中心点偏移、尺寸和朝向。训练时用真实标注框分配正负样本推理时对预测结果做非极大值抑制去除重叠的冗余框。第五步是后处理与坐标变换。把体素索引还原成真实三维坐标加上中心点偏移得到物体中心再根据预测的尺寸和朝向生成完整的三维框。最后把检测框从网络使用的坐标系变换回世界坐标系供下游模块使用。4.2 正负样本分配策略的代码级理解正负样本分配是检测训练里最影响收敛的环节之一。3D检测里常用的策略有两种基于锚框的IoU匹配和基于中心点的距离匹配。基于锚框的匹配沿用2D检测的思路计算每个锚框和真实框的三维IoUIoU超过高阈值的锚框标为正样本低于低阈值的标为负样本中间的忽略。三维IoU的计算比二维复杂因为要考虑朝向角。两个长方体如果朝向不同即使中心重合交叠体积也会变化。实际实现时通常把三维IoU拆解成鸟瞰图IoU和高度IoU的乘积来近似计算快但精度有损失。基于中心点的匹配更直接把真实框中心投影到特征图上离中心最近的体素或者一定半径内的体素标为正样本其余为负样本。这种策略省去了锚框设计但正样本数量少需要配合焦点损失或者正样本加权来缓解类别不平衡。我在小物体检测任务上对比过两种策略中心点匹配对小物体更友好因为小物体在特征图上的中心点更明确锚框匹配容易因为尺寸不匹配导致正样本太少。提示不管用哪种匹配策略都要检查每个真实框分配到的正样本数量。如果某个真实框一个正样本都没分到说明匹配参数设置有问题这个物体在训练中会被完全忽略推理时大概率漏检。我习惯在训练脚本里加一行统计打印每帧里零正样本的真实框数量正常应该接近零。4.3 损失函数各分量的权重怎么调3D检测的损失函数通常是多个分量的加权和分类损失、中心点回归损失、尺寸回归损失、朝向回归损失。每个分量的量纲和数值范围不一样权重设置直接影响训练效果。分类损失用焦点损失处理正负样本不平衡焦点损失的聚焦参数一般取2这个值在2D和3D检测里都比较通用。中心点回归损失用平滑L1或者高斯热力图损失数值范围小权重通常在1附近。尺寸回归用对数比值数值也在小范围内权重1左右。朝向损失如果用正弦余弦编码两个分量的损失直接相加权重1即可如果用角度回归因为角度范围是负π到π损失数值可能偏大权重需要适当降低。我调权重的顺序是先把分类损失和中心点损失调稳确保网络能大致找到物体位置然后加入尺寸损失观察检测框大小是否合理最后加入朝向损失。每次只调一个分量的权重观察验证集上对应指标的变化。如果加入某个分量后整体指标下降说明这个分量的权重太大或者回归目标设计有问题需要回退检查。4.4 推理阶段的非极大值抑制参数3D非极大值抑制和2D的逻辑一样按类别分组按置信度排序保留最高分的框抑制和它重叠度超过阈值的同类框。但3D的重叠度计算要考虑朝向而且不同类别的合理重叠程度不一样。行人密集场景里两个行人挨得很近是常态抑制阈值设太高会误删车辆之间通常不会重叠阈值可以设低一些。实际工程里我通常按类别设置不同的抑制阈值行人用较高的阈值比如0.5到0.6车辆用较低的阈值比如0.3到0.4。另外3D检测框在高度方向上的重叠也要考虑两个物体在鸟瞰图上重叠但高度错开比如天桥上的车和地面上的车不应该互相抑制。所以计算重叠度时最好用完整的三维IoU而不是只用鸟瞰图IoU。5. 常见问题与排查技巧实录5.1 训练损失不下降的排查路径训练3D检测模型时损失不下降原因可能出在数据、网络、损失函数或者优化器任何一个环节。我一般按以下顺序排查。先看数据加载是否正确。把预处理后的点云可视化出来确认物体还在、地面已剔除、检测范围没裁错。再检查标注框和点云是否对齐把标注框画到点云上看框是否正好包住物体。这一步能排除大部分数据问题。然后看正负样本分配。打印每个真实框分配到的正样本数量如果大量真实框零正样本说明匹配策略或参数有问题。再打印正样本的预测值和真实值对比看网络是否在往正确方向学。接着检查损失函数各分量的数值范围。如果某个分量的损失值异常大比如比其他分量大两个数量级说明该分量的回归目标设计有问题或者权重过大梯度被它主导了。最后检查学习率和优化器。3D检测网络通常比2D网络深稀疏卷积的梯度传播路径也特殊学习率太大容易发散太小收敛慢。我一般从1e-3开始试配合余弦退火调度。5.2 检测框位置整体偏移的定位方法检测框整体往一个方向偏移通常不是网络学错了而是坐标系变换或者体素索引还原环节出了系统性错误。排查方法是拿一个标注框手动走一遍从真实坐标到体素索引、再到网络预测、最后还原回真实坐标的完整链路看每一步的数值是否和预期一致。常见错误点包括体素索引的起点偏移有的实现从0开始有的从1开始、体素中心点和体素角点的混淆体素化时用角点坐标还原时忘了加半个体素尺寸、坐标系变换矩阵的方向搞反世界到传感器和传感器到世界用反了。我遇到过一次偏移最后发现是体素化时用了floor取整还原时直接乘体素尺寸忘了加0.5个体素偏移导致所有检测框往负方向偏了半个体素。5.3 远处物体漏检的改善手段远处物体点云稀疏特征弱是3D检测的固有难点。改善手段可以从数据、网络和后处理三个层面入手。数据层面可以在训练时对远处区域做点云增密或者用物体级复制粘贴把远处物体的点云样本增强。网络层面可以在特征金字塔的浅层高分辨率层增加检测头专门负责小物体和远处物体或者用注意力机制让网络更关注稀疏区域。后处理层面可以降低远处区域的置信度阈值因为远处物体本身得分就偏低用统一阈值容易漏掉。我实测下来最有效的是在浅层特征图上加一个专门的小物体检测头配合针对小物体的正样本分配策略降低正样本的中心距离阈值远处行人的召回率能提升十来个点。代价是浅层特征图分辨率高检测头计算量增加推理速度会降一些需要根据实际场景权衡。5.4 常见问题速查表现象可能原因排查手段解决方向损失不下降正样本太少、学习率不当、数据标注错位打印正样本数量、可视化标注框调整匹配策略、调学习率、修正标注检测框整体偏移体素索引还原错、坐标变换矩阵方向错手动走一遍坐标链路修正索引偏移、检查变换矩阵远处物体漏检点云稀疏、特征弱、阈值过高分区域统计召回率浅层加检测头、降低远处阈值朝向预测不稳定角度周期性未处理、对称物体歧义检查朝向损失设计正弦余弦编码加分类消歧推理速度慢体素太细、骨干网络太深统计各模块耗时增大体素、减少下采样层数密集场景误抑制NMS阈值过高、鸟瞰图IoU不准可视化抑制前后框按类别调阈值、用三维IoU6. 我在这份笔记里最想强调的几件事回头看这份笔记技术细节写了不少但真正让我少踩坑的往往是那些看起来不起眼的工程习惯。第一个习惯是任何坐标变换都写单元测试。拿一个已知坐标的点经过变换链路再变换回来看是否回到原点。这个测试花不了十分钟但能省掉两天的调试。第二个习惯是可视化贯穿始终。点云、标注框、预测框、特征图响应能画出来的都画出来看很多问题肉眼一看就明白靠打印数值反而绕远路。第三个习惯是先跑通再优化。3D检测的配置项多如牛毛一开始不要追求最优参数用最保守的配置把整条流水线跑通确认数据流和坐标链路没问题再逐个模块调优。这个方向后续还可以往多帧时序融合、自监督预训练、跨传感器域适应这些方向扩展每一个都是能单独写一篇笔记的体量。我目前正在整理时序融合部分的实践记录等攒够了再单独成文。