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文件格式 头部 + 点数据 点云数据结构 pcl::PointCloud<T> 读写操作 load / save 文件格式细节 • 头部字段:VERSION, FIELDS... • 数据模式:ASCII / Binary • 字段类型:I, U, F, D • 有序 vs 无序点云 数据结构细节 • width / height / points • is_dense 标志 • 常见点类型 • 自定义点类型 操作细节 • loadPCDFile • savePCDFileASCII • savePCDFileBinary • 点数据遍历 核心:理解文件格式 → 映射到内存结构 → 高效读写操作

这张图把本章内容串起来了。从PCD文件格式出发,到内存中的数据结构,再到具体的读写操作,每一步都有对应的细节。你掌握了这个流程,点云基础操作就算入门了。

可可精品资料库