第2章:点云基础——数据结构、可视化与坐标系变换

各位同学,欢迎来到点云实战的第二课。

说实话,很多初学者一上来就急着跑配准算法,结果连点云长什么样、怎么存、怎么转坐标系都搞不清楚。我当年刚接触ToF传感器时也犯过这个错——拿到一帧深度图,直接当成普通图像处理,结果点云拼接出来全是扭曲的。嗯,今天我们就从最基础的东西讲起。

2.1 点云数据结构:PCL vs Open3D

点云本质上就是一堆三维点的集合。每个点通常包含(x, y, z)坐标,有时还带强度、颜色、法向量等信息。我个人习惯把点云理解成「带坐标的像素」——你想想看,图像是二维网格,点云就是三维空间里的散点。

目前最主流的两个点云库是PCL(Point Cloud Library)和Open3D。我两个都用过,简单说说区别:

特性 PCL Open3D
语言 C++为主,Python绑定较弱 Python原生,接口友好
安装难度 依赖多,编译容易踩坑 pip install 搞定
数据结构 pcl::PointCloud\ open3d.geometry.PointCloud
可视化 pcl::visualization o3d.visualization.draw_geometries
适用场景 工业级、嵌入式、实时系统 快速原型、科研、教学
我的建议:如果你做产品落地,PCL更稳;如果只是做算法验证或课程作业,Open3D能让你少掉一半头发。

2.1.1 PCL中的点云结构

PCL里最常用的点类型是 pcl::PointXYZpcl::PointXYZRGB。我曾在项目中用 PointXYZI 存ToF的强度信息,效果不错。

// PCL点云创建与赋值
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
cloud->width = 100;
cloud->height = 1;  // 无序点云
cloud->points.resize(cloud->width * cloud->height);

for (size_t i = 0; i < cloud->points.size(); ++i) {
    cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
    cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
    cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
}
注意:PCL中 height = 1 表示无序点云,height > 1 才是有序点云(类似图像)。ToF传感器输出的通常是 height = 1 的无序点云,但如果你用深度图转点云,记得设置正确的宽高。

2.1.2 Open3D中的点云结构

Open3D就简洁多了。我特别喜欢它的Python接口,写起来像在写伪代码。

import open3d as o3d
import numpy as np

# 创建点云
points = np.random.rand(100, 3).astype(np.float64)
cloud = o3d.geometry.PointCloud()
cloud.points = o3d.utility.Vector3dVector(points)

# 添加颜色
colors = np.random.rand(100, 3).astype(np.float64)
cloud.colors = o3d.utility.Vector3dVector(colors)

print(f"点云点数: {len(cloud.points)}")

你看,三行代码就搞定了。Open3D内部用numpy数组存储,所以和Python生态无缝衔接。我曾经在项目里直接用 np.asarray(cloud.points) 取数据做滤波,非常方便。

2.2 点云可视化

可视化是调试点云算法最重要的手段。没有之一。我见过太多人对着命令行里的数字硬调参数,结果调一整天都找不到问题——其实看一眼点云形状就明白了。

2.2.1 PCL可视化

#include <pcl/visualization/cloud_viewer.h>

pcl::visualization::CloudViewer viewer("点云查看器");
viewer.showCloud(cloud);
while (!viewer.wasStopped()) {
    // 保持窗口打开
}

PCL的可视化窗口支持鼠标交互:左键旋转、滚轮缩放、右键平移。嗯,这里有个坑——PCL的CloudViewer是单线程的,如果你在主线程里做其他计算,窗口可能会卡死。我建议用 PCLVisualizer 类,可以多线程控制。

2.2.2 Open3D可视化

import open3d as o3d

# 简单可视化
o3d.visualization.draw_geometries([cloud], window_name="Open3D点云")

# 高级可视化:添加坐标系
o3d.visualization.draw_geometries(
    [cloud, o3d.geometry.TriangleMesh.create_coordinate_frame(size=0.5)],
    window_name="带坐标系的点云"
)
核心技巧:可视化时一定要加坐标系!我刚开始做多视角拼接时,经常把点云方向搞反。后来养成习惯,每次显示点云都带上坐标轴,一眼就能看出点云朝向对不对。

2.3 点云坐标系与变换

坐标系是点云处理中最容易出错的地方。说白了,不同传感器、不同算法、不同库用的坐标系可能都不一样。我踩过的坑包括:

  • ToF传感器输出的是相机坐标系(Z轴朝前),但PCL默认是Z轴朝上
  • Open3D的坐标系是右手系,但有些数据集用的是左手系
  • 旋转矩阵和平移向量的顺序搞反,导致点云飞出去

2.3.1 常见坐标系

坐标系 X轴 Y轴 Z轴 典型应用
相机坐标系 ToF、RGB-D相机
世界坐标系 SLAM、机器人导航
Open3D默认 可视化、渲染

2.3.2 变换矩阵

点云变换的核心就是4x4齐次变换矩阵。我习惯用 T = [R | t; 0 0 0 1] 这种形式。

import numpy as np
import open3d as o3d

# 构建变换矩阵:绕Z轴旋转45度,平移(1, 0, 0)
theta = np.pi / 4
R = np.array([
    [np.cos(theta), -np.sin(theta), 0],
    [np.sin(theta),  np.cos(theta), 0],
    [0,              0,             1]
])
t = np.array([1.0, 0.0, 0.0])

T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = t

# 应用变换
cloud_transformed = cloud.transform(T)
避坑指南:我曾经在项目中把旋转矩阵写成了转置形式,结果点云拼接出来全是镜像。后来我养成了一个习惯——每次做变换前,先用一个已知点(比如原点)验证一下变换是否正确。

2.4 本章知识体系

下面这张图是我自己总结的点云基础结构,你可以把它当作学习路线图:

点云基础:知识体系 数据结构 PCL Open3D 可视化 PCL Viewer Open3D Viz 坐标系与变换 相机坐标系 变换矩阵 应用:点云配准、多视角拼接 核心要点 • 数据结构:PCL适合C++工业级,Open3D适合Python快速原型 • 可视化:永远带上坐标系,一眼看出点云朝向 • 坐标系:不同传感器坐标系不同,变换前先验证 • 变换矩阵:R和t的顺序、齐次坐标的用法要记牢
个人经验:学点云基础,别贪多。把PCL和Open3D各跑通一个demo,然后拿自己的ToF数据试一遍可视化。我当年就是这么入门的——先看到点云长什么样,后面学配准才有感觉。

好了,这一章的内容就到这里。数据结构、可视化、坐标系变换,这三样东西是点云处理的「三件套」。你想想看,后面做配准、做拼接,本质上都是在做坐标系变换。所以这一章的基础打牢了,后面会轻松很多。


公众号:蓝海资料掘金营,微信deep3321