代码浅析DLIO(三)---子图构建

0. 简介

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

在这里插入图片描述


1. computeMetrics、computeSpaciousness、computeSpaciousness–计算度量指标

下面的代码包含了计算稀疏度和密度的两个函数。这里面的GICP相关的值都是上一帧的结果传入的。computeSpaciousness函数通过计算点云数据中点到原点的距离,求出其中位数,并对其进行平滑处理,最终将结果存入metrics.spaciousness中。而computeDensity函数则根据是否完成GICP优化,来获取机器人当前的密度值,并同样进行平滑处理,最终将结果存入metrics.density中。这两个指标都是用于评价机器人定位精度的重要指标。

void dlio::OdomNode::computeMetrics() {
  this->computeSpaciousness(); //计算稀疏度
  this->computeDensity();      //计算密度
}

void dlio::OdomNode::computeSpaciousness() {

  // 计算点的范围
  std::vector<float> ds;

  for (int i = 0; i <= this->original_scan->points.size();
       i++) { //根据getScanFromROS拿到的原始点云数据
    float d = std::sqrt(
        pow(this->original_scan->points[i].x, 2) +
        pow(this->original_scan->points[i].y, 2)); //计算点到原点的距离
    ds.push_back(d);                               //将距离存入ds
  }

  // 求中值
  std::nth_element(
      ds.begin(), ds.begin() + ds.size() / 2,
      ds.end()); // 用于在一个序列中找到第k小的元素,其中k由第二个参数指定
  float median_curr = ds[ds.size() / 2];  //对应的中值的索引
  static float median_prev = median_curr; //存入到上一个时刻的中值
  float median_lpf =
      0.95 * median_prev + 0.05 * median_curr; //?算出来还是一个值
  median_prev = median_lpf;                    //同理

  // push
  this->metrics.spaciousness.push_back(median_lpf);
}

void dlio::OdomNode::computeDensity() {

  float density;

  if (!this->geo
           .first_opt_done) { //如果第一次优化未完成(没有完成GICP),则认为没有密度
    density = 0.;
  } else {
    density = this->gicp.source_density_; //将GICP累计的density传入
  }

  static float density_prev = density;
  float density_lpf = 0.95 * density_prev + 0.05 * density;
  density_prev = density_lpf;

  this->metrics.density.push_back(density_lpf);
}

2. buildSubmap–构建子图

构建子地图的函数。子地图是由一组关键帧组成的,这些关键帧被用于后续的GICP匹配。该函数首先计算当前姿态与关键帧集合中的姿态之间的距离,以确定哪些关键帧应该被包含在子地图中。然后,它使用计算出的距离来获取前K个最近邻关键帧姿态的索引。接下来,它获取凸包索引,即提取一些不必要的关键帧。然后,它获取凸包上每个关键帧之间的距离,并获取凸包的前k个最近邻的索引。接下来,它获取凹包索引,即提取一些不必要的关键帧。然后,它获取凹包上每个关键帧之间的距离,并获取凹包的前k个最近邻的索引。最后,它连接所有子地图的点云和法向量,重新初始化子地图云和法线,并将当前子地图云赋值给子地图的点云,将当前子地图的法向量赋值给子地图的法向量,将子地图的点云赋值给gicp_temp的目标点云,将gicp_temp的目标点云的kd树赋值给子地图的kd树,将当前帧的索引赋值给上一帧的索引。如果子地图与上一次迭代时发生了变化,则将标志位置为true。并将相应的值传给GICP

