imu的静止零偏噪声标定与积分

news2025/1/12 22:56:05

示例使用的Imu为轮趣科技 n100 mini其中imu出来的数据的坐标系是基于ROS坐标系的
在这里插入图片描述

Eigen::Quaterniond q_ahrs(ahrs_frame_.frame.data.data_pack.Qw,
                          ahrs_frame_.frame.data.data_pack.Qx,
                          ahrs_frame_.frame.data.data_pack.Qy,
                          ahrs_frame_.frame.data.data_pack.Qz);

Eigen::Quaterniond q_r =                          
    Eigen::AngleAxisd( PI, Eigen::Vector3d::UnitZ()) * 
    Eigen::AngleAxisd( PI, Eigen::Vector3d::UnitY()) * 
    Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitX());

Eigen::Quaterniond q_rr =                          
    Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitZ()) * 
    Eigen::AngleAxisd( 0.00000, Eigen::Vector3d::UnitY()) * 
    Eigen::AngleAxisd( PI, Eigen::Vector3d::UnitX());

Eigen::Quaterniond q_out =  q_r * q_ahrs * q_rr

为什么要右乘q_rr?

回答:你可以把*q_rr去掉,重新编译后,通过TF转换看到,实际上这个坐标是倒着的,还需要绕X轴转180°才是我们ROS里面用到的坐标系。

只左乘q_r的话是可以完成TF的坐标变换的,但是我们通常在ros里用到的坐标系是前左上,所以还要通过右乘q_rr来修正坐标系的位姿

备注:这款IMU是九轴IMU,融合了磁力计,也就是原始的yaw角指北是0度,范围为0-360°,顺时针增大。转换到imu单品ROS标准下的坐标后 航向应该特殊处理为东北天坐标系。
在这里插入图片描述

输入的单帧Imu示例

  seq: 90498
  stamp: 
    secs: 1698127594
    nsecs: 155961838
  frame_id: "gyro_link"
orientation: 
  x: 0.0049992394633591695
  y: 0.002799155190586991
  z: -0.1353550255298614
  w: -0.9907805919647217
orientation_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
angular_velocity: 
  x: -0.0013670891057699919
  y: 0.0030183307826519012
  z: -0.002960443962365389
angular_velocity_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
linear_acceleration: 
  x: 0.04501450061798096
  y: -0.09672745317220688
  z: 9.795211791992188
linear_acceleration_covariance: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

imu_integration.h

#ifndef SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H
#define SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H

#include "eigen_types.h"
#include "imu.h"
#include "nav_state.h"

namespace sad {

/**
 * 本程序演示单纯靠IMU的积分
 */
class IMUIntegration {
   public:
    IMUIntegration(const Vec3d& gravity, const Vec3d& init_bg, const Vec3d& init_ba)
        : gravity_(gravity), bg_(init_bg), ba_(init_ba) {}

    // 增加imu读数
    void AddIMU(const IMU& imu) {
        double dt = imu.timestamp_ - timestamp_;
        if (dt > 0 && dt < 0.1) 
        {
            // 假设IMU时间间隔在0至0.1以内
            p_ = p_ + v_ * dt + 0.5 * gravity_ * dt * dt + 0.5 * (R_ * (imu.acce_ - ba_)) * dt * dt;
            v_ = v_ + R_ * (imu.acce_ - ba_) * dt + gravity_ * dt;
            R_ = R_ * Sophus::SO3d::exp((imu.gyro_ - bg_) * dt);
        }
        // 更新时间
        timestamp_ = imu.timestamp_;
    }

    /// 组成NavState
    NavStated GetNavState() const { return NavStated(timestamp_, R_, p_, v_, bg_, ba_); }

    SO3 GetR() const { return R_; }
    Vec3d GetV() const { return v_; }
    Vec3d GetP() const { return p_; }

   private:
    // 累计量
    SO3 R_;
    Vec3d v_ = Vec3d::Zero();
    Vec3d p_ = Vec3d::Zero();

    double timestamp_ = 0.0;

    // 零偏,由外部设定
    Vec3d bg_ = Vec3d::Zero();
    Vec3d ba_ = Vec3d::Zero();

    Vec3d gravity_ = Vec3d(0, 0, -9.8);  // 重力
};

}  // namespace sad

#endif  // SLAM_IN_AUTO_DRIVING_IMU_INTEGRATION_H

static_imu_init.h

