3. 点云数据读写

点云处理的第一步是什么?不是滤波,不是配准,而是把数据读进来。

我见过不少新手,算法写得头头是道,结果卡在文件读写上——格式不对、坐标轴反了、内存爆了。说白了,读写是基本功,但坑也不少。这一章我们就来把Open3D和PCL的读写操作捋清楚,顺便聊聊格式转换的那些事。

本章核心:掌握Open3D和PCL两种主流库的点云读写API,理解常见格式(PCD、PLY、LAS/LAZ、TXT)的差异,能独立完成格式转换。

3.1 为什么读写这么重要?

你想想看,传感器采集到的原始点云,通常不是你能直接用的格式。Velodyne的PCAP包、Livox的LVX文件、无人机航测的LAS数据……每个厂家都有自己的“方言”。

我在项目里遇到过最头疼的事:客户给了10TB的LAS数据,结果团队里有人用Open3D读LAS,有人用PCL读PCD,还有人用Python的laspy读——最后合并数据时坐标系统全乱了。嗯,从那以后我定了个规矩:统一入口格式,再进处理管线。

3.2 Open3D读写:简单直接

Open3D的API设计得很清爽。读文件一行,写文件一行,没什么花哨的。

3.2.1 读取点云

import open3d as o3d

# 读取PCD文件
pcd = o3d.io.read_point_cloud("scan.pcd")
print(f"点云点数: {len(pcd.points)}")
print(f"点云类型: {pcd}")

# 读取PLY文件
pcd_ply = o3d.io.read_point_cloud("mesh.ply")
# 读取TXT文件(每行 x y z 或 x y z r g b)
pcd_txt = o3d.io.read_point_cloud("points.txt", format="xyz")
我的习惯:Open3D读取TXT时默认只读xyz,如果你有rgb信息,记得用 format="xyzrgb"。我踩过这个坑,读进来全是黑点,排查了半天才发现是格式参数没写对。

3.2.2 保存点云

# 保存为PCD(二进制格式,体积小)
o3d.io.write_point_cloud("output.pcd", pcd, write_ascii=False)

# 保存为PLY(带颜色)
o3d.io.write_point_cloud("output.ply", pcd, write_ascii=True)

# 保存为TXT
o3d.io.write_point_cloud("output.txt", pcd, write_ascii=True)

注意write_ascii参数。二进制格式读写快、体积小,但不可读;ASCII格式可以用文本编辑器打开,方便调试。我个人习惯:调试用ASCII,上线用二进制。

3.3 PCL读写:功能强大但稍显繁琐

PCL是C++的天下,但Python绑定(python-pcl)也能用。不过说实话,PCL的Python接口维护得不太好,我建议你在C++项目里用PCL,Python项目里优先用Open3D。

3.3.1 C++版本(PCL原生)

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

// 读取PCD
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
if (pcl::io::loadPCDFile<pcl::PointXYZ>("scan.pcd", *cloud) == -1) {
    PCL_ERROR("读取PCD失败\n");
    return -1;
}

// 保存PCD(二进制压缩)
pcl::io::savePCDFileBinaryCompressed("output.pcd", *cloud);

// 读取PLY
pcl::PolygonMesh mesh;
pcl::io::loadPLYFile("mesh.ply", mesh);

// 保存PLY
pcl::io::savePLYFile("output.ply", mesh);
我曾经踩过的坑:PCL的loadPCDFile默认只读xyz,如果你有强度(intensity)或RGB信息,必须用对应的点类型,比如pcl::PointXYZI或pcl::PointXYZRGB。否则数据读进来是空的,而且不会报错——这种“静默失败”最坑人。

3.3.2 Python版本(python-pcl)

import pcl

# 读取PCD
cloud = pcl.load("scan.pcd")
print(f"点数: {cloud.size}")

# 保存PCD
pcl.save(cloud, "output.pcd")

# 注意:python-pcl不支持PLY直接读写,需要先转格式

python-pcl的功能比Open3D弱不少,而且安装麻烦(依赖VTK、Boost)。我个人建议:除非你必须在PCL管线里做后续处理,否则别用python-pcl做读写。

3.4 常见格式对比

