4、目标检测:基于深度学习的3D检测(PointPillars/VoxelNet)、传统方法(滑动窗口+特征匹配)

聊到3D目标检测,很多新手第一反应就是“上深度学习”。没错,现在PointPillars、VoxelNet这些模型确实很火,效果也好。但我想说一句:传统方法真的过时了吗?我在实际项目中遇到过好几次,算力受限或者数据量不够的时候,反而是滑动窗口+特征匹配这种“老办法”救了场。

这一章,咱们就把两种路子都捋一遍。你想想看,只有理解了传统方法的局限,你才能真正明白深度学习为什么能赢;反过来,知道深度学习的“坑”,你才会在资源紧张时想起传统方法这个备胎。

4.1 传统方法:滑动窗口 + 特征匹配

说白了,传统3D检测的思路跟2D图像上的滑动窗口差不多。只不过咱们是在3D空间里滑,而且特征不是像素,是点云的手工特征。

4.1.1 滑动窗口策略

嗯,这里要注意:3D空间的滑动窗口,计算量是指数级增长的。假设你在BEV(鸟瞰图)上做,窗口大小是1m×1m,步长0.5m,一个100m×100m的区域,你就要滑4万个窗口。如果再考虑高度方向……我建议你直接放弃。

所以实际工程中,我们一般会加一些先验知识来缩小搜索范围:

  • 地面约束:先做地面分割,只在地面以上一定高度范围内搜索
  • 尺寸先验:比如检测车辆,窗口尺寸固定为4m×2m×1.5m
  • ROI过滤:只在高反射率区域或路沿附近滑动
我的经验: 我曾经在一个嵌入式项目里用滑动窗口做行人检测,窗口尺寸设成0.8m×0.8m×1.8m,步长0.4m。配合地面约束,总算把单帧处理时间压到了200ms以内。虽然慢,但至少能跑。

4.1.2 手工特征设计

窗口滑完了,每个窗口里都是一堆点云。怎么判断里面有没有目标?这就需要手工特征了。常用的有这么几种:

特征名称 描述 适用场景
点云数量 窗口内点的总数 快速过滤空窗口
高度分布 点云在Z轴上的分布直方图 区分车辆与行人
反射强度统计 强度值的均值、方差 区分金属物体与植被
形状描述子 如VFH(视点特征直方图) 精细分类

特征提取完,一般接一个SVM或者随机森林做分类。说白了,这就是一个“手工特征 + 浅层分类器”的pipeline。

4.1.3 传统方法的优缺点

  • 优点:不需要GPU,不需要大量标注数据,可解释性强
  • 缺点:召回率低,对遮挡和稀疏点云效果差,速度慢
避坑指南: 我曾经在雨雾天气测试传统方法,点云反射强度特征完全失效——因为雨滴的反射干扰太大了。后来我加了一个“点云密度归一化”的预处理,才勉强稳住。如果你要在恶劣天气下用传统方法,一定要先做数据清洗。

4.2 深度学习方法:PointPillars

好,聊完传统方法,咱们来看看现在最主流的PointPillars。为什么先讲它?因为它在速度和精度之间取得了很好的平衡,工业界用得最多。

4.2.1 核心思想:把点云“压”成柱子

PointPillars的思路其实很粗暴:把3D点云在BEV平面上划分成一个个网格(柱子),每个柱子里的点云用PointNet提取特征,然后拼成一个伪图像,最后用2D检测头做检测。

为什么会这样设计?因为直接处理3D点云太慢了。VoxelNet用3D卷积,计算量爆炸。PointPillars把问题降维到2D,速度直接起飞。

关键点: PointPillars的“柱子”不是立方体,而是无限高的柱体。它只关心XY平面上的划分,Z轴方向不做切分。这样每个柱子里的点云数量可控,特征提取效率高。

4.2.2 网络结构

我习惯把PointPillars分成三个模块:

  1. Pillar Feature Net:每个柱子内的点云,用PointNet提取特征,输出一个C维的特征向量
  2. Backbone(2D CNN):把特征向量拼成伪图像(H×W×C),然后用2D卷积做特征提取
  3. Detection Head(SSD):用SSD的检测头输出3D边界框

代码实现上,Pillar Feature Net的核心就这几行:

