ARTICLE DETAIL

资讯详情

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

IMU安装姿态校正与坐标系变换:从歪装到等效数据的完整链路

IMU安装姿态校正与坐标系变换:从歪装到等效数据的完整链路 把IMU模块装上机器人之后第一个让我血压升高的时刻是看到板卡上三轴加速度计的读数明明车子停在水平地面上z轴读数应该是9.8x轴应该接近0结果x轴上稳稳地挂着4.5。后来我意识到这不是模块坏了而是我把IMU的安装姿态搞歪了——模块的坐标系和载体的坐标系差了大概27度。这个现象在无人机、机器人、自动驾驶、手持SLAM里太常见了。要解决它就需要把任意安装姿态下的IMU原始输出换算成标准安装姿态下的等效数据。这篇文章就把这条换算链路从头到尾拆一遍坐标系怎么定义、旋转矩阵怎么选、安装角怎么获取、代码怎么写、yaw轴对齐有什么坑以及它和后面做lidar imu标定、kalibr相机imu联合标定时怎么衔接。文章的目标读者是准备把IMU真正用进机器人系统里的工程师不管是做VIO、做轮式里程计还是做组合导航这套思路都通用。1. 为什么歪着装会影响整条IMU数据处理链路1.1 三个最直接的歪后果在解释等效数据之前先得把不处理的代价说清楚。IMU输出的原始数据是载体自身加速度和角速度在三轴上的投影这个投影关系是相对于IMU自身坐标系的。一旦模块装歪了数据链路从源头上就带上了坐标系错位的问题。第一个后果是重力投影混叠。静止状态下真实的重力方向是垂直向下的标准安装时它应该完全落在z轴上。一旦模块偏了重力就会在x轴、y轴上出现分量。很多做行人航位推算的朋友拿到数据后发现静止时三轴加速度的模长是9.8这没错但光看单轴数据就怎么看怎么不对劲。滤波算法如果假设静止时加速度为[0,0,9.8]就会产生系统性偏差。第二个后果是运动加速度分解错误。车体向前加速时真正作用在载体上的推力沿车体x轴。可如果IMU的x轴和车体x轴之间有个夹角读出来的向前运动就会有一部分被分到y轴甚至z轴上。对于VIO或者轮式里程计这类依赖IMU运动约束的算法这意味着速度估计方向被污染轨迹自然就会飘。第三个后果是姿态解算和控制无法直接使用。很多IMU模组内部自带姿态解算输出欧拉角或者四元数。这些姿态量是IMU坐标系相对于世界坐标系的姿态。但在机器人控制里我们需要的是载体坐标系相对于世界坐标系的姿态。两个坐标系之间隔着一个安装旋转直接用会把横滚、俯仰、偏航全部污染掉。做云台稳定或者无人机飞控的朋友应该深有体会装好的IMU如果不做坐标系校正飞控调参再努力也压不住奇怪的角度跳变。1.2 标准安装姿态是怎么定义的所谓标准安装姿态指的是IMU的三轴和载体坐标系的三轴完全对齐。那载体坐标系怎么定义一般遵循右手定则约定x轴朝载体前进方向y轴朝左或者朝右不同团队习惯不同但定义后必须全链路统一z轴垂直向上。这里有一个工程上容易出现分歧的点z轴是向上还是向下。很多IMU模块出厂时把z轴定义为重力方向即向下也就是东北天坐标系里的天被定义成了负z。做车载系统时大家更习惯z轴向上。所以标准安装姿态一定不能只写三轴对齐要把载体坐标系的三轴方向明确写进设计文档里。顺带说一句如果IMU模块本身可以选择安装方向有些工业级模块支持x朝前或者x朝后那软件上就多一个自由度但这也意味着坐标系变换的逻辑要写成可配置项不能写死在代码里。1.3 问题在数学上到底是什么把歪着装抽象成数学问题其实就一句话存在一个刚体旋转把IMU坐标系转成了载体坐标系。这个旋转可以用旋转矩阵、欧拉角或者四元数表示。我们拿到的是IMU坐标系下的数据想要的是载体坐标系下的数据中间需要的操作就是一次坐标变换。有人会问为什么不直接在硬件安装时把这套角度保证到位原因很现实结构件有公差焊接有变形螺丝有松动产品迭代过程中安装位置会改。与其每次改完结构都重新标定硬件不如在软件里做一次坐标变换把姿态差异作为标定参数存下来。这套思路在后来的lidar imu标定、相机imu联合标定里也一样都是先做粗对齐再用最优化算法求精细残差。2. 坐标系变换的本质旋转矩阵怎么选、怎么构造2.1 旋转矩阵的物理意义很多新手第一次看到旋转矩阵时会被吓到但它的物理意义其实很直白同一个向量在不同坐标系下读数不同旋转矩阵负责把这两个读数换算起来。假设有一个向量(\mathbf{v})它在IMU坐标系下读数是(\mathbf{v}{imu})在载体坐标系下读数是(\mathbf{v}{body})那么[ \mathbf{v}{body} R{imu2body} \cdot \mathbf{v}_{imu} ]这里的(R_{imu2body})就是把IMU坐标系的向量坐标变换到载体坐标系的向量坐标的矩阵。注意这里并不需要对向量做旋转操作只是同一个物理量换了一把尺子来量。这个关系是后续所有变换的基石。角速度、加速度、磁场强度只要它们是定义在IMU坐标系下的矢量都可以用同一个矩阵变换到载体坐标系下。2.2 从R到四元数再到欧拉角为什么建议别直接用欧拉角欧拉角最直观横滚、俯仰、偏航一眼就看明白。但直接用欧拉角做计算有两大坑一是万向锁俯仰角接近90度时横滚和偏航变得不可区分二是旋转顺序问题同一个欧拉角组按先横滚后俯仰和先俯仰后横滚得到的结果完全不同而且还有内旋外旋的区别不写清楚上下文就会闹笑话。在实际工程里我建议内部计算统一用旋转矩阵或者四元数欧拉角只在打印日志和人类查看的时候用。比如要构造安装姿态的旋转矩阵可以从欧拉角生成但生成时一定要明确旋转顺序。常用的顺序是ZYX也就是先绕z轴偏航再绕y轴俯仰最后绕x轴横滚。工程上还有别的约定比如Ros的rpy习惯是先绕z再绕y再绕x但不同工具库的接口注释要仔细读别想当然。如果拿到的是四元数那就更省心了。四元数乘法可以替代矩阵连乘而且没有万向锁问题。scipy.spatial.transform.Rotation这个类在四元数和旋转矩阵之间切换非常方便调试阶段建议直接用它。2.3 三个数据通道的变换关系IMU输出通常有角速度、加速度以及部分模组自带的姿态四元数。这三个通道的变换方式略微不同我整理了一个表数据通道变换方式说明角速度 (\omega_{imu})(\omega_{body} R_{imu2body} \cdot \omega_{imu})角速度是矢量直接做坐标变换加速度 (a_{imu})(a_{body} R_{imu2body} \cdot a_{imu})同样直接变换重力分量也会被一起变换IMU姿态四元数 (q_{imu})(q_{body} q_{imu} \otimes q_{corr}^{-1})四元数变换可等价于矩阵连乘这里尤其要注意四元数那行。很多朋友在四元数变换时容易搞错顺序导致变换后的姿态反而更乱。后面会专门展开讲。3. 安装姿态参数从哪来设计直给和粗略标定两条路线3.1 机械设计值直接构造最省事的方案是直接在结构设计阶段把安装角确定下来。比如用3D打印做了一个支架IMU插进去的角度就是你建模时定的角度。这时候直接根据设计值构造旋转矩阵就行不需要额外标定。我一向建议即使采用设计值也要在代码里留一个参数配置文件把这三个角度横滚、俯仰、偏航写在配置文件里。原因很简单3D打印有公差装配有应力焊接会变形用户拆一次再装回去角度可能就偏了。写死常量会让后期排查问题变得很痛苦。另外一个隐蔽的风险是安装面本身不是水平的。有些结构件设计图上画得挺平实际加工出来有角度偏差这个偏差单靠肉眼很难发现。可以用水平仪或者倾角仪先测一下安装面的实际角度再修正设计值。3.2 为什么仍然要做一次粗标定即使有设计值我还是会在每次装完新结构后做一次粗标定。粗标定的目的不是追求0.01度的精度而是把大的安装偏差比如几度到几十度找回来让数据在量级和方向上先说得通。精细的标定是kalibr那套工具干的事情它不是适用于所有场景。比如有些现场只有一块IMU板子加一台笔记本连转台都没有那就不可能做等效的精细标定。粗标定只需要一块水平地面和十几秒的静止数据成本几乎为零但收益很大。粗标定的另外一个意义是提供初值。后面做lidar imu标定、相机imu联合标定时优化算法都需要一个不差太多的初始外参。初始化给得离谱非线性优化很容易收敛到局部极小值。所以粗标定出来的角度直接作为精标定程序的初始值能大幅提高成功率。3.3 拿到安装角的粗标定实操步骤第一步把设备放到水平地面上静止。静止这种表述其实不够严格要保证设备在采集期间没有平移和旋转最好垫一块防震垫避免地面震动传入。第二步采集至少5秒钟的加速度数据对每一轴取平均。因为静止时加速度计理论上只感受重力所以这个平均值就是重力在IMU坐标系下的投影。第三步根据重力投影计算横滚角和俯仰角。假设IMU坐标准确定义为x朝前y朝左z朝上静止时测量的加速度是((a_x, a_y, a_z))那么[ \text{roll} \arctan2(a_y, a_z) ] [ \text{pitch} \arctan2(-a_x, \sqrt{a_y^2 a_z^2}) ]这个横滚和俯仰描述的是IMU坐标系相对于水平面的倾斜也就是安装姿态的横滚分量和俯仰分量。注意这个公式只在大致水平的前提下有效如果倾斜超过45度建议改用更有鲁棒性的方法。第四步偏航角怎么获取。纯靠加速度计是无法确定偏航的因为重力方向在绕z轴旋转时完全不变。后续专门有一章讨论偏航对齐这里先记住粗标定阶段可以把偏航设成0或者设计值后续再用外部参考修正。第五步验证。把算出来的角度代进旋转矩阵对静止数据进行变换变换后的重力向量应该接近[0, 0, -1]或者[0, 0, 9.8]这里要看坐标系定义。4. 完整实现流程从原始数据到标准安装姿态等效数据4.1 静态初始化确定当前重力方向讲完了原理进入具体实现。建议把坐标变换封装成独立模块输入是IMU原始数据和安装参数输出是变换后的等效数据。第一步是静态初始化。目的是确认当前设备的水平状态在数据上的表现形式也可以用来验证安装角是否正确。算法上其实很简单取一段静止数据按轴平均然后归一化得到重力方向在IMU坐标系下的单位向量。这里的坑在于静止的判定。如果设备还在晃动就采集平均值会被运动加速度污染。所以我一般会先计算加速度计输出的模长如果模长在9.8附近波动小于一个阈值比如0.1 m/s²才认为处于静止状态。更严格的做法是同时检查陀螺仪角速度的模角速度长时间接近0才能算静止。4.2 构造变换矩阵并作用于三轴数据构造旋转矩阵可以直接用scipy或numpy核心代码如下import numpy as np from scipy.spatial.transform import Rotation # 安装姿态参数绕z轴偏航、绕y轴俯仰、绕x轴横滚 # 顺序选择ZYX单位度 roll_deg 5.0 pitch_deg -2.0 yaw_deg 30.0 r_imu_to_body Rotation.from_euler(zyx, [yaw_deg, pitch_deg, roll_deg], degreesTrue) R r_imu_to_body.as_matrix() def transform_imu_data(gyro_raw, accel_raw, R): gyro_raw: (N, 3) 原始角速度数据 accel_raw: (N, 3) 原始加速度数据 返回变换到标准安装姿态后的角速度和加速度 gyro_body (R gyro_raw.T).T accel_body (R accel_raw.T).T return gyro_body, accel_body这短短几行就是整个任意安装姿态到标准安装姿态的核心。注意R gyro_raw.T的意思是每个时间步的3维向量都和R左乘。如果你习惯用(N, 3)的数组先转置再乘再转置回来逻辑上是等价的。我见过有人在这里把矩阵乘法的顺序写反得到的结果就是越变越歪而且这种错误还特别隐蔽静止时看不出来只有动起来才会发现数据方向完全不对。4.3 四元数输出的变换很多IMU模组会同时输出姿态四元数例如JY901、BNO080这类模块。这个四元数的含义通常是世界坐标系到IMU坐标系的旋转也就是它描述的是IMU在世界的姿态。但不同厂家的约定不尽相同我强烈建议先读一遍模组手册里的坐标系定义再决定变换方式。如果IMU输出的四元数(q_{imu})表示从世界坐标系到IMU坐标系的旋转那么要得到从世界坐标系到载体坐标系的旋转(q_{body})可以用以下公式[ q_{body} q_{corr}^{-1} \otimes q_{imu} ]其中(q_{corr})是安装姿态对应的四元数。如果IMU输出的四元数表示从IMU坐标系到世界坐标系的旋转那就要用另一个公式[ q_{body} q_{imu} \otimes q_{corr}^{-1} ]这两个公式的区别就是乘法的左右顺序不同。我踩过这个坑第一次做四元数变换时直接套了网上一个没写清楚约定的帖子结果输出的姿态在静止时都抖得不行。后来我把加速度计的方向校验也加进去才定位到是四元数变换方向反了。为了不跳进同一个坑我建议在代码里同时输出加速度计重力投影作为校验。如果四元数变换后算出来的重力方向和加速度计变换后的重力方向一致说明变换公式用对了。方向相反就一定有问题。def quat_multiply(q1, q2): q1, q2: [x, y, z, w] 格式的四元数 返回 q1 * q2 x1, y1, z1, w1 q1 x2, y2, z2, w2 q2 return np.array([ w1*x2 x1*w2 y1*z2 - z1*y2, w1*y2 - x1*z2 y1*w2 z1*x2, w1*z2 x1*y2 - y1*x2 z1*w2, w1*w2 - x1*x2 - y1*y2 - z1*z2 ]) def transform_quat(q_imu, q_corr): # 根据你确认的约定选择下面的其中一种 # 情形1q_imu 表示世界到IMU # q_body quat_multiply(quat_conjugate(q_corr), q_imu) # 情形2q_imu 表示IMU到世界 # q_body quat_multiply(q_imu, quat_conjugate(q_corr)) pass4.4 验证变换是否正确的三个检查点变换完之后必须做验证。我一般会盯三个检查点。第一静止状态下变换后的加速度向量应该接近[0, 0, -1]如果z轴朝上或者[0, 0, 9.8]如果习惯用数值单位。如果静止时x轴或者y轴上还挂着大小可观的分量说明横滚和俯仰角度用得不对。第二绕某个轴转动时陀螺仪对应轴应该有干净的角速度响应而另外两个轴应该基本为零。比如你手拿着设备绕载体坐标系的x轴旋转变换后的陀螺x轴应该有读数y和z应该接近0。如果y和z也有明显的同频分量说明安装姿态里的横滚角或者俯仰角没标对。第三向前推动设备变换后的加速度x轴应该有一个正的尖峰。这个测试可以直观地验证整个坐标系是否已经对齐到车体。这三个检查点写成一个自动化的测试脚本每次现场装完新设备就跑一遍效率比自己肉眼看曲线高得多。5. 航向对齐yaw轴校准最容易被忽略5.1 重力对齐不代表航向对齐上面讲的粗标定流程里横滚和俯仰可以靠重力来确定但偏航角没有办法靠IMU自身静止测量来确定。原因很简单重力方向只和横滚、俯仰相关你怎么绕z轴旋转重力在IMU坐标系的投影都不变。所以如果你只是做了加速度计静态标定得到的安装姿态是缺了偏航分量的。这时候直接做等效数据变换横滚俯仰是对的但航向差一个未知的角度。很多朋友在前期调试时没注意这个问题因为横滚和俯仰偏差导致的错误非常明显只要一加速就能发现。而偏航偏差则会在转弯、绕圈时慢慢累积最后表现为轨迹整体转了一个角度难以一次定位到根因。5.2 yaw偏离会带来什么后果偏航偏差对不同类型的系统影响不同。对于无人机飞控这类靠磁力计修正航向的系统偏航偏差会让磁力计的航向参考被错误旋转导致飞行时打方向的手感不对。对于VIO系统偏航偏差会导致视觉观测和IMU预积分之间的约束错误系统虽然能跑起来但转弯时容易出现漂移。最典型的是激光雷达和IMU的标定场景。lidar imu标定本质上就是在求激光雷达坐标系和IMU坐标系之间的外参其中包含一个旋转。如果你在采集数据之前先做了一次安装姿态变换但偏航角是错的那IMU数据和实际运动之间就始终差一个固定偏航角标定算法会把误差分一部分到外参里另一部分则表现为不可消除的残差。5.3 工程上获取yaw的几种办法第一种利用机械限位。如果安装座上有明显的销钉、键槽或者防呆结构那么IMU的航向是受到约束的可以直接把设计值填进去。这个方法最稳但前提是机械加工精度足够。第二种利用磁力计。如果IMU模组内置磁力计可以测量地磁场方向来确定航向。但磁力计对环境中的铁磁物质非常敏感电机、喇叭、铁质支架都会让磁场变形。工业现场用这个方法要小心最好先在空旷环境校准磁力计。第三种利用外部运动信息。比如让机器人沿着一条直线向前运动或者绕已知轴旋转通过对比IMU角速度方向和实际运动方向来估算偏航。这种方法比较适合有运动平台的场景。第四种直接交给外参标定去吸收。在kalibr相机imu联合标定或者lidar imu标定流程中初值给一个粗略的偏航角剩下的残差会让优化算法自动调整外参旋转。前提是系统本身可观测也就是运动激励足够丰富。如果只是静止不动或者匀速直线运动标定结果往往不可靠。工程上我的建议是先想办法确认偏航角再进入精标定流程。不要指望精标定一定能帮你把初始的偏航错误修正回来尤其当运动激励不够丰富的时候。6. 安装姿态处理与外参标定怎么衔接6.1 安装姿态和外参标定的关系这里要理清一个概念安装姿态变换得到的标准安装姿态等效数据说的是IMU相对于载体坐标系的姿态它把IMU的数据统一到载体坐标系下来表达。而外参标定解决的是另一个问题不同传感器坐标系之间的变换。比如lidar imu标定求得的是激光雷达坐标系到IMU坐标系的变换关系包括旋转和平移。这个外参和IMU自身的安装姿态是相互独立的两个量但使用上会叠加。如果你在软件里已经做了安装姿态变换那后续的外参变换就要基于变换后的载体坐标系来做顺序不能乱。我见过一个项目里工程师在代码里加了安装姿态校正然后又让标定算法去估计传感器到标准IMU坐标系的外参结果两个变换叠加在一起精度反而更差。正确的做法是安装姿态校正在数据预处理层完成外参标定在传感器融合层完成各司其职。6.2 粗标定与kalibr、lidar imu标定的衔接流程实际工程里的推荐流程是这样的先用本章第一节的粗标定估算出安装姿态角作为初值然后采集一段包含各种旋转和线性加速的运动数据再把这段数据连同初值一起送入kalibr或者lidar imu标定工具做精标定。在kalibr相机imu联合标定里除了外参还会估计IMU自身的噪声密度、随机游走、尺度因子和轴间非正交偏差。如果IMU的安装姿态和真实值差太多这些内参估计也会被污染标定出来的噪声参数在后续使用时会不满足滤波器假设最后表现为定位精度上不去。所以我的建议是粗标定不是可选项而是精标定的保险。宁可粗标定多花五分钟也不要在后续融合调试时浪费几天。6.3 内参标定、imu检测逻辑与数据质量聊到kalibr就绕不开IMU内参标定。内参是IMU自身的出厂误差参数包括加速度计的尺度、零偏、轴间非正交陀螺仪的尺度、零偏、轴间非正交。这些内参出厂时大致在某个范围内但对精度要求高的系统来说需要重新标定。内参标定和安装姿态校正是两个层面的问题。安装姿态解决的是IMU坐标系相对载体坐标系歪了多少内参解决的是IMU内部三轴不是理想正交的、存在增益和零偏误差。先做安装姿态校正再做内参精标定顺序上更合理。否则过大的一阶旋转误差会污染内参估计。imu检测逻辑指的是对IMU数据有效性的判断。在预处理阶段至少要检查三件事数据是否有跳变、陀螺是否饱和、加速度模长是否长时间偏离重力值。安装姿态变换本身不会改变数据的噪声特性但坐标系旋转会重新分配噪声在各个轴上的表现所以变换后的数据依然要做质量检测。6.4 软件里把安装姿态做成参数配置最后给一个工程建议把安装姿态参数做成独立的配置文件而不是写死在代码里。无论是设计值也好、粗标定结果也罢都放到配置项里。现在很多团队在做软件迭代同一套代码要跑在不同机器人平台上IMU的安装位置和角度经常不一样。如果你在代码里把安装角定义为构造函数参数或者yaml配置项换平台时就只需要改配置不用改代码这套逻辑在后面做lidar imu标定和相机imu联合标定的时候也能复用。配置项至少包括这几个字段安装角roll、pitch、yaw旋转顺序数据坐标系定义x朝前还是朝后y朝左还是朝右z朝上还是朝下以及四元数约定。把这些字段都显式地写清楚比在代码里默默做个scipy转换要稳妥得多。实际调试中我还会在配置里加一个调试模式开关。打开后程序会输出变换前后的重力投影、陀螺零偏等关键中间量方便现场人员快速验证安装参数是否设置正确。这个小功能在项目交付时特别有用现场工程师不会每次都在CRT终端里看数据一个清晰的调试日志能省掉很多沟通成本。这段时间做下来我最大的体会是IMU数据处理里最花时间的往往不是算法本身而是坐标系约定不清导致的反复试错。安装姿态变换的数学非常简单难的是把谁的坐标系、什么约定、输出什么含义这些细节全部固定下来。建议动手之前先把坐标系定义写到设计文档里然后在代码里加校验让程序在数据不对劲的时候主动报警而不是让偏差在融合结果里慢慢发酵。如果你也正在被IMU数据歪着读的问题困扰希望这篇内容能帮你少走几步弯路。
返回列表