第2章 点云数据格式:PCD、BIN、PLY、LAS 与数据结构
做激光雷达感知,第一关就是跟点云数据打交道。你拿到的文件可能是 .pcd、.bin、.ply 或者 .las,每种格式背后都有它的设计逻辑和适用场景。我个人习惯先把这些格式摸透,不然写代码时总感觉在盲人摸象。
这一章,咱们就掰开揉碎聊聊这些格式长什么样,以及点云内部的数据结构——说白了就是每个点都带了哪些信息。搞懂了这些,后面做预处理、检测、跟踪才心里有底。
2.1 四种主流点云格式
先说说最常见的四种格式。你想想看,不同厂商的激光雷达、不同数据集,用的格式可能完全不一样。我最早做KITTI数据集时,满眼都是 .bin 文件,后来转到自动驾驶项目,又跟 .pcd 杠上了。
| 格式 | 扩展名 | 特点 | 常见场景 |
|---|---|---|---|
| PCD | .pcd | Point Cloud Library 原生格式,支持 ASCII 和二进制 | PCL 生态、科研原型 |
| BIN | .bin | 纯二进制,无头信息,紧凑高效 | KITTI、Waymo 等数据集 |
| PLY | .ply | Polygon File Format,支持顶点和面片 | 三维重建、Mesh 处理 |
| LAS | .las | ASPRS 标准,含分类、回波等丰富属性 | 测绘、地理信息系统 |
.bin 和 .pcd 是主力。BIN 格式读取极快,适合大规模训练;PCD 格式自带头信息,调试时一目了然。
2.2 PCD 格式详解
PCD 是 PCL 库的亲儿子。它的头信息里会写明点云有多少个点、每个字段是什么类型。我习惯用 ASCII 版本做可视化调试,但训练时一定转成二进制,速度差好几倍。
# 一个典型的 PCD 文件头
VERSION 0.7
FIELDS x y z intensity
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 115384
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 115384
DATA ascii
0.1 0.2 0.3 45
0.4 0.5 0.6 120
...
注意 HEIGHT 为 1 表示无序点云,如果大于 1 就是有序点云(比如来自 Velodyne 的扫描线)。
VIEWPOINT 字段,把点云坐标搞偏了好几米。如果你用 PCL 的 pcl::io::loadPCDFile,默认会应用视角变换,记得确认传感器坐标系。
2.3 BIN 格式——又快又糙
BIN 格式说白了就是一堆 float32 的二进制流,没有头,没有注释。每个点通常占 4 个 float:x, y, z, intensity。KITTI 数据集就是这么干的。
// 读取 KITTI bin 文件的伪代码
FILE *fp = fopen("000000.bin", "rb");
float data[4];
while (fread(data, sizeof(float), 4, fp) == 4) {
float x = data[0];
float y = data[1];
float z = data[2];
float intensity = data[3];
// 处理每个点
}
读取速度极快,但缺点也很明显——你不知道里面到底存了几个字段。我踩过坑:有一次数据集偷偷在 intensity 后面加了 ring 和 timestamp,我按老代码读,结果全乱了。
2.4 PLY 格式——不止于点
PLY 格式比 PCD 更灵活,它除了存点坐标,还能存法向量、颜色、甚至三角面片。做三维重建的同学应该很熟悉。不过做目标检测时,我很少用 PLY,因为它的面片信息对检测来说太冗余了。
ply
format ascii 1.0
element vertex 50000
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 100000
property list uchar int vertex_indices
end_header
0.1 0.2 0.3 255 0 0
...
看到 element face 了吗?这就是它跟纯点云格式最大的区别。如果你只需要点坐标,用 PCD 或 BIN 更轻量。
2.5 LAS 格式——测绘老大哥
LAS 是测绘领域的标准格式,由 ASPRS 维护。它除了 xyz,还包含分类(地面、植被、建筑等)、回波次数、扫描角度等。我参与过一个路侧感知项目,客户给的就是 LAS 格式,里面连点云分类都做好了。
| 属性 | 说明 | 典型值 |
|---|---|---|
| Classification | 点云分类 | 2=地面, 6=建筑, 1=未分类 |
| Return Number | 回波序号 | 1, 2, 3... |
| Intensity | 回波强度 | 0~65535 |
| GPS Time | GPS 时间戳 | 浮点数 |
不过 LAS 格式解析起来稍微麻烦点,需要依赖 liblas 或 laspy。我个人建议:如果不是测绘背景,优先转成 PCD 或 BIN 再处理。
2.6 点云数据结构:XYZI 与 XYZRGB
聊完文件格式,咱们看看点云内部的数据结构。说白了,每个点就是一组向量,最常见的两种是 XYZI 和 XYZRGB。
- XYZI:x, y, z 坐标 + intensity(反射强度)。激光雷达的标配,intensity 对区分目标材质很有用。
- XYZRGB:x, y, z 坐标 + 颜色(红绿蓝)。通常来自 RGB 相机投影或彩色激光雷达。
有些数据集还会扩展字段,比如 ring(激光线束编号)、timestamp(相对时间戳)。在 PCL 中,你可以自定义 PointXYZIR 结构体来容纳这些信息。
// PCL 自定义点类型示例
struct PointXYZIR {
float x, y, z; // 坐标
float intensity; // 强度
uint16_t ring; // 线束编号
double timestamp; // 时间戳
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
} EIGEN_ALIGN16;
POINT_CLOUD_REGISTER_POINT_STRUCT(PointXYZIR,
(float, x, x)
(float, y, y)
(float, z, z)
(float, intensity, intensity)
(uint16_t, ring, ring)
(double, timestamp, timestamp)
)
嗯,这里要注意:自定义点类型一定要注册到 PCL 的宏里,不然序列化会出问题。我曾经因为这个 bug 排查了一下午。
2.7 格式转换与实战建议
实际项目中,你经常需要在不同格式间切换。我一般用 PCL 的 pcl::io 模块做转换,或者写 Python 脚本用 numpy + struct 处理 BIN 文件。
⚡ 效率建议: 训练数据管线里,尽量统一成 BIN 格式。它没有头信息解析开销,内存映射(mmap)也方便。我自己的项目里,所有原始数据落地都是 .bin,只有可视化时才转成 .pcd。
最后分享一个我常用的转换片段,把 BIN 转成 PCD(ASCII 格式方便调试):
import numpy as np
import struct
def bin_to_pcd(bin_path, pcd_path):
points = np.fromfile(bin_path, dtype=np.float32).reshape(-1, 4)
with open(pcd_path, 'w') as f:
f.write("# .PCD v0.7 - Point Cloud Data\n")
f.write(f"FIELDS x y z intensity\nSIZE 4 4 4 4\nTYPE F F F F\nCOUNT 1 1 1 1\n")
f.write(f"WIDTH {points.shape[0]}\nHEIGHT 1\nPOINTS {points.shape[0]}\nDATA ascii\n")
for p in points:
f.write(f"{p[0]:.6f} {p[1]:.6f} {p[2]:.6f} {p[3]:.0f}\n")
你看,核心逻辑就这么几行。搞懂了数据格式,后面的预处理、特征提取才能玩得转。