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

news2024/11/17 19:35:36

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作为线程函数,同时传递了关键帧数据和时间戳。这样做是为了避免在主循环中窃取资源。

…详情请参照古月居

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

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

相关文章

three.js球体实现

作者&#xff1a;baekpcyyy&#x1f41f; 使用three.js渲染出可以调节大小的立方体 1.搭建开发环境 1.首先新建文件夹用vsc打开项目终端 2.执行npm init -y 创建配置文件夹 3.执行npm i three0.152 安装three.js依赖 4.执行npm I vite -D 安装 Vite 作为开发依赖 5.根…

C#文件流二进制文件的读写

目录 一、BinaryWriter类 二、BinaryReader类 三、示例 1.源码 2.生成效果 二进制文件的写入与读取主要是通过BinaryWriter类和BinaryReader类来实现的。 一、BinaryWriter类 BinaryWriter类以二进制形式将基元类型写入流&#xff0c;并支持用特定的编码写入字符串&#…

Linux环境搭建(Ubuntu22.04)+ 配置共享文件夹(Samba)

Linux开发环境准备 搭建Linux开发环境所需要的软件如下&#xff1a; VMware虚拟机&#xff1a;用于运行Linux操作系统的虚拟机软件之一&#xff0c;VMware下载安装在文章中不做说明&#xff0c;可自行百度谢谢Ubuntu光盘镜像&#xff1a;用于源代码编译&#xff0c;有闲置计算…

C#开发的OpenRA游戏之属性SelectionDecorations(14)

C#开发的OpenRA游戏之属性SelectionDecorations(14) 前面分析选择类时,还有一个功能,就是把选中物品的状态和生命值显示出来。 它是通过下面的函数来实现: protected override IEnumerable<IRenderable> RenderSelectionBars(Actor self, WorldRenderer wr, bool …

【探索Linux】—— 强大的命令行工具 P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )

阅读导航 引言一、信号捕捉1. 内核实现信号捕捉过程2. sigaction() 函数&#xff08;1&#xff09;函数原型&#xff08;2&#xff09;参数说明&#xff08;3&#xff09;返回值&#xff08;4&#xff09;函数使用 二、可重入函数与不可重入函数1. 可重入函数条件2. 不可重入函…

突破界限:R200科研无人车,开辟研究新天地

提到科研无人车&#xff0c;大家可能首先想到的是其在自动驾驶和其他先进技术领域的应用。然而&#xff0c;随着科技的不断进步&#xff0c;科研无人车已经在智慧城市建设、商业服务、地质勘探、环境保护、农业技术革新、灾害应急和自动化服务等多个领域发挥着至关重要的作用。…

钢贸行业ERP系统:实现全面管理与持续增长的利器

去年在上海举办的数字化钢贸高峰论坛中提出钢贸业亟需数字化转型&#xff0c;因为在大力发展数字经济的时代背景下&#xff0c;行业进行数字化转型已经成为一种必然趋势。在今年以前&#xff0c;一些钢贸商一直沿用着以前非常粗放的管理手段&#xff0c;比如手写账本。而如果使…

揭秘近期CSGO市场小幅回暖的真正原因

揭秘近期CSGO市场小幅回暖的真正原因 最近市场小幅度回暖&#xff0c;第一个原因则是到处都在说buff要开租赁了&#xff0c;市场要开始爆燃了。童话听到这些消息实在是绷不住了&#xff0c;出来给大家讲一下自己的看法&#xff0c;大家理性思考一下。 Buff出不出租赁跟市场燃不…

【开源视频联动物联网平台】开箱即用的物联网项目介绍

写一个开箱即用的物联网项目捐献给Dromara组织 一、平台简介 MzMedia开源视频联动物联网平台&#xff0c;简单易用&#xff0c;更适合中小企业和个人学习使用。适用于智能家居、农业监测、水利监测、工业控制&#xff0c;车联网&#xff0c;监控直播&#xff0c;慢直播等场景。…

二.运算符

运算符 1.算术运算符2.比较运算符3.逻辑运算符 1.算术运算符 算数运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行 - * / 和 取模%运算 1.加减法运算符 mysql> SELECT 100,100 0,100 - 0,100 50,100 50 - …