格式 扩展名 特点 适用场景
PCD .pcd PCL原生格式,支持二进制/ASCII,可存储任意字段 PCL管线、机器人导航
PLY .ply 支持点云+网格,带颜色/法线,广泛用于图形学 三维重建、Mesh处理
LAS/LAZ .las/.laz 航测标准格式,支持分类、强度、回波等 测绘、林业、城市规划
TXT/CSV .txt/.csv 纯文本,可读性强,但体积大、读写慢 调试、小数据交换
OBJ .obj Wavefront格式,常用于3D建模软件 与Blender/Maya交换数据
一句话总结:PCD是PCL的“亲儿子”,PLY是通用格式,LAS是测绘标配,TXT是调试利器。选格式时先想清楚下游工具链用什么。

3.5 格式转换实战

实际项目中,格式转换是家常便饭。我整理了几种最常见的转换场景。

3.5.1 PCD ↔ PLY(Open3D实现)

import open3d as o3d

# PCD → PLY
pcd = o3d.io.read_point_cloud("input.pcd")
o3d.io.write_point_cloud("output.ply", pcd)

# PLY → PCD
pcd = o3d.io.read_point_cloud("input.ply")
o3d.io.write_point_cloud("output.pcd", pcd)

就这么简单。Open3D内部自动处理了字段映射,你不需要关心细节。

3.5.2 LAS → PCD(使用laspy + Open3D)

import laspy
import open3d as o3d
import numpy as np

# 读取LAS
las = laspy.read("input.las")
points = np.vstack((las.x, las.y, las.z)).T

# 如果有颜色信息
if hasattr(las, 'red'):
    colors = np.vstack((las.red, las.green, las.blue)).T / 65535.0
else:
    colors = None

# 构建Open3D点云
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
if colors is not None:
    pcd.colors = o3d.utility.Vector3dVector(colors)

# 保存为PCD
o3d.io.write_point_cloud("output.pcd", pcd)
注意:LAS的颜色值是16位(0-65535),而Open3D用0-1的浮点数。记得做归一化,否则颜色会炸掉。我刚开始做航测数据时没注意这个,出来的点云一片惨白。

3.5.3 TXT → PCD(批量转换)

import open3d as o3d
import glob

# 批量转换所有TXT文件
for txt_file in glob.glob("data/*.txt"):
    pcd = o3d.io.read_point_cloud(txt_file, format="xyz")
    pcd_file = txt_file.replace(".txt", ".pcd")
    o3d.io.write_point_cloud(pcd_file, pcd)
    print(f"转换完成: {txt_file} → {pcd_file}")

3.6 知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到:数据从传感器来,经过格式转换进入处理管线,最终输出为不同格式。

点云数据读写与格式转换知识体系 传感器原始数据 常见点云格式 PCD PLY LAS/LAZ TXT/CSV OBJ 读写工具 Open3D PCL (C++) laspy 格式转换核心操作 PCD ↔ PLY LAS → PCD TXT → PCD 统一格式进入处理管线

3.7 避坑指南

最后,我把自己这些年踩过的坑整理成清单,你遇到类似问题可以直接查。

  • 坐标轴方向:不同传感器坐标系不同(LiDAR通常是X向前,Z向上;相机是Z向前,Y向下)。读写时注意坐标轴映射,否则点云会“躺”在地上。
  • 颜色值范围:PCD和PLY用0-255的整数,Open3D用0-1的浮点数,LAS用0-65535的整数。转换时务必归一化。
  • 内存爆炸:读取大文件(>1亿点)时,不要一次性读入内存。用read_point_cloud的stride参数做降采样,或者用LAS的chunk_iterator分块读取。
  • 字段丢失:PCD转PLY时,强度(intensity)字段可能会丢失。如果下游需要强度信息,建议用PCL的savePLYFile并指定字段。
  • 文件损坏:我曾经遇到过PCD文件头信息错误(比如WIDTH和POINTS不匹配),导致读取失败。可以用文本编辑器打开PCD文件,手动检查头信息。
重要提醒:永远不要在生产环境中直接修改原始数据文件。先备份,再转换。我见过有人把原始LAS覆盖了,结果整个项目重做——那滋味,不好受。