2. 点云数据结构与存储格式

点云数据是三维空间中离散点的集合,每个点通常携带空间坐标及多种属性信息。在实际工程中,点云数据的获取方式(激光雷达、摄影测量、结构光扫描等)决定了数据的组织形态,而存储格式则直接影响读写效率、压缩率与跨平台兼容性。本章将系统解析常见点云格式的底层结构、点云属性的物理含义,以及有序/无序数据在组织与检索上的核心差异,帮助学习者为后续的滤波、配准、分割等算法操作建立扎实的数据基础。

点云数据组织与存储格式全景图 数据获取 激光雷达 / 摄影测量 结构光 / 深度相机 点云属性 坐标 (X,Y,Z) 必选 强度 / 颜色 / 法向量 时间戳 / 回波数 数据组织方式 有序点云(行列映射) 无序点云(散乱存储) KD树 / 八叉树索引 存储格式 PCD / PLY / LAS XYZ / BIN ASCII / 二进制 空间索引结构 KD树:K维二叉分割 八叉树:递归立方体细分 加速近邻搜索与配准 格式选型要点 PCD:PCL原生,灵活扩展 LAS:LiDAR行业标准 PLY:含网格与颜色 下游应用 点云滤波 → 配准 → 分割 → 特征提取 → 三维重建 → 目标识别 数据格式与组织方式决定预处理效率与算法上限

2.1 常见点云格式解析

2.1.1 核心原理:格式的本质是数据序列化协议

点云存储格式本质上定义了三维点数据在磁盘或内存中的序列化方式。不同格式在头信息结构、数据编码(ASCII或二进制)、属性扩展能力、压缩支持等方面存在显著差异。选择何种格式,取决于数据采集设备、处理软件生态、存储空间限制以及下游算法需求。

格式 头信息 数据编码 典型属性 主要应用场景
PCD 文本头(VERSION/FIELDS/SIZE等) ASCII 或二进制 XYZ、强度、RGB、法向量 PCL库原生格式,算法开发与调试
PLY 文本头(element/property) ASCII 或二进制(小端/大端) XYZ、颜色、法向量、网格面片 三维扫描、网格重建、可视化
LAS 公共头块(Public Header Block) 二进制(定长记录) XYZ、强度、回波、分类、GPS时间 机载/地面LiDAR,测绘行业标准
XYZ 无头信息 纯文本(每行3个浮点数) 仅坐标 简单交换、快速测试
BIN 无头信息 二进制(float32数组) 由外部约定决定 深度学习预处理、大规模数据加载

2.1.2 判断方法:如何根据场景选择格式

选择存储格式时,应遵循以下判断流程:

  1. 确认数据来源:如果是Velodyne、Ouster等机械式LiDAR,通常输出PCD或自定义二进制;如果是测绘级Riegl或Leica设备,则原生支持LAS。
  2. 评估下游工具链:若使用PCL库,PCD格式兼容性最好;若使用CloudCompare或MeshLab,PLY更通用;若涉及GIS或测绘成果提交,必须使用LAS。
  3. 权衡文件大小与读写速度:二进制格式(PCD binary、LAS、BIN)比ASCII快3-10倍,文件体积减少约40%-60%。
  4. 考虑属性扩展需求:PCD允许自定义FIELDS,LAS通过额外字节(Extra Bytes)扩展,PLY通过自定义property扩展。
经验提醒:在深度学习项目中,BIN格式(纯float32数组)配合NumPy的 fromfile 函数读取速度最快,适合大规模训练数据管线。但BIN格式丢失了所有元信息,必须额外保存一份配置JSON记录点云维度、属性顺序和坐标系信息。

2.1.3 案例拆解:PCD与LAS格式的工程对比

某智慧城市项目需要处理机载LiDAR采集的城市级点云,单帧数据量约5000万点,包含坐标、强度、分类和GPS时间。项目初期使用PCD二进制格式存储,后期因成果交付要求改为LAS 1.4格式。

初始现象:PCD文件单帧约1.2GB,加载耗时约8秒;LAS 1.4格式单帧约900MB,加载耗时约5秒。但LAS格式在ArcGIS和ERDAS中可直接读取,无需格式转换。