良心推荐免费白嫖的电子书制作与发布平台,快来试试噢~

电子书的出现极大的改变了人们的阅读习惯&#xff0c;与传统的纸质文献相比呢&#xff0c;电子书具有存储量大、体积小、成本低、信息更新快、方便阅读等不可替代的优势&#xff0c;受到了越来越多人的喜爱。 那怎么去制作一个高级又炫酷的电子书呢&#xff1f;今天小编就专门…

java源码-数组

背景 上传图片&#xff0c;需要对图片格式进行校验&#xff0c;这是就可以使用数组 1、什么是数组&#xff1f; Java 语言中提供的数组是用来存储固定大小的同类型元素。 如&#xff1a;可以声明一个数组变量&#xff0c;如 numbers[100] 来代替直接声明 100 个独立变量 numb…

Minio开源高性能高可靠存储搭建

一、minio的特征 1、高性能 MinIO 是一种高性能、S3 兼容的对象存储。它专为大规模 AI/ML、数据湖和数据库工作负载而构建&#xff0c;并且它是由软件定义的存储。不需要购买任何专有硬件&#xff0c;就可以在云上和普通硬件上拥有分布式对象存储。MinIO拥有开源 GNU AGPL v3…

C陷阱与缺陷——第3章 语义陷阱

1. 指针和数组 C语言中只有一维数组&#xff0c;而且数组的大小必须在编译器就作为一个常数确定下来&#xff0c;然而在C语言中数组的元素可以是任何类型的对象&#xff0c;当然也可以是另外的一个数组&#xff0c;这样&#xff0c;要仿真出一个多维数组就不是难事。 对于一个…

OpenCvSharp从入门到实践-(06)创建图像

目录 1、创建图像 1.1实例1-创建黑色图像 1.2实例2-创建白色图像 1.3实例3-创建随机像素的雪花点图像 2、图像拼接 2.1水平拼接图像 2.2垂直拼接图像 2.3实例4-垂直和水平两种方式拼接两张图像 在OpenCV中&#xff0c;黑白图像其实就是一个二维数组&#xff0c;彩色图像…

vscode插件问题

1 Vscode code颜色变化 最外层标签颜色变成白色 其他标签有颜色&#xff0c;css代码颜色有些变成白色 是安装的另一个插件vue影响的&#xff0c;卸载就能恢复正常的颜色 2 配置Vue项目的代码片段 css 样式代码片段 配置css.json上后偶尔能用偶尔不能用&#xff0c;Vscode 右下…

Flutter应用程序的加固原理

在移动应用开发中&#xff0c;Flutter已经成为一种非常流行的技术选项&#xff0c;可以同时在Android和iOS平台上构建高性能、高质量的移动应用程序。但是&#xff0c;由于其跨平台特性&#xff0c;Flutter应用程序也面临着一些安全风险&#xff0c;例如反编译、代码泄露、数据…

Egg.js的方法扩展

Extend-application 方法扩展 eggjs的方法的扩展和编写 Egg.js可以对内部的五种对象进行扩展&#xff0c;以下是可扩展的对象、说明、this指向和使用方式。 application对象方法拓展 按照Egg的约定&#xff0c;扩展的文件夹和文件的名字必须是固定的。比如要对application扩…

【ZEDSLAM】Ubuntu18.04系统ZED 2i双目相机SDK安装、联合标定、SLAM测试

0.设备、环境和说明 笔记本电脑i5-8300H、GTX 1060、32GRAM 因为后面要测试Vins-Fusion和ORB-SLAM3&#xff0c;所以推荐安装Ubuntu 18.04&#xff08;或者Ubuntu 20.04&#xff09; ROS 1&#xff08;不建议用比Ubuntu18更低的版本&#xff09; ROS一键安装命令&#xff1a;…

Android textView 显示: STRING_TOO_LARGE

默认情况下&#xff0c;TextView只能显示大约32K的字符。如果你的字符串超过这个限制&#xff0c;你将收到一个错误&#xff1a;“String too large”。 <string content" ...."/>问题点是&#xff1a;getResource().getString(R.string.content) 得到的是&am…