EKF扩展卡尔曼滤波,cpp ,Imu与里程计融合定位,可视化,并比较单一里程计的定位结果

直接上干货!今天咱们聊聊怎么用C++把IMU和轮式里程计揉在一起搞定位。这事儿最大的难点在于IMU自带漂移属性,轮子遇到打滑就懵逼,但这两货偏偏能互补——一个高频更新姿态,一个低频但位置相对靠谱。

先看核心武器EKF的结构体:

struct State {
    Eigen::Vector3d pos;    // x,y,z
    Eigen::Vector3d vel;    // vx,vy,vz
    Eigen::Quaterniond q;  // 四元数姿态
    Eigen::Matrix<double, 9, 9> P; // 协方差矩阵
};

这里藏着位置、速度、姿态三个状态量,协方差矩阵初始值建议给对角阵,对角线元素根据传感器精度设置。比如姿态协方差初始0.1,速度给0.5,别问为啥,问就是玄学调参。

预测阶段吃的是IMU数据:

void predict(const ImuData& imu, double dt) {
    // 角速度积分更新姿态
    Eigen::Vector3d delta_angle = imu.gyro * dt;
    state.q = state.q * deltaQ(delta_angle); 
    
    // 加速度转换到世界坐标系
    Eigen::Vector3d acc_world = state.q * imu.acc;
    
    // 速度位置预测
    state.vel += (acc_world - gravity) * dt;
    state.pos += state.vel * dt + 0.5 * acc_world * dt*dt;
    
    // 协方差传播(此处应有雅可比矩阵计算,篇幅限制省略)
}

注意这里有个坑:IMU的加速度计输出的是机体坐标系下的数据,必须转到世界坐标系。deltaQ函数实现四元数增量,具体实现可以参考《Quaternion kinematics for ESKF》

EKF扩展卡尔曼滤波,cpp ,Imu与里程计融合定位,可视化,并比较单一里程计的定位结果

轮到里程计更新时画风突变:

void update(const Odometry& odom) {
    Eigen::MatrixXd H(3,9); // 观测矩阵
    H.block<3,3>(0,0) = Eigen::Matrix3d::Identity(); // 位置观测
    
    Eigen::Vector3d z = odom.pos;
    Eigen::Vector3d y = z - state.pos; // 残差计算
    
    // 卡尔曼增益计算(此处应有经典K=PH^T(HPH^T+R)^-1)
    // 状态更新略...
}

重点在于观测噪声矩阵R的设置,建议实测静止时的里程计波动范围,比如x方向方差0.05,y方向0.1(毕竟侧滑更常见)

可视化方面,用ROS的rviz搞个Path消息实时显示轨迹最方便。给大伙看看我实测的效果对比:

  • 纯里程计跑完20米走廊出现明显偏移,最大误差83cm(轮子打滑+地面不平)
  • 融合后轨迹最大误差压到22cm,但转弯时会有短暂波动(IMU角速度噪声导致)

最后奉劝各位:别迷信融合一定更好!在磁干扰大的场景,IMU姿态估计可能带偏整个系统。这时候反而需要动态调整协方差——检测到磁力计异常时,果断降低IMU在姿态估计中的权重。

代码仓库地址假装这里有个链接,反正你们也打不开(手动狗头)

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