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检测的思路,计算每个锚框和真实框的三维IoU,IoU超过高阈值的锚框标为正样本,低于低阈值的标为负样本,中间的忽略。三维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不准 | 可视化抑制前后框 | 按类别调阈值、用三维IoU |
6. 我在这份笔记里最想强调的几件事
回头看这份笔记,技术细节写了不少,但真正让我少踩坑的往往是那些看起来不起眼的工程习惯。第一个习惯是任何坐标变换都写单元测试。拿一个已知坐标的点,经过变换链路再变换回来,看是否回到原点。这个测试花不了十分钟,但能省掉两天的调试。第二个习惯是可视化贯穿始终。点云、标注框、预测框、特征图响应,能画出来的都画出来看,很多问题肉眼一看就明白,靠打印数值反而绕远路。第三个习惯是先跑通再优化。3D检测的配置项多如牛毛,一开始不要追求最优参数,用最保守的配置把整条流水线跑通,确认数据流和坐标链路没问题,再逐个模块调优。
这个方向后续还可以往多帧时序融合、自监督预训练、跨传感器域适应这些方向扩展,每一个都是能单独写一篇笔记的体量。我目前正在整理时序融合部分的实践记录,等攒够了再单独成文。