3. 欧拉角与四元数:姿态表示的两种武器

说到姿态表示,圈里一直有个老话题——欧拉角和四元数到底选哪个?我刚开始做飞控那会儿,觉得欧拉角多直观啊,俯仰、横滚、偏航,一看就懂。直到有一次,无人机在天上突然“抽搐”了一下,差点炸机……查了半天,原来是万向锁搞的鬼。从那以后,我对四元数再也不敢轻视了。

今天咱们就把这两个东西掰开揉碎,看看它们各自有什么本事,又该怎么互相转换。

3.1 欧拉角(RPY)——直观但带坑

欧拉角用三个角度描述旋转:横滚(Roll)、俯仰(Pitch)、偏航(Yaw)。说白了,就是绕X轴、Y轴、Z轴分别转多少度。

优点:直观、易理解、占用内存小(3个浮点数)。

缺点:存在万向锁问题、插值不平滑、旋转顺序依赖性强。

万向锁是什么?我举个例子。你把手机平放在桌上,先绕Z轴转90度(偏航),再绕Y轴转90度(俯仰),这时候你会发现——绕X轴的旋转和绕Z轴的旋转效果重叠了。你丢失了一个自由度。这就是万向锁。

避坑指南:我曾经在机械臂控制中用过欧拉角做插值,结果末端执行器走了一条奇怪的弧线。后来换成四元数球面插值,轨迹才变得平滑。如果你的系统涉及大角度旋转或连续姿态变化,欧拉角不是好选择。

3.2 四元数——数学优雅,工程实用

四元数是个超复数,形式为 q = w + xi + yj + zk,其中 w 是实部,(x, y, z) 是虚部。它用4个参数表示旋转,没有奇点,插值平滑。

特性 欧拉角 四元数
参数数量 3 4
万向锁 存在 不存在
插值平滑性 差 好(球面插值)
计算效率 高(三角函数多) 高(乘法为主)
直观性 强 弱
内存占用 12字节 16字节

你想想看,四元数为什么没有万向锁?因为它把旋转表示成绕一个单位向量旋转某个角度,而不是分解成三个轴的顺序旋转。这就避免了“顺序依赖”带来的奇点。

3.3 相互转换方法

实际工程中,我们经常需要在欧拉角和四元数之间来回切换。比如传感器输出欧拉角,但姿态解算用四元数。下面给出Python实现。

3.3.1 欧拉角 → 四元数

假设旋转顺序为 ZYX(偏航→俯仰→横滚),这是航空领域最常用的约定。

import math

def euler_to_quaternion(roll, pitch, yaw):
    """
    将欧拉角(弧度)转换为四元数
    旋转顺序:ZYX(偏航→俯仰→横滚)
    """
    cy = math.cos(yaw * 0.5)
    sy = math.sin(yaw * 0.5)
    cp = math.cos(pitch * 0.5)
    sp = math.sin(pitch * 0.5)
    cr = math.cos(roll * 0.5)
    sr = math.sin(roll * 0.5)

    w = cr * cp * cy + sr * sp * sy
    x = sr * cp * cy - cr * sp * sy
    y = cr * sp * cy + sr * cp * sy
    z = cr * cp * sy - sr * sp * cy

    return (w, x, y, z)

3.3.2 四元数 → 欧拉角

def quaternion_to_euler(w, x, y, z):
    """
    将四元数转换为欧拉角(弧度)
    输出顺序:(roll, pitch, yaw)
    """
    # 计算俯仰角(注意万向锁检测)
    sin_pitch = 2.0 * (w * y - z * x)
    
    # 检测万向锁
    if abs(sin_pitch) >= 1.0:
        pitch = math.copysign(math.pi / 2, sin_pitch)
        # 此时横滚和偏航耦合,通常设横滚为0
        roll = 0.0
        yaw = math.atan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
    else:
        pitch = math.asin(sin_pitch)
        roll = math.atan2(2.0 * (w * x + y * z), 1.0 - 2.0 * (x * x + y * y))
        yaw = math.atan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
    
    return (roll, pitch, yaw)

个人经验:我在做IMU数据融合时,习惯用四元数做状态估计,只在日志记录和调试界面才转成欧拉角。这样既避免了万向锁,又方便人看。另外,四元数转欧拉角时一定要做万向锁检测,否则你会得到NaN。

3.4 知识体系总览

下面这张图帮你理清欧拉角和四元数的关系,以及在实际系统中的使用流程。

姿态表示知识体系 欧拉角 (RPY) 3参数 | 直观 | 有奇点 四元数 4参数 | 无奇点 | 平滑 euler_to_quaternion() quaternion_to_euler() 应用场景 人机交互界面 显示姿态给操作员 传感器数据 IMU/磁力计输出 姿态解算 卡尔曼/互补滤波 控制指令 PID/轨迹规划 推荐策略 内部计算用四元数 → 日志/调试转欧拉角 → 人机交互显示RPY 注意事项 ① 四元数必须归一化 ② 欧拉角旋转顺序要统一 ③ 转换时注意万向锁检测

3.5 工程实践建议

说了这么多,到底该怎么选?我给出几条实战经验:

  • 传感器驱动层:用欧拉角接收原始数据(大部分IMU芯片直接输出RPY)
  • 姿态解算层:转成四元数做滤波和融合(避免万向锁,计算效率高)
  • 控制层:用四元数做插值和指令生成(球面插值平滑自然)
  • 显示层:转回欧拉角给人看(直观,方便调试)

又一个坑:四元数一定要归一化!我见过有人直接拿传感器原始四元数做运算,结果姿态越算越偏。原因就是四元数模长偏离了1。每次更新后加一行 q /= norm(q),能省很多调试时间。

嗯,欧拉角和四元数就聊到这儿。记住一句话:人看用欧拉角,机器算用四元数。掌握了转换方法,你在姿态估计、机器人控制、无人机飞控这些领域就能游刃有余了。