3、坐标系统与空间变换:世界坐标系、传感器坐标系、刚体变换

坐标系统,说白了就是给空间里的每个点一个“身份证号”。做多传感器融合,最怕的就是各说各话——激光雷达说目标在(1,0,0),相机说在(0,1,0),其实指的是同一个点,但坐标系没对齐。我刚开始做融合时,就吃过这个亏,数据对了好久才发现是坐标轴方向定义反了。

这一章,咱们把坐标系统和空间变换彻底讲透。你理解了这些,后面做传感器标定、数据对齐,才能心里有底。

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

先搞清楚两个基本概念:

  • 世界坐标系:一个固定的参考系,所有传感器数据最终都要统一到这里。比如自动驾驶里,通常把车辆启动时的位置设为原点,x轴指北,y轴指东,z轴朝天。
  • 传感器坐标系:每个传感器自己的“小天地”。相机以光心为原点,激光雷达以旋转中心为原点,IMU以自身测量中心为原点。

为什么要区分?因为每个传感器安装位置不同,朝向也不同。你想想看,车顶的激光雷达和保险杠上的毫米波雷达,看到的同一个障碍物,坐标值肯定不一样。

核心原则:所有传感器数据,最终都要变换到同一个世界坐标系下,才能做融合。否则就是鸡同鸭讲。

3.2 刚体变换:旋转矩阵与平移向量

从一个坐标系到另一个坐标系,本质上是刚体变换——物体本身不变,只是我们看它的角度和位置变了。刚体变换由两部分组成:

  • 旋转:用旋转矩阵 R 表示,3×3 的正交矩阵,行列式为 +1。
  • 平移:用平移向量 t 表示,3×1 的列向量。

公式很简单:p' = R * p + t。其中 p 是原坐标系下的点,p' 是目标坐标系下的点。

我记得有一次做多雷达融合,两个雷达的安装角度差了 2.3 度,我一开始没做旋转补偿,结果融合出来的目标位置总是有 30 厘米的偏差。后来加上旋转矩阵,问题立刻解决。嗯,细节决定成败。

3.2.1 旋转矩阵的性质

旋转矩阵有几个重要性质,写代码时经常用到:

性质 数学表达 实际意义
正交性 RTR = I 旋转不改变向量长度
行列式为 1 det(R) = 1 保证是刚体旋转,不是镜像
逆等于转置 R-1 = RT 反向旋转直接用转置,省计算量

小技巧:代码里判断一个矩阵是不是合法的旋转矩阵,就检查 RTR 是否接近单位矩阵,以及行列式是否接近 1。我一般用 np.allclose(R.T @ R, np.eye(3)) 来验证。

3.2.2 平移向量的坑

平移向量看起来简单,但有个容易搞混的地方:平移的方向。公式 p' = R * p + t 里的 t,是从原坐标系原点指向目标坐标系原点的向量,在原坐标系下表示。

我曾经在写标定程序时,把平移向量方向搞反了,结果所有点都往反方向偏了 1 米。排查了半天,才发现是 t 的符号问题。所以,写代码时一定要注释清楚:# t: 从传感器坐标系原点指向世界坐标系原点的向量,在传感器坐标系下表示。

3.3 四元数与欧拉角

旋转矩阵有 9 个参数,但实际自由度只有 3 个。参数多了,容易引入冗余和数值问题。所以工程上更常用四元数和欧拉角。

3.3.1 欧拉角

欧拉角用三个角度表示旋转:绕 x 轴(滚转 roll)、绕 y 轴(俯仰 pitch)、绕 z 轴(偏航 yaw)。直观,但有个大问题——万向锁。

当俯仰角接近 ±90° 时,滚转和偏航会变得无法区分。我做过一个无人机项目,飞控里用欧拉角做姿态解算,飞机垂直爬升时姿态突然跳变,差点炸机。后来换成四元数,问题才解决。