void dlio::OdomNode::buildSubmap(State vehicle_state) {

  // 清除用于子地图的关键帧索引向量
  this->submap_kf_idx_curr.clear();

  // 计算当前姿态与关键帧集合中的姿态之间的距离
  std::unique_lock<decltype(this->keyframes_mutex)> lock(
      this->keyframes_mutex); //通过decltype关键字可以获得变量的类型,并加上互斥锁
  std::vector<float> ds; //用于存储当前帧与关键帧之间的距离
  std::vector<int> keyframe_nn; //用于存储当前帧与关键帧之间的索引
  for (int i = 0; i < this->num_processed_keyframes;
       i++) { //获取当前时刻所有的关键帧
    float d =
        sqrt(pow(vehicle_state.p[0] - this->keyframes[i].first.first[0], 2) +
             pow(vehicle_state.p[1] - this->keyframes[i].first.first[1], 2) +
             pow(vehicle_state.p[2] - this->keyframes[i].first.first[2],
                 2));         //计算当前帧与关键帧之间的距离
    ds.push_back(d);          //将距离存入ds
    keyframe_nn.push_back(i); //将索引存入keyframe_nn
  }
  lock.unlock();

  // 获取前K个最近邻关键帧姿态的索引
  this->pushSubmapIndices(ds, this->submap_knn_, keyframe_nn);

  // 获取凸包索引,其实就是提取一些不必要的关键帧
  this->computeConvexHull();

  // 获取凸包上每个关键帧之间的距离
  std::vector<float> convex_ds;
  for (const auto &c : this->keyframe_convex) {
    convex_ds.push_back(ds[c]); //根据对应的索引将结果压入
  }

  // 获取凸包的前k个最近邻的索引
  this->pushSubmapIndices(convex_ds, this->submap_kcv_, this->keyframe_convex);

  // 获取凹包索引,其实就是提取一些不必要的关键帧
  this->computeConcaveHull();

  // 获取凸包上每个关键帧之间的距离
  std::vector<float> concave_ds;
  for (const auto &c : this->keyframe_concave) {
    concave_ds.push_back(ds[c]);
  }

  // 获取凹包的前k个最近邻的索引
  this->pushSubmapIndices(concave_ds, this->submap_kcc_,
                          this->keyframe_concave);

  // 连接所有子地图的点云和法向量
  std::sort(this->submap_kf_idx_curr.begin(),
            this->submap_kf_idx_curr.end()); //对当前帧的索引进行排序
  auto last = std::unique(this->submap_kf_idx_curr.begin(),
                          this->submap_kf_idx_curr.end()); //去除重复的元素
  this->submap_kf_idx_curr.erase(
      last, this->submap_kf_idx_curr.end()); //删除重复的元素

  // 对当前和之前的子地图的索引列表进行排序
  std::sort(this->submap_kf_idx_curr.begin(), this->submap_kf_idx_curr.end());
  std::sort(this->submap_kf_idx_prev.begin(), this->submap_kf_idx_prev.end());

  // 检查子地图是否与上一次迭代时发生了变化
  if (this->submap_kf_idx_curr != this->submap_kf_idx_prev) {

    this->submap_hasChanged = true; //如果发生了变化,则将标志位置为true

    // 暂停以防止从主循环中窃取资源,如果主循环正在运行。
    this->pauseSubmapBuildIfNeeded();

    // 重新初始化子地图云和法线
    pcl::PointCloud<PointType>::Ptr submap_cloud_(
        boost::make_shared<pcl::PointCloud<PointType>>());
    std::shared_ptr<nano_gicp::CovarianceList> submap_normals_(
        std::make_shared<nano_gicp::CovarianceList>());

    for (auto k : this->submap_kf_idx_curr) { //遍历当前帧的索引

      // 创建当前子地图云
      lock.lock();
      *submap_cloud_ += *this->keyframes[k].second; //将当前帧的点云压入
      lock.unlock();

      // 获取相应子地图云点的法向量
      submap_normals_->insert(std::end(*submap_normals_),
                              std::begin(*(this->keyframe_normals[k])),
                              std::end(*(this->keyframe_normals[k])));
    }

    this->submap_cloud = submap_cloud_; //将当前帧的点云赋值给子地图的点云
    this->submap_normals =
        submap_normals_; //将当前帧的法向量赋值给子地图的法向量

    // 如果主循环正在运行,请暂停以防止窃取资源
    this->pauseSubmapBuildIfNeeded();

    this->gicp_temp.setInputTarget(
        this->submap_cloud); //将子地图的点云赋值给gicp_temp的目标点云
    this->submap_kdtree =
        this->gicp_temp
            .target_kdtree_; //将gicp_temp的目标点云的kd树赋值给子地图的kd树

    this->submap_kf_idx_prev =
        this->submap_kf_idx_curr; //  将当前帧的索引赋值给上一帧的索引
  }
}

