IMU状态预积分代码实现 —— IMU状态预积分类

IMU状态预积分代码实现 —— IMU状态预积分类

  • 实现IMU状态预积分类

实现IMU状态预积分类

首先,实现预积分自身的结构。一个预积分类应该存储一下数据:

  • 预积分的观测量 △ R ~ i j , △ v ~ i j , △ p ~ i j \bigtriangleup \tilde{R} _{ij},\bigtriangleup \tilde{v} _{ij},\bigtriangleup \tilde{p} _{ij} R~ij,v~ij,p~ij
  • 预积分开始时的IMU零偏 b g , b a b_{g},b_{a} bg,ba
  • 在积分时期内的测量噪声 Σ i , k + 1 \Sigma _{i,k+1} Σi,k+1
  • 各积分量对IMU零偏的雅克比矩阵
  • 整个积分时间 △ t i j \bigtriangleup t_{ij} tij

以上都是必要的信息。除此之外,也可以将IMU的读数记录在预积分类中(当然,也可以不记录,因为都已经积分过了)。同时,IMU的测量噪声和零偏随机游走噪声也可以作为配置参数,写在预积分类中。

声明这个类

class IMUPreintegration {

参数配置项
其中包括:

  • 陀螺仪初始零偏
  • 加速度计初始零偏
  • 陀螺噪声
  • 加计噪声
    /// 参数配置项
    /// 初始的零偏需要设置,其他可以不改
    struct Options {
        Options() {}
        Vec3d init_bg_ = Vec3d::Zero();  // 初始零偏
        Vec3d init_ba_ = Vec3d::Zero();  // 初始零偏
        double noise_gyro_ = 1e-2;       // 陀螺噪声,标准差
        double noise_acce_ = 1e-1;       // 加计噪声,标准差
    };

构造函数

IMUPreintegration(Options options = Options());

中间省略函数的声明,之后再写。

下面完成类的成员变量定义
整体预积分时间

    double dt_ = 0;                          // 整体预积分时间

噪声矩阵,累积噪声矩阵 Σ i , k + 1 \Sigma _{i,k+1} Σi,k+1 ,测量噪声矩阵 C o v ( η d , k ) Cov(\eta_{d,k} ) Cov(ηd,k)

    Mat9d cov_ = Mat9d::Zero();              // 累计噪声矩阵
    Mat6d noise_gyro_acce_ = Mat6d::Zero();  // 测量噪声矩阵

预积分开始时的IMU零偏 b g , b a b_{g},b_{a} bg,ba

    // 零偏
    Vec3d bg_ = Vec3d::Zero();
    Vec3d ba_ = Vec3d::Zero();

预积分的观测量 △ R ~ i j , △ v ~ i j , △ p ~ i j \bigtriangleup \tilde{R} _{ij},\bigtriangleup \tilde{v} _{ij},\bigtriangleup \tilde{p} _{ij} R~ij,v~ij,p~ij

    // 预积分观测量
    SO3 dR_;
    Vec3d dv_ = Vec3d::Zero();
    Vec3d dp_ = Vec3d::Zero();

各积分量对IMU零偏的雅克比矩阵

    // 雅可比矩阵
    Mat3d dR_dbg_ = Mat3d::Zero();
    Mat3d dV_dbg_ = Mat3d::Zero();
    Mat3d dV_dba_ = Mat3d::Zero();
    Mat3d dP_dbg_ = Mat3d::Zero();
    Mat3d dP_dba_ = Mat3d::Zero();

因为IMU零偏相关的噪声项并不直接和预积分类有关,所以将它们挪到优化类当中。这个类主要完成对IMU数据进行预积分操作,然后提供积分之后的观测量与噪声值。

下面来看单个IMU的积分函数,首先在类中进行声明。

    /**
     * 插入新的IMU数据
     * @param imu   imu 读数
     * @param dt    时间差
     */
    void Integrate(const IMU &imu, double dt);

来看函数具体实现

整体而言,它按照以下顺序更新内部的成员变量:

