2. 点云数据基础:PCD/PLY/XYZ格式解析、点云数据结构、常用点云库(PCL/Open3D)介绍
好,咱们正式开始聊点云数据。说实话,很多新手一上来就急着调滤波参数,结果连数据长什么样都没搞清楚。我当年也犯过这个错——拿到一个PCD文件直接读,然后发现点云全是NaN,折腾了半天才知道是格式没对齐。
这一节,咱们就把地基打牢。你想想看,点云说白了就是一堆三维点的集合,每个点带着坐标(x,y,z),有时候还带着颜色、强度、法向量这些附加属性。但怎么把这些点存到文件里?不同格式有什么区别?代码里怎么操作?这些就是今天要聊的。
2.1 点云文件格式:PCD、PLY、XYZ
市面上点云格式少说几十种,但最常用的就三个:PCD、PLY、XYZ。我个人的习惯是:做算法原型用XYZ,做工程落地用PCD,做可视化展示用PLY。为什么?咱们一个一个说。
2.1.1 PCD格式(Point Cloud Data)
PCD是PCL库的亲儿子格式。它的特点是:头部信息极其丰富。你看一个PCD文件的开头,就能知道这个点云有多少点、每个点有哪些字段、数据是二进制还是ASCII、点云是稠密还是稀疏。
一个典型的PCD文件长这样:
# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z rgb
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 640
HEIGHT 480
VIEWPOINT 0 0 0 1 0 0 0
POINTS 307200
DATA ascii
0.123 0.456 0.789 4294967295
0.234 0.567 0.890 4278190080
...
关键字段解读:
- FIELDS:每个点的字段名,常见的有 x y z rgb normal curvature
- SIZE:每个字段占用的字节数,float一般是4字节
- TYPE:数据类型,F=float, I=signed int, U=unsigned int
- WIDTH/HEIGHT:如果是组织化的点云(比如深度图转的),HEIGHT>1;如果是无序点云,HEIGHT=1,WIDTH=点数
- POINTS:总点数,等于WIDTH * HEIGHT
我的经验: 我在做激光雷达SLAM时,经常遇到PCD文件打不开的情况。后来发现是WIDTH和HEIGHT写反了。记住:WIDTH是列数,HEIGHT是行数,跟图像矩阵是反的。
2.1.2 PLY格式(Polygon File Format)
PLY格式最早是斯坦福大学搞出来的,用来存那个著名的斯坦福兔子。它的特点是:既能存点云,又能存三角网格。如果你做三维重建,最后输出模型通常用PLY。
PLY也分ASCII和二进制两种。我建议你调试用ASCII,部署用二进制。二进制PLY体积小、读写快,但肉眼没法看。
ply
format ascii 1.0
element vertex 8
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 6
property list uchar int vertex_indices
end_header
0 0 0 255 0 0
1 0 0 0 255 0
0 1 0 0 0 255
1 1 0 255 255 0
0 0 1 255 0 255
1 0 1 0 255 255
0 1 1 128 128 128
1 1 1 255 255 255
3 0 1 2
3 1 3 2
...
注意: PLY的顶点和面是分开定义的。如果你只关心点云,可以忽略face部分。但有些库(比如Open3D)默认会尝试读取face,如果文件里没有face信息,可能会报错。我曾经被这个坑过——读一个只有顶点的PLY文件,Open3D直接崩溃了。
2.1.3 XYZ格式
XYZ格式是最简单的点云格式,没有之一。就是每行三个数字,x y z,空格或制表符分隔。没有头部,没有元数据,纯粹的点坐标。
0.123 0.456 0.789
0.234 0.567 0.890
0.345 0.678 0.901
为什么还要用这种简陋的格式?因为快。我在做实时SLAM的时候,日志文件就是XYZ格式,每帧点云直接追加写入,不需要解析头部,IO开销极小。而且用Python读XYZ就三行代码:
import numpy as np
points = np.loadtxt('cloud.xyz')
# points.shape = (N, 3)
三种格式对比:
| 特性 | PCD | PLY | XYZ |
|---|---|---|---|
| 头部信息 | 丰富 | 丰富 | 无 |
| 支持颜色 | 是 | 是 | 否 |
| 支持网格 | 否 | 是 | 否 |
| 读写速度 | 中等 | 中等 | 最快 |
| 跨平台性 | PCL生态 | 广泛 | 通用 |
| 适用场景 | PCL处理 | 三维重建 | 快速IO |
2.2 点云数据结构
聊完文件格式,咱们看看点云在内存里是怎么组织的。说白了,点云数据结构就两种:有序点云和无序点云。
2.2.1 有序点云(Organized Point Cloud)
有序点云长什么样子?它其实就是一个二维矩阵,每个像素对应一个三维点。比如深度相机拍出来的点云,640×480的分辨率,那就是307200个点,排列成一个640列480行的矩阵。
有序点云的好处是:可以利用邻域关系。你想找某个点的邻居?直接看矩阵里上下左右就行了,不需要建KD-Tree。我在做RGB-D SLAM时,深度图转点云就是有序的,做平面拟合直接取3×3邻域,速度飞快。
在PCL里,判断一个点云是否有序很简单:
pcl::PointCloud<pcl::PointXYZ> cloud;
if (cloud.isOrganized()) {
std::cout << "有序点云,宽:" << cloud.width << " 高:" << cloud.height << std::endl;
}
2.2.2 无序点云(Unorganized Point Cloud)
无序点云就是一堆点堆在一起,没有行列关系。激光雷达扫出来的点云基本都是无序的——每个激光束的发射角度不同,点与点之间没有固定的网格关系。
无序点云的处理依赖空间索引结构。最常用的就是KD-Tree和八叉树。KD-Tree适合做K近邻搜索,八叉树适合做体素滤波和碰撞检测。
避坑指南: 我曾经把无序点云当成有序点云处理,直接取第i行第j列的点,结果发现点全乱套了。记住:只有深度相机/ToF相机出来的点云才可能是有序的,激光雷达出来的99%是无序的。
2.3 常用点云库:PCL与Open3D
现在主流的点云处理库就两个:PCL和Open3D。我两个都用,但场景不同。PCL是C++的工业级库,功能全面但学习曲线陡;Open3D是Python友好的库,上手快、可视化好。
2.3.1 PCL(Point Cloud Library)
PCL是点云处理的老大哥,从滤波、配准、分割到识别,几乎什么都有。它的核心数据结构是pcl::PointCloud<T>,T可以是PointXYZ、PointXYZRGB、PointNormal等。
一个典型的PCL读取PCD文件的例子:
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("cloud.pcd", *cloud) == -1) {
PCL_ERROR("读取PCD文件失败\n");
return -1;
}
std::cout << "加载了 " << cloud->points.size() << " 个点" << std::endl;
PCL的优点是稳定、高效、功能全。但缺点也很明显:编译麻烦(依赖多)、API设计老旧(大量boost)、文档质量参差不齐。
2.3.2 Open3D
Open3D是Intel实验室搞的,后来开源了。它的设计理念是「易用性优先」。用Python写Open3D,代码量比PCL少一半以上。
同样的读取操作,Open3D只需要:
import open3d as o3d
cloud = o3d.io.read_point_cloud("cloud.pcd")
print(f"加载了 {len(cloud.points)} 个点")
# 可视化
o3d.visualization.draw_geometries([cloud])
Open3D的杀手锏是可视化。它的可视化窗口支持交互操作:旋转、缩放、选取点,甚至还能录制视频。我做算法调试时,90%的时间都在用Open3D看效果。
我的建议:
- 做产品原型、快速验证 → 用Open3D(Python)
- 做嵌入式部署、性能优化 → 用PCL(C++)
- 两个都学 → 因为很多公司两个都用,面试也会问
2.4 本章知识体系
说了这么多,咱们用一张图把本章的知识结构串起来:
嗯,这张图基本把本章的核心内容都串起来了。你想想看,从文件格式到内存结构,再到处理库,其实是一条线:数据怎么存 → 数据怎么组织 → 数据怎么处理。搞清楚了这条线,后面学滤波算法就轻松多了。
最后提醒一句: 不管你用PCL还是Open3D,一定要搞清楚你读进来的点云是什么坐标系。我见过太多人把激光雷达坐标系和相机坐标系搞混,结果滤波出来的点云全飞到天上去了。下一节咱们就开始讲具体的滤波方法,到时候坐标系问题还会反复提到。
可可精品资料库