3、点云坐标系与变换:世界坐标系与传感器坐标系、欧拉角与旋转矩阵、使用Open3D进行点云旋转与平移

做激光SLAM这几年,我踩过最大的坑,就是坐标系搞混了。

你想想看,雷达扫出来一堆点,明明感觉位置是对的,但一拼接就错位。查了半天,最后发现是旋转方向搞反了。嗯,这种经历,我相信做点云的朋友多少都遇到过。

所以这一章,咱们就把坐标系和变换这件事彻底聊透。说白了,就是搞清楚「点从哪里来,要到哪里去」。

3.1 世界坐标系 vs 传感器坐标系

先问个问题:雷达扫到的点,是以谁为原点?

答案是雷达自己。雷达每次发射激光,测出距离和角度,算出来的坐标(x, y, z),都是以雷达的物理中心为原点的。这就是传感器坐标系,也叫局部坐标系。

但问题来了——雷达装在车上,车在动。雷达每一帧扫到的点,都是相对于当时那个位置的。如果你直接把两帧的点云叠在一起,肯定对不上。

所以我们需要一个固定的参考系,叫世界坐标系。所有传感器数据,最终都要变换到世界坐标系下,才能做拼接、建图。

核心概念:

  • 传感器坐标系:以传感器自身为原点,随传感器移动而移动
  • 世界坐标系:固定不变,通常以机器人启动位置或地图原点为基准
  • 变换:把传感器坐标系下的点,通过旋转和平移,映射到世界坐标系

我在项目里见过有人直接把雷达坐标当成世界坐标用,结果建出来的地图全是歪的。后来我养成了一个习惯:拿到点云数据,第一件事就是确认坐标系定义。

3.2 欧拉角与旋转矩阵

坐标系变换,核心就两件事:旋转平移

平移好理解,就是加个偏移量。旋转呢?我们得用数学来描述「一个坐标系怎么转才能和另一个坐标系对齐」。

3.2.1 欧拉角

欧拉角是最直观的旋转表示方式。说白了,就是绕三个轴分别转多少度。

  • 绕X轴旋转:称为翻滚角(Roll)
  • 绕Y轴旋转:称为俯仰角(Pitch)
  • 绕Z轴旋转:称为偏航角(Yaw)

但欧拉角有个大坑——万向锁。当俯仰角接近±90°时,翻滚和偏航会变得无法区分。我曾经在调试一个无人机项目时,就因为这个原因,姿态解算直接炸了。从那以后,我内部计算一律用旋转矩阵或四元数,欧拉角只用来给人看。

避坑指南:欧拉角适合做可视化展示,不适合做插值和迭代计算。如果你要做连续旋转,建议用旋转矩阵或四元数。

3.2.2 旋转矩阵

旋转矩阵是一个3x3的正交矩阵,行列式为+1。它把一个三维向量旋转到另一个方向。

绕X轴旋转θ角的矩阵:

R_x(θ) = [[1, 0, 0],
          [0, cosθ, -sinθ],
          [0, sinθ, cosθ]]

绕Y轴旋转θ角的矩阵:

R_y(θ) = [[cosθ, 0, sinθ],
          [0, 1, 0],
          [-sinθ, 0, cosθ]]

绕Z轴旋转θ角的矩阵:

R_z(θ) = [[cosθ, -sinθ, 0],
          [sinθ, cosθ, 0],
          [0, 0, 1]]

多个旋转组合时,只需要把矩阵乘起来就行。注意顺序:先绕Z转,再绕Y转,最后绕X转,对应的矩阵乘法是 R = R_x * R_y * R_z。

我的习惯:写代码时,我会把旋转矩阵和位移向量拼成一个4x4的齐次变换矩阵。这样一次矩阵乘法就能完成旋转+平移,代码更干净。

3.3 使用Open3D进行点云旋转与平移

理论讲完了,咱们直接上代码。Open3D处理点云变换非常方便。

3.3.1 加载点云并可视化

import open3d as o3d
import numpy as np

# 读取点云
pcd = o3d.io.read_point_cloud("example.pcd")
print(f"点云点数: {len(pcd.points)}")

# 可视化原始点云
o3d.visualization.draw_geometries([pcd], window_name="原始点云")

3.3.2 点云平移

# 定义平移向量 (沿X轴移动2米,沿Y轴移动1米,沿Z轴移动0.5米)
translation = np.array([2.0, 1.0, 0.5])

# 平移点云
pcd_translated = pcd.translate(translation)

# 可视化平移后的点云
o3d.visualization.draw_geometries([pcd_translated], window_name="平移后点云")

3.3.3 点云旋转

# 定义旋转角度 (弧度)
theta = np.pi / 4  # 45度

# 构建绕Z轴的旋转矩阵
R = pcd.get_rotation_matrix_from_xyz((0, 0, theta))

# 旋转点云
pcd_rotated = pcd.rotate(R, center=(0, 0, 0))

# 可视化旋转后的点云
o3d.visualization.draw_geometries([pcd_rotated], window_name="旋转后点云")

注意:rotate方法的center参数指定旋转中心。如果不指定,默认绕原点旋转。如果你想让点云绕自身中心旋转,可以先计算点云质心,再以质心为旋转中心。

3.3.4 使用齐次变换矩阵

# 构建4x4齐次变换矩阵
T = np.eye(4)
T[:3, :3] = R  # 旋转部分
T[:3, 3] = [2.0, 1.0, 0.5]  # 平移部分

# 应用变换
pcd_transformed = pcd.transform(T)

# 可视化
o3d.visualization.draw_geometries([pcd_transformed], window_name="变换后点云")

我个人更推荐用齐次变换矩阵的方式。为什么?因为一次变换就能搞定旋转+平移,而且多个变换可以连乘,代码可读性高很多。

3.4 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张图:

点云坐标系与变换知识体系 坐标系 世界坐标系 vs 传感器坐标系 旋转表示 欧拉角 · 旋转矩阵 · 四元数 Open3D实现 平移 · 旋转 · 齐次变换 传感器坐标系:以雷达为中心 世界坐标系:固定参考系 变换:从局部到全局的映射 欧拉角:Roll/Pitch/Yaw 旋转矩阵:3x3正交矩阵 万向锁:欧拉角的致命缺陷 translate():点云平移 rotate():点云旋转 transform():齐次变换 核心:理解坐标系关系 → 选择合适旋转表示 → 用Open3D高效实现

3.5 实战中的注意事项

最后,分享几个我在实际项目中总结的经验:

  1. 坐标系定义要统一:项目一开始就定好坐标系规则,比如X轴朝前、Y轴朝左、Z轴朝上。团队所有人都用同一套标准。
  2. 旋转顺序要明确:欧拉角的旋转顺序不同,结果完全不同。我习惯用Z-Y-X顺序,这也是机器人领域最常用的。
  3. 验证变换是否正确:每次做完变换,我都会可视化一下,看看点云是否在预期位置。肉眼检查虽然土,但很有效。
  4. 注意单位:角度用弧度还是度数?Open3D里默认是弧度。我曾经因为忘记转换,把点云转到了天上去。

小技巧:如果你不确定旋转矩阵对不对,可以用单位向量做测试。比如把(1,0,0)旋转90度,看是不是变成(0,1,0)。这种小测试能帮你快速定位问题。

好了,坐标系和变换这部分就聊到这里。记住一句话:坐标系是点云处理的基石,变换是连接不同坐标系的桥梁。搞懂了这些,后面的点云配准、建图才会顺风顺水。


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