代码浅析DLIO(四)---位姿更新

0. 简介

我们刚刚了解过DLIO的整个流程,我们发现相比于Point-LIO而言,这个方法更适合我们去学习理解,同时官方给出的结果来看DLIO的结果明显好于现在的主流方法,当然指的一提的是,这个DLIO是必须需要六轴IMU的,所以如果没有IMU的画,那只有DLO可以使用了。

在这里插入图片描述

1. getNextPose–通过IMU + S2M + GEO获取下一个姿态

下面的函数作用主要是获取下一个姿态的函数,主要是通过IMU、S2M和GEO三种方式获取。首先,检查新子地图是否准备就绪,如果准备好了并且子地图发生了变化,则将当前全局子地图设置为目标点云,并设置子图的kdtree以及将目标云的法线设置为子地图的法线。接着,使用全局IMU变换作为初始猜测,将当前子地图与全局地图对齐,并在全局坐标系中获取最终变换。然后,更新下一个全局位姿,并进行几何观察器更新。最终,该函数返回下一个姿态。

/**
 * @brief 通过IMU + S2M + GEO获取下一个姿态
 *
 */
void dlio::OdomNode::getNextPose() {

  // 检查新子地图是否准备好可供使用
  this->new_submap_is_ready =
      (this->submap_future.wait_for(std::chrono::seconds(0)) ==
       std::future_status::ready); //等待子地图准备好

  if (this->new_submap_is_ready &&
      this->submap_hasChanged) { //如果子地图准备好了,并且子地图发生了变化

    // 将当前全局子地图设置为目标点云
    this->gicp.registerInputTarget(this->submap_cloud);

    // 设置子图的kdtree,之前就是直接从target_kdtree_拿出来的,这个有必要嘛?
    this->gicp.target_kdtree_ = this->submap_kdtree;

    // 将目标云的法线设置为子地图的法线
    this->gicp.setTargetCovariances(this->submap_normals);

    this->submap_hasChanged = false;
  }

  // 使用全局IMU变换作为初始猜测,将当前子地图与全局地图对齐
  pcl::PointCloud<PointType>::Ptr aligned(
      boost::make_shared<pcl::PointCloud<PointType>>());
  this->gicp.align(*aligned); // 设置对齐后的地图

  // 在全局坐标系中获取最终变换
  this->T_corr =
      this->gicp.getFinalTransformation(); // 根据对齐后的地图来校准转换
  this->T = this->T_corr * this->T_prior;

  // 更新下一个全局位姿,现在源点云和目标点云都在全局坐标系中,所以变换是全局的
  this->propagateGICP();

  // 几何观察器更新
  this->updateState();
}

2. updateState–更新GEO信息

这段代码是通过GEO更新状态的,主要分为以下几个步骤:

  1. 锁定线程以防止状态被PropagateState访问,保证线程安全。
  2. 获取机器人当前的位置、四元数和时间差。
  3. 通过拿到的四元数和预测的四元数构造误差的四元数,然后根据误差的四元数构建四元数校正量,对应公式7。
  4. 将误差的四元数转换到机器人的body坐标系下。
  5. 更新加速度偏差和陀螺仪偏差,同时限制偏差的最大值。
  6. 更新机器人的速度和位置,以及四元数,同时存储前一个姿态、方向和速度。
void dlio::OdomNode::updateState() {

  // 锁定线程以防止状态被PropagateState访问
  std::lock_guard<std::mutex> lock(this->geo.mtx);

  Eigen::Vector3f pin = this->lidarPose.p;              // 位置
  Eigen::Quaternionf qin = this->lidarPose.q;           // 四元数
  double dt = this->scan_stamp - this->prev_scan_stamp; // 时间差

  Eigen::Quaternionf qe, qhat, qcorr;
  qhat = this->state.q;

  // 构造误差的四元数
  qe = qhat.conjugate() * qin; //通过拿到的四元数和预测的四元数构造误差的四元数

  double sgn = 1.;
  if (qe.w() < 0) { // 如果误差的四元数的w小于0
    sgn = -1;
  }

  // 构建四元数校正量,对应公式7
  qcorr.w() = 1 - abs(qe.w());  // 误差的四元数的w部分
  qcorr.vec() = sgn * qe.vec(); // 误差的四元数的向量部分
  qcorr = qhat * qcorr;         // 误差的四元数

  Eigen::Vector3f err = pin - this->state.p;
  Eigen::Vector3f err_body;

  err_body =
      qhat.conjugate()._transformVector(err); // 误差的四元数转换到body坐标系下

  double abias_max = this->geo_abias_max_;
  double gbias_max = this->geo_gbias_max_;

  // 更新加速度偏差
  this->state.b.accel -= dt * this->geo_Kab_ * err_body;
  this->state.b.accel =
      this->state.b.accel.array().min(abias_max).max(-abias_max);

  // 更新陀螺仪偏差
  this->state.b.gyro[0] -= dt * this->geo_Kgb_ * qe.w() * qe.x();
  this->state.b.gyro[1] -= dt * this->geo_Kgb_ * qe.w() * qe.y();
  this->state.b.gyro[2] -= dt * this->geo_Kgb_ * qe.w() * qe.z();
  this->state.b.gyro =
      this->state.b.gyro.array().min(gbias_max).max(-gbias_max);

  // 更新速度和位置
  this->state.p += dt * this->geo_Kp_ * err;
  this->state.v.lin.w += dt * this->geo_Kv_ * err;

  this->state.q.w() += dt * this->geo_Kq_ * qcorr.w();
  this->state.q.x() += dt * this->geo_Kq_ * qcorr.x();
  this->state.q.y() += dt * this->geo_Kq_ * qcorr.y();
  this->state.q.z() += dt * this->geo_Kq_ * qcorr.z();
  this->state.q.normalize();

  // 存储前一个姿态、方向和速度
  this->geo.prev_p = this->state.p;
  this->geo.prev_q = this->state.q;
  this->geo.prev_vel = this->state.v.lin.w;
}

