点云数据基础:数据结构、常见格式与读写方法

点云数据是三维感知与处理的核心载体,无论是激光雷达扫描的室外场景,还是结构光相机获取的物体表面,最终都会以离散点的集合形式呈现。理解点云的数据结构、文件格式以及读写机制,是进行滤波、配准、分割与重建的前提。本章将系统拆解点云的属性维度、主流格式的差异,并给出工程实践中的读写策略与踩坑指南。

点云数据组织与格式转换链路 数据源 LiDAR / 相机 / 重建 点云数据结构 XYZ + RGB + 强度 + 法向量 属性维度可扩展 文件格式 PCD / PLY / LAS / XYZ ASCII / 二进制 读写方法 PCL / Open3D Python / C++ 属性维度对比 属性 含义 典型用途 存储代价 XYZ 三维坐标 几何基础 3 × float32 = 12B RGB 颜色信息 纹理映射/分类 3 × uint8 = 3B 强度 回波能量 地物分类/标定 1 × float32 = 4B 法向量 表面方向 曲面重建/渲染 3 × float32 = 12B

核心原理:点云属性维度与内存布局

点云本质上是一组在三维空间中离散分布的点的集合,每个点除了必须的几何坐标外,还可以携带多种附加属性。从数据结构角度看,点云可以视为一个 N×D 的矩阵,其中 N 为点的数量,D 为每个点的属性维度。最常见的属性包括:

  • XYZ(几何坐标):每个点的三维位置,通常使用 float32 或 float64 存储。这是所有点云处理的基础,决定了点云的空间形状。
  • RGB(颜色):红绿蓝三通道颜色值,通常使用 uint8(0-255)或 uint16(0-65535)存储。颜色信息来源于相机采集或人工着色,常用于语义分割、可视化与纹理映射。
  • 强度(Intensity):激光雷达回波信号的强度值,反映目标表面的反射率。强度值通常为 float32 或 uint16,在道路标线提取、地物分类中具有重要价值。
  • 法向量(Normal):每个点所在局部表面的法线方向,通常由邻域点拟合平面计算得出。法向量是曲面重建、点云配准和特征描述的关键输入。

在内存布局上,点云数据通常采用 结构体数组(SoA) 或 数组结构体(AoS) 两种方式。PCL 库默认使用 AoS 方式,即每个点是一个包含所有属性的结构体,连续存储;而 Open3D 则更倾向于 SoA 方式,将不同属性分别存储为独立数组。选择哪种布局取决于算法对缓存友好性的需求:AoS 适合逐点遍历,SoA 适合按属性批量计算。

工程提示:在 C++ 中定义点云类型时,应使用 pcl::PointXYZ、pcl::PointXYZRGB、pcl::PointXYZI 等预定义类型,避免自定义结构体导致的内存对齐问题。在 Python 中,Open3D 的 PointCloud 对象通过 points、colors、normals 属性分别管理不同维度,操作更直观。

常见文件格式:PCD、PLY、LAS、XYZ 的对比与选型

点云数据的存储格式多种多样,不同格式在压缩率、可扩展性、行业适配性上各有优劣。下表对比了四种最常用的格式:

格式 全称 数据组织 支持属性 典型应用 优缺点
PCD Point Cloud Data ASCII / 二进制 XYZ、RGB、强度、法向量、自定义字段 PCL 生态、算法开发 灵活性强,支持自定义字段;文件头信息丰富
PLY Polygon File Format ASCII / 二进制 XYZ、RGB、法向量、面片信息 三维扫描、网格处理 支持多边形网格;可扩展性好
LAS Lidar Data Exchange 二进制(压缩/非压缩) XYZ、强度、回波数、分类、GPS 时间 测绘、地理信息系统 行业标准,支持大规模点云;压缩率高
XYZ Plain Text ASCII 仅 XYZ(可扩展为 XYZRGB 等) 简单交换、调试 格式简单,可读性强;文件体积大,无压缩

PCD 格式详解

PCD 是 PCL 库的原生格式,文件头包含版本号、字段定义、尺寸、类型、点数等元信息。其最大的优势是支持自定义字段,例如可以添加 temperature 或 confidence 等业务属性。PCD 支持 ASCII 和二进制两种编码,二进制模式在读写速度上显著优于 ASCII,但文件不可直接阅读。

# .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 5
HEIGHT 1
VIEWPOINT 0 0 0 1 0 0 0
POINTS 5
DATA ascii
0.1 0.2 0.3 4294967296
0.5 0.6 0.7 4294967296
...

PLY 格式详解

PLY 格式起源于斯坦福图形实验室,设计初衷是存储三维扫描仪获取的几何数据。它既可以存储点云,也可以存储三角网格。PLY 文件头使用关键字 element 和 property 定义数据布局,支持 vertex 和 face 两种元素类型。在工业级扫描场景中,PLY 常用于保存带有法向量和颜色的高精度模型。

LAS 格式详解

LAS 是 ASPRS(美国摄影测量与遥感学会)制定的激光雷达数据标准,广泛应用于测绘、林业、城市规划等领域。LAS 文件采用二进制存储,包含公共头块、变长记录和点数据记录三部分。点数据记录中除了 XYZ 和强度外,还包含回波次数、分类标签、扫描角度、GPS 时间等专业属性。LAS 1.4 版本支持最多 15 个额外字节的用户自定义字段。

