4. 点云读写与格式转换:常见点云格式(PCD、PLY、LAS、TXT、BIN),Open3D读写操作,格式转换实战(BIN转PCD,PCD转PLY)
点云读写,说白了就是跟各种文件格式打交道。我刚开始做这行的时候,光是被格式问题就折腾了好几个通宵。你想想看,传感器采集回来的数据,可能是二进制,可能是文本,还有各种行业标准格式。不会读写,后面所有算法都是空中楼阁。
这一章,我们就来彻底搞定它。
4.1 常见点云格式一览
先看看市面上常见的几种格式。我按使用场景给你捋一捋。
| 格式 | 后缀 | 特点 | 典型场景 |
|---|---|---|---|
| PCD | .pcd | Point Cloud Library 原生格式,支持头文件描述 | PCL 生态、学术研究 |
| PLY | .ply | 斯坦福大学开发,支持顶点和面片 | 三维重建、网格处理 |
| LAS | .las / .laz | ASPRS 标准,含分类、强度等信息 | 测绘、LiDAR 点云 |
| TXT | .txt / .xyz | 纯文本,每行 x y z,可带 r g b | 调试、简单数据交换 |
| BIN | .bin | 二进制裸数据,无头文件 | KITTI 数据集、工业传感器 |
嗯,这里要注意:BIN 格式没有标准头文件。我在项目中遇到过,不同厂家给的 .bin 文件,数据排列方式可能完全不同。有的按 xyzxyz 排,有的按 xyzrgb 排,还有的带时间戳。所以读 BIN 之前,一定要先搞清楚数据协议。
4.2 Open3D 读写操作
Open3D 是我个人最常用的点云处理库。它把读写操作封装得特别干净,几行代码就能搞定。
4.2.1 读取点云
import open3d as o3d
# 读取 PCD 文件
pcd = o3d.io.read_point_cloud("example.pcd")
print(f"点云点数: {len(pcd.points)}")
print(f"点云类型: {pcd}")
# 读取 PLY 文件
pcd_ply = o3d.io.read_point_cloud("mesh.ply")
print(f"PLY 点云点数: {len(pcd_ply.points)}")
你看,就这么简单。Open3D 会自动根据文件后缀判断格式。不过,LAS 格式需要额外安装 laspy,Open3D 原生不支持直接读 LAS。
4.2.2 写入点云
# 写入 PCD 文件
o3d.io.write_point_cloud("output.pcd", pcd)
# 写入 PLY 文件(带颜色)
o3d.io.write_point_cloud("output_color.ply", pcd, write_ascii=False)
# 写入 ASCII 格式的 PCD
o3d.io.write_point_cloud("output_ascii.pcd", pcd, write_ascii=True)
这里有个坑:write_ascii 参数。默认是 False,写入二进制格式。二进制文件小,读写快。但如果你要手动查看数据内容,就得用 ASCII 模式。我曾经为了调试一个 bug,把二进制文件转成 ASCII,才发现是某个点的 z 坐标异常大。
4.2.3 读取 TXT 文件
Open3D 没有直接读 .txt 的函数。我一般用 NumPy 先读,再转成点云。
import numpy as np
# 假设 TXT 文件每行是 x y z
data = np.loadtxt("points.txt")
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(data)
# 如果带颜色,每行是 x y z r g b
data_rgb = np.loadtxt("points_rgb.txt")
pcd.points = o3d.utility.Vector3dVector(data_rgb[:, :3])
pcd.colors = o3d.utility.Vector3dVector(data_rgb[:, 3:6] / 255.0)
4.3 格式转换实战
格式转换是日常工作中最频繁的操作。下面我挑两个最典型的场景,手把手带你走一遍。
4.3.1 BIN 转 PCD
KITTI 数据集用的就是 .bin 格式。每个点占 4 个 float,分别是 x, y, z, reflectance。注意,这里没有颜色。
import numpy as np
import open3d as o3d
def bin_to_pcd(bin_path, pcd_path):
# 读取二进制文件
raw_data = np.fromfile(bin_path, dtype=np.float32)
# 每 4 个 float 为一个点 (x, y, z, reflectance)
raw_data = raw_data.reshape(-1, 4)
# 提取 xyz
points = raw_data[:, :3]
# 创建点云对象
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# 保存为 PCD
o3d.io.write_point_cloud(pcd_path, pcd)
print(f"转换完成: {bin_path} -> {pcd_path}")
# 使用示例
bin_to_pcd("000000.bin", "000000.pcd")
为什么用 np.fromfile?因为它是按二进制直接读,效率最高。我测试过,读一个 100MB 的 .bin 文件,不到 0.1 秒。
points = points[~np.isnan(points).any(axis=1)]。
4.3.2 PCD 转 PLY
这个就简单了。Open3D 直接支持。
import open3d as o3d
def pcd_to_ply(pcd_path, ply_path):
# 读取 PCD
pcd = o3d.io.read_point_cloud(pcd_path)
# 保存为 PLY(二进制格式,文件更小)
o3d.io.write_point_cloud(ply_path, pcd, write_ascii=False)
print(f"转换完成: {pcd_path} -> {ply_path}")
# 使用示例
pcd_to_ply("scan.pcd", "scan.ply")
如果你需要保留颜色,确保 PCD 文件里已经包含了 RGB 信息。Open3D 在转换时会自动保留。
4.3.3 批量转换
实际项目中,经常要处理成百上千个文件。我写了个批量转换脚本,分享给你。
import os
import glob
import open3d as o3d
def batch_convert(input_dir, output_dir, input_ext, output_ext):
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
# 查找所有输入文件
pattern = os.path.join(input_dir, f"*.{input_ext}")
files = glob.glob(pattern)
for i, file_path in enumerate(files):
# 生成输出文件名
base_name = os.path.basename(file_path)
name_without_ext = os.path.splitext(base_name)[0]
output_path = os.path.join(output_dir, f"{name_without_ext}.{output_ext}")
# 读取并转换
pcd = o3d.io.read_point_cloud(file_path)
o3d.io.write_point_cloud(output_path, pcd)
# 打印进度
if (i + 1) % 10 == 0:
print(f"已转换 {i + 1}/{len(files)} 个文件")
# 使用示例:将目录下所有 PCD 转为 PLY
batch_convert("pcd_files/", "ply_files/", "pcd", "ply")
嗯,这里要注意:批量转换时,最好加个进度提示。不然等半天不知道进度,心里没底。
4.4 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图。
从这张图可以看出来,整个知识体系分三层:格式认知 → 读写操作 → 实战转换。每一层都依赖上一层。你先把格式搞明白,再学读写,最后做转换,就水到渠成了。