【PLIO学习总结】laserMapping中的时间戳与状态更新逻辑

本文仅用于个人学习总结记录。如有错误,请批评指正。

0、PLIO简要思路

从PLIO的论文中,可以知道,完整的PLIO算法采用IMU和LiDAR数据同时作为“输入”,维护状态变量包括加速度和角速度。

同时,PLIO是一种distortion-free的方法,即不需要进行去畸变。
之所以是不需要去畸变,是因为对每个雷达点都进行状态更新,然后将lidar点投回世界系作为地图点。

有了这个基本概念,我们就开始看laserMappingmain函数中的处理流程。

本文按照“IMU作为output”,即IMU不仅用于预测,也具有观测模型。此时,use_imu_as_input为false,imu_en为true。

1、不同时刻的更新逻辑

对于一个LIO系统,同时存在LiDAR和IMU数据,下图画出了对齐以后的两个时间轴。

在这里插入图片描述
可以看出,一次lidar的scan之间有多个绿色的雷达点,同时也有多个IMU测量。
两个IMU的测量间,可能有多个LiDAR pint;
两个LiDAR point间,也可能有多个IMU(虽然我不知道为啥,但代码的逻辑是这么处理的。实测发现,基本上两个Point之间只会出现“1个”IMU)

在代码的sync_packages时,把LiDAR和IMU数据进行了打包,保证了在后续处理时,永远是一个完整的scan中LiDAR Point
和IMU数据。

那么,在收到一个IMU或一个LiDAR时,理论上都进行状态更新。
但是,在代码实现中,永远是在每个LiDAR Point到来时进行更新。只不过,如果这个point到来时,buffer中下一个IMU数据如果是这个point之前,则需要用IMU的数据进行更新;否则,只更新LiDAR Point这部分。

1.1 无IMU数据

这里我们把这种情况叫做 Case 1,具体的示意图如下:

在这里插入图片描述
即当前时间是一个lidar point对应的时刻,前一次状态更新时刻time_predict_last_const和当前时刻time_current之间没有插入IMU数据。

此时,需要做的事情为:

  • 根据上一次IMU的输入,进行预测操作,预测状态量,和对应的协方差;
  • 根据LiDAR的观测和残差,更新状态量和协方差。

1.2 有IMU的数据

这里我们把这种情况叫做 Case 2,具体的示意图如下:

在这里插入图片描述

这就稍微有一些复杂了,因为上一个“状态完整更新”时刻应该是上一个Point,但当前的Point和前一个Point之间,有了多个IMU数据(一般是1个)。

此时,做的事情包括:

  • 对第1个IMU时刻,计算上一个Point到这个IMU的时间间隔dt,然后预测新的状态,和协方差;
  • 利用第1个IMU的数据,观测模型,计算残差,并进行更新全部状态和协方差;
  • 如果有多个IMU,则重复上面两个步骤。注意,重复时,时间间隔为距离上一个IMU测量的时间间隔,而不是到前一个LiDAR Point的;
  • 完成所有IMU时刻的预测和更新之后,处理新的LiDAR Point的数据,此时再执行1.1里面的两步:计算当前Point到前面最近IMU的时间间隔dt,预测状态和协方差;根据LiDAR的数据进行更新。

1.3 完整的时间戳示意图

在这里插入图片描述

2、代码

捋清楚上面的过程,就可以看代码了。这里,只保留代码的框架。


// IMU作为状态量进行更新
if (!use_imu_as_input){
    // 对所有(时间压缩后的)雷达点进行更新
    for (k = 0; k < time_seq.size(); k++)       
    {
        PointType &point_body  = feats_down_body->points[idx+time_seq[k]];
        time_current = point_body.curvature / 1000.0 + pcl_beg_time;    // 获取当前雷达点时间戳
        if(imu_en)
        {
            bool imu_comes = time_current > imu_next.header.stamp.toSec();
            // 是否有了新的IMU数据?如果有,执行下面while,即case2中所说的内容;如果没有,跳过这部分,只是简单的case1
            while (imu_comes)
            {
                imu_next = *(imu_deque.front());                
                // 获得buffer中的IMU,这个IMU的时间戳处于“上一次更新”和“当前雷达点”时间戳之间

                double dt = imu_last.header.stamp.toSec() - time_predict_last_const;
                kf_output.predict(dt, Q_output, input_in, true, false);                 // case2中,预测到当前IMU时刻时,状态的预测;
                double dt_cov = imu_last.header.stamp.toSec() - time_update_last;       // 问题:
                if (dt_cov > 0.0)
                {
                    kf_output.predict(dt_cov, Q_output, input_in, false, true);         // case2中,预测到当前IMU时刻时,协方差的状态;
                    kf_output.update_iterated_dyn_share_IMU();                          // case2中,IMU进行update
                }
            }
        }

        double dt = time_current - time_predict_last_const;             
        kf_output.predict(dt, Q_output, input_in, true, false);         // case1,lidar点到前一个预测时刻,状态的预测
        time_predict_last_const = time_current;
        
        if (!kf_output.update_iterated_dyn_share_modified())            // case1,lidar进行update
        {
            idx = idx+time_seq[k];				// 获取下一个 Lidar Point的索引
            continue;
        }
    }
}

