点云算法整理与常用计算模块:从数据到模型,再到工程与量化
在接触智能驾驶算法之前,对"感知"的理解往往更多来自图像世界:图像是规则的二维网格,像素密集、连续,卷积网络天然适配。
而当真正进入 3D 感知、多传感器融合、BEV 表达以及工程落地之后,点云成了一个绕不开的核心对象,也成了很多复杂问题的源头。
本文梳理了从「会用点云模型」到「逐步理解点云数据、算法结构与工程落地」这一阶段的点云算法认知。希望读完后,能形成一张清晰可复用的点云算法认知地图:点云是什么、为什么难、主流路线怎么选、模块怎么串、工程和量化哪里最容易翻车。
1. 点云是什么?先把"数据本身"讲清楚
1.1 点云的本质:三维世界的离散采样
点云(Point Cloud)可以看成:
一组在 3D 空间中的离散点,用来刻画真实世界的几何结构。
在自动驾驶里,点云主要来自 LiDAR。每个点至少包含:
- x, y, z:三维坐标(在某个坐标系下)
常见还会带:
- intensity(反射强度)
- timestamp(时间戳/相位)
- ring(激光线号)
工程里最常见的张量形状是:
[N, C] (例如 [N, 4]:x,y,z,intensity)
N 是点的数量,且 N 每帧都可能变化。
1.2 点云 vs 图像:不是"稀疏图像",而是另一种数据范式
这是理解点云算法的第一道坎。
| 维度 | 图像 | 点云 |
|---|---|---|
| 数据结构 | 规则网格 (H×W×C) | 无序点集 (N×C) |
| 密度 | 高且均匀 | 稀疏且不均 |
| 连续性 | 强 | 弱 |
| 空白区域 | 背景也有意义 | 大量"什么都没有" |
| 关键难点 | 表达纹理/语义 | 表达几何/结构 |
一句话总结:图像是规则空间上的信号,点云是不规则空间中的几何采样。因此 CNN 不能直接"吃"原始点云。
1.3 点云在自动驾驶里为什么重要?
它天然提供:
- 真实深度(不是估计出来的)
- 高精度几何结构(距离、尺寸、形状)
- 对光照不敏感
很多系统的分工逐渐变成:
- 图像:语义/纹理/类别
- 点云:几何/距离/空间结构
- BEV:统一的空间中间表示(融合/检测都方便)
2. 点云算法到底在做什么?——核心矛盾与第一步"结构化"
点云算法的根本矛盾只有一个:
不规则、稀疏、动态点数的点集,如何变成规则、可计算、可部署的结构?
所以点云网络最关键的不是"backbone 多深",而是最开始那几步:
- 点怎么分桶(voxel/pillar)
- 同一个桶里点怎么聚合
- 聚合后的特征怎么回到 BEV 网格
可以把点云 pipeline 粗暴理解成:
点云(无序) → 结构化(有序) → BEV(规则网格) → 2D 检测头
3. 三条主流路线:Point / Voxel / Pillar(先建立全景图)
3.1 Point-based:直接处理点(PointNet 系)
- 思路:对每个点做 MLP + 聚合
- 优点:概念直观,保留几何细节
- 缺点:点数大时很吃算力,工程落地难
- 在自动驾驶主流 3D 检测里用得不算多(更多出现在局部模块或研究里)
3.2 Voxel-based:体素化(VoxelNet / SparseConv)
思路:把 XYZ 切成三维网格(体素 voxel),体素内聚合点,形成稀疏 3D 特征,再做 3D / Sparse 计算。
典型信息流:
点云 → XYZ voxelize → voxel feature → 3D Conv / SparseConv → 沿 Z collapse → BEV
- 优点:高度信息被显式建模,表达能力强
- 代价:3D / 稀疏算子复杂,动态稀疏结构对部署要求高
3.3 Pillar-based:PointPillars(工程常用)
思路:只在 XY 平面划网格,Z 不切层,一个柱子覆盖整根高度。柱内点做 PointNet/MLP 聚合,再直接 scatter 到 BEV。
典型信息流:
点云 → XY pillarize → pillar feature → scatter → BEV → 2D backbone
- 优点:避免 3D 卷积,2D 友好,工程效率高
- 代价:高度结构没有显式网格,只能靠"隐式编码进通道"
4. 认知分水岭:scatter → BEV vs collapse → BEV(必须讲透)
4.1 scatter → BEV(PointPillars)
- 不建立 Z 维网格
- 同一 XY 位置的所有高度信息,最终被压进一个 pillar embedding
- 高度关系是隐式的(点的 z/统计量进入通道)
- 得到的是原生 2D 的 BEV
4.2 collapse → BEV(VoxelNet / SparseConv)
- Z 维被切成层(体素高度层)
- 先在 3D 空间里学习"上下(高度)结构"
- 再沿 Z 折叠成 BEV(sum/max/learned collapse)
- 得到的是"压扁后的 3D"
4.3 一句话总结
scatter 是"先放弃高度,再用网络弥补";collapse 是"先理解高度,再做信息压缩"。
为什么这影响精度?因为复杂场景里同一 XY 位置可能出现多层结构(桥下/车顶/地面等)。"高度结构"建模得越早、越显式,模型越容易学对。
5. CenterPoint 到底是什么?它在体系里扮演什么角色?
很多人把 CenterPoint 当作"点云网络",其实更准确地说:
CenterPoint 是 BEV 上的检测范式(head 思想),不是点云编码方式。
它做的事是:
- 在 BEV 上预测中心点 heatmap
- 回归尺寸、方向、速度等属性
它前面接什么都可以:
- PointPillars 输出的 BEV
- VoxelNet/SparseConv collapse 后的 BEV
所以应该这样理解组织关系:
(点云编码方式:Pillars / Voxels / Sparse) → BEV feature → (检测范式:CenterPoint head)
6. 常用计算模块地图:从点到 BEV 的关键桥梁
下面这几类模块,在阅读任何点云工程代码时都会反复遇到。
6.1 Voxelization / Pillarization(分桶)
关键超参:
- 点云范围(x_min..x_max 等)
- voxel/pillar size(网格分辨率)
- 每格最大点数(max_points_per_voxel)
- 最大 voxel 数(max_voxels)
它们直接决定:精度、速度、内存三者取舍。
6.2 Pillar/Voxel Feature Encoder(桶内聚合)
常见做法:
- 对点做相对坐标(相对 pillar 中心/均值)
- 拼接强度、距离等
- MLP + max/mean 聚合
很多实现的本质就是:一个轻量版 PointNet。
6.3 Scatter / BEV Pooling(稀疏 → 稠密)
这一步决定了能否"像 2D 一样做检测"。
- 输入:[M, C] 的稀疏桶特征 + 坐标
- 输出:[B, C, H, W] 的 BEV 伪图像
这也是工程和量化最容易翻车的模块。
7. 重头戏:PointPillarsScatter 两种写法(原始实现 vs 优化实现)
在 PointPillars 中,Scatter 是一个工程分水岭模块。算法上它只做一件事:把稀疏 pillar 特征铺回规则 BEV 画布。
7.1 输入输出先讲明白
- voxel_features: [M, C](全 batch 的 pillar 拼接)
- coors: [M, 4],常见 [batch_id, z, y, x]
- 输出:canvas: [B, C, H, W]
7.2 原始直观实现:手写 scatter(容易理解,但工程不友好)
import torch
import torch.nn as nn
from typing import List
class NaivePointPillarsScatter(nn.Module):
def __init__(self, num_input_features: int, output_shape: List[int]):
super().__init__()
self.nchannels = num_input_features
self.ny = int(output_shape[0]) # H
self.nx = int(output_shape[1]) # W
def forward(self, voxel_features: torch.Tensor, coors: torch.Tensor, batch_size: int):
H, W = self.ny, self.nx
C = self.nchannels
# flat_index = b*(H*W) + y*W + x
flat_indices = coors[:, 0] * (H * W) + coors[:, 2] * W + coors[:, 3]
flat_indices = flat_indices.to(torch.long)
# flat canvas: [B*H*W, C]
canvas = torch.zeros(
batch_size * H * W,
C,
dtype=voxel_features.dtype,
device=voxel_features.device,
)
canvas[flat_indices] = voxel_features
# -> [B, C, H, W]
canvas = canvas.view(batch_size, H, W, C).permute(0, 3, 1, 2).contiguous()
return canvas
7.3 为什么原始实现工程代价很高?
- BHW*C 巨大,但绝大多数格子是空的(全 0)
- canvas[indices] = ... 是典型不规则写入(高级索引/scatter),对很多平台不友好
- 会引入大中间张量、动态图不稳定
- 量化时 observer 统计容易被稀疏零分布拖偏
原始写法适合帮助理解,最终落地通常要"算子化"。
7.4 工程优化实现:算子化 point_pillars_scatter(推荐)
项目推荐使用地平线内部实现(加速了)作为"工程最佳实践",使用示例如下:
import torch
import torch.nn as nn
from horizon_plugin_pytorch.nn.functional import point_pillars_scatter
class Net(nn.Module):
def __init__(self, num_input_features, output_shape) -> None:
super().__init__()
self.nchannels = num_input_features
self.nx = output_shape[1]
self.ny = output_shape[0]
def forward(self, voxel_features, coords, batch_size):
out_shape = (batch_size, self.nchannels, self.ny, self.nx)
batch_canvas = point_pillars_scatter(
voxel_features, coords, out_shape)
return batch_canvas
优势不是"代码更短",而是:
- 避免 Python 层不规则 scatter
- 形状更稳定、更利于图编译
- 更适配芯片与量化策略(算子边界清晰)
8. 芯片落地:点云模型真正难在哪?
从工程落地角度,难点通常不在卷积,而在:
- 索引密集:scatter / gather / pooling
- 动态稀疏结构:每帧点数、桶数变化
- 大空间画布:H×W 很大但有效点很少
- 稀疏算子支持:SparseConv 往往受限于平台能力
这也是为什么很多边缘平台更偏好:
PointPillars + 2D backbone + CenterPoint head,而不是全套 SparseConv。
9. 量化:点云模型为什么更容易"炸"?
点云模型量化的难点通常来自:
9.1 分布极端
- 空桶很多(大量 0)
- 少数桶点数很高
- 少数位置特征幅值很大
这会导致 scale 被极端值拉爆,有效精度下降。
9.2 索引 + 数值混合
Scatter/Pooling 里常见"索引控制数据流",量化边界放错容易出怪问题。
9.3 信息被压得太早
如果高度信息过早被压缩(或被量化误差污染),后续 backbone 再强也补不回来。
工程建议:
- 先验证浮点中间量分布:pillar 数、稀疏率、Z 分布
- 再决定哪些模块保持高精度 / 哪些模块量化
- 对 scatter/pooling 这类模块谨慎设置量化边界
- 尽量使用平台已优化算子替代手写索引逻辑
10. 结语:点云算法不是"更复杂的 CNN"
点云算法的复杂,不在网络结构,而在"把不规则三维数据变成规则表示"的整条链路。理解 scatter vs collapse,理解桥梁模块(voxel/pillar/scatter/pooling)在何处丢信息、何处放大误差,才算真正开始"看懂点云"。
浙公网安备 33010602011771号