4. 点云基础操作:读取与保存PCD文件、点云数据结构、字段与类型
点云处理的第一步,说白了就是跟文件打交道。
你拿到一堆激光雷达数据,总得知道怎么读进来、怎么看懂里面的结构、怎么再存回去。我刚开始接触PCD文件时,也踩过不少坑。今天咱们就把这块彻底讲透。
4.1 PCD文件:点云的通用语言
PCD,全称Point Cloud Data,是Point Cloud Library(PCL)定义的一种文件格式。它不是什么高深的东西,就是一个文本或二进制文件,用来存点云数据。
我个人习惯用PCD格式,原因很简单:通用、易读、工具链成熟。几乎所有点云库都支持它。
4.1.1 PCD文件长什么样?
咱们先看一个最简单的PCD文件内容(ASCII格式):
# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z
SIZE 4 4 4
TYPE F F F
COUNT 1 1 1
WIDTH 5
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 5
DATA ascii
0.1 0.2 0.3
0.4 0.5 0.6
0.7 0.8 0.9
1.0 1.1 1.2
1.3 1.4 1.5
嗯,这里要注意:头部(Header)和点数据之间用空行隔开。头部里每一行都定义了关键信息。
4.1.2 头部字段详解
| 字段 | 含义 | 示例值 |
|---|---|---|
| VERSION | PCD格式版本 | 0.7 |
| FIELDS | 每个点的字段名 | x y z rgb |
| SIZE | 每个字段占用的字节数 | 4 4 4 4 |
| TYPE | 每个字段的数据类型 | F F F F |
| COUNT | 每个字段的元素个数 | 1 1 1 1 |
| WIDTH | 点云宽度(无序点云=点数) | 5 |
| HEIGHT | 点云高度(无序点云=1) | 1 |
| VIEWPOINT | 传感器位姿(平移+四元数) | 0 0 0 1 0 0 0 |
| POINTS | 点云总点数 | 5 |
| DATA | 数据存储格式 | ascii / binary |
你想想看,有了这些信息,解析器就知道怎么读后面的数据了。比如FIELDS是x y z,那每行三个数,分别对应x、y、z坐标。
4.2 点云数据结构:从文件到内存
文件读进来了,数据在内存里怎么组织?这是关键。
在PCL中,点云数据用 pcl::PointCloud<T> 这个模板类表示。T就是点的类型,比如 pcl::PointXYZ、pcl::PointXYZRGB 等等。
4.2.1 核心数据结构
// 一个典型的点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 它内部包含:
// - width: 点云宽度
// - height: 点云高度
// - points: 存储所有点的std::vector
// - is_dense: 是否包含无效点(NaN)
// - sensor_origin_: 传感器原点
// - sensor_orientation_: 传感器朝向
我记得刚开始用PCL时,总搞不清 width 和 height 的关系。其实很简单:
- 无序点云:height = 1,width = 总点数
- 有序点云:height > 1,width = 每行点数,总点数 = width * height
有序点云通常来自深度相机或某些激光雷达的扫描线数据。你想想看,如果你知道每个点在哪条扫描线上,做滤波、分割就方便多了。
4.2.2 常见的点类型
| 点类型 | 包含字段 | 典型用途 |
|---|---|---|
| PointXYZ | x, y, z | 纯几何信息 |
| PointXYZI | x, y, z, intensity | 带反射强度 |
| PointXYZRGB | x, y, z, r, g, b | 带颜色信息 |
| PointXYZRGBA | x, y, z, r, g, b, a | 带透明度 |
| PointNormal | x, y, z, normal_x, normal_y, normal_z | 带法向量 |
| PointXYZINormal | x, y, z, intensity, normal_x, normal_y, normal_z | 综合信息 |
我在项目中遇到过一个问题:用 PointXYZ 存带强度的数据,结果强度信息全丢了。后来才意识到,字段类型必须匹配。你存了强度,就得用 PointXYZI 或自定义类型。
4.3 读取与保存:实战代码
光说不练假把式。咱们直接上代码。
4.3.1 读取PCD文件
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
// 创建点云对象
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
// 读取PCD文件
if (pcl::io::loadPCDFile<pcl::PointXYZ>("scan.pcd", *cloud) == -1)
{
PCL_ERROR("读取PCD文件失败!\n");
return -1;
}
std::cout << "读取成功!点数:" << cloud->points.size() << std::endl;
这里有个坑:loadPCDFile 返回 -1 表示失败。我曾经因为路径写错,排查了半天。建议你用绝对路径或先检查文件是否存在。
4.3.2 保存PCD文件
// 保存为ASCII格式(可读性好,文件大)
pcl::io::savePCDFileASCII("output_ascii.pcd", *cloud);
// 保存为二进制格式(速度快,文件小)
pcl::io::savePCDFileBinary("output_binary.pcd", *cloud);
// 保存为压缩二进制格式(文件最小)
pcl::io::savePCDFileBinaryCompressed("output_compressed.pcd", *cloud);
我个人习惯:调试时用ASCII,正式处理用Binary。ASCII方便用文本编辑器查看,Binary读写速度快很多。你想想看,一个百万点的点云,ASCII文件可能上百MB,Binary只有几十MB。
4.3.3 访问和修改点数据
// 遍历所有点
for (size_t i = 0; i < cloud->points.size(); ++i)
{
// 获取第i个点的坐标
float x = cloud->points[i].x;
float y = cloud->points[i].y;
float z = cloud->points[i].z;
// 修改坐标(比如平移)
cloud->points[i].x += 1.0f;
}
// 使用范围for循环(C++11起)
for (auto& point : cloud->points)
{
point.z *= 2.0f; // 拉伸Z轴
}
const auto& 可以避免不必要的拷贝,提升性能。
4.4 字段与类型:深入理解
为什么PCL要搞这么多点类型?说白了,不同的传感器和应用场景需要不同的数据。
4.4.1 字段类型对照
PCD文件中的TYPE字段定义了每个字段的数据类型:
| TYPE代码 | C++类型 | 字节数 | 说明 |
|---|---|---|---|
| I | int32_t | 4 | 有符号整数 |
| U | uint32_t | 4 | 无符号整数 |
| F | float | 4 | 单精度浮点 |
| D | double | 8 | 双精度浮点 |
嗯,这里要注意:大部分点云数据用float就够了。double精度高但占用空间翻倍,除非你做高精度测绘,否则没必要。
4.4.2 自定义点类型
有时候内置类型不够用,比如你想加个时间戳或标签。PCL允许你自定义:
// 自定义点类型:带时间戳和标签
struct PointWithTimestamp
{
PCL_ADD_POINT4D; // 添加x, y, z, padding
double timestamp; // 时间戳
int label; // 标签
EIGEN_MAKE_ALIGNED_OPERATOR_NEW // 内存对齐
} EIGEN_ALIGN16;
POINT_CLOUD_REGISTER_POINT_STRUCT(
PointWithTimestamp,
(float, x, x)
(float, y, y)
(float, z, z)
(double, timestamp, timestamp)
(int, label, label)
)
我在项目中遇到过需要同时存点云和每个点的置信度,自定义类型就派上了用场。不过要提醒你:自定义类型需要注册到PCL的序列化系统中,否则读写会出问题。
4.5 避坑指南
- 路径问题:PCL的IO函数不支持中文路径,文件名最好用英文+数字。
- 内存对齐:自定义点类型必须用
EIGEN_MAKE_ALIGNED_OPERATOR_NEW,否则在64位系统上可能崩溃。 - NaN值:点云中可能出现NaN(比如无效测量点),处理前最好用
is_dense检查或手动过滤。 - 版本兼容:PCL 1.8和1.12的PCD文件格式略有差异,跨版本读写时注意测试。
4.6 知识体系总览
下面这张图帮你理清本章的核心逻辑:
这张图把本章内容串起来了。从PCD文件格式出发,到内存中的数据结构,再到具体的读写操作,每一步都有对应的细节。你掌握了这个流程,点云基础操作就算入门了。
可可精品资料库