3、遗留问题

现在还遗留了一个问题,就是代码中有两个变量,记录上一次XXX的时间:
time_predict_last_consttime_update_last
从名字上看,前者是“上一次predict的时刻”,后者是“上一次update的时刻”。二者什么区别么?

在代码中,二者的区别体现在

if(!prop_at_freq_of_imu)
{
    double dt_cov = time_current - time_update_last;
    if (dt_cov > 0.0)
    {
        kf_output.predict(dt_cov, Q_output, input_in, false, true);
        time_update_last = time_current;   							// 这里更新了“上一次更新时间戳”
    }
}
kf_output.predict(dt, Q_output, input_in, true, false);
time_predict_last_const = time_current;								// 这里更新了“上一次预测时间戳”

即,如果prop_at_freq_of_imu是false的,两者不一样;
但是,目前版本的代码中,prop_at_freq_of_imu一直是true,因此,这里时间戳有一些的混乱。

所以,在作者的代码中,有这么一行:

time_predict_last_const = imu_last.header.stamp.toSec(); // big problem 

作者注释了“大问题”,可能是一些功能增删造成的混乱吧。但应该不影响上面部分的大逻辑。

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

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

相关文章

手搭手RocketMQ发送消息

消息中间件的对比 消息中间件 ActiveMQ RabbitMQ RocketMQ kafka 开发语言 java erlang java scala 单击吞吐量 万级 万级 10万级 10万级 时效性 ms us ms ms 可用性 高(主从架构) 高(主从架构) 非常高(主从架构) 非常高(主从架构) 消息中间件: acti…

flutter入门

本文真对 Flutter 的技术特性&#xff0c;做了一些略全面的入门级的介绍&#xff0c;如果你听说过Flutter&#xff0c;想去了解它&#xff0c;但是又不想去翻厚厚的API&#xff0c;那么本文就是为你准备的。 随着纯客户端到Hybrid技术&#xff0c;到RN&Weex&#xff0c;再…

Vue2(五):收集表单数据、过滤器、内置指令和自定义指令

一、回顾 总结Vue监视数据 1.Vue监视数据的原理&#xff1a; 1.vue会监视data中所有层次的数据。 2.如何监测对象中的数据?通过setter实现监视&#xff0c;且要在new Vue时就传入要监测的数据。(1&#xff09;.对象中后追加的属性&#xff0c;Vue默认不做响应式处理(2&#…

苍穹外卖学习-----2024/03/010---redis,店铺营业状态设置

1.Redis入门 2.在Java中操作Redis 3.店铺营业状态设置 BUG!!! 今天在启动项目时&#xff0c;用到了Redis缓存数据库&#xff0c;但是却出现了报错信息&#xff1a; ERR Client sent AUTH, but no password is set。Caused by: io.lettuce.core.RedisCommandExecutionException…

Codeforces Round 933 (Div. 3) A~D

比赛链接 : codeforces.com/contest/1941 A . Rudolf and the Ticket 直接暴力即可 ; #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \n #define lowbit(x) (x&(-x)) #define sz(a) (int)a.size() #define p…

【阿里云系列】-基于云效构建部署Springboot项目到ACK

介绍 为了提高项目迭代的速度加速交付产品给客户&#xff0c;我们通常会选择CICD工具来减少人力投入产生的成本&#xff0c;开源的工具比如有成熟的Jenkins&#xff0c;但是本文讲的是阿里云提高的解决方案云效平台&#xff0c;通过配置流水线的形式实现项目的快速部署到服务器…

LeetCode101题:对称二叉树(python3)

对称二叉树定义&#xff1a; 对于树中 任意两个对称节点 L 和 R &#xff0c;一定有&#xff1a; L.val R.val &#xff1a;即此两对称节点值相等。 L.left.val R.right.val &#xff1a;即 L的 左子节点 和 R 的 右子节点 对称。 L.right.val R.left.val &#xff1a;即 L…

arcgis在GIS滑坡易发性分析中的应用技术研究