3. buildKeyframesAndSubmap–创建子图点云和法线的创建

下面代码的主要功能是创建子图,包括子图的点云和法线的创建。首先,程序获取未处理的关键帧,然后将其转换到世界坐标系下。在转换过程中,程序使用了关键帧的变换矩阵和点云数据,通过调用pcl库中的transformPointCloud函数,将关键帧点云转换到世界坐标系下。同时,程序也需要更新关键帧的协方差(法向量),将其同样转换到世界坐标系下。

在转换完成后,程序会发布关键帧。这里使用了std::thread创建了一个线程,并将发布关键帧的函数publishKeyframe作为线程函数,同时传递了关键帧数据和时间戳。这样做是为了避免在主循环中窃取资源。

…详情请参照古月居

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

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

相关文章

问题记录-maven依赖升级或替换(简单版)

问题背景 项目被检测到有高危漏洞&#xff0c;需要对部分jar进行升级。以一个jar为例记录一下升级过程。 1 找到高危漏洞的包 如果装了maven helper插件则可以在下面查看当前模块依赖包 2 使用maven命令 执行下面这个命令&#xff0c;会将当前项目的信息打印出来&#xff0c;…

基于MYSQL+Tomcat+Eclipse开发的超市订单管理系统

基于MYSQLTomcatEclipse开发的超市订单管理系统 项目介绍&#x1f481;&#x1f3fb; 该系统运行需要基于JDK7来进行运行 超市订单管理系统是一款针对超市订单进行管理的软件&#xff0c;旨在提高订单处理效率&#xff0c;降低管理成本。该系统包括以下功能模块&#xff1a; 系…

Java后端开发——SpringMVC商品管理程序

Java后端开发——SpringMVC商品管理程序 今日目标 Spring MVC框架介绍掌握SpringMVC的核心类的原理及配置掌握SpringMVC的常用注解掌握SpringMVC的增删改查编程 Spring MVC框架介绍 Spring MVC&#xff08;Model-View-Controller&#xff09;是一个基于Java的开源框架&#x…

记一次:Python的学习笔记一

前言&#xff1a;之前学习的Python笔记&#xff0c;已经过去很多年了&#xff0c;不久前重新翻了出来练习练习。不完善的地方在缝缝补补 一、环境搭建 1.1、Python的win环境安装 0、python-3.12.0软件安装&#xff1a;Win11环境搭建python-3.12.0-amd64 这里小小注意一下&a…

NoSql非关系型数据库

前言&#xff1a;Nosql not only sql&#xff0c;意即“不仅仅是sql”&#xff0c;泛指非关系型数据库。这些类型的数据存储不需要固定的模式&#xff08;当然也有固定的模式&#xff09;&#xff0c;无需多余的操作就可以横向扩展。NoSql数据库中的数据是使用聚合模型来进行处…

基于Java SSM框架实现实现四六级英语报名系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现四六级英语报名系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个高校四六级报名管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作…

U-boot(七):U-boot移植

本文主要探讨基于210官方U-boot源码移植。 移植基础 tar -jxvf android_uboot_smdkv210.tar.bz2cd u-boot-samsung-devrm -rf onenand_ipl onenand_bl1 lib_avr32 lib_blackfin lib_i386 lib_m68k lib_mips lib_microblaze lib_nios lib_nios2 lib_ppc lib_sh lib_sparccd bo…

pycharm怎么同时打开2个项目?

pycharm怎么同时打开2个项目&#xff1f;当使用vue等前端的时候&#xff0c;后台也需要同时用pycharm打开操作&#xff0c;怎么用pycharm同时打开前后端呢&#xff1f; 当我们第一次用pycharm的时候&#xff0c;新建一个项目&#xff0c;习惯选择此窗口&#xff0c;而且勾选不再…

【Linux】OpenSSH 命令注入漏洞(CVE-2020-15778)(iptables屏蔽22端口方式)

背景 漏洞名称&#xff1a;OpenSSH 命令注入漏洞(CVE-2020-15778) 详细描述&#xff1a;OpenSSH&#xff08;OpenBSD Secure Shell&#xff09;是OpenBSD计划组的一套用于安全访问远程计算机的连接工具。该工具是SSH协议的开源实现&#xff0c;支持对所有的传输进行加密&#…

