4. 旋转表示方法:旋转矩阵、欧拉角(RPY)、四元数、轴角表示及相互转换
说到旋转表示,这大概是刚入行时最容易踩坑的地方了。我记得第一次做多传感器融合,明明标定流程都走完了,点云投影到图像上就是歪的。折腾了两天才发现——是欧拉角的万向锁问题。从那以后,我对每种旋转表示法的脾气都摸得清清楚楚。
说白了,三维空间里的旋转,数学上有很多种描述方式。每种方式都有自己的长处和短板。你想想看,旋转矩阵直观但冗余,欧拉角好理解但会锁死,四元数计算稳但反直觉,轴角最自然但不好直接插值。咱们做工程,得知道什么时候该用哪个。
4.1 旋转矩阵
旋转矩阵是最基础的表示法。它是一个 3×3 的正交矩阵,行列式为 +1。用 R 表示,满足 RTR = I。
它的核心作用是把一个向量从一个坐标系变换到另一个坐标系。比如点 p 在坐标系 A 下的坐标是 pA,在坐标系 B 下的坐标是 pB,那么:
p_B = R_AB * p_A
这里 R_AB 表示从 A 到 B 的旋转矩阵。
旋转矩阵的性质:
- 正交性:RTR = I,R-1 = RT
- 行列式为 +1(纯旋转,不含反射)
- 三个列向量互相正交,且都是单位向量
- 9 个元素,但只有 3 个自由度
我在项目中一般不会直接用旋转矩阵做插值或优化。为什么?因为 9 个参数要满足 6 个约束方程,优化起来太麻烦。但旋转矩阵做坐标变换是最直接的,尤其在激光雷达点云变换时,我习惯用矩阵乘法批量处理。
4.2 欧拉角(RPY)
欧拉角用三个角度来描述旋转。最常用的是 RPY 顺序:先绕 X 轴转 Roll(横滚),再绕 Y 轴转 Pitch(俯仰),最后绕 Z 轴转 Yaw(偏航)。
嗯,这里要注意:欧拉角的顺序很重要。同样的三个角度,顺序不同,结果完全不同。我见过有人把 RPY 和 YPR 搞混,结果标定出来的外参全错了。
| 角度 | 符号 | 范围 | 物理含义 |
|---|---|---|---|
| 横滚 | Roll (φ) | [-π, π] | 绕 X 轴旋转,像飞机侧倾 |
| 俯仰 | Pitch (θ) | [-π/2, π/2] | 绕 Y 轴旋转,像飞机抬头低头 |
| 偏航 | Yaw (ψ) | [-π, π] | 绕 Z 轴旋转,像飞机转向 |
万向锁(Gimbal Lock): 当 Pitch = ±90° 时,Roll 和 Yaw 的旋转轴会重合,丢失一个自由度。我曾经在做车载激光雷达标定时,车辆停在坡道上,Pitch 刚好接近 90°,结果欧拉角表示直接崩了。从那以后,我只要涉及连续旋转或插值,一律用四元数。
欧拉角转旋转矩阵的公式(RPY 顺序):
R = R_z(ψ) * R_y(θ) * R_x(φ)
其中:
R_x(φ) = [[1, 0, 0],
[0, cosφ, -sinφ],
[0, sinφ, cosφ]]
R_y(θ) = [[cosθ, 0, sinθ],
[0, 1, 0],
[-sinθ, 0, cosθ]]
R_z(ψ) = [[cosψ, -sinψ, 0],
[sinψ, cosψ, 0],
[0, 0, 1]]
4.3 四元数
四元数是我个人最喜欢的旋转表示法。它用四个参数 q = (w, x, y, z) 来表示旋转,其中 w 是实部,x、y、z 是虚部。单位四元数满足 w² + x² + y² + z² = 1。
为什么我喜欢它?因为四元数没有万向锁,插值平滑(可以用 Slerp),而且计算效率高。在激光雷达 SLAM 和 IMU 融合中,我几乎只用四元数。
实用技巧: 四元数表示绕单位向量 (ux, uy, uz) 旋转 θ 角度:
q = (cos(θ/2), ux·sin(θ/2), uy·sin(θ/2), uz·sin(θ/2))
这个形式在做轴角转换时特别有用。
四元数乘法(表示旋转的复合):
q1 * q2 = (w1*w2 - x1*x2 - y1*y2 - z1*z2,
w1*x2 + x1*w2 + y1*z2 - z1*y2,
w1*y2 - x1*z2 + y1*w2 + z1*x2,
w1*z2 + x1*y2 - y1*x2 + z1*w2)
用四元数旋转一个向量 v:
v' = q * v * q_conj
其中 q_conj = (w, -x, -y, -z) 是共轭四元数。
4.4 轴角表示
轴角表示是最符合直觉的:用一个单位向量 u 表示旋转轴,一个标量 θ 表示旋转角度。记作 (u, θ) 或直接用向量形式 θ·u。
轴角的好处是参数少(3 个),物理意义明确。但它有两个问题:一是当 θ 接近 0 时,轴的方向不稳定;二是不能直接做插值。
轴角转旋转矩阵(罗德里格斯公式):
R = I + sinθ * [u]× + (1 - cosθ) * [u]ײ
其中 [u]× 是 u 的反对称矩阵:
[u]× = [[0, -u_z, u_y],
[u_z, 0, -u_x],
[-u_y, u_x, 0]]
4.5 相互转换
实际工程中,我们经常需要在不同表示法之间来回切换。下面这张图是我自己总结的转换关系:
4.6 关键转换公式
下面是我工作中最常用的几个转换,直接贴代码:
四元数 → 旋转矩阵
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²)]]
旋转矩阵 → 四元数
w = sqrt(1 + R[0][0] + R[1][1] + R[2][2]) / 2
x = (R[2][1] - R[1][2]) / (4*w)
y = (R[0][2] - R[2][0]) / (4*w)
z = (R[1][0] - R[0][1]) / (4*w)
数值稳定性: 当 w 接近 0 时,上面公式会出问题。我一般会判断 w 的大小,如果 w 太小,就改用其他三个元素的最大值做分母。具体实现时,可以比较 R 的迹,选择数值最稳定的分支。
欧拉角 → 四元数(RPY 顺序)
q = q_z(yaw) * q_y(pitch) * q_x(roll)
其中:
q_x(roll) = (cos(roll/2), sin(roll/2), 0, 0)
q_y(pitch) = (cos(pitch/2), 0, sin(pitch/2), 0)
q_z(yaw) = (cos(yaw/2), 0, 0, sin(yaw/2))
轴角 → 四元数
q = (cos(θ/2), u_x*sin(θ/2), u_y*sin(θ/2), u_z*sin(θ/2))
四元数 → 轴角
θ = 2 * acos(w)
u = (x, y, z) / sin(θ/2)
避坑指南: 我曾经在写四元数转轴角时,忘了处理 θ=0 的情况。结果 sin(0)=0,除零错误直接让程序崩溃。后来我加了一个判断:如果 w 接近 1,直接返回 θ=0,轴任意。
4.7 工程选型建议
说了这么多,到底该用哪个?我个人的经验是:
- 做标定优化:用四元数。因为四元数只有 4 个参数加一个单位约束,优化起来比旋转矩阵的 9 个参数加 6 个约束简单得多。
- 做点云变换:用旋转矩阵。因为矩阵乘法可以直接用 GPU 或 SIMD 加速,批量处理几百万个点很高效。
- 做可视化调试:用欧拉角。人眼一看就懂,方便排查问题。
- 做 IMU 积分:用四元数。因为 IMU 的角速度积分天然适合四元数更新。
你想想看,如果选错了表示法,轻则代码跑得慢,重则出现万向锁或者数值不稳定。所以每次开始一个新模块,我都会先想清楚:这个场景下,哪种旋转表示最合适?