我国是地质灾害多发国家&#xff0c;地质灾害的发生无论是对于地质环境还是人类生命财产的安全都会带来较大的威胁&#xff0c;因此需要开展地质灾害风险普查。利用遥感&#xff08;RS&#xff09;技术进行地质灾害调查工作具有宏观、快速、准确的特点&#xff0c;能反映出地质…

优先级队列(堆)(1)

目录 一. 优先级队列 1.1 概念 二. 优先级队列的模拟实现 2.1 堆的概念 2.2 堆的存储方式 2.3 堆的创建 2.3.1 堆向下调整 2.3.2 堆的创建 2.3.3 建堆的时间复杂度 2.4 堆的插入与删除 2.4.1 堆的插入 2.4.2 堆的删除 常见习题&#xff1a; 一. 优先级队列 1.1 概…

力扣:数组篇

1、数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合。 需要两点注意的是 数组下标都是从0开始的。数组内存空间的地址是连续的 因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添元素的时候&#xff0c;就难免要移动其他元素的地址。 …

【基础CSS】

本文章属于学习笔记&#xff0c;在https://www.freecodecamp.org/chinese/learn/2022/responsive-web-design/中练习 二、 CSS 样式&#xff0c;新建一个文件.css&#xff0c;该文件不含有style标签 <style>. h1&#xff0c;h2&#xff0c;p{ text-align&#xff1a;ce…

03-自媒体文章发布-黑马头条

自媒体文章发布 1)自媒体前后端搭建 1.1)后台搭建 ①&#xff1a;资料中找到heima-leadnews-wemedia.zip解压 拷贝到heima-leadnews-service工程下&#xff0c;并指定子模块 执行leadnews-wemedia.sql脚本 添加对应的nacos配置 spring:datasource:driver-class-name: com…

Linux:导出环境变量命令export

相关阅读 Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 Linux中的内建命令export命令用于创建一个环境变量&#xff0c;或将一个普通变量导出为环境变量&#xff0c;并且在这个过程中&#xff0c;可以给该环境变量赋值。 下面…

Java后端八股文之java基础

文章目录 0.Java 中有 8 种基本数据类型1. 为什么浮点数运算会丢失精度&#xff1f;如何解决&#xff1f;2. 面向对象的三大特征2.1 封装2.2 继承2.3 多态 3. 深拷贝和浅拷贝的区别&#xff1f;什么是引用拷贝&#xff1f;4. equals方法与“”方法4.1 4.2 equals方法 5.hashcod…

计算机组成原理实验报告1 | 实验1.1 运算器实验(键盘方式)

本文整理自博主大学本科《计算机组成原理》课程自己完成的实验报告。 —— *实验环境为学校机房实验箱。 目录 一、实验目的 二、实验内容 三、实验步骤及实验结果 Ⅰ、单片机键盘操作方式实验 1、实验连线&#xff08;键盘实验&#xff09; 2、实验过程 四、实验结果的…

TortoiseSVN 报错:The server unexpectedly closed the connetion

前言 CentOS7Linux 安装subversionmod_dav_svn&#xff0c;搭建subversion(svn)服务器 The server unexpectedly closed the connetion 解决办法 重启Apache服务 shell> systemctl restart httpd

12 list的使用

文档介绍 文档介绍 1.list是可以在常数范围内的任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代 2.list的底层是带头双向链表循环结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和…

【JavaScript】数据类型转换 ① ( 隐式转换 和 显式转换 | 常用的 数据类型转换 | 转为 字符串类型 方法 )

文章目录 一、 JavaScript 数据类型转换1、数据类型转换2、隐式转换 和 显式转换3、常用的 数据类型转换4、转为 字符串类型 方法 一、 JavaScript 数据类型转换 1、数据类型转换 在 网页端 使用 HTML 表单 和 浏览器输入框 prompt 函数 , 接收的数据 是 字符串类型 变量 , 该…

docker容器镜像管理+compose容器编排(持续更新中)

目录 一、 Docker的基本组成 二、 容器和镜像的关系 2.1 面向对象角度 2.2 从镜像容器角度 三、 容器命令 3.1 使用Ubuntu 3.1.1 下载镜像 3.1.2 新建和启动容器 run 3.1.3交互式 compose编排与部署 1. docker-compose部署 2. docker-compose.yml模板 …

社区维修平台|基于SpringBoot+ Mysql+Java+JSP技术的社区维修平台设计与实现(可运行源码+数据库+设计文档+部署说明+视频演示)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 住户后台功能 维修员前台功能 维修员后台功能 管理员功能登录 系统功能设计 数据库E…