  1. 更新位置和速度的测量值
  2. 更新运动模型的噪声矩阵
  3. 更新观测量对零偏的各雅克比矩阵
  4. 更新旋转部分的测量值
  5. 更新积分时间在这里插入代码片
void IMUPreintegration::Integrate(const IMU &imu, double dt) {

去掉零偏的测量

    Vec3d gyr = imu.gyro_ - bg_;  // 陀螺
    Vec3d acc = imu.acce_ - ba_;  // 加计

更新预积分速度观测量和位置观测量

        // 更新dv, dp
        dp_ = dp_ + dv_ * dt + 0.5f * dR_.matrix() * acc * dt * dt;
        dv_ = dv_ + dR_ * acc * dt;

对应公式为
在这里插入图片描述
在这里插入图片描述
预积分旋转观测 dR先不更新,因为A, B阵还需要现在的dR

下面计算运动方程雅克比矩阵系数A、B阵,用于更新噪声项
在这里插入图片描述
在这里插入图片描述

    // 运动方程雅可比矩阵系数,A,B阵,
    // 另外两项在后面
    Eigen::Matrix<double, 9, 9> A;
    A.setIdentity();
    Eigen::Matrix<double, 9, 6> B;
    B.setZero();

加速度计的伴随矩阵和t的平方

    Mat3d acc_hat = SO3::hat(acc);
    double dt2 = dt * dt;

公式中的这个地方有用到,避免重复计算
在这里插入图片描述

    A.block<3, 3>(3, 0) = -dR_.matrix() * dt * acc_hat;
    A.block<3, 3>(6, 0) = -0.5f * dR_.matrix() * acc_hat * dt2;
    A.block<3, 3>(6, 3) = dt * Mat3d::Identity();

计算A矩阵中对应的各个块,分别对应公式如下,A矩阵中的A.block<3, 3>(0, 0)块,之后用更新完的dR 更新
在这里插入图片描述

    B.block<3, 3>(3, 3) = dR_.matrix() * dt;
    B.block<3, 3>(6, 3) = 0.5f * dR_.matrix() * dt2;

更新B矩阵的各块,分别对应公式如下
在这里插入图片描述

    // 更新各雅可比
    dP_dba_ = dP_dba_ + dV_dba_ * dt - 0.5f * dR_.matrix() * dt2;                     
    dP_dbg_ = dP_dbg_ + dV_dbg_ * dt - 0.5f * dR_.matrix() * dt2 * acc_hat * dR_dbg_; 
    dV_dba_ = dV_dba_ - dR_.matrix() * dt;                                             
    dV_dbg_ = dV_dbg_ - dR_.matrix() * dt * acc_hat * dR_dbg_;     

更新各雅克比矩阵对应公式依次为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面更新预积分旋转部分观测量

    // 旋转部分
    Vec3d omega = gyr * dt;         // 转动量
    Mat3d rightJ = SO3::jr(omega);  // 右雅可比
    SO3 deltaR = SO3::exp(omega);   // exp后
    dR_ = dR_ * deltaR;             // 更新预积分旋转部分观测量

对应公式:
在这里插入图片描述
其中右雅克比矩阵的计算是为了更新上面的B矩阵

    A.block<3, 3>(0, 0) = deltaR.matrix().transpose();
    B.block<3, 3>(0, 0) = rightJ * dt;

利用更新完的dR和右雅克比矩阵更新A、B阵中对应的块
对应公式:
在这里插入图片描述

    // 更新噪声项
    cov_ = A * cov_ * A.transpose() + B * noise_gyro_acce_ * B.transpose();

利用填充好的A阵和B阵,来更新噪声项
对应公式如下:
在这里插入图片描述
其中 C o v ( η d , k ) Cov(\eta_{d,k} ) Cov(ηd,k)即代码中的noise_gyro_acce_的构成就是陀螺仪和加计的噪声构成的对角矩阵,在构造函数中构成的

    const float ng2 = options.noise_gyro_ * options.noise_gyro_;
    const float na2 = options.noise_acce_ * options.noise_acce_;
    noise_gyro_acce_.diagonal() << ng2, ng2, ng2, na2, na2, na2;

下则继续更新预积分旋转观测量对陀螺仪零偏的雅克比矩阵

    // 更新dR_dbg
    dR_dbg_ = deltaR.matrix().transpose() * dR_dbg_ - rightJ * dt;  

对应公式如下:
在这里插入图片描述

最后增加积分时间:

    // 增量积分时间
    dt_ += dt;

这样就完成了一次对IMU数据的操作。需要注意的是,如果不进行优化,则预积分和直接积分的效果是完全一致的,都是将IMU数据一次性地积分。在预积分之后,也可以向ESKF一样,从起始状态向最终状态进行预测。

预测函数实现如下:

    /**
     * 从某个起始点开始预测积分之后的状态
     * @param start 起始时时刻状态
     * @return  预测的状态
    */
    NavStated IMUPreintegration::Predict(const sad::NavStated &start, const Vec3d &grav) const {
        SO3 Rj = start.R_ * dR_;
        Vec3d vj = start.R_ * dv_ + start.v_ + grav * dt_;
        Vec3d pj = start.R_ * dp_ + start.p_ + start.v_ * dt_ + 0.5f * grav * dt_ * dt_;

        auto state = NavStated(start.timestamp_ + dt_, Rj, pj, vj);
        state.bg_ = bg_;
        state.ba_ = ba_;
        return state;
    }

与ESKF不同的是,预积分可以对多个IMU数据进行预测,可以从任意起始时刻向后预测,而ESKF通常只在当前状态下,针对单个IMU数据,向下一时刻预测。

获取修正之后的观测量,bias可以与预积分时期的不同,会有一阶修正

// 预积分旋转零偏更新修正后测量值
SO3 IMUPreintegration::GetDeltaRotation(const Vec3d &bg) { return dR_ * SO3::exp(dR_dbg_ * (bg - bg_)); }

对应公式为:
在这里插入图片描述
预积分速度零偏更新修正后测量值

    // 预积分速度零偏更新修正后测量值
    Vec3d IMUPreintegration::GetDeltaVelocity(const Vec3d &bg, const Vec3d &ba) {
        return dv_ + dV_dbg_ * (bg - bg_) + dV_dba_ * (ba - ba_);
    }

对应公式为:
在这里插入图片描述
预积分位置零偏更新修正后测量值

    // 预积分位置零偏更新修正后测量值
    Vec3d IMUPreintegration::GetDeltaPosition(const Vec3d &bg, const Vec3d &ba) {
        return dp_ + dP_dbg_ * (bg - bg_) + dP_dba_ * (ba - ba_);
    }

对应公式为:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python爬虫--scrapy+selenium框架】超详细的Python爬虫scrapy+selenium框架学习笔记(保姆级别的,非常详细)

六&#xff0c;selenium 想要下载PDF或者md格式的笔记请点击以下链接获取 python爬虫学习笔记点击我获取 Scrapyselenium详细学习笔记点我获取 Python超详细的学习笔记共21万字点我获取 1&#xff0c;下载配置 ## 安装&#xff1a; pip install selenium## 它与其他库不同…

【C++】C++11新特性:列表初始化、声明、新容器、右值引用、万能引用和完美转发

目录 一、列表初始化 1.1 { } 初始化 1.2 std::initializer_list 二、声明 2.1 auto 2.2 decltype 2.3 nullptr 三、新容器 四、右值引用和移动语义 4.1 左值和左值引用 4.2 右值和右值引用 4.3 左值引用与右值引用比较 4.4 右值引用使用场景和意义&#xff1a;移…

AI能否代替ACE

什么是ACE ? 申请ACE需要以下条件: 1.发表与oracle相关的技术博客 2.参与Oracle相关的技术大会 3.对Oracle社区做出贡献。 这正好是AI应用的场景吗? 在一个群里有个群友质疑AI落地,以及应用领域? Kelvin:我一直在迷茫&#xff0c;学不好。这么多有趣AI 问题&…

冯喜运:5.31晚间黄金原油行情分析及尾盘操作策略

【黄金消息面分析】&#xff1a;周五&#xff08;5月31日&#xff09;&#xff0c;最新发布的数据显示&#xff0c;美国4月核心PCE物价指数月率录得0.2%&#xff0c;低于预期(0.3%)&#xff0c;经济学家认为&#xff0c;核心指数比整体指数更能反映通胀。除此之外&#xff0c;美…

[openwrt-21.02]openwrt-21.02 make menuconfig不显示luci-app-firewall问题分析及解决方案

问题描述 make menuconfig在 在applications界面没有luci-app-firewall 问题分析 首先重新执行 ./scripts/feeds update -a ./scripts/feeds install -a 然后再次执行make menuconfig&#xff0c;依然不显示&#xff0c;所以不是feeds安装的问题 最后看到log有个openmptc…

P3881

最小值最大 二分&#xff1a;枚举两个牛之间的最小距离&#xff0c;左端点是1&#xff0c;右端点是篱笆总长度。 Check数组&#xff1a; 如果两头牛之间距离是Mid不合法&#xff0c;则返回0&#xff08;false&#xff09;&#xff1b; 如果两头牛之间距离是Mid合法&#xf…

Beamer中二阶导、一阶导数的显示问题

Beamer中二阶导、一阶导数的显示问题 在beamer中表示 f ′ f f′和 f ′ ′ f f′′时发现导数符号距离 f f f很近 \documentclass{beamer} \usepackage{amsmath,amssymb}\begin{document} \begin{frame}\frametitle{Derivative}Derivative:\[f^{\prime}(x) \quad f \quad…

基于安卓的虫害识别软件设计--(1)模型训练与可视化

引言 简介&#xff1a;使用pytorch框架&#xff0c;从模型训练、模型部署完整地实现了一个基础的图像识别项目计算资源&#xff1a;使用的是Kaggle&#xff08;每周免费30h的GPU&#xff09; 1.创建名为“utils_1”的模块 模块中包含&#xff1a;训练和验证的加载器函数、训练…

C++ 特殊运算符

一 赋值运算符 二 等号作用 三 优先级和结合顺序 四 左值和右值 五 字节数运算符 条件运算符 使用条件运算符注意 逗号运算符 优先级和结合顺序 总结

【C++】问题及补充(2)

string s2“hello word”;是怎么进行隐式类型转换的 在这里&#xff0c;"hello world"是一个C字符串常量&#xff0c;而s2是一个std::string类型的变量。当你将C字符串常量赋值给一个std::string类型的变量时&#xff0c;会发生隐式类型转换。编译器会将C字符串常量转…

Vue常用自定义指令、纪录篇

文章目录 一、元素尺寸发生变化时二、点击元素外自定义指令三、元素拖拽自定义指令四、防抖自定义指令五、节流自定义指令六、权限判断自定义指令 一、元素尺寸发生变化时 使用场景&#xff1a; 当元素的尺寸发生变化时需要去适配一些元素时。 或者在元素尺寸发生变化时要去适配…

下载安装nvm,使用nvm管理node.js版本

目录 一、下载安装nvm&#xff08;windows&#xff09; 二、使用nvm管理node.js版本 &#xff08;1&#xff09;nvm命令行 &#xff08;2&#xff09; 使用nvm管理node.js版本 ①查看nvm版本 ②显示活动的node.js版本 ③列出可供下载的node.js版本 ④安装node.js指定版本 ⑤列出…

19.Redis之集群

1.集群的基本介绍 集群 这个词.广义的集群,只要你是多个机器,构成了分布式系统, 都可以称为是一个"集群"前面主从结构,哨兵模式,也可以称为是"广义的集群”狭义的集群,redis 提供的集群模式, 这个集群模式之下,主要是要解决,存储空间不足的问题(拓展存储空间) …

原生小程序一键获取手机号

1.效果图 2.代码index.wxml <!-- 获取手机号 利用手机号快速填写的功能&#xff0c;将button组件 open-type 的值设置为 getPhoneNumber--><button open-type"getPhoneNumber" bindgetphonenumber"getPhoneNumber">获取手机号</button> …

【再探】设计模式—访问者模式、策略模式及状态模式

访问者模式是用于访问复杂数据结构的元素&#xff0c;对不同的元素执行不同的操作。策略模式是对于具有多种实现的算法&#xff0c;在运行过程中可动态选择使用哪种具体的实现。状态模式是用于具有不同状态的对象&#xff0c;状态之间可以转换&#xff0c;且不同状态下对象的行…

Threejs(WebGL)绘制线段优化:Shader修改gl.LINES模式为gl.LINE_STRIP

目录 背景 思路 Threejs实现 记录每条线的点数 封装原始裁剪索引数据 封装合并几何体的缓冲数据&#xff1a;由裁剪索引组成的 IntArray 守住该有的线段&#xff01; 修改顶点着色器 修改片元着色器 完整代码 WebGL实现类似功能&#xff08;简易版&#xff0c;便于测…

极验4点选逆向 JS逆向分析 最新版验证码

目录 声明&#xff01; 一、请求流程分析 二、加密参数w与payload 三、参数w生成位置 四、结果展示&#xff1a; 原创文章&#xff0c;请勿转载&#xff01; 本文内容仅限于安全研究&#xff0c;不公开具体源码。维护网络安全&#xff0c;人人有责。 声明&#xff01; 本文章…

mirth Connect 自定义JAVA_HOME

mirth Connect 自定义JAVA_HOME 1、背景 服务器上安装了两个不同版本的Java&#xff0c;我希望Mirth服务使用与默认系统不同的版本。自定义指定java版本 2、解决方法 2.1 优先级说明 系统变量JAVA_HOME (设置后&#xff0c;mirth会根据这个进行启动运行服务&#xff0c;优先级…

家政预约小程序10公众号集成

目录 1 使用测试号3 工作流配置4 配置关注事件脚本5 注册开放平台6 获取公众号access_token6 实现关注业务逻辑总结 我们本次实战项目构建的相当于一个预约平台&#xff0c;既有家政企业&#xff0c;也有家政服务人员还有用户。不同的人员需要收到不同的消息&#xff0c;比如用…

根据状态转移图实现时序电路 (三段式状态机)

看图编程 * ** 代码 module seq_circuit(input C ,input clk ,input rst_n,output wire Y ); reg [1:0] current_stage ; reg [1:0] next_stage ; reg Y_reg; //输出//第一段 &#xff1a; 初始化当前状态和…