3. 刚体变换与齐次坐标:旋转矩阵、平移向量、齐次坐标矩阵、欧拉角与四元数

刚体变换,说白了就是物体在三维空间里「挪个地方」——不拉伸、不扭曲,只是换个位置和朝向。激光雷达每次扫描,得到的点云都在传感器自己的坐标系里。要想把这些点放到车体坐标系、或者全局地图里,就必须搞懂刚体变换。

我个人习惯把刚体变换拆成两部分看:旋转和平移。旋转决定朝向,平移决定位置。两者合在一起,就是一个完整的刚体变换。

3.1 旋转矩阵:三维空间里的方向描述

旋转矩阵是个 3×3 的正交矩阵,行列式为 +1。它的每一列,代表旋转后的坐标轴在原坐标系下的方向向量。

举个例子,绕 Z 轴旋转 θ 角,旋转矩阵长这样:

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

绕 X 轴和 Y 轴的类似,只是把 sin/cos 放到对应位置。我刚开始做标定时,经常把符号搞反——顺时针和逆时针混了。后来养成习惯:右手法则,拇指指向旋转轴正方向,四指弯曲方向就是正角度方向。

我的小技巧: 写旋转矩阵前,先在纸上画个坐标系,标出旋转方向。写代码时再对着检查一遍,能省很多调试时间。

3.2 平移向量:从原点到原点的位移

平移向量 t = [t_x, t_y, t_z]^T,表示从源坐标系原点到目标坐标系原点的位移。注意方向——是「从 A 到 B」还是「B 在 A 中的坐标」?我见过不少同事在这上面栽跟头。

旋转加平移,完整的刚体变换公式:

p' = R * p + t

其中 p 是源坐标系下的点,p' 是目标坐标系下的点。R 是旋转矩阵,t 是平移向量。

避坑指南: 我曾经在项目里把平移方向搞反了,结果点云全部偏到马路对面。排查了整整一下午才发现——t 向量写成了负值。记住:公式是 p' = R*p + t,不是 p' = R*p - t。

3.3 齐次坐标矩阵:把旋转和平移塞进一个矩阵

每次都要写 R*p + t,有点啰嗦。而且旋转和平移是分开的,不方便连续变换。齐次坐标就是来解决这个问题的。

把三维点 p = [x, y, z]^T 扩展成四维齐次坐标 [x, y, z, 1]^T。然后构造一个 4×4 的齐次变换矩阵:

T = [
  [R_11, R_12, R_13, t_x],
  [R_21, R_22, R_23, t_y],
  [R_31, R_32, R_33, t_z],
  [0,    0,    0,    1  ]
]

这样,一次矩阵乘法就完成了旋转和平移:

p'_hom = T * p_hom

连续变换就更方便了。比如从激光雷达到车体,再到全局地图:

T_map_lidar = T_map_vehicle * T_vehicle_lidar

矩阵乘法顺序很重要——先右后左。我习惯从右往左读:先做激光雷达到车体,再做车体到地图。

核心要点: 齐次变换矩阵的逆矩阵,就是反向变换。计算逆矩阵时,R 部分转置,t 部分取 -R^T * t。不用真的去求 4×4 矩阵的逆,那样太慢了。

3.4 欧拉角:直观但容易踩坑

欧拉角用三个角度描述旋转:绕 X 轴(roll)、绕 Y 轴(pitch)、绕 Z 轴(yaw)。直观好理解,但坑不少。

最大的问题是万向锁——当 pitch 接近 ±90° 时,roll 和 yaw 的旋转轴会重合,丢失一个自由度。我在做无人机标定时遇到过,飞机俯仰到 90° 时,航向角突然乱跳。

另一个问题是旋转顺序。常见的顺序有 ZYX、ZYZ、XYZ 等。不同顺序得到的欧拉角完全不同。ROS 里默认是 ZYX(先绕 Z,再绕 Y,最后绕 X)。

旋转顺序 常见应用场景 说明
ZYX 无人机、车辆姿态 yaw → pitch → roll,ROS 默认
XYZ 机械臂末端 绕固定轴旋转,常用于工业机器人
ZYZ 某些数学库 避免万向锁,但不够直观
避坑指南: 我曾经在两个模块之间传递欧拉角,一个用 ZYX,一个用 XYZ,结果标定出来的外参完全不对。后来我强制团队:所有欧拉角必须标注旋转顺序,否则一律用四元数。

3.5 四元数:稳定高效的旋转表示

四元数 q = [w, x, y, z]^T,其中 w 是实部,x、y、z 是虚部。它没有万向锁问题,插值平滑,计算效率高。

单位四元数表示旋转:

q = [cos(θ/2), sin(θ/2)*u_x, sin(θ/2)*u_y, sin(θ/2)*u_z]^T

其中 u = [u_x, u_y, u_z]^T 是旋转轴单位向量,θ 是旋转角度。

用四元数旋转一个点 p:

p' = q * p * q_conjugate

这里 p 要写成纯四元数 [0, x, y, z]^T。q_conjugate 是 q 的共轭 [w, -x, -y, -z]^T。

我的习惯: 在代码里统一用四元数做内部计算,只在用户界面或日志输出时转成欧拉角。这样既避免了万向锁,又方便调试时直观理解。

3.6 四种表示方式的对比

表示方式 优点 缺点 推荐场景
旋转矩阵 直观,容易组合变换 9 个参数,有冗余 连续变换、坐标转换
欧拉角 直观,参数少 万向锁,顺序依赖 人机交互、调试
四元数 无万向锁,插值平滑 不够直观 内部计算、滤波、优化
轴角 紧凑,几何意义明确 插值困难 旋转增量、李代数

3.7 知识体系结构图

下面这张图梳理了刚体变换的核心概念和它们之间的关系:

刚体变换知识体系 刚体变换 旋转 (Rotation) 平移 (Translation) 旋转矩阵 欧拉角 四元数 平移向量 t 齐次变换矩阵 T (4×4) 左上 3×3: R 右上 3×1: t p' = T · p = R · p + t | 连续变换:T₁₃ = T₁₂ · T₂₃

3.8 实战中的选择建议

说了这么多,到底该用哪种?我根据实际项目经验,给几个建议:

  • 做标定优化时:用四元数 + 平移向量。参数少(7 个),没有奇异性,适合非线性优化。
  • 做坐标转换时:用齐次变换矩阵。一次乘法搞定,连续变换方便。
  • 做可视化调试时:转成欧拉角看。直观,一眼能看出姿态对不对。
  • 做插值动画时:用四元数球面线性插值(SLERP)。平滑无抖动。
一句话总结: 内部计算用四元数,坐标转换用齐次矩阵,人机交互用欧拉角。各取所长,别死磕一种。