# 伪代码:Pillar Feature Net
def pillar_feature_net(points, pillar_indices):
    # points: [N, 4] (x, y, z, intensity)
    # pillar_indices: [N] 每个点属于哪个柱子
    
    # 1. 计算每个点相对于柱子中心的偏移
    center = get_pillar_center(pillar_indices)
    offset = points[:, :2] - center
    
    # 2. 拼接特征:原始坐标 + 偏移 + 强度
    features = torch.cat([points, offset], dim=1)  # [N, 6]
    
    # 3. 对每个柱子内的点做PointNet
    pillar_features = scatter_max(features, pillar_indices)  # [P, C]
    
    return pillar_features
我的经验: 实际部署时,我建议把每个柱子内的最大点数限制在100个以内。超过的直接随机采样,不够的补零。这样能保证batch size稳定,推理速度更快。

4.2.3 训练技巧

训练PointPillars时,有几个坑我踩过:

  • 数据增强:一定要做全局旋转和翻转。我试过不做增强,验证集AP直接掉了5个点
  • GT采样:把真实目标复制粘贴到场景中,增加正样本数量。这个技巧在KITTI上能提2-3个点
  • 学习率:建议用余弦退火,初始学习率0.001,batch size 32

4.3 深度学习方法:VoxelNet

VoxelNet是PointPillars的前身,思路更直接:把点云划分成3D体素(voxel),然后用3D卷积处理。精度更高,但速度慢很多。

4.3.1 Voxel Feature Encoding(VFE)

每个体素内的点云,用VFE层提取特征。VFE的核心就是PointNet的变体:对每个点做MLP,然后做最大池化,得到体素级别的特征。

# 伪代码:VFE层
def VFE(voxel_points):
    # voxel_points: [M, 4] 一个体素内的M个点
    # 1. 逐点MLP
    point_features = MLP(voxel_points)  # [M, C]
    # 2. 最大池化得到体素特征
    voxel_feature = torch.max(point_features, dim=0)  # [C]
    # 3. 拼接逐点特征和体素特征
    output = torch.cat([point_features, voxel_feature.expand(M, -1)], dim=1)
    return output

4.3.2 3D卷积与RPN

VFE提取完特征后,得到一个4D张量(C×D×H×W)。然后接3D卷积层做下采样,最后用RPN(区域提议网络)输出检测结果。

这里有个问题:3D卷积的计算量是O(C×D×H×W×K³),K是卷积核大小。你想想看,如果体素分辨率是0.1m,一个100m×100m×4m的区域,D=40, H=1000, W=1000,这计算量……

避坑指南: 我曾经尝试在Jetson TX2上部署VoxelNet,结果单帧推理时间超过5秒。后来我不得不把体素分辨率降到0.4m,精度掉了不少,但总算能跑到1秒以内。如果你算力有限,建议直接上PointPillars。

4.4 两种方法的对比与选择

说了这么多,到底该用哪种?我个人的建议是这样的:

场景 推荐方法 理由
有GPU,追求精度 VoxelNet 3D卷积能捕捉更精细的几何结构
有GPU,追求实时性 PointPillars 速度比VoxelNet快10倍以上
无GPU,算力受限 传统方法 不需要深度学习框架,CPU就能跑
数据量少(<1000帧) 传统方法 深度学习容易过拟合

嗯,这里还要补充一点:实际工程中,很多人会做“混合方案”。比如用传统方法做快速初筛,再用深度学习做精细检测。我在一个园区无人车项目里就这么干过,效果还不错。

核心总结: 3D检测没有银弹。PointPillars是工业界的主流选择,VoxelNet适合对精度有极致要求的场景,传统方法则是资源受限时的保底方案。理解每种方法的原理和局限,比盲目追求“最新模型”更重要。
3D目标检测方法对比 传统方法 滑动窗口 手工特征 SVM/随机森林 后处理(NMS) 深度学习方法 PointPillars VoxelNet Pillar Feature Net VFE + 3D Conv 2D Backbone + SSD 3D RPN 核心对比 传统方法 深度学习方法 ✅ 无需GPU ✅ 高精度 ✅ 可解释性强 ✅ 鲁棒性强 ❌ 召回率低 ❌ 需要大量数据

最后说一句:不管你选哪种方法,数据预处理和标定永远是第一步。我见过太多人一上来就调模型,结果发现点云坐标系都没对齐——那真是白忙活。


公众号:蓝海资料掘金营,微信deep3321