分析判断:PCD的FIELDS定义灵活,但头信息中每个点记录的大小需手动计算;LAS格式有严格的公共头块和变长记录(VLR),对点记录格式(Point Data Record Format)有明确规范。LAS 1.4支持最多15个额外字节,可存储回波、分类、RGB等属性。

实际操作:使用PDAL(Point Data Abstraction Library)进行格式转换,关键命令如下:

# 将PCD转换为LAS 1.4
pdal translate input.pcd output.las \
  --writers.las.format=1.4 \
  --writers.las.extra_dims="all"

# 查看LAS文件头信息
pdal info output.las --metadata

结果对比:转换后LAS文件体积减少约25%,在QGIS中加载速度提升40%。但LAS格式对法向量存储支持较弱,需通过额外字节或单独文件保存。

风险与代价:LAS格式对属性名称有长度限制(32字符),自定义属性需谨慎命名;同时LAS的二进制布局固定,若需频繁增删属性,不如PCD灵活。

复盘经验:格式选型应提前确认最终交付要求,避免后期大规模转换。对于算法研究阶段,PCD更便捷;对于工程交付,LAS是更稳妥的选择。

2.2 点云属性详解

2.2.1 核心原理:属性是点云语义的载体

点云中每个点不仅包含三维坐标,还携带多种属性信息。这些属性是后续分类、分割、识别等算法的重要输入特征。理解每种属性的物理含义和取值范围,是正确使用点云数据的前提。

属性类型 物理含义 典型范围 常见用途
X, Y, Z 三维空间坐标(米或毫米) 取决于传感器量程 几何计算、配准、重建
强度 (Intensity) 激光回波信号强度 0-255 或 0-65535 地物分类、路面提取
颜色 (RGB) 相机映射的RGB值 0-255 每通道 纹理映射、语义分割
法向量 (Normal) 局部表面的法线方向 单位向量 (nx,ny,nz) 表面重建、特征描述
回波数 (Echo) 激光脉冲多次回波编号 1-5 或更多 植被穿透、边缘检测
时间戳 (GPSTime) GPS标准时间 周内秒或时间 多帧融合、运动补偿

2.2.2 判断方法:属性完整性与数据质量评估

在实际工程中,经常需要判断点云数据是否满足算法输入要求。建议按以下步骤检查:

  • 检查坐标范围:使用 pcl::getMinMax3D 或NumPy的 min/max 统计坐标极值,判断是否存在离群点或坐标系错误。
  • 验证强度分布:绘制强度直方图,正常数据应呈近似正态分布或双峰分布;若出现大量0值或饱和值,需检查传感器增益设置。
  • 确认法向量一致性:法向量应指向同一侧(如朝向视点),否则会影响表面重建效果。
  • 检查时间戳连续性:对于多帧拼接数据,时间戳应单调递增,且帧间间隔符合传感器帧率。
风险提示:不同传感器对强度值的定义差异很大。Velodyne VLP-16的强度范围是0-255,而Riegl VQ-780i的强度范围是0-65535。在跨传感器数据融合时,必须进行强度归一化处理,否则分类算法会产生严重偏差。

2.2.3 小型场景化说明:法向量计算与方向一致性

假设我们有一片墙面点云,使用PCL计算法向量时,默认采用协方差分析(PCA)方法。对于每个查询点,取其邻域内的K个近邻点,计算协方差矩阵的最小特征值对应的特征向量即为法向量方向。但PCA方法存在法向量方向二义性——法向量可能指向墙面内侧或外侧。

解决方法是使用 flipNormalTowardsViewpoint 函数,将法向量统一翻转至指向传感器视点方向。对于室内扫描场景,视点通常位于传感器位置;对于机载LiDAR,视点位于飞行器位置。若忽略此步骤,后续的区域生长分割算法可能将同一平面分裂为多个区域。

2.3 数据组织方式:有序/无序与空间索引

2.3.1 核心原理:组织方式决定检索效率

点云数据在内存中的组织方式直接影响近邻搜索、体素滤波等操作的效率。有序点云(Organized Point Cloud)具有明确的行列结构,可通过像素坐标直接索引;无序点云(Unorganized Point Cloud)则需借助空间索引结构加速查询。

