2. 3D空间表示方法:体素、点云、网格、隐式表示
各位同学,今天我们来聊聊3D空间表示。说实话,这个问题我当年刚入行时也困惑了很久。自动驾驶场景里,障碍物是三维的,但我们的传感器(摄像头、激光雷达)采集到的数据,要么是二维图像,要么是稀疏的点。怎么把这些数据变成计算机能理解的3D表示?
我个人的经验是:没有完美的表示方法,只有最适合场景的表示方法。下面我把四种主流方法掰开揉碎了讲清楚。
2.1 体素(Voxel)—— 3D世界的像素
体素就是3D版本的像素。你把空间切成一个个小立方体,每个立方体要么被占据(1),要么空闲(0)。
核心思想:用离散的网格去近似连续的3D空间。
我在做激光雷达障碍物检测时,最早用的就是体素。为什么呢?因为它的数据结构太直观了——就是一个3D数组,形状是 (W, H, D),每个元素是0或1。
# 一个简单的体素表示示例
import numpy as np
# 假设我们有一个 10x10x10 的体素空间
voxel_grid = np.zeros((10, 10, 10), dtype=np.int8)
# 在位置 (3, 4, 5) 处放置一个障碍物
voxel_grid[3, 4, 5] = 1
# 检查某个位置是否被占据
if voxel_grid[3, 4, 5] == 1:
print("该位置有障碍物")
优点:
- 数据结构简单,适合GPU并行计算
- 空间局部性好,邻居关系天然存在
- 适合做3D卷积(3D CNN)
缺点:
- 内存爆炸:分辨率每翻一倍,内存占用变成8倍
- 稀疏性:大部分体素是空的,浪费严重
- 精度受限:体素大小决定了最小可检测物体
我曾经踩过的坑:在项目中用0.1m分辨率的体素覆盖100m×100m×5m的区域,结果内存直接爆了。后来改用稀疏体素(只存储被占据的体素),才解决了问题。
2.2 点云(Point Cloud)—— 最接近传感器原始数据
点云就是一堆三维点的集合。每个点有 (x, y, z) 坐标,有时还带强度、颜色等信息。
你想想看,激光雷达扫一下,出来的就是点云。所以点云是自动驾驶里最直接的3D表示。
# 点云数据示例
point_cloud = [
[1.2, 3.4, 5.6], # 第一个点
[2.3, 4.5, 6.7], # 第二个点
[3.4, 5.6, 7.8], # 第三个点
]
优点:
- 数据紧凑:只存储有意义的点,不浪费空间
- 精度高:点的坐标可以是浮点数,理论上无限精度
- 与传感器天然对齐:不需要额外的转换
缺点:
- 无序性:点的顺序不影响表示,这让神经网络很难处理
- 稀疏性:远距离点很稀疏,近距离点很密集
- 缺乏拓扑信息:点与点之间没有连接关系
我的建议:如果你要做点云处理,一定要理解PointNet和PointNet++。它们解决了点云的无序性问题。我当年花了两周才真正搞懂PointNet的对称函数设计,嗯,值得的。
2.3 网格(Mesh)—— 表面表示之王
网格用三角形(或四边形)来逼近物体表面。每个三角形由三个顶点和一条法线组成。
说白了,网格就是3D建模里最常用的格式。你玩的游戏里的角色、汽车,都是网格。
# 一个简单的三角形网格
# 顶点列表
vertices = [
[0.0, 0.0, 0.0], # v0
[1.0, 0.0, 0.0], # v1
[0.0, 1.0, 0.0], # v2
]
# 三角形面片(用顶点索引表示)
faces = [
[0, 1, 2], # 一个三角形
]
优点:
- 表面表示精确:适合渲染和可视化
- 数据量可控:用少量三角形就能表示复杂形状
- 拓扑信息丰富:知道哪些点连在一起
缺点:
- 不适合深度学习:网格的拓扑结构不固定,很难输入到神经网络
- 生成困难:从点云生成网格需要复杂的重建算法
- 不擅长表示内部结构:网格只描述表面
注意:在自动驾驶中,网格主要用于高精地图和3D重建。实时障碍物检测很少直接用网格,因为计算量太大。我见过有人尝试用网格做检测,结果一帧处理了500毫秒...嗯,显然不现实。
2.4 隐式表示(Implicit Representation)—— 最近的大热门
隐式表示不直接存储几何信息,而是用一个函数来描述空间。比如:
- 有符号距离函数(SDF): f(x, y, z) = 到最近表面的距离(带符号)
- 占据场(Occupancy Field): f(x, y, z) = 该点被占据的概率
这就是Occupancy Network的核心思想!
# 隐式表示示例:一个球的SDF
def sphere_sdf(x, y, z, radius=1.0, center=(0, 0, 0)):
cx, cy, cz = center
distance = np.sqrt((x-cx)**2 + (y-cy)**2 + (z-cz)**2)
return distance - radius # 负值表示内部,正值表示外部
# 查询点 (0.5, 0.5, 0.5) 是否在球内
sdf_value = sphere_sdf(0.5, 0.5, 0.5)
if sdf_value < 0:
print("点在球内部")
else:
print("点在球外部")
优点:
- 无限分辨率:你可以以任意精度查询空间中的任意点
- 内存高效:只需要存储一个神经网络(或函数参数)
- 拓扑灵活:可以表示任意形状,包括有孔洞的物体
- 适合学习:用神经网络拟合隐式函数,端到端训练
缺点:
- 推理速度慢:每个点都要通过网络前向传播
- 难以可视化:需要先提取等值面(用Marching Cubes算法)
- 训练不稳定:隐式函数的优化比较困难
为什么Occupancy Network选择隐式表示?
因为隐式表示可以连续地、高精度地描述3D空间。你想想看,体素是离散的,点云是稀疏的,网格是表面的。而隐式表示可以告诉你空间中每一个点的占据情况——这正是障碍物检测需要的。
2.5 四种方法的对比总结
| 特性 | 体素 | 点云 | 网格 | 隐式表示 |
|---|---|---|---|---|
| 数据结构 | 3D数组 | 点集 | 顶点+面片 | 函数/网络 |
| 内存占用 | 高(随分辨率立方增长) | 低(只存有效点) | 中(取决于三角形数量) | 低(只存网络参数) |
| 精度 | 受分辨率限制 | 高(浮点坐标) | 高(可细化) | 理论上无限 |
| 深度学习友好度 | 高(3D CNN) | 中(需特殊网络) | 低(拓扑不固定) | 高(NeRF, Occupancy Network) |
| 推理速度 | 快 | 快 | 慢 | 慢(逐点查询) |
| 适用场景 | 语义分割、检测 | 检测、跟踪 | 重建、渲染 | 新视图合成、占用预测 |
我的个人经验:在实际项目中,我通常混合使用多种表示。比如用体素做粗检测(快速定位障碍物),然后用隐式表示做精细形状重建。这样既保证了速度,又保证了精度。你想想看,这不就是取长补短吗?
2.6 小结
好了,四种3D表示方法讲完了。我再说说我的理解:
- 体素:简单粗暴,适合做3D卷积,但内存是硬伤
- 点云:最接近传感器,数据紧凑,但无序性让网络设计头疼
- 网格:渲染和建模的王者,但深度学习不太待见它
- 隐式表示:新宠儿,灵活且强大,但速度是瓶颈
下一章我们会深入Occupancy Network,看看它如何用隐式表示来做障碍物检测。到时候你会看到,这些基础概念是怎么串起来的。
嗯,今天就到这里。有问题随时问我。