基于Java SSM框架实现高校二手交易平台系统项目【项目源码+论文说明】

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

在氮化镓和AlGaN上的湿式数字蚀刻

引言 由于其独特的材料特性&#xff0c;III族氮化物半导体广泛应用于电力、高频电子和固态照明等领域。加热的四甲基氢氧化铵(TMAH)和KOH3处理的取向相关蚀刻已经被用于去除III族氮化物材料中干法蚀刻引起的损伤&#xff0c;并缩小垂直结构。 不幸的是&#xff0c;由于化学蚀…

【Node.js】笔记整理 3 -npm

写在最前&#xff1a;跟着视频学习只是为了在新手期快速入门。想要学习全面、进阶的知识&#xff0c;需要格外注重实战和官方技术文档&#xff0c;文档建议作为手册使用 系列文章 【Node.js】笔记整理 1 - 基础知识【Node.js】笔记整理 2 - 常用模块【Node.js】笔记整理 3 - n…

Linux常用命令——badblocks命令

在线Linux命令查询工具 badblocks 查找磁盘中损坏的区块 补充说明 badblock命令用于查找磁盘中损坏的区块。 硬盘是一个损耗设备&#xff0c;当使用一段时间后可能会出现坏道等物理故障。电脑硬盘出现坏道后&#xff0c;如果不及时更换或进行技术处理&#xff0c;坏道就会越…

山人求道篇:一、交易系统与现代战争

声明&#xff1a; 本人不进行任何投资建议&#xff0c;也不出售任何包括策略、算法的程序代码。 仅作为个人的2023年开发心路总结&#xff0c;有任何异议可以在评论区留言&#xff0c;可以讨论&#xff0c;如果你杠&#xff0c;那就是你对。 这世上有很多条路&#xff0c;每个…

零基础自学编程,中文编程工具下载,中文编程工具构件之弹出菜单构件简介

一、前言&#xff1a; 零基础自学编程&#xff0c;中文编程工具下载&#xff0c;中文编程工具构件之弹出菜单构件简介 编程系统化教程链接 https://jywxz.blog.csdn.net/article/details/134073098?spm1001.2014.3001.5502 给大家分享一款中文编程工具&#xff0c;零基础…

VSCode编译多个文件

打开tasks.json文件&#xff0c;找到"$ {file}“这一行。然后在其后面添加你想要编译的文件即可。 比如我这里是想要编译当前文件夹下的所有.cpp文件&#xff0c;那么我就直接把”$ {file}"给注释掉了&#xff0c;然后再使用通配符来代表所有.cpp文件。 最后解释以下…

Git——使用Git进行程序开发

主要介绍个人开发提交记录的主要流程&#xff0c;包括以下内容&#xff1a; 索引- 提交的暂存区。查看工作的状态和内部变更。如何读取用于描述变更的已扩展统一diff格式。支持查询和交互的提交&#xff0c;修改提交。创建、显示和选择&#xff08;切换&#xff09;分支。切换…

ruoyi-vue 整合EMQX接收MQTT协议数据

EMQX安装完成后&#xff0c;需要搭建客户端进行接收数据进一步对数据处理&#xff0c;下面介绍基于若依分离版开源框架来整合EMQX方法。 1.application.yml 添加代码 mqtt:hostUrl: tcp://localhost:1883username: devpassword: devclient-id: MQTT-CLIENT-DEVcleanSession: …

从0开始学习JavaScript--JavaScript 中 `let` 和 `const` 的区别及最佳实践

在JavaScript中&#xff0c;let 和 const 是两个用于声明变量的关键字。尽管它们看起来很相似&#xff0c;但它们之间有一些重要的区别。本篇博客将深入探讨 let 和 const 的用法、区别&#xff0c;并提供一些最佳实践&#xff0c;以确保在代码中正确使用它们。 let 和 const …

简单字符串处理

答案&#xff1a; #include <stdio.h> #include <string.h> #define MAX 51 //该定义宏为字符串最大长度 int main() {char arr[MAX] { 0 }; gets(arr); //读取存给arrint len 0, i 0, num 0;len strlen(arr); //len代表字符串长度for (i 0; i &l…