#ifndef SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H
#define SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H

#include "eigen_types.h"
#include "imu.h"
#include "odom.h"

#include <deque>

namespace sad {

/**
 * IMU水平静止状态下初始化器
 * 使用方法:调用AddIMU, AddOdom添加数据,使用InitSuccess获取初始化是否成功
 * 成功后,使用各Get函数获取内部参数
 *
 * 初始化器在每次调用AddIMU时尝试对系统进行初始化。在有odom的场合,初始化要求odom轮速读数接近零;没有时,假设车辆初期静止。
 * 初始化器收集一段时间内的IMU读数,按照书本3.5.4节估计初始零偏和噪声参数,提供给ESKF或者其他滤波器
 */
class StaticIMUInit {
   public:
    struct Options 
    {
        Options() {}
        double init_time_seconds_ = 10.0;     // 静止时间
        int init_imu_queue_max_size_ = 2000;  // 初始化IMU队列最大长度
        int static_odom_pulse_ = 5;           // 静止时轮速计输出噪声
        double max_static_gyro_var = 0.5;     // 静态下陀螺测量方差
        double max_static_acce_var = 0.05;    // 静态下加计测量方差
        double gravity_norm_ = 9.81;          // 重力大小
        bool use_speed_for_static_checking_ = false;  // 是否使用odom来判断车辆静止(部分数据集没有odom选项)
    };

    /// 构造函数
    StaticIMUInit(Options options = Options()) : options_(options) {}

    /// 添加IMU数据
    bool AddIMU(const IMU& imu);
    /// 添加轮速数据
    bool AddOdom(const Odom& odom);

    /// 判定初始化是否成功
    bool InitSuccess() const { return init_success_; }

    /// 获取各Cov, bias, gravity
    Vec3d GetCovGyro() const { return cov_gyro_; }
    Vec3d GetCovAcce() const { return cov_acce_; }
    Vec3d GetInitBg() const { return init_bg_; }
    Vec3d GetInitBa() const { return init_ba_; }
    Vec3d GetGravity() const { return gravity_; }

   private:
    /// 尝试对系统初始化
    bool TryInit();

    Options options_;                 // 选项信息
    bool init_success_ = false;       // 初始化是否成功
    Vec3d cov_gyro_ = Vec3d::Zero();  // 陀螺测量噪声协方差(初始化时评估)
    Vec3d cov_acce_ = Vec3d::Zero();  // 加计测量噪声协方差(初始化时评估)
    Vec3d init_bg_ = Vec3d::Zero();   // 陀螺初始零偏
    Vec3d init_ba_ = Vec3d::Zero();   // 加计初始零偏
    Vec3d gravity_ = Vec3d::Zero();   // 重力
    bool is_static_ = false;          // 标志车辆是否静止
    std::deque<IMU> init_imu_deque_;  // 初始化用的数据
    double current_time_ = 0.0;       // 当前时间
    double init_start_time_ = 0.0;    // 静止的初始时间
};

}  // namespace sad

#endif  // SLAM_IN_AUTO_DRIVING_STATIC_IMU_INIT_H

static_imu_init.cc

#include "math_utils.h"