组织方式 数据结构 索引复杂度 适用场景
有序点云 二维矩阵 (H×W) O(1) 直接访问 RGB-D相机、结构光扫描
无序点云 + KD树 K维二叉搜索树 O(log N) 平均 通用点云处理、配准
无序点云 + 八叉树 递归立方体细分 O(log N) 平均 体素滤波、碰撞检测

2.3.2 操作流程:构建KD树与八叉树

KD树构建流程:

  1. 选择分割维度:计算各维度方差,选择方差最大的维度作为分割轴。
  2. 选择分割点:将该维度上的中位数作为节点值,确保左右子树平衡。
  3. 递归构建:对左子集和右子集重复上述步骤,直到子集大小小于设定阈值。

八叉树构建流程:

  1. 确定根节点包围盒:计算点云的最小/最大坐标,形成立方体。
  2. 递归细分:若节点内点数超过阈值(如16个),则将该立方体等分为8个子立方体。
  3. 终止条件:达到最大深度(如10层)或节点内点数小于阈值。
// PCL中构建KD树
pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
kdtree.setInputCloud(cloud);

// 最近邻搜索
pcl::PointXYZ search_point;
std::vector<int> point_idx(1);
std::vector<float> point_sq_dist(1);
kdtree.nearestKSearch(search_point, 1, point_idx, point_sq_dist);

// 半径搜索
std::vector<int> radius_idx;
std::vector<float> radius_sq_dist;
kdtree.radiusSearch(search_point, 0.1, radius_idx, radius_sq_dist);

2.3.3 案例拆解:大规模无序点云的近邻搜索优化

某自动驾驶团队需要处理64线LiDAR采集的点云,单帧约12万点,配准算法需要频繁执行近邻搜索。初始实现使用暴力搜索(Brute Force),每帧配准耗时约2.3秒,无法满足实时性要求。

关键输入数据:点云密度不均,近处点密集(每平方米约2000点),远处点稀疏(每平方米约50点)。

分析判断:暴力搜索的时间复杂度为O(N²),在12万点规模下不可行。KD树在均匀分布数据上表现良好,但面对密度不均的点云,八叉树的自适应细分特性更优。

实际操作:

  1. 使用PCL的 pcl::octree::OctreePointCloudSearch 构建八叉树,分辨率设为0.2米。
  2. 对比KD树(FLANN)与八叉树的搜索耗时。
  3. 调整八叉树分辨率参数,观察对搜索精度的影响。

结果对比:

方法 构建耗时 单次近邻搜索耗时 配准总耗时
暴力搜索 — 14.2 ms 2.3 s
KD树 (FLANN) 18 ms 0.08 ms 0.42 s
八叉树 (0.2m) 12 ms 0.11 ms 0.38 s

风险与代价:八叉树在点云密度突变区域(如近处与远处交界)可能出现搜索精度下降,需通过设置最小体素内点数来平衡。KD树在低维数据上性能稳定,但内存占用略高于八叉树。

复盘经验:对于自动驾驶场景,KD树与八叉树性能接近,但八叉树天然支持体素滤波和动态更新,更适合需要频繁增删点的SLAM场景。

2.3.4 常见误区与总结复盘

误区1:认为有序点云一定优于无序点云。实际上,有序点云虽然索引快,但要求传感器输出严格的网格结构,且无法处理遮挡和空洞。无序点云配合空间索引,在通用性上更胜一筹。
误区2:忽略坐标系一致性。不同格式可能使用不同坐标系(如LAS使用WGS84经纬度,PCD使用局部笛卡尔坐标),直接混用会导致严重错误。
误区3:过度依赖默认参数。KD树的叶子节点大小、八叉树的分辨率都需要根据点云密度和搜索半径进行调优,默认参数往往不是最优解。

总结复盘:本章系统梳理了点云数据的存储格式、属性定义和组织方式。在实际项目中,建议先明确数据来源和下游算法需求,再选择合适的格式与索引结构。对于算法原型验证,PCD+KD树是最快捷的路径;对于工程化部署,LAS+八叉树在数据交换和空间查询方面更具优势。下一阶段的学习中,读者应动手实践不同格式的读写操作,并对比不同索引结构在真实数据上的性能差异,从而建立对点云数据底层的直观认知。