ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IMU姿态估计:从加速度计到互补滤波的roll与pitch实战

IMU姿态估计:从加速度计到互补滤波的roll与pitch实战 1. 从传感器到姿态为什么我们需要关心roll和pitch搞过飞控、平衡车或者手机屏幕旋转的人对IMU惯性测量单元这个玩意儿肯定不陌生。它通常由加速度计和陀螺仪组成有些还带磁力计能输出三轴加速度、三轴角速度甚至三轴磁场强度。但拿到这些原始数据只是第一步真正难的是怎么从这些带噪声的读数里把物体的姿态角——也就是roll横滚角、pitch俯仰角和yaw偏航角——给估计出来。我最早接触这块是在做四轴飞行器的时候。当时天真地以为加速度计读出来的重力分量直接反三角函数一算姿态角不就出来了结果飞机一振动角度就疯狂跳变根本没法用。后来才明白姿态估计本质上是一个传感器融合问题加速度计对低频姿态敏感但噪声大陀螺仪积分平滑但会漂移两者必须互补。而roll和pitch这两个角因为可以直接从重力向量中解算出来成了整个姿态估计里最基础也最关键的一环。这篇文章适合谁看如果你正在做IMU相关的项目——不管是飞控、云台稳定、可穿戴设备、还是人体姿态估计里的惯性动捕——只要涉及到从原始数据算姿态角这篇内容都能帮你把原理和实操细节捋清楚。我会从最基础的坐标系定义讲起一步步推导roll和pitch的估计方法再结合代码和实际调试经验把那些文档里不会写的坑都摊开来说。2. 坐标系与旋转顺序姿态角的定义根基2.1 地理坐标系与机体坐标系聊姿态角之前必须先统一坐标系。不同厂商的IMU芯片坐标系定义可能完全不一样这是新手最容易栽跟头的地方。通常我们采用东北天ENU作为地理参考系X轴指向东Y轴指向北Z轴指向天。而机体坐标系则固定在IMU模块上一般定义为X轴指向右Y轴指向前Z轴指向上。但注意有些芯片比如某些型号的MPU系列默认轴向是反的或者Z轴指向下这时候如果不做轴向变换算出来的姿态角符号全是错的。我踩过的坑第一次用某款IMU时静止状态下pitch角显示接近180度排查了半天才发现芯片的Z轴定义是朝下的而我的代码假设朝上。所以拿到一个IMU第一件事就是查数据手册里的轴向图确认每个轴的正方向。2.2 欧拉角与旋转矩阵的关系姿态角本质上是欧拉角的一种形式描述机体坐标系相对于地理坐标系的旋转关系。从地理系到机体系的旋转可以用三个连续旋转来表示。常见的旋转顺序有Z-Y-X即先绕Z轴转yaw再绕Y轴转pitch最后绕X轴转roll这也是航空航天领域最常用的顺序。旋转矩阵R可以写成三个基本旋转矩阵的乘积R Rz(yaw) * Ry(pitch) * Rx(roll)展开后从地理系到机体系的旋转矩阵元素为R [cos(pitch)cos(yaw), sin(roll)sin(pitch)cos(yaw)-cos(roll)sin(yaw), cos(roll)sin(pitch)cos(yaw)sin(roll)sin(yaw); cos(pitch)sin(yaw), sin(roll)sin(pitch)sin(yaw)cos(roll)cos(yaw), cos(roll)sin(pitch)sin(yaw)-sin(roll)cos(yaw); -sin(pitch), sin(roll)cos(pitch), cos(roll)cos(pitch)]这个矩阵是后面所有推导的核心。注意第三行第一列是 -sin(pitch)这意味着pitch角可以直接从旋转矩阵的(3,1)元素反解出来。而roll角则与(3,2)和(3,3)元素有关。这就是为什么roll和pitch可以从重力向量中估计——因为重力向量在地理系中是[0, 0, -g]或者[0, 0, g]取决于Z轴方向定义经过旋转矩阵变换到机体系后其分量就包含了roll和pitch的信息。2.3 为什么yaw不能从加速度计估计这里要特别说明加速度计只能估计roll和pitch无法估计yaw。原因很简单——重力向量始终沿地理系的Z轴绕Z轴旋转即改变yaw不会改变重力在机体系中的投影。所以yaw必须靠磁力计或者陀螺仪积分来获取。这也是为什么很多低成本IMU模块在没有磁力计的情况下yaw会随时间漂移。理解了这一点你就明白为什么标题特意强调“roll与pitch的估计原理”——因为这两个角是加速度计能直接贡献的部分也是整个姿态估计中最可靠、最基础的部分。3. 加速度计估计roll和pitch原理与推导3.1 静止状态下的重力向量分析当IMU静止时加速度计只感受到重力加速度。假设机体坐标系定义为X右、Y前、Z上地理系为东北天那么重力向量在地理系中为[0, 0, -g]因为重力指向下而Z轴朝天。经过旋转矩阵R变换到机体系a_body R * [0, 0, -g]^T展开后得到a_x -g * (-sin(pitch)) g * sin(pitch) a_y -g * (sin(roll)cos(pitch)) -g * sin(roll)cos(pitch) a_z -g * (cos(roll)cos(pitch)) -g * cos(roll)cos(pitch)注意这里a_x、a_y、a_z是加速度计在机体系下的读数。实际芯片输出的加速度值符号可能相反因为测量的是比力但比例关系不变。从这三个等式可以反解出pitch arcsin(a_x / g) roll arctan2(-a_y, -a_z) 或者 arctan2(a_y, a_z)取决于符号约定更通用的做法是直接用分量比值roll arctan2(a_y, a_z) pitch arctan2(-a_x, sqrt(a_y^2 a_z^2))这个公式是我在实际项目中最常用的因为它避免了arcsin在接近±90度时的数值问题而且不需要预先知道g的精确值因为比值消掉了g。3.2 实际代码实现与注意事项用Python写出来大概是这样import numpy as np def accel_to_roll_pitch(ax, ay, az): 从加速度计读数估计roll和pitch 输入ax, ay, az 为机体系下的加速度单位任意比值即可 输出roll, pitch弧度 roll np.arctan2(ay, az) pitch np.arctan2(-ax, np.sqrt(ay**2 az**2)) return roll, pitch这段代码看起来简单但有几个坑必须注意第一符号约定。不同IMU的轴向定义不同如果发现roll或pitch方向反了先检查轴向再检查公式里的符号。我一般会拿一个已知姿态比如水平放置来验证水平时roll和pitch都应该接近0。第二归一化。实际加速度计读数包含噪声和运动加速度直接代入公式会在动态情况下产生很大误差。所以通常要先做低通滤波或者只在静止/准静态条件下使用。第三万向节死锁。当pitch接近±90度时roll和yaw会耦合此时公式仍然能算出数值但物理意义变得模糊。这是欧拉角的固有缺陷不是公式的问题。3.3 动态情况下的误差来源实际使用中IMU很少处于完全静止状态。运动产生的加速度会叠加在重力上导致估计的roll和pitch出现偏差。比如一个四轴飞行器在加速前飞时加速度计的X轴读数会包含前向加速度如果直接用上面的公式pitch角会被错误地估计。解决办法有两种一是用陀螺仪的高频数据做互补滤波二是用更复杂的卡尔曼滤波。但无论哪种方法加速度计提供的roll和pitch都是作为“长期参考”来修正陀螺仪积分的漂移。理解这一点对后面做传感器融合至关重要。4. 陀螺仪积分与互补滤波让roll和pitch更稳4.1 陀螺仪积分的基本原理陀螺仪输出的是角速度单位通常是度/秒或弧度/秒。要得到角度直接对时间积分即可roll_gyro roll_prev gyro_x * dt pitch_gyro pitch_prev gyro_y * dt但这里有个关键问题陀螺仪的角速度是在机体系下测量的而roll和pitch的变化率并不等于机体系下的角速度分量。严格来说欧拉角的变化率与机体系角速度的关系需要通过一个变换矩阵来转换[roll_dot, pitch_dot, yaw_dot]^T W * [gx, gy, gz]^T其中W矩阵为W [1, sin(roll)tan(pitch), cos(roll)tan(pitch); 0, cos(roll), -sin(roll); 0, sin(roll)/cos(pitch), cos(roll)/cos(pitch)]这个矩阵在pitch接近±90度时会奇异所以实际实现中很多低成本方案会简化处理当pitch较小时直接认为roll_dot ≈ gxpitch_dot ≈ gy。这种近似在pitch不超过±45度时误差可以接受但大角度下必须用完整公式。4.2 互补滤波的实操细节互补滤波的核心思想很简单加速度计估计的roll和pitch在低频段准确陀螺仪积分在高频段准确。所以用高通滤波提取陀螺仪的高频成分用低通滤波提取加速度计的低频成分然后相加。最常用的形式是roll alpha * (roll_prev gyro_x * dt) (1 - alpha) * roll_accel pitch alpha * (pitch_prev gyro_y * dt) (1 - alpha) * pitch_accel其中alpha通常取0.95到0.98之间。alpha越大越信任陀螺仪动态响应越好但漂移修正越慢alpha越小越信任加速度计静态精度高但动态时容易被运动加速度干扰。我实测下来的经验对于四轴飞行器alpha取0.98比较合适对于慢速运动的手势识别设备alpha取0.95就够了。这个参数需要根据具体应用场景调整没有万能值。4.3 代码实现与参数调试class ComplementaryFilter: def __init__(self, alpha0.98, dt0.01): self.alpha alpha self.dt dt self.roll 0.0 self.pitch 0.0 def update(self, ax, ay, az, gx, gy): # 加速度计估计 roll_acc np.arctan2(ay, az) pitch_acc np.arctan2(-ax, np.sqrt(ay**2 az**2)) # 陀螺仪积分 self.roll self.alpha * (self.roll gx * self.dt) (1 - self.alpha) * roll_acc self.pitch self.alpha * (self.pitch gy * self.dt) (1 - self.alpha) * pitch_acc return self.roll, self.pitch调试时我一般会这样做先把alpha设为1.0只看陀螺仪积分观察漂移速度再把alpha设为0.0只看加速度计观察噪声水平最后在两者之间找平衡。通常需要反复试几次用示波器或者串口绘图工具看波形最直观。注意互补滤波虽然简单但它假设加速度计只感受重力。如果应用场景中存在持续加速度比如车辆急加速互补滤波的效果会大打折扣这时候需要考虑卡尔曼滤波或者引入其他传感器。5. 从理论到落地完整姿态估计流程与避坑指南5.1 数据采集与预处理拿到IMU原始数据后第一步不是直接算角度而是做预处理。我通常按这个顺序来检查数据范围确认加速度计量程是否合适。如果量程设得太小运动时容易饱和设得太大静态分辨率又不够。一般飞控用±4g或±8g可穿戴设备用±2g就够了。零偏校准静止时采集几百个样本求平均值作为零偏后续读数减去零偏。陀螺仪的零偏尤其重要因为积分会放大误差。温度补偿高精度应用需要考虑温度对零偏的影响低成本方案可以忽略但要知道有这个误差源。低通滤波加速度计和陀螺仪都有高频噪声通常用一个截止频率50Hz左右的低通滤波器先滤一遍。5.2 姿态估计的完整代码框架把前面的内容串起来一个完整的roll/pitch估计流程如下import numpy as np from collections import deque class IMUAttitudeEstimator: def __init__(self, alpha0.98, dt0.01, calib_samples500): self.alpha alpha self.dt dt self.roll 0.0 self.pitch 0.0 self.accel_bias np.zeros(3) self.gyro_bias np.zeros(3) self.calib_samples calib_samples self.calib_buffer deque(maxlencalib_samples) def calibrate(self, ax, ay, az, gx, gy, gz): 静止校准采集样本求零偏 self.calib_buffer.append([ax, ay, az, gx, gy, gz]) if len(self.calib_buffer) self.calib_samples: data np.array(self.calib_buffer) self.accel_bias np.mean(data[:, :3], axis0) self.gyro_bias np.mean(data[:, 3:], axis0) # 加速度计零偏中Z轴应该接近g所以减去后Z轴接近0 # 但注意这里假设校准时机体水平 return True return False def update(self, ax, ay, az, gx, gy, gz): # 减去零偏 ax - self.accel_bias[0] ay - self.accel_bias[1] az - self.accel_bias[2] gx - self.gyro_bias[0] gy - self.gyro_bias[1] gz - self.gyro_bias[2] # 加速度计估计 roll_acc np.arctan2(ay, az) pitch_acc np.arctan2(-ax, np.sqrt(ay**2 az**2)) # 互补滤波 self.roll self.alpha * (self.roll gx * self.dt) (1 - self.alpha) * roll_acc self.pitch self.alpha * (self.pitch gy * self.dt) (1 - self.alpha) * pitch_acc return self.roll, self.pitch这段代码可以直接拿去用但要注意校准阶段必须保证IMU静止且水平。如果校准时机体有倾斜零偏会包含重力分量后续估计全错。5.3 常见问题速查表问题现象可能原因排查方法解决方案静止时角度缓慢漂移陀螺仪零偏未校准静止观察10秒看角度变化重新校准增加校准样本数动态时角度跳变加速度计噪声大或运动加速度干扰对比静止和运动时的波形增大alpha或改用卡尔曼滤波roll和pitch方向相反轴向定义不匹配手动倾斜IMU观察角度变化方向调整公式符号或交换轴pitch接近90度时roll突变万向节死锁观察pitch接近±90度时的表现改用四元数表示姿态角度始终有固定偏差安装误差或校准不准用水平仪对比实际角度重新校准或加入安装矩阵补偿5.4 进阶方向从互补滤波到卡尔曼滤波互补滤波够用但不够好。如果项目对姿态精度要求高比如VINS视觉惯性导航系统或者lidar imu标定就需要上卡尔曼滤波。卡尔曼滤波的核心是把roll和pitch作为状态量用陀螺仪做预测用加速度计做更新同时估计零偏。我实际用下来扩展卡尔曼滤波EKF在动态场景下的表现比互补滤波好很多尤其是存在持续加速度的时候。但EKF的参数调试也更麻烦过程噪声和观测噪声的协方差矩阵需要根据实际数据调。如果刚开始做建议先用互补滤波把流程跑通再逐步升级。另外如果你做的是人体姿态估计或者多人姿态估计IMU通常贴在肢体上roll和pitch的估计会面临更复杂的运动模式。这时候可以考虑用Madgwick或Mahony算法它们基于四元数计算量小而且没有万向节死锁问题。这两个算法的开源实现很多直接移植就行。6. 一些掏心窝子的实操心得做IMU姿态估计这些年踩过的坑比写过的代码还多。分享几个我觉得最有用的经验第一永远不要相信第一次读到的数据。拿到新IMU先静止放半小时看零偏稳定性。有些廉价IMU的零偏随温度变化能差好几度每秒这种就必须做温度补偿否则后面怎么滤波都白搭。第二加速度计的安装位置很重要。如果IMU不是装在旋转中心运动时的离心加速度会被误认为重力分量导致roll和pitch估计错误。我做过一个云台项目IMU装在离旋转轴10厘米的地方快速转动时角度误差超过5度。后来把IMU移到旋转中心附近问题立刻消失。第三调试时一定要有参考基准。我通常会用手机上的水平仪App作为粗略参考或者用高精度转台做标定。没有基准的调试就是盲人摸象你永远不知道误差是来自算法还是传感器本身。第四dt的精度比想象中重要。陀螺仪积分对时间间隔很敏感如果dt不稳定比如用delay做定时积分误差会累积得很快。最好用硬件定时器或者系统的高精度时钟来获取dt。第五别忽视磁力计。虽然roll和pitch不需要磁力计但如果你后面要做完整的姿态估计包括yaw磁力计是必须的。而且磁力计可以帮助修正roll和pitch在动态情况下的误差因为磁场向量提供了另一个参考方向。当然磁力计需要做椭球拟合校准这是另一个大话题了。最后说一个我最近在做的方向把IMU和视觉融合做姿态估计。视觉可以提供绝对姿态参考IMU提供高频运动信息两者互补效果很好。如果你也在做类似的事情欢迎交流。这个领域还有很多值得挖的东西尤其是imu标定和lidar imu标定这块实际工程中的细节远比论文里写的复杂。
返回列表