警告:如果你做的是全姿态运动(比如无人机、机器人手臂),千万别只用欧拉角。四元数才是正道。

3.3.2 四元数

四元数用 4 个参数表示旋转:q = w + xi + yj + zk,其中 w 是实部,x、y、z 是虚部。它没有奇点,插值平滑,计算效率高。

四元数转旋转矩阵的公式:

R = [[1-2(y²+z²),   2(xy-wz),   2(xz+wy)],
     [  2(xy+wz), 1-2(x²+z²),   2(yz-wx)],
     [  2(xz-wy),   2(yz+wx), 1-2(x²+y²)]]

看着复杂?别怕,实际开发中直接用库函数就行。比如在 Python 里:

from scipy.spatial.transform import Rotation as R

# 四元数 [x, y, z, w] 转旋转矩阵
q = [0.707, 0, 0, 0.707]  # 绕 x 轴旋转 90 度
rot = R.from_quat(q)
matrix = rot.as_matrix()
print(matrix)

3.3.3 什么时候用什么?

表示方式 优点 缺点 推荐场景
旋转矩阵 直观,容易组合 9 个参数,有冗余 理论推导、坐标变换计算
欧拉角 直观,三个角度 万向锁,插值困难 人机交互、小角度姿态
四元数 无奇点,插值平滑 不够直观 姿态估计、SLAM、滤波

我个人习惯:存储和计算用四元数,展示和调试用欧拉角。这样既避免了万向锁,又方便人理解。

3.4 坐标转换实战

理论说完了,来点实际的。假设你有一个激光雷达,安装在小车前方 0.5 米、高 0.3 米处,俯仰角向下 5 度。现在雷达检测到一个点,在雷达坐标系下是 (2, 1, 0)。怎么转换到世界坐标系?

步骤很简单:

  1. 定义旋转:根据安装角度,构造旋转矩阵(或四元数)。
  2. 定义平移:根据安装位置,构造平移向量。
  3. 执行变换:p_world = R * p_lidar + t。

代码实现:

import numpy as np
from scipy.spatial.transform import Rotation as R

# 1. 雷达安装参数
# 俯仰角 -5 度(向下),偏航 0,滚转 0
pitch_deg = -5.0
rot = R.from_euler('xyz', [0, pitch_deg, 0], degrees=True)
R_matrix = rot.as_matrix()

# 2. 平移向量(雷达坐标系原点在世界坐标系下的位置)
# 假设世界坐标系原点在小车后轴中心
t = np.array([0.5, 0.0, 0.3])  # x向前,y向左,z向上

# 3. 雷达检测到的点
p_lidar = np.array([2.0, 1.0, 0.0])

# 4. 坐标变换
p_world = R_matrix @ p_lidar + t
print(f"世界坐标系下的点: {p_world}")

避坑指南:我曾经在写变换时,把旋转和平移的顺序搞反了。记住,先旋转,后平移。如果先平移再旋转,结果完全不对。

3.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你对照着看,思路会更清晰。

坐标系统与空间变换知识体系 世界坐标系 传感器坐标系 刚体变换 旋转矩阵 R 平移向量 t 齐次矩阵 欧拉角 (roll/pitch/yaw) 四元数 (w,x,y,z) 轴角表示 (旋转向量) 实战:传感器标定 → 坐标对齐 → 数据融合 先旋转后平移 | 四元数存储 | 欧拉角调试 💡 核心思路:所有传感器数据 → 统一到世界坐标系 → 再做融合。坐标系没对齐,融合就是白做。 个人经验:存储用四元数,展示用欧拉角,计算用旋转矩阵。各取所长。

嗯,这一章的内容就这些。坐标系统是融合的基石,你把它搞扎实了,后面学传感器标定、时间同步、状态估计,都会轻松很多。记住我强调的那句话:先旋转,后平移;存储用四元数,展示用欧拉角。这是我在无数项目中踩坑踩出来的经验。