第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 |
| 适用场景 | 工业级、嵌入式、实时系统 | 快速原型、科研、教学 |
2.1.1 PCL中的点云结构
PCL里最常用的点类型是 pcl::PointXYZ 和 pcl::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);
}
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 本章知识体系
下面这张图是我自己总结的点云基础结构,你可以把它当作学习路线图:
好了,这一章的内容就到这里。数据结构、可视化、坐标系变换,这三样东西是点云处理的「三件套」。你想想看,后面做配准、做拼接,本质上都是在做坐标系变换。所以这一章的基础打牢了,后面会轻松很多。
公众号:蓝海资料掘金营,微信deep3321