XYZ 格式详解

XYZ 是最简单的点云格式,每行三个浮点数,以空格或逗号分隔。虽然它不支持元数据,但因其极简性,常用于程序间快速交换数据或调试。实际工程中,常扩展为 XYZRGB 或 XYZI 格式,即在每行末尾追加颜色或强度值。

格式选型建议:如果项目基于 PCL 开发,优先使用 PCD 二进制格式;如果涉及测绘或 GIS 系统,必须使用 LAS;如果需要在不同软件间交换且数据量不大,PLY 是稳妥选择;临时调试或日志记录时,使用 XYZ 文本格式。

读写方法:PCL 与 Open3D 实战操作

点云文件的读写是数据处理的第一步,不同库提供了差异化的 API。以下分别介绍 PCL(C++)和 Open3D(Python)的典型读写方式。

PCL 读写示例

#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/point_types.h>

// 读取 PCD 文件
pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
if (pcl::io::loadPCDFile<pcl::PointXYZRGB>("scene.pcd", *cloud) == -1) {
    PCL_ERROR("Couldn't read file.\n");
    return -1;
}

// 写入 PLY 文件
pcl::io::savePLYFileBinary("output.ply", *cloud);

// 自定义字段的 PCD 写入
pcl::PointCloud<pcl::PointXYZI> cloud_xyzi;
cloud_xyzi.points.resize(100);
for (auto& p : cloud_xyzi.points) {
    p.x = rand() % 100 / 100.0;
    p.y = rand() % 100 / 100.0;
    p.z = rand() % 100 / 100.0;
    p.intensity = rand() % 255;
}
pcl::io::savePCDFileBinary("synthetic.pcd", cloud_xyzi);

Open3D 读写示例

import open3d as o3d
import numpy as np

# 读取点云
pcd = o3d.io.read_point_cloud("scene.ply")
print(f"Loaded {len(pcd.points)} points")

# 访问属性
points = np.asarray(pcd.points)      # (N, 3)
colors = np.asarray(pcd.colors)      # (N, 3) 范围 [0, 1]
normals = np.asarray(pcd.normals)    # (N, 3)

# 写入 PCD 格式
o3d.io.write_point_cloud("output.pcd", pcd, write_ascii=False)

# 从 numpy 数组创建点云
xyz = np.random.rand(1000, 3).astype(np.float64)
pcd_new = o3d.geometry.PointCloud()
pcd_new.points = o3d.utility.Vector3dVector(xyz)
o3d.io.write_point_cloud("random.xyz", pcd_new)

案例拆解:多传感器点云格式统一与属性保留

在某自动驾驶数据采集项目中,团队需要融合 64 线激光雷达(输出 PCD 格式)和工业相机(输出 PLY 格式)的数据。初始阶段,算法工程师发现两个问题:一是 PCD 文件中的强度值范围是 0-255,而 PLY 文件中的颜色值范围是 0-1,直接拼接导致特征不一致;二是 LAS 格式的测绘数据需要导入同一套处理流程,但 LAS 中的 GPS 时间戳在 PCD 中无法直接存储。

针对上述问题,团队制定了以下处理策略:

  1. 统一属性规范:定义内部标准点云格式为 PCD 二进制,字段固定为 x y z intensity rgb,其中 intensity 归一化到 [0,1],rgb 使用 uint8 三通道。
  2. 格式转换脚本:使用 Python 编写转换工具,读取 PLY 时自动将颜色从 [0,1] 映射到 [0,255];读取 LAS 时提取 XYZ、强度、分类字段,丢弃 GPS 时间(或存入自定义字段 gps_time)。
  3. 内存优化:对于超过 5000 万点的 LAS 文件,采用分块读取策略,每次处理 100 万点,避免内存溢出。
  4. 验证机制:转换后随机抽取 1000 个点,对比原始文件的坐标和属性值,确保误差小于 1e-6。

最终,统一后的数据格式使得后续的滤波和分割算法无需关心数据来源,处理效率提升了约 30%。复盘时发现,最容易被忽视的是 强度值的位深差异——部分传感器输出 16 位强度,而 PCD 默认使用 32 位浮点,直接转换会丢失精度。因此,在格式转换时必须明确位深并做相应缩放。

常见误区与总结复盘

  • 误区一:混淆 RGB 与强度。RGB 是相机采集的颜色信息,强度是激光雷达的回波能量,两者物理意义完全不同,不能互相替代。在融合处理时,应分别归一化。
  • 误区二:忽略文件头信息。PCD 和 PLY 的文件头中包含了点数、字段类型、视角等关键信息,直接跳过文件头解析会导致数据错位。
  • 误区三:ASCII 与二进制混用。ASCII 格式可读但体积大、读取慢;二进制格式紧凑但不可读。在性能敏感的生产环境中,应统一使用二进制格式。
  • 误区四:法向量未归一化。许多算法假设法向量为单位向量,如果从文件读取的法向量未归一化,会导致配准或重建结果异常。
经验总结:点云数据管理的核心是“属性清晰、格式统一、读写高效”。建议在项目初期就定义好内部数据规范,并编写健壮的格式转换工具。对于大规模点云,务必使用二进制格式并考虑内存映射(mmap)技术。同时,定期校验数据完整性,防止因文件损坏导致算法崩溃。