namespace sad {

bool StaticIMUInit::AddIMU(const IMU& imu) 
{
    if (init_success_) 
    {
        return true;
    }

    if (options_.use_speed_for_static_checking_ && !is_static_) 
    {
        LOG(WARNING) << "等待车辆静止";
        init_imu_deque_.clear();
        return false;
    }

    if (init_imu_deque_.empty()) 
    {
        // 记录初始静止时间
        init_start_time_ = imu.timestamp_;
    }

    // 记入初始化队列
    init_imu_deque_.push_back(imu);

    double init_time = imu.timestamp_ - init_start_time_;  // 初始化经过时间
    if (init_time > options_.init_time_seconds_) 
    {
        // 尝试初始化逻辑
        TryInit();
    }

    // 维持初始化队列长度
    while (init_imu_deque_.size() > options_.init_imu_queue_max_size_) 
    {
        init_imu_deque_.pop_front();
    }

    current_time_ = imu.timestamp_;
    return false;
}

bool StaticIMUInit::AddOdom(const Odom& odom) {
    // 判断车辆是否静止
    if (init_success_) {
        return true;
    }

    if (odom.left_pulse_ < options_.static_odom_pulse_ && odom.right_pulse_ < options_.static_odom_pulse_) {
        is_static_ = true;
    } else {
        is_static_ = false;
    }

    current_time_ = odom.timestamp_;
    return true;
}

bool StaticIMUInit::TryInit() 
{
    if (init_imu_deque_.size() < 10) {
        return false;
    }

    // 计算均值和方差
    Vec3d mean_gyro, mean_acce;
    math::ComputeMeanAndCovDiag(init_imu_deque_, mean_gyro, cov_gyro_, [](const IMU& imu) { return imu.gyro_; });
    math::ComputeMeanAndCovDiag(init_imu_deque_, mean_acce, cov_acce_, [this](const IMU& imu) { return imu.acce_; });

    // 以acce均值为方向,取9.8长度为重力
    LOG(INFO) << "mean acce: " << mean_acce.transpose();
    gravity_ = -mean_acce / mean_acce.norm() * options_.gravity_norm_;

    // 重新计算加计的协方差
    math::ComputeMeanAndCovDiag(init_imu_deque_, mean_acce, cov_acce_,
                                [this](const IMU& imu) { return imu.acce_ + gravity_; });

    // 检查IMU噪声
    if (cov_gyro_.norm() > options_.max_static_gyro_var) {
        LOG(ERROR) << "陀螺仪测量噪声太大" << cov_gyro_.norm() << " > " << options_.max_static_gyro_var;
        return false;
    }

    if (cov_acce_.norm() > options_.max_static_acce_var) {
        LOG(ERROR) << "加计测量噪声太大" << cov_acce_.norm() << " > " << options_.max_static_acce_var;
        return false;
    }

    // 估计测量噪声和零偏
    init_bg_ = mean_gyro;
    init_ba_ = mean_acce;

    LOG(INFO) << "IMU 初始化成功,初始化时间= " << current_time_ - init_start_time_ << ", bg = " << init_bg_.transpose()
              << ", ba = " << init_ba_.transpose() << ", gyro sq = " << cov_gyro_.transpose()
              << ", acce sq = " << cov_acce_.transpose() << ", grav = " << gravity_.transpose()
              << ", norm: " << gravity_.norm();
    LOG(INFO) << "mean gyro: " << mean_gyro.transpose() << " acce: " << mean_acce.transpose();
    init_success_ = true;
    return true;
}

}  // namespace sad

main.cpp

#include<ros/ros.h>
#include<imu_integration.h>
#include "sensor_msgs/Imu.h"
#include "tf/tf.h"
#include<nav_msgs/Odometry.h>
#include<static_imu_init.h>
// 该实验中,我们假设零偏已知
Vec3d gravity(-0.050622,0.102474,-9.809334);  // 重力方向
Vec3d init_bg(-0.005136,-0.000981,-0.003482);
Vec3d init_ba(-0.000118,0.000239,-0.022851);
sad::IMUIntegration imu_integ1(gravity, init_bg, init_ba);

ros::Publisher pub_;

sad::StaticIMUInit imu_init;  // 使用默认配置



sad::IMU imu_format(const sensor_msgs::Imu &imu_msg)
{

    sad::IMU imu;
    imu.timestamp_=imu_msg.header.stamp.toSec();
    imu.acce_.x()=imu_msg.linear_acceleration.x;
    imu.acce_.y()=imu_msg.linear_acceleration.y;
    imu.acce_.z()=imu_msg.linear_acceleration.z;
    imu.gyro_.x()=imu_msg.angular_velocity.x;
    imu.gyro_.y()=imu_msg.angular_velocity.y;
    imu.gyro_.z()=imu_msg.angular_velocity.z;
    return imu;

}
void IMUCallback(const sensor_msgs::Imu &imu_msg)
{
    sad::IMU imu_out=imu_format(imu_msg);

    /// IMU 处理函数
    if (!imu_init.InitSuccess()) 
    {
        imu_init.AddIMU(imu_out);
        return;
    }
    //imu_init.GetInitBg(), imu_init.GetInitBa(), imu_init.GetGravity()
    static bool imu_inited = false;

    if(!imu_inited)
    {
    
        ROS_INFO("bg:%f,%f,%f",imu_init.GetInitBg().x(),imu_init.GetInitBg().y(),imu_init.GetInitBg().z());
        ROS_INFO("ba:%f,%f,%f",imu_init.GetInitBa().x(),imu_init.GetInitBa().y(),imu_init.GetInitBa().z());
        ROS_INFO("g:%f,%f,%f",imu_init.GetGravity().x(),imu_init.GetGravity().y(),imu_init.GetGravity().z());
        imu_inited=true;
    }

    imu_integ1.AddIMU(imu_out);
    Eigen::Matrix3d R=imu_integ1.GetR().matrix();
    nav_msgs::Odometry odom;
    odom.header.frame_id="odom";
    odom.header.stamp=imu_msg.header.stamp;
    odom.child_frame_id="base_link";
    odom.pose.pose.position.x=imu_integ1.GetP().x();
    odom.pose.pose.position.y=imu_integ1.GetP().y();
    odom.pose.pose.position.z=imu_integ1.GetP().z();
    Eigen::Quaterniond q;
    q=R.block<3,3>(0,0);
    odom.pose.pose.orientation.x=q.x();
    odom.pose.pose.orientation.y=q.y();
    odom.pose.pose.orientation.z=q.z();
    odom.pose.pose.orientation.w=q.w();
    odom.twist.twist.linear.x=imu_integ1.GetV().x();
    odom.twist.twist.linear.y=imu_integ1.GetV().y();
    odom.twist.twist.linear.z=imu_integ1.GetV().z();
    pub_.publish(odom);
}

