2. 旋转矩阵参数:欧拉角、旋转矩阵与四元数的参数化表示
说到旋转参数化,我得先坦白一件事。刚入行那会儿,我觉得欧拉角就够用了,简单直观。直到有一次做无人机姿态解算,万向锁把整个系统搞崩了——嗯,从那以后我才老老实实把四元数啃下来。
今天咱们就把这三种表示法掰开揉碎。你想想看,旋转在三维空间里其实就三件事:绕哪个轴、转多少度、顺序怎么排。但不同的表示法,坑可完全不一样。
2.1 欧拉角:最直观,但最危险
欧拉角说白了就是三个角度:绕X轴转的叫滚转角(roll),绕Y轴转的叫俯仰角(pitch),绕Z轴转的叫偏航角(yaw)。
我个人习惯用ZYX顺序,也就是先绕Z轴转ψ,再绕Y轴转θ,最后绕X轴转φ。写成矩阵就是:
R = Rz(ψ) * Ry(θ) * Rx(φ)
展开之后长这样:
R = [[cosψ cosθ, cosψ sinθ sinφ - sinψ cosφ, cosψ sinθ cosφ + sinψ sinφ],
[sinψ cosθ, sinψ sinθ sinφ + cosψ cosφ, sinψ sinθ cosφ - cosψ sinφ],
[-sinθ, cosθ sinφ, cosθ cosφ]]
当俯仰角θ = ±90°时,cosθ = 0,你会发现滚转和偏航的旋转轴重合了。这时候系统会丢失一个自由度。我曾经在机械臂标定中踩过这个坑,手臂在奇异点附近疯狂抖动,差点把工件甩飞。
欧拉角的优势是直观,调试时看一眼数值就知道姿态大概什么样。但做插值?别想了。两个姿态之间的欧拉角插值路径会非常诡异,物体在空中翻跟头都是轻的。
2.2 旋转矩阵:计算友好,但冗余
旋转矩阵是个3×3的正交矩阵,满足RTR = I,行列式为+1。说白了,它就是把一个坐标系里的向量,映射到另一个坐标系。
我个人觉得旋转矩阵最大的好处是组合方便。你要连续旋转?直接矩阵乘法就行:
R_total = R3 * R2 * R1
但问题也很明显——9个参数,只有3个自由度。剩下的6个是约束条件。你想想看,如果因为数值误差导致矩阵不再正交,那它就不是旋转矩阵了,而是带剪切的仿射变换。
每次更新旋转矩阵后,我都会做一次正交化:R ← (R * RT)-1/2 * R。虽然多了一步计算,但能避免累积误差把系统搞崩。
旋转矩阵的插值也不容易。你不能直接对9个元素做线性插值,那样得到的矩阵大概率不是正交的。得用矩阵对数映射到李代数,再插值——嗯,这就绕到四元数了。
2.3 四元数:优雅,但抽象
四元数是个超复数,形式是q = w + xi + yj + zk,其中i² = j² = k² = ijk = -1。单位四元数(模长为1)可以用来表示旋转。
为什么用四元数?三个理由:
- 无万向锁——这是最核心的优势
- 插值平滑——球面线性插值(SLERP)能给出最短路径
- 计算高效——比矩阵少一半的乘法
从旋转矩阵转四元数,公式是这样的:
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时,分母会很小,导致数值爆炸。我曾经在IMU数据融合时遇到过这个问题,解算出来的四元数直接飞了。解决办法是判断w的绝对值,如果太小就改用其他元素做分母。
四元数的乘法也很有意思。两个旋转q1和q2的复合,不是简单的元素相乘:
q = q1 * q2
w = w1*w2 - x1*x2 - y1*y2 - z1*z2
x = w1*x2 + x1*w2 + y1*z2 - z1*y2
y = w1*y2 - x1*z2 + y1*w2 + z1*x2
z = w1*z2 + x1*y2 - y1*x2 + z1*w2
注意顺序——q1 * q2表示先转q2再转q1,跟矩阵乘法是反的。这个坑我见过不少人掉进去。
2.4 三种表示法的对比
| 特性 | 欧拉角 | 旋转矩阵 | 四元数 |
|---|---|---|---|
| 参数数量 | 3 | 9 | 4 |
| 自由度 | 3 | 3(含6个约束) | 3(含1个约束) |
| 万向锁 | 有 | 无 | 无 |
| 插值 | 困难 | 需李代数 | SLERP |
| 计算效率 | 高 | 低 | 中 |
| 直观性 | 高 | 低 | 低 |
做可视化调试用欧拉角,做底层计算用四元数,需要跟OpenGL/DirectX交互时转成旋转矩阵。说白了,各取所长。
2.5 知识体系结构图
这张图把三种表示法的核心特征都标出来了。你看,欧拉角在最左边,直观但有硬伤;旋转矩阵在中间,计算方便但冗余;四元数在最右边,抽象但优雅。实际项目中,我通常会在不同阶段切换使用——调试用欧拉角,核心算法用四元数,渲染用矩阵。
我曾经在写SLAM前端时,直接用欧拉角做优化变量。结果每次迭代都要处理万向锁附近的奇异值,代码里全是if-else。后来换成四元数做优化,虽然雅可比矩阵推导麻烦了点,但再也不用担心奇异问题了。
嗯,三种表示法各有各的脾气。选对工具,你的代码能少一半的bug。