4. 目标检测与特征提取:基于PointPillars的3D目标检测、目标状态向量定义
好,咱们进入正题。这一章聊的是目标检测和特征提取,说白了就是怎么从一堆点云里把车、行人、骑行者这些目标给“揪”出来,并且给它们贴上标签。
我个人习惯把这一步叫做“从无序到有序”。点云本身是杂乱无章的,但我们要把它变成结构化的目标信息。这里最经典的方法之一,就是PointPillars。
4.1 为什么是PointPillars?
你可能会问,点云检测方法那么多,VoxelNet、SECOND、PointRCNN……为什么我偏偏要讲PointPillars?
原因很简单:快,而且准。
我在项目中遇到过这样的场景:车上的计算平台算力有限,但要求检测延迟必须控制在50毫秒以内。VoxelNet虽然精度不错,但3D体素化后计算量太大,跑起来有点吃力。PointPillars呢?它把3D空间压缩成了2D的“柱子”(Pillar),然后用2D卷积来处理。你想想看,这计算量一下子就降下来了。
嗯,这里要注意:PointPillars并不是简单地丢弃了高度信息。它把每个柱子内的点云特征(比如高度、反射强度)都保留了下来,作为额外的通道输入到网络中。所以它既保留了3D信息,又享受了2D卷积的高效。
核心思想:将点云划分为垂直的柱子(Pillars),在每个柱子内提取点云特征,然后投影到2D平面,用2D卷积神经网络进行检测。
4.2 PointPillars 的工作流程
咱们一步步拆解。整个过程可以分为三个大阶段:
- 点云到Pillar的转换
- 特征提取与伪图像生成
- 2D卷积检测头
下面这张图可以帮你快速建立整体认知:
4.3 具体实现细节
4.3.1 点云到Pillar的转换
首先,我们把点云所在的3D空间,在x-y平面上划分成一个个均匀的网格。每个网格就是一个Pillar。比如,网格大小可以设为0.16m x 0.16m。
然后,对于每个Pillar,我们只保留它里面的点。如果一个Pillar里的点太多(比如超过100个),就随机采样一部分。如果太少,就用0填充。
这里有个坑:每个Pillar里的点数量可能差异很大。我曾经遇到过这种情况:路面上空旷的地方,一个Pillar里可能只有两三个点;但到了密集的灌木丛或者车旁边,一个Pillar里可能挤了几百个点。如果不做采样或填充,网络训练会非常不稳定。
我的经验:每个Pillar最多保留100个点,少于5个点的Pillar直接丢弃。这样既能保证特征丰富度,又能控制计算量。
4.3.2 特征提取
对于每个Pillar里的点,我们不仅要保留它原始的(x, y, z, intensity)信息,还要加上一些相对位置信息。比如:
- 该点相对于Pillar中心的偏移量 (x_c, y_c, z_c)
- 该点相对于整个点云中心的偏移量 (x_p, y_p, z_p)
这样一来,每个点就变成了一个9维的向量。然后,我们用一个小型的PointNet网络来提取每个Pillar的特征。PointNet的输出是一个固定长度的特征向量,代表了这个Pillar的整体信息。
说白了,这一步就是把每个Pillar里的点云“压缩”成一个特征向量。
4.3.3 伪图像生成
所有Pillar的特征向量组合起来,就形成了一个伪图像。这个伪图像的高度和宽度就是x-y方向上的网格数量,通道数就是特征向量的维度。
举个例子:假设x方向有400个网格,y方向有400个网格,特征向量维度是64。那么伪图像的尺寸就是 (400, 400, 64)。
你看,这不就跟一张普通的RGB图像很像了吗?只不过通道数从3变成了64。接下来,我们就可以用2D卷积神经网络来处理它了。
4.4 目标状态向量定义
检测出目标之后,我们需要用一个向量来描述它的状态。这个向量就是目标状态向量。它包含了我们关心的所有信息。
我一般把状态向量定义为7维:
| 维度 | 符号 | 含义 | 说明 |
|---|---|---|---|
| 1 | x | 目标中心x坐标 | 通常在世界坐标系下 |
| 2 | y | 目标中心y坐标 | 同上 |
| 3 | z | 目标中心z坐标 | 地面高度 |
| 4 | l | 目标长度 | 车辆:4-5m;行人:0.5-1m |
| 5 | w | 目标宽度 | 车辆:1.8-2m;行人:0.5-0.8m |
| 6 | h | 目标高度 | 车辆:1.5-2m;行人:1.5-1.8m |
| 7 | θ | 目标朝向角 | 范围:[-π, π] |
如果要做跟踪,我们还需要加上速度信息。这时候状态向量就变成了9维:
| 维度 | 符号 | 含义 | 说明 |
|---|---|---|---|
| 8 | vx | x方向速度 | 单位:m/s |
| 9 | vy | y方向速度 | 单位:m/s |
注意:朝向角θ的定义非常关键。我见过不少新手把朝向角和偏航角搞混。在自动驾驶中,我们通常用朝向角来表示车头指向的方向,范围是[-π, π]。而且,朝向角是周期性的,π和-π其实是同一个方向。这在做回归损失计算时是个麻烦事,需要特殊处理。
4.5 损失函数与训练
PointPillars的损失函数和SSD类似,包含分类损失和回归损失两部分。
分类损失用Focal Loss,用来解决正负样本不平衡的问题。回归损失用Smooth L1 Loss,用来回归位置、尺寸和朝向。
这里有个细节:朝向角的回归。我们不能直接回归θ的绝对值,因为θ有周期性。常用的做法是回归sin(θ)和cos(θ),或者回归相对于某个anchor的偏移量。
我个人习惯用后者。具体来说,对于每个anchor box,我们回归它相对于ground truth的偏移量:
Δx = (x_gt - x_a) / d_a
Δy = (y_gt - y_a) / d_a
Δz = (z_gt - z_a) / h_a
Δl = log(l_gt / l_a)
Δw = log(w_gt / w_a)
Δh = log(h_gt / h_a)
Δθ = sin(θ_gt - θ_a)
其中,d_a = sqrt(l_a^2 + w_a^2)
这样处理之后,回归目标就变得平滑了,网络也更容易收敛。
4.6 避坑指南
最后,分享几个我在实际项目中踩过的坑:
- 点云范围的选择:不要贪大。我曾经把检测范围设到100米,结果远处的点太稀疏,检测效果很差,还拖慢了速度。后来我改成前向70米,侧向40米,效果好了很多。
- Pillar大小的选择:太小了计算量大,太大了检测精度下降。0.16m是一个比较常用的值,但如果你主要检测行人,可以适当缩小到0.1m。
- 数据增强:点云的数据增强很重要。我常用的方法包括:全局旋转、全局缩放、随机丢弃点、以及GT采样(把其他场景中的目标复制到当前场景中)。
总结一下:PointPillars的核心思想是把3D点云转换成2D伪图像,然后用2D卷积来做检测。目标状态向量定义了我们要跟踪的对象的属性,包括位置、尺寸、朝向和速度。这两者结合起来,就构成了目标跟踪的基础。