3. updateKeyframes–更新关键帧

在做完位置更新后就会更新关键帧,通过计算当前姿态与轨迹中所有关键帧姿态的差异,然后根据距离和旋转角度是否超过一定阈值来判断是否需要更新关键帧。具体实现过程如下:

首先,遍历轨迹中所有关键帧,计算当前姿态与每个关键帧之间的距离,并记录最近的关键帧的索引和距离。同时,计算当前姿态附近的关键帧数量。

然后,获取最近关键帧的姿态和旋转,并计算当前姿态与最近关键帧之间的距离和旋转角度。如果旋转角度超过了设定的阈值,或者距离超过了设定的阈值且附近关键帧数量小于等于1,则认为需要更新关键帧。

最后,如果需要更新关键帧,则将当前姿态和点云、时间戳、法向量、变换矩阵等信息存储到关键帧向量中。

整个函数实现了关键帧的自适应更新,可以在SLAM系统中提高精度和效率。

void dlio::OdomNode::updateKeyframes() {

  // 计算轨迹中所有姿态和旋转的差异
  float closest_d = std::numeric_limits<float>::infinity();
  int closest_idx = 0;
  int keyframes_idx = 0;

  int num_nearby = 0;

  for (const auto &k : this->keyframes) {

    // 计算当前姿态与关键帧中的姿态之间的距离,这里和更新submap的操作一样
    float delta_d = sqrt(pow(this->state.p[0] - k.first.first[0], 2) +
                         pow(this->state.p[1] - k.first.first[1], 2) +
                         pow(this->state.p[2] - k.first.first[2], 2));

    //计算当前姿态附近的数量
    if (delta_d <= this->keyframe_thresh_dist_ * 1.5) {
      ++num_nearby;
    }

    // 将其存储到变量中
    if (delta_d < closest_d) {
      closest_d = delta_d;         //最近的距离
      closest_idx = keyframes_idx; //最近的关键帧的索引
    }

    keyframes_idx++;
  }

  // 获取最接近的姿势和相应的旋转
  Eigen::Vector3f closest_pose =
      this->keyframes[closest_idx].first.first; //最近的关键帧的位置
  Eigen::Quaternionf closest_pose_r =
      this->keyframes[closest_idx].first.second; //最近的关键帧的旋转

  // 计算当前姿势与最近的姿势之间的距离,和closest_d一致
  float dd = sqrt(pow(this->state.p[0] - closest_pose[0], 2) +
                  pow(this->state.p[1] - closest_pose[1], 2) +
                  pow(this->state.p[2] - closest_pose[2], 2));

  // 使用SLERP计算方向差异
  Eigen::Quaternionf dq;

  if (this->state.q.dot(closest_pose_r) <
      0.) { //如果两个四元数的点积小于0,说明两个四元数的方向相反
    Eigen::Quaternionf lq = closest_pose_r; //将最近的关键帧的旋转赋值给lq
    lq.w() *= -1.;
    lq.x() *= -1.;
    lq.y() *= -1.;
    lq.z() *= -1.;
    dq = this->state.q * lq.inverse(); //计算当前姿态与最近的姿态之间的旋转
  } else {
    dq = this->state.q *
         closest_pose_r.inverse(); //计算当前姿态与最近的姿态之间的旋转
  }

  double theta_rad =
      2. * atan2(sqrt(pow(dq.x(), 2) + pow(dq.y(), 2) + pow(dq.z(), 2)),
                 dq.w()); //计算当前姿态与最近的姿态之间的旋转角度
  double theta_deg = theta_rad * (180.0 / M_PI); //将弧度转换为角度

  // 更新关键帧
  bool newKeyframe = false;

  if (abs(dd) > this->keyframe_thresh_dist_ ||
      abs(theta_deg) >
          this->keyframe_thresh_rot_) { //如果距离或者旋转角度超过阈值
    newKeyframe = true;
  }

  if (abs(dd) <= this->keyframe_thresh_dist_ &&
      abs(theta_deg) > this->keyframe_thresh_rot_ &&
      num_nearby <=
          1) { //如果距离小于阈值,但是旋转角度超过阈值,且附近的关键帧数量小于等于1
    newKeyframe = true;
  }

  if (abs(dd) <= this->keyframe_thresh_dist_) { //如果距离小于阈值
    newKeyframe = false;
  } else if (abs(dd) <= 0.5) {
    newKeyframe = false;
  }

  if (newKeyframe) {

    // 更新关键帧向量
    std::unique_lock<decltype(this->keyframes_mutex)> lock(
        this->keyframes_mutex);
    this->keyframes.push_back(std::make_pair(
        std::make_pair(this->lidarPose.p, this->lidarPose.q),
        this->current_scan)); //将当前的姿态和点云压入到keyframes中
    this->keyframe_timestamps.push_back(
        this->scan_header_stamp); //将当前的时间戳压入到keyframe_timestamps中
    this->keyframe_normals.push_back(
        this->gicp
            .getSourceCovariances()); //将当前的法向量压入到keyframe_normals中
    this->keyframe_transformations.push_back(
        this->T_corr); //将当前的变换矩阵压入到keyframe_transformations中
    lock.unlock();
  }
}