int main(int argc, char** argv) 
{
    ros::init(argc, argv, "imu_integration");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("/imu", 100, IMUCallback);
    pub_=n.advertise<nav_msgs::Odometry>("/imu_integration_odom",10);
    ros::spin();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1127830.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Kotlin 协程初探 | 京东物流技术团队

1 它是什么&#xff08;协程 和 Kotlin协程&#xff09; 1.1 协程是什么 维基百科&#xff1a;协程&#xff0c;英文Coroutine [kəru’tin] &#xff08;可入厅&#xff09;&#xff0c;是计算机程序的一类组件&#xff0c;推广了协作式多任务的子程序&#xff0c;允许执行被…

QSPI介绍

0 Preface/Foreword 1 QSPI介绍

数据结构与算法课后题-第七章(顺序查找和折半查找)

牛刀小试&#xff0c;做一下小题&#xff0c;检查一下自己的基础知识掌握的情况。 文章目录 牛刀小试1牛刀小试2牛刀小试3牛刀小试4牛刀小试5牛刀小试6牛刀小试7牛刀小试8牛刀小试9牛刀小试10牛刀小试11牛刀小试12牛刀小试13牛刀小试14牛刀小试15 牛刀小试1 牛刀小试2 错题分析…

从「纯野妆」到「降温妆」,解析小红书“热词爆款学”

白开水妆、视觉降温妆、亚裔辣妹妆......打开小红书的美妆板块&#xff0c;你会发现许多这类极具创意的妆容热词。小红书用户乐于尝鲜、乐于创新&#xff0c;具有强大的创造能力&#xff0c;热衷于为产品、为妆容、为穿搭起“外号”。这些“外号”往往能突破原有思维的束缚&…

el-select multiple表单校验问题

el-select multiple表单校验问题 <el-form refform :modelform><el-form-item propvulTypes label漏洞类型><el-select v-modelform.vulTypes changevulTypeChange><el-option v-foritem in vulList :keyitem :labelitem :valueitem></el-option&g…

智能井盖传感器特点是什么?

在城市基础设施建设过程中&#xff0c;无论是国际大都市还是小县城&#xff0c;井盖所导致的问题会严重影响着城市地下生命线。井盖如若出现移动翻转等现象&#xff0c;是市民生命安全的潜在隐患&#xff0c;也有可能会影响下水道&#xff0c;供水管道等正常运行。所以传统井盖…

HashMap 源码解析

目录 一. 前言 二. 哈希表 三. 源码解析 3.1. 数据结构 3.2. 类结构 3.3. 字段属性 3.4. 构造方法 3.5. 确定哈希桶数组索引位置 3.6. 添加元素 3.7. 扩容机制 3.8. 删除元素 3.9. 查找元素 一. 前言 HashMap基于哈希表的Map接口实现&#xff0c;是以key-value存储…

JavaScript 函数 eval() , json字符串转换

eval() eval() 函数计算 JavaScript 字符串&#xff0c;并把它作为脚本代码来执行。 如果参数是一个表达式&#xff0c;eval() 函数将执行表达式。如果参数是Javascript语句&#xff0c;eval()将执行 Javascript 语句 console.log(eval(2 2)); // Expected output: 4console…

基于SSM的论文投稿系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

PMP考试时间是什么时候?

PMP官方公布&#xff0c;一般来说&#xff0c;一年有4次&#xff0c;分别在3月、6月、9月和12月。具体日期或者时间变动看官方通知。 pmp干货&#xff1a;点击免费刷题&#xff0c;PMP第七版&#xff0c;预测敏捷资料免费分享&#xff01; 来说一下考试的相关情况 1、考试题型…

【EI会议征稿】第十届机电一体化与工业信息学国际学术研讨会(ISMII 2024)

第十届机电一体化与工业信息学国际学术研讨会&#xff08;ISMII 2024&#xff09; 2024 10th International Symposium on Mechatronics and Industrial Informatics 随着往年九届的成功举办&#xff0c;2024年第十届机电一体化与工业信息学国际学术研讨会&#xff08;ISMII…

docker安装mqtt服务器, 并测试连接

docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8084:8084 -p 8883:8883 -p 18083:18083 emqx/emqx:5.3.0 使用mqttx进行测试: 参考: 下载 EMQX

自定义命名不同类型文件,隐藏编号轻松整理,一键操作高效便捷!

你是否曾经因为文件名混乱而烦恼&#xff0c;或者因为编号重复而感到困扰&#xff1f;让我们一起解决这个问题&#xff0c;推荐一款强大的文件改名工具&#xff0c;帮助你个性化文件改名&#xff0c;自定义命名不同类型文件&#xff0c;隐藏编号轻松整理&#xff01; 首先我们…

软件测试进阶篇----Python

Python python语言的学习技巧&#xff1a;多写多敲 要求能够掌握基础知识&#xff0c;能够使用python实现自动化脚本的开发即可&#xff01;&#xff01;&#xff01; 一、python语言的特点 python是一种胶水语言&#xff1a;python需求和其他的行业结合在一起才能发挥更大…

Go语言Goroutine

在本教程中&#xff0c;我们将讨论如何使用 Goroutines 在 Go 中实现并发。 什么是 Goroutine&#xff1f; Goroutine 是与其他函数或方法同时运行的函数或方法。Goroutines 可以被认为是轻量级线程。与线程相比&#xff0c;创建 Goroutine 的成本很小。因此&#xff0c;Go 应…

AI基础软件:如何自主构建大+小模型?

导读&#xff1a;AI 基础软件作为大型 AI 模型的底座&#xff0c;承载着顶层大模型的建设&#xff0c;也是大模型应用落地的关键。为了更好地支持大模型的训练和演进&#xff0c;设计与开发基础软件便显得尤为重要。本文分享了九章云极DataCanvas如何自主构建大 小模型的经验与…

1024程序员节:理解编码背后的艺术

1024的含义 "1024"在中国互联网文化中有两个主要的含义&#xff1a; 1024是2的10次方&#xff0c;这在计算机科学中是一个重要的数字&#xff0c;因为计算机的基础是二进制。因此&#xff0c;程序员们常常把1024作为一个特殊的日子来庆祝&#xff0c;也就是10月24日…

如何把项目上传到Gitee(详细教程)

找到项目根目录右键打开Git Bash Here 输入命令&#xff1a;git init 回车 输入命令&#xff1a;git status 输入命令&#xff1a;git add . 输入命令&#xff1a;git status git commit -m 项目描述 在Gitee官网注册好账号后&#xff0c;git 新建项目 填写补充git项目信息及…

SAP采购发票差异处理

&#xff08;一&#xff09; 税金差异 一般情况下&#xff0c;供应商的开票金额与我们的入库金额一致&#xff0c;不过有时也会出现不一致的情况&#xff0c;如通过金税系统开票出现尾差&#xff0c;或是开票价格大于订单价格。本文介绍如何处理采购发票中的税金差异。 采购订…

【单调栈】84. 柱状图中最大的矩形、60天刷题总结

提示&#xff1a;努力生活&#xff0c;开心、快乐的一天 文章目录 84. 柱状图中最大的矩形&#x1f4a1;解题思路&#x1f914;遇到的问题&#x1f4bb;代码实现&#x1f3af;题目总结 总结数组链表哈希表数组作为哈希表set作为哈希表map作为哈希表 字符串要不要使用库函数双指…