资讯动态

四元数姿态解算:从数学原理到飞控工程实践

发布时间:2026/9/9 21:27:14 来源:尧图企业网站定制
我第一次接触四元数是在调飞控姿态解算的时候。当时IMU库里直接给了一组(w, x, y, z)文档上就写着“表示姿态”我以为把四个数转成欧拉角就能看清姿态了。结果一跑起来俯仰角到90度附近时数据乱跳我还以为是板子坏了排查了半天才发现是欧拉角表示本身在奇点附近退化。这个经历让我意识到搞姿态处理的人如果不懂四元数迟早要被奇点坑一回。后来我把四元数从数学规则到飞控里的实际用法完整捋了一遍发现无人机、机器人、云台甚至AR设备里到处都在用它尤其是姿态解算环节绕不开。这篇就当一次复盘讲讲四元数到底解决了什么问题、数学上怎么工作、实际工程里怎么用给正在学IMU或做姿态估计的朋友一个接地气的参考。1. 从姿态描述的痛点说起为什么绕不开四元数1.1 欧拉角很直观但90度附近的坑很难受描述物体在空间中的朝向最直觉的办法是欧拉角先绕一个轴转偏航角yaw再绕另一个轴转俯仰角pitch最后绕第三个轴转横滚角roll。姿态传感器、云台、机械臂控制里用这套非常普遍因为你看到的就是角度调试方便脑子里能直接想象出来。但欧拉角有个绕不过去的坑叫万向锁。当pitch转到正负90度时yaw和roll的旋转轴重合在一起两个自由度塌缩成一个姿态变得不可区分。想象你把手机竖直立起来镜头朝上这时你绕着屏幕法线转和绕着手机长边转画面都在同一个轴上旋转你就没法再分开控制这两个角了。这个现象不是传感器坏了是数学表示本身的奇点。对无人机这种可以做任意姿态飞行的系统来说一旦出现这种状态姿态控制器会完全迷失方向。我早期调云台时踩过这个坑目标俯仰角超过85度以后画面就开始诡异地扭。查遍代码和数据发现运算本身没毛病就是欧拉角在接近奇点时退化了矩阵解算病态输出不稳定。所以只要你的系统允许大角度姿态变化欧拉角就只适合用来给人看不适合作为内部运算的核心表示。1.2 旋转矩阵没奇点但运算成本不划算欧拉角不行直接用旋转矩阵呢3x3旋转矩阵可以无奇异地描述任意姿态不会出现万向锁空间变换关系也十分清晰在许多标定和坐标系变换里都是标准表达。但它的代价也很直接9个元素要存乘法运算量几乎比四元数翻倍。更麻烦的是旋转矩阵必须满足正交且行列式为1的约束。在循环更新过程中浮点数值误差会慢慢让矩阵变得“不像旋转矩阵”必须定期做施密特正交化修正回来。姿态更新通常跑在1kHz甚至更高频率每步都做正交化计算开销一下就上去了。有人会说嵌入式芯片性能也不差省这点运算有意义吗其实姿态解算只是飞控里的一小部分后面还有控制律、传感器融合滤波、电调PWM更新。资源能省则省而且省下来的不只是运算还有代码复杂度和维护成本。四元数只有4个数字更新一次只需要若干次乘加然后做一次归一化就好没有正交性约束的问题光这一点就比旋转矩阵干净很多。1.3 四元数一次解决两个问题没有奇点也更省计算四元数是1843年哈密顿发明的从数学上看是复数在三维空间的扩展一个标量加上三个虚数单位。真正让它成为姿态解算主流的原因是它能无奇异地表示空间中的任意旋转四个参数比旋转矩阵紧凑旋转复合用一次四元数乘法就能完成还天然适合做旋转插值。从几何上讲任何姿态都可以理解为“绕某个轴的单一旋转”四元数直接保存的就是这个轴的朝向和转过角度的一半。因为不用把姿态拆成三段固定轴的欧拉角所以不存在“中间某个角度导致轴重合”的问题。在工程上这意味着从-180度到180度随便转甚至在空间里面打滚姿态表示都不会崩。这就是四元数在姿态解算里不可替代的位置。2. 四元数的数学内核别看只是四个数规则大有讲究2.1 一个标量加一个三维向量半角设计是理解关键四元数一般写成q w xi yj zk也可以看作(w, x, y, z)。w是标量部分后面的(x, y, z)是向量部分。描述旋转时把w理解成cos(θ/2)虚部向量理解成旋转轴单位向量乘以sin(θ/2)θ是绕该轴旋转的角度。这里最关键的是半角设计。为什么不是直接存cosθ因为如果直接存θ对应项两次旋转复合时的公式会非常复杂存半角后旋转复合退化成一次简洁的四元数乘法。也就是说使用四元数时你存的是一个旋转动作的“一半”这个别扭的设计恰恰是四元数好用的原因。在无人机飞控里一个单位四元数就相当于一条“旋转指令”旋转轴在哪、转多少度。比如绕Z轴转90度对应的四元数就是(cos45°, 0, 0, sin45°)。你不需要去想象什么万向锁也不需要记住一群矩阵乘法的顺序只要盯着这个轴和角就够了。2.2 乘法、共轭、模长绕开这三个概念就绕不开四元数四元数乘法哈密顿积定义的展开式很长看着吓人但工程上通常封装成函数后就没几个人再去手工展开它。比起记住公式更重要的是记住两个性质。第一四元数乘法不满足交换律q1⊗q2和q2⊗q1通常不一样。这对应三维旋转复合不交换的现象先绕Z轴转90度再绕X轴转90度和先绕X轴转90度再绕Z轴转90度结果完全不同。代码里谁左谁右一定要按公式来否则姿态会诡异到让你怀疑人生。第二单位四元数的乘法等价于旋转复合。一个单位四元数q1表示旋转R1q2表示旋转R2那么先做R2再做R1整体的合成旋转可以用q1⊗q2表示。但网上不同资料里的符号约定不一致有人用q2⊗q1所以动手实现前一定要先确认自己代码里的旋转顺序约定再写乘法顺序。然后是共轭和模长。共轭就是虚部取反q* (w, -x, -y, -z)。模长|q|就是平方和开根号。单位四元数的逆等于它的共轭这表示反方向旋转。想取消一次旋转直接乘它的共轭就行这个操作在修正算法里经常出现。2.3 旋转向量的标准公式v q ⊗ v ⊗ q*要把一个三维向量v按四元数q旋转先把它写成纯四元数pv (0, vx, vy, vz)然后计算pv q ⊗ pv ⊗ q*得到的纯四元数虚部就是旋转后的向量。这个“两边都要乘”的结构可以理解为左边q负责把向量转过去右边q*负责把坐标系“换回来”并保证模长不变。完整展开这个公式其实就是四元数到旋转矩阵的36个系数很多库函数里提供的quat_to_mat就是这么推导出来的。学习阶段我建议用几个特殊用例验证公式和代码。比如q取单位四元数(1,0,0,0)旋转后向量应该完全不变再比如q表示绕Z轴转90度作用到X轴单位向量(1,0,0)上结果应该是Y轴方向(0,1,0)。这三个用例能排查掉一大半符号错误和轴顺序问题我在换传感器平台后都会先跑一遍这组验证。这里也能解释为什么旋转向量公式能避开万向锁它本质上只做“绕一个三维空间中的轴直接转一个角度”而不是把旋转拆成三段固定轴旋转。欧拉角的问题在于它把一次旋转强制分解成三次固定轴的连续旋转当中间某个角的奇点出现时前后两个轴就重合了。四元数不搞这种分解所以无论物体怎么翻表示都不会退化。2.4 插值平滑动画和路径规划也用得上四元数四元数还有一个欧拉角和旋转矩阵都不好替代的优势旋转插值非常平滑。在3D动画、云台轨迹过渡、机械臂路径规划里经常需要让姿态从A连续过渡到B。如果直接对旋转矩阵做线性插值中间会出现缩放和不自然的扭曲对欧拉角线性插值在奇点附近更是会翻车。四元数的球面线性插值slerp是标准做法先计算两个四元数之间的夹角再按插值参数t在球面上等比例取点。公式可以写成slerp(q1, q2, t) q1 * sin((1-t)θ)/sinθ q2 * sin(tθ)/sinθ其中θ是两四元数夹角。实现时要注意如果q1和q2的点积为负说明它们表示同一旋转但符号相反需要先把其中一个取负再做插值否则会绕远路。姿态解算本身一般不直接做slerp但云台平滑和动画混合里它是主力。3. 四元数姿态解算从IMU数据到空间姿态3.1 先搞清楚传感器各自擅长什么四元数姿态解算在飞控里的典型输入来自IMU也就是惯性测量单元里面通常集成了陀螺仪和加速度计外接或内置磁力计。这三类传感器各有个性和短板搞不清它们就调不好算法。陀螺仪测量角速度短时间内积分得到的角度非常准响应也快但长时间积分会漂移因为零偏和噪声会不断累积。加速度计测量比力在静止时能提供重力方向在机体坐标系的投影从而推算俯仰和横滚但它对运动加速度很敏感无人机一加速数据就会带上一大堆干扰。磁力计测量地磁场方向能提供绝对航向可它极其容易被电机、电调和金属结构干扰。姿态解算要做的事就是把它们融合起来。我的理解是把陀螺仪当作短期姿态的“记忆者”把加速度计和磁力计当作长期姿态的“修正者”。记忆者短期可靠、长期偏航修正者长期靠谱、短期噪声大。算法设计的核心就是怎么加权、怎么反馈。3.2 四元数微分方程和一阶积分更新陀螺仪读到的机体角速度是ω(ωx, ωy, ωz)在四元数表示里把它写成纯四元数ω_q (0, ωx, ωy, ωz)。四元数对时间的导数满足dq/dt 0.5 * q ⊗ ω_q这里的乘法顺序取决于坐标系定义和旋转方向约定实践中必须严格对齐否则会发生旋转方向反转的诡异问题。一阶龙格库塔更新公式很直接q_new q dq/dt * dt然后立刻归一化。下面是Python版本方便你快速验证。import numpy as np def quat_update(q, gyro, dt): # q: (w, x, y, z)gyro: (wx, wy, wz) 单位为 rad/sdt 单位为 s w, x, y, z q gx, gy, gz gyro # dq/dt 0.5 * q * (0, wx, wy, wz) dw 0.5 * (-gx*x - gy*y - gz*z) dx 0.5 * ( gx*w gy*z - gz*y) dy 0.5 * (-gx*z gy*w gz*x) dz 0.5 * ( gx*y - gy*x gz*w) qn np.array([w dw*dt, x dx*dt, y dy*dt, z dz*dt]) return qn / np.linalg.norm(qn)这段更新本身不难难点在外面。第一角速度符号和机体坐标轴的对应关系取决于IMU的安装方向和右手定则传感器数据手册通常会给但你最好自己做一个已知方向的旋转来验证。第二dt必须跟实际采样间隔一致。我见过有人陀螺仪是1kHz采样但循环里忘了换算时间积分快了1000倍姿态直接疯转。用固定模拟输入和理论值对比很容易发现这种问题。3.3 用加速度计修正漂移Mahony算法怎么做如果只用上面的纯积分姿态很快会飘走因为陀螺仪零偏和噪声一直在累积。经典的Mahony互补滤波算法思路清晰、计算量小、调参直观是四元数姿态解算里非常实用的方案。算法的核心思想是根据当前四元数预测机体坐标系下的重力方向v再与实际加速度计测量值a做叉积得到误差向量e。这个叉积误差方向就是“当前姿态偏了多少”的信息通过比例积分PI反馈补偿到陀螺仪角速度上然后再做四元数积分更新。预测重力的公式符号约定需要严格对齐为vx 2 * (q1q3 - q0q2)vy 2 * (q0q1 q2q3)vz q0q0 - q1q1 - q2q2 q3q3叉积误差ex ayvz - azvyey azvx - axvzez axvy - ayvx。然后做PI修正gx Kpex Kiex_intgy Kpey Kiey_intgz Kpez Kiez_int。其中ex_int、ey_int、ez_int是误差积分项用来补偿陀螺零偏。Kp典型值在0.1到1.0之间Ki建议先给0观察静态误差收敛情况再慢慢增加。Ki给太大会导致姿态振荡。代码实现可以直接参考开源社区里常见的MahonyAHRS库很多MCU工程里都有移植。真正需要花心思的不是抄代码而是把坐标系对准加速度计三个轴的方向、陀螺仪三个轴的方向、四元数的右手定则这三者对不上修正项就是错的。3.4 磁力计修正需要航向时再加如果只需要俯仰和横滚用加速度计修正就够了。需要航向yaw时就得引入磁力计。磁力计测得的是地磁场在机体坐标系下的方向先转换到水平面再跟当前四元数预测的航向做差用同样的PI结构去修正。磁力计的坑在于它太容易受干扰了。无人机上电调一开启磁场一变yaw就开始飘甚至出现姿态整体拧着走的现象。解决办法是做好磁力计校准一般用椭球拟合把硬磁和软磁干扰消掉同时在安装上尽量远离电机和粗大金属件。在修正增益上磁力计要设置得比加速度计更保守否则稍微有点干扰航向就抖得没法用。4. 实操落地我在飞控里用四元数的几个关键环节4.1 初始化别从全零开始四元数初始值必须是单位四元数。最稳妥的做法是用初始欧拉角或初始旋转矩阵去构造四元数再归一化。比如上电时传感器水平静止那么姿态就是0对应(1,0,0,0)。但如果传感器安装有偏差或者上电时就不是水平状态就需要先用加速度计算出初始pitch和roll再用磁力计算出初始yaw最后用欧拉角转四元数。从欧拉角转四元数的公式有很多等价写法常见航空惯例ZYX顺序下q0 cos(r/2)cos(p/2)cos(y/2) sin(r/2)sin(p/2)sin(y/2)q1 sin(r/2)cos(p/2)cos(y/2) - cos(r/2)sin(p/2)sin(y/2)q2 cos(r/2)sin(p/2)cos(y/2) sin(r/2)cos(p/2)sin(y/2)q3 cos(r/2)cos(p/2)sin(y/2) - sin(r/2)sin(p/2)cos(y/2)注意这个公式的写法依赖旋转顺序定义。不同飞控或库可能用不同约定工程上一定要先确认姿态约定再套公式。我在移植代码时遇到过一次姿态翻转的问题最后发现就是初始四元数的旋转顺序跟后级不一致。4.2 每步都归一化但要聪明地处理单位四元数才表示纯旋转模长一旦偏离1旋转就会附带一个缩放效果姿态更新越来越离谱。所以每步更新后必须归一化这基本是共识。但这不意味着每次都要无脑开平方。在MCU上如果主频不高可以先算模长平方偏离阈值大于某个值比如1e-6再做一次归一化或者用快速平方根倒数近似。现代单片机带FPU的话直接sqrt也没压力关键是别漏掉这个步骤。还有一个容易被忽略的符号问题q和-q表示同一个旋转。在滤波器内部要尽量保持符号一致否则在姿态融合或插值时会出现跳变。常见的做法是保证w分量非负或者在连续两次更新之间检查点积不为负。这个细节在处理长时间运行的日志数据时特别重要。4.3 四元数转欧拉角只给调试看内部运算用四元数但调试时还是想看角度。标准转换公式是roll atan2(2*(q0q1 q2q3), 1 - 2*(q1q1 q2q2))pitch asin(2*(q0q2 - q3q1))yaw atan2(2*(q0q3 q1q2), 1 - 2*(q2q2 q3q3))写C代码时尤其注意atan2在C标准库里的参数顺序是(y, x)别写成(x, y)。pitch的asin参数是2*(q0q2 - q3q1)由于浮点误差可能略超[-1,1]要手动钳制到[-1,1]否则返回NaN。我在日志里见过大量NaN查到最后都是这里漏了钳制。至于俯仰角到90度附近yaw和roll突然跳变这是欧拉角表示的固有奇点不是解算错了。所以内部控制回路一定不要用欧拉角只在给人看的时候转换。调试时如果必须看角度可以把pitch限制在±89度范围内至少日志不会出现恐怖的跳变。4.4 常见问题与排查技巧我把自己调试过程中踩过的坑整理成了一张速查表遇到问题先照着排查。现象可能原因排查与处理静止时姿态缓慢漂移陀螺零偏未补偿上电静态采样500到1000个点求平均零偏初始化时减掉或调大Ki输出角度来回抖动加速度计噪声大、Kp过高降低Kp对加速度计做低通滤波或提高采样率后均值旋转到某位置后数据变NaN欧拉角转换时asin参数超界钳制asin参数检查四元数是否归一化航向始终慢慢偏磁力计未校准或受磁干扰做椭球拟合校准远离电机磁场再测四元数更新后模长越来越大更新后忘归一化每步强制归一化旋转方向反了坐标系轴方向约定不一致用已知方向旋转验证必要时把某个轴的角速度取负yaw在180度附近来回跳atan2在边界处的符号变化输出层做角度差mod处理或直接保留四元数用于控制其中磁力计那条我在无人机上折腾了最久。当时地面测试一切正常一键起飞后航向就开始偏后来才发现是磁力计离电调太近电调一上电磁场方向就被干扰到没法看。最后换到机臂末端并做了校准问题才解决。这类问题不是算法能补救的安装和布线往往比调参更重要。5. 最后分享一个调四元数时的习惯我自己调姿态解算时有一个固定动作先录一段传感器日志然后在离线环境里跑同样一组数据把前后两次的四元数点积打印出来。如果点积接近1说明姿态连续平滑如果偶尔点积变负说明四元数发生了符号翻转需要统一符号。这个小技巧排查跳变和插值异常非常有效比盯着一堆角度日志直观得多。调参顺序上建议别贪多。先从最简的纯积分开始确认陀螺仪方向和数据率都对再把加速度计修正接进来最后才加磁力计。每加一层就验证一层等全部加完系统出问题的概率会小很多排错范围也能快速缩小。Kp可以先给0.2Ki给0观察静态误差如果姿态能在几秒内收敛且在运动时跟手再慢慢调Ki补偿零偏。四元数确实反直觉但一旦接受它那套运算规则你会发现姿态表达、旋转复合、插值变换都变得特别干净。我后来做机械臂和AR姿态处理时直接复用同一套四元数核心几乎没有再被姿态表示的坑绊过。希望你也能少走我走过的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价