4. propagateState–传播GEO状态

然后我们整个基本都过完了,只有一个IMU的feedback中漏掉的propagateState函数。通过IMU测量值来更新机器人的状态。首先通过一个mutex锁来确保线程安全。然后获取IMU测量的时间间隔,并将当前状态下的四元数和角速度赋值给变量qhat和omega。接下来将机体坐标系下的加速度转换到世界坐标系下,并使用加速度传播公式更新机器人的位置和速度。同时,将机器人的角速度更新为世界坐标系下的角速度。然后使用重力计传播公式更新四元数,确保其归一化。最后,将机器人的角速度和四元数更新到状态中。

…详情请参照古月居

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

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

相关文章

Redis--10--Pipeline

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Pipeline举例比较普通模式与 PipeLine 模式小结&#xff1a; Pipeline 前面我们已经说过&#xff0c;Redis客户端执行一条命令分为如下4个部分:1&#xff09;发送命…

基于Java SSM框架+Vue实现汉服文化平台网站项目【项目源码+论文说明】

基于java的SSM框架Vue实现汉服文化平台系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个汉服文化平台网站 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将…

【论文阅读】ICRA: An Intelligent Clustering Routing Approach for UAV Ad Hoc Networks

文章目录 论文基本信息摘要1.引言2.相关工作3.PROPOSED SCHEME4.实验和讨论5.总结补充 论文基本信息 《ICRA: An Intelligent Clustering Routing Approach for UAV Ad Hoc Networks》 《ICRA:无人机自组织网络的智能聚类路由方法》 Published in: IEEE Transactions on Inte…

Selenium自动化测试 —— 模拟鼠标键盘的操作事件

鼠标操作事件 在实际的web产品测试中&#xff0c;对于鼠标的操作&#xff0c;不单单只有click()&#xff0c;有时候还要用到右击、双击、拖动等操作&#xff0c;这些操作包含在ActionChains类中。 ActionChains类中鼠标操作常用方法&#xff1a; 首先导入ActionChains类&#…

国际语音群呼系统的产品优势有哪些?为什么要使用国际语音群呼系统?

一、国际语音群呼系统的产品优势&#xff1a; 1.巨量群呼 支持大容量并发群呼&#xff0c;呼叫不受限制&#xff0c;充裕的线路保障造就百万级平台容量&#xff0c;可以短时间内同时拨打大量电话&#xff0c;让语音快速到达&#xff0c;大大提高发送效率&#xff1b; 2.自主…

数字媒体技术基础之:常见字体类型

字体 Font在数字设计和排版中起着至关重要的作用&#xff0c;不同的字体类型为文本呈现和创意表达提供了丰富多样的可能性。 .fon 字体 .fon 文件是 Windows 早期系统中使用的一种字体文件格式。 特点&#xff1a; 1、基于像素的位图字体。 2、不支持无损缩放&#xff0c;主要用…

揭秘原型链:探索 JavaScript 面向对象编程的核心(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

VIT总结

关于transformer、VIT和Swin T的总结 1.transformer 1.1.注意力机制 An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a wei…

Windows微软常用运行库合集2023

微软常用运行库合集适用于Windows系统的运行库合集包&#xff0c;基于微软官方的运行库而制作的&#xff0c;包括了常用的vb&#xff0c;vc2005/2008/2010/2012/2013/2017/2019/2005-2022&#xff0c;Microsoft Universal C Runtime&#xff0c;VS 2010 Tools For Office Runti…

Windows远程桌面提示出现身份验证错误 要求的函数不支持

现象 解决方案&#xff1a; 在cmd运行框输入&#xff1a;gpedit.msc打开组策略编辑器路径&#xff1a;计算机配置→管理模板→Windows组件→远程桌面服务→远程桌面会话主机→安全开启远程连接要求使用指定的安全层 禁用要求使用网络级别的身份验证对远程连接的用户进行身份验…

HttpRunner自动化测试之实现参数化传递

参数化实现及重复执行 参数化测试&#xff1a;在接口测试中&#xff0c;为了实现不同组数据对同一个功能模块进行测试&#xff0c;需要准备多组测试数据对模块进行测试的过程。 在httprunner中可以通过如下方式实现参数化&#xff1a; 1、在YAML/JSON 中直接指定参数列表 2、…

Python标准库:math库【侯小啾python领航班系列(十六)】

Python标准库:math库【侯小啾python领航班系列(十六)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…

面试 Java 基础八股文十问十答第三期

面试 Java 基础八股文十问十答第三期 作者&#xff1a;程序员小白条&#xff0c;个人博客 ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 21.说下Java8的Stream流的常用方法 答: forEach遍历、find、match进行匹配reduce进行归约&#xff0c;比如求和&#xff0c;乘&#xff0c;除聚合…

上门按摩APP小程序,抓住机遇创新服务新模式;

上门按摩APP小程序&#xff1a;抓住机遇&#xff0c;创新服务新模式&#xff1b; 随着现代人对生活质量要求的提高&#xff0c;上门按摩服务正成为一种新的、受欢迎的生活方式。通过APP小程序&#xff0c;用户可以轻松预约按摩服务&#xff0c;解决身体疲劳问题&#xff0c;享受…

汇编语言实现音乐播放器

目标程序 用汇编语言实现一个音乐播放器&#xff0c;并支持点歌 Overview 乐曲是按照一定的高低、长短和强弱关系组成的音调&#xff0c;在一首乐曲中&#xff0c;每个音符的音高和音长与频率和节拍有关&#xff0c;因此我们要分别为3首要演奏的乐曲定义一个频率表和一个节拍…

Java Web——动态Web开发核心-Servlet

1. 官方文档 官方文档地址&#xff1a;Overview (Servlet 4.0 API Documentation - Apache Tomcat 9.0.83) servlet 与 Tomcat 的关系&#xff1a;Tomcat 支持 Servlet Tomcat 是一个开源的 Java 服务器&#xff0c;它主要用来提供 Web 服务&#xff0c;包括 HTTP 请求和响应…

<JavaEE> volatile关键字 -- 保证内存可见性、禁止指令重排序

目录 一、内存可见性 1.1 Java内存模型(JMM) 1.2 内存可见性演示 二、指令重排序 三、关键字 volatile 一、内存可见性 1.1 Java内存模型(JMM) 1&#xff09;什么是Java内存模型&#xff08;JMM&#xff09;&#xff1f;Java内存模型即Java Memory Model&#xff0c;简…

【重点】【双指针】11. 盛最多水的容器

题目 注意&#xff1a;二维接雨水&#xff0c;有墙的&#xff0c;有线的&#xff0c;着这个属于线的。 class Solution {public int maxArea(int[] height) {if (height.length < 2) {return 0;}int left 0, right height.length - 1, res 0;while (left < right) {…

三轴加速度计LIS2DW12开发(2)----基于中断信号获取加速度数据

三轴加速度计LIS2DW12开发.2--轮基于中断信号获取加速度数据 概述视频教学样品申请生成STM32CUBEMX串口配置IIC配置CS和SA0设置INT1设置串口重定向参考程序初始换管脚获取ID复位操作BDU设置开启INT1中断设置传感器的量程配置过滤器链配置电源模式设置输出数据速率中断判断加速…

【动态规划】LeetCode-931.下降路径最小和

&#x1f388;算法那些事专栏说明&#xff1a;这是一个记录刷题日常的专栏&#xff0c;每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目&#xff0c;在这立下Flag&#x1f6a9; &#x1f3e0;个人主页&#xff1a;Jammingpro &#x1f4d5;专栏链接&…