代码浅析Point-LIO

0. 简介

对于最近出来的Point-LIO(鲁棒高带宽激光惯性里程计),本人还是非常该兴趣的,为此花了一些时间重点分析了Point-LIO的代码,并研究了它相较于Fast-LIO2的区别

1. laserMapping.cpp

第一部分就是实现对激光雷达视场角的图像分割。首先定义了一个BoxPointType类型的局部地图(LocalMap_Points)和一个bool类型的变量(Localmap_Initialized),表示是否已经初始化局部地图。然后,在lasermap_fov_segment()函数中,根据激光雷达的姿态计算出激光雷达的位置(pos_LiD),并根据移动阈值(MOV_THRESHOLD)判断是否需要移动局部地图。如果需要移动,则计算新的局部地图边界(New_LocalMap_Points),并将需要移除的框添加到cub_needrm中。最后,根据cub_needrm中的框删除点云,完成图像分割。

BoxPointType LocalMap_Points;
bool Localmap_Initialized = false;
void lasermap_fov_segment() //针对激光雷达视场角来完成图像分割
{
  cub_needrm.shrink_to_fit(); //将容量设置为容器的长度

  V3D pos_LiD;
  if (use_imu_as_input) {
    pos_LiD =
        kf_input.x_.pos + kf_input.x_.rot.normalized() *
                              Lidar_T_wrt_IMU; //计算激光雷达在当前位姿下的位置
  } else {
    pos_LiD =
        kf_output.x_.pos + kf_output.x_.rot.normalized() * Lidar_T_wrt_IMU;
  }
  if (!Localmap_Initialized) { //判断是否需要初始化局部地图
    //将局部地图的边界设置为当前位置的正方形区域,并将Localmap_Initialized设置为true
    for (int i = 0; i < 3; i++) {
      LocalMap_Points.vertex_min[i] = pos_LiD(i) - cube_len / 2.0;
      LocalMap_Points.vertex_max[i] = pos_LiD(i) + cube_len / 2.0;
    }
    Localmap_Initialized = true;
    return;
  }
  float dist_to_map_edge[3][2];
  bool need_move = false;
  //如果不需要初始化,则计算激光雷达当前位姿与局部地图边界的距离,并根据移动阈值判断是否需要移动局部地图
  for (int i = 0; i < 3; i++) {
    dist_to_map_edge[i][0] = fabs(pos_LiD(i) - LocalMap_Points.vertex_min[i]);
    dist_to_map_edge[i][1] = fabs(pos_LiD(i) - LocalMap_Points.vertex_max[i]);
    if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE ||
        dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE)
      need_move = true;
  }
  //如果需要移动,则计算新的局部地图边界
  if (!need_move)
    return;
  BoxPointType New_LocalMap_Points, tmp_boxpoints;
  New_LocalMap_Points = LocalMap_Points;
  float mov_dist = max((cube_len - 2.0 * MOV_THRESHOLD * DET_RANGE) * 0.5 * 0.9,
                       double(DET_RANGE * (MOV_THRESHOLD - 1)));
  for (int i = 0; i < 3; i++) {
    tmp_boxpoints = LocalMap_Points;
    if (dist_to_map_edge[i][0] <= MOV_THRESHOLD * DET_RANGE) {
      New_LocalMap_Points.vertex_max[i] -= mov_dist;
      New_LocalMap_Points.vertex_min[i] -= mov_dist;
      tmp_boxpoints.vertex_min[i] = LocalMap_Points.vertex_max[i] - mov_dist;
      cub_needrm.emplace_back(tmp_boxpoints); //将需要移除的框添加到cub_needrm中
    } else if (dist_to_map_edge[i][1] <= MOV_THRESHOLD * DET_RANGE) {
      New_LocalMap_Points.vertex_max[i] += mov_dist;
      New_LocalMap_Points.vertex_min[i] += mov_dist;
      tmp_boxpoints.vertex_max[i] = LocalMap_Points.vertex_min[i] + mov_dist;
      cub_needrm.emplace_back(tmp_boxpoints);
    }
  }
  LocalMap_Points = New_LocalMap_Points;

  points_cache_collect();
  if (cub_needrm.size() > 0)
    int kdtree_delete_counter = ikdtree.Delete_Point_Boxes(
        cub_needrm); //收集点云缓存,并根据cub_needrm中的框删除点云,返回删除的框数量。
}

下面我们来看看怎么使用这个函数的,下面的代码主要实现了以下的操作:

1.对激光雷达采集到的点云进行空间下采样和时间压缩;

2.初始化地图kd-tree;

3.使用迭代最近点算法(ICP)和卡尔曼滤波更新地图。其中,ICP主要用于点云配准,卡尔曼滤波则用于对机器人位姿进行估计和更新。

lasermap_fov_segment();
      /*** downsample the feature points in a scan ***/
      t1 = omp_get_wtime();
      if (space_down_sample) { //空间下采样
        downSizeFilterSurf.setInputCloud(feats_undistort);
        downSizeFilterSurf.filter(*feats_down_body);
        sort(feats_down_body->points.begin(), feats_down_body->points.end(),
             time_list); //按时间排序
      } else {
        feats_down_body = Measures.lidar;
        sort(feats_down_body->points.begin(), feats_down_body->points.end(),
             time_list);
      }
      time_seq = time_compressing<int>(feats_down_body); //时间压缩
      feats_down_size = feats_down_body->points.size();  //点云数量

      /*** initialize the map kdtree ***/
      if (!init_map) {
        if (ikdtree.Root_Node == nullptr) //
        // if(feats_down_size > 5)
        {
          ikdtree.set_downsample_param(filter_size_map_min); //设置滤波参数
        }

        feats_down_world->resize(feats_down_size); //初始化点云
        for (int i = 0; i < feats_down_size; i++) {
          pointBodyToWorld(&(feats_down_body->points[i]),
                           &(feats_down_world->points[i])); //转换到世界坐标系
        }
        for (size_t i = 0; i < feats_down_world->size(); i++) {
          init_feats_world->points.emplace_back(
              feats_down_world
                  ->points[i]); //将转换到世界坐标西的点云加入到init_feats_world
        }
        if (init_feats_world->size() < init_map_size) //等待构建地图
          continue;
        ikdtree.Build(init_feats_world->points); //构建地图
        init_map = true;
        publish_init_kdtree(pubLaserCloudMap); //(pubLaserCloudFullRes);
        continue;
      }
      /*** ICP and Kalman filter update ***/
      normvec->resize(feats_down_size);
      feats_down_world->resize(feats_down_size);

      Nearest_Points.resize(feats_down_size);

      t2 = omp_get_wtime(); //初始化t2为当前时间

      /*** iterated state estimation ***/
      crossmat_list.reserve(feats_down_size);
      pbody_list.reserve(feats_down_size);
      // pbody_ext_list.reserve(feats_down_size);

      //对于每个点,将其坐标转换为V3D类型的point_this
      for (size_t i = 0; i < feats_down_body->size(); i++) {
        V3D point_this(feats_down_body->points[i].x,
                       feats_down_body->points[i].y,
                       feats_down_body->points[i].z);
        pbody_list[i] = point_this;
        //如果使用外参估计
        if (extrinsic_est_en) {
          if (!use_imu_as_input) {
            //对于每个点,使用卡尔曼滤波估计出的外参对其进行坐标变换
            point_this = kf_output.x_.offset_R_L_I.normalized() * point_this +
                         kf_output.x_.offset_T_L_I;
          } else {
            point_this = kf_input.x_.offset_R_L_I.normalized() * point_this +
                         kf_input.x_.offset_T_L_I;
          }
        } else {
          // 使用Lidar_R_wrt_IMU和Lidar_T_wrt_IMU对其进行变换
          point_this = Lidar_R_wrt_IMU * point_this + Lidar_T_wrt_IMU;
        }
        M3D point_crossmat;
        point_crossmat << SKEW_SYM_MATRX(point_this); //根据当前点生成矩阵
        crossmat_list[i] = point_crossmat;
      }

      if (!use_imu_as_input) {
        bool imu_upda_cov = false; //是否需要更新imu的协方差
        effct_feat_num = 0;
        /**** point by point update ****/

        double pcl_beg_time =
            Measures
                .lidar_beg_time; //首先设置pcl_beg_time为Measures.lidar_beg_time,idx为-1
        idx = -1;
        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; //找到对应的点并计算出当前时间time_current

          if (is_first_frame) {
            if (imu_en) { //如果是第一帧,且启用了IMU,那么需要找到最近的IMU数据
              while (time_current > imu_next.header.stamp.toSec()) {
                imu_last = imu_next;
                imu_next = *(imu_deque.front());
                imu_deque.pop_front();
                // imu_deque.pop();
              }
              //计算出对应的角速度和加速度
              angvel_avr << imu_last.angular_velocity.x,
                  imu_last.angular_velocity.y, imu_last.angular_velocity.z;
              acc_avr << imu_last.linear_acceleration.x,
                  imu_last.linear_acceleration.y,
                  imu_last.linear_acceleration.z;
            }
            is_first_frame = false;
            imu_upda_cov = true;
            time_update_last = time_current;
            time_predict_last_const = time_current;
          }
          if (imu_en) {
            bool imu_comes = time_current > imu_next.header.stamp.toSec();
            // 如果启用了IMU,那么需要在当前时间之前的IMU数据都进行卡尔曼滤波更新
            while (imu_comes) {
              imu_upda_cov = true;
              //将IMU数据中的角速度和线性加速度分别存储到angvel_avr和acc_avr中
              angvel_avr << imu_next.angular_velocity.x,
                  imu_next.angular_velocity.y, imu_next.angular_velocity.z;
              acc_avr << imu_next.linear_acceleration.x,
                  imu_next.linear_acceleration.y,
                  imu_next.linear_acceleration.z;

              /*** 对协方差进行更新 ***/
              imu_last = imu_next; //将当前IMU数据存储为imu_last
              imu_next = *(imu_deque.front()); //将下一个IMU数据存储为imu_next
              imu_deque.pop_front();
              double dt = imu_last.header.stamp.toSec() -
                          time_predict_last_const; //接着计算时间差dt
              kf_output.predict(dt, Q_output, input_in, true,
                                false); //通过kf_output.predict函数进行预测
              time_predict_last_const =
                  imu_last.header.stamp.toSec(); // big problem
              imu_comes = time_current > imu_next.header.stamp.toSec();
              // if (!imu_comes)
              {
                double dt_cov = imu_last.header.stamp.toSec() -
                                time_update_last; //就计算时间差dt_cov

                if (dt_cov > 0.0) {
                  time_update_last = imu_last.header.stamp.toSec();
                  double propag_imu_start = omp_get_wtime();

                  kf_output.predict(dt_cov, Q_output, input_in, false,
                                    true); //行卡尔曼滤波预测

                  propag_time += omp_get_wtime() - propag_imu_start;
                  double solve_imu_start = omp_get_wtime();
                  kf_output.update_iterated_dyn_share_IMU(); //进行eskf迭代更新
                  solve_time += omp_get_wtime() - solve_imu_start;
                }
              }
            }
          }

          double dt = time_current - time_predict_last_const;
          double propag_state_start = omp_get_wtime();
          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);
          propag_time += omp_get_wtime() - propag_state_start;
          time_predict_last_const = time_current;
          // if(k == 0)
          // {
          // fout_imu_pbp << Measures.lidar_last_time - first_lidar_time <<
          // " " << imu_last.angular_velocity.x << " " <<
          // imu_last.angular_velocity.y << " " <<
          // imu_last.angular_velocity.z \ // << " " << imu_last.linear_acceleration.x << " " <<
          // imu_last.linear_acceleration.y << " " <<
          // imu_last.linear_acceleration.z << endl;
          // }

          double t_update_start = omp_get_wtime();

          if (feats_down_size < 1) {
            ROS_WARN("No point, skip this scan!\n");
            idx += time_seq[k];
            continue;
          }
          if (!kf_output.update_iterated_dyn_share_modified()) {
            idx = idx + time_seq[k];
            continue;
          }

          if (prop_at_freq_of_imu) {
            double dt_cov = time_current - time_update_last;
            if (!imu_en &&
                (dt_cov >=
                 imu_time_inte)) // //需要在当前时间之前的时间间隔大于imu_time_inte时进行卡尔曼滤波协方差更新
            {
              double propag_cov_start = omp_get_wtime();
              kf_output.predict(
                  dt_cov, Q_output, input_in, false,
                  true); //对于每个时间片中的点,进行卡尔曼滤波更新,并将点转换到世界坐标系中
              imu_upda_cov = false;
              time_update_last = time_current;
              propag_time += omp_get_wtime() - propag_cov_start;
            }
          }

          solve_start = omp_get_wtime();

点击代码浅析Point-LIO - 古月居 可查看全文

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

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

相关文章

Python学习从0到1 day24 第二阶段 SQL ① SQL基础语法

还是会再见的 —— 24.4.10 MySQL基础及常用操作博主已整理在了两个专栏中&#xff0c;具体查看博主两个专栏的文章 ① Mysql数据库 ② 深入学习MySQL数据库 DDL —— 数据库管理 DDL —— 数据表管理 DML 数据操作语言 数据插入 INSERT 数据删除 DELETE 数据更新 UPDATE 注意…

短剧在线搜索PHP网站源码

源码简介 短剧在线搜索PHP网站源码&#xff0c;自带本地数据库500数据&#xff0c;共有6000短剧视频&#xff0c;与短剧猫一样。 搭建环境 PHP 7.3 Mysql 5.6 安装教程 1.上传源码到网站目录中 2.修改【admin.php】中&#xff0c; $username ‘后台登录账号’; $passwor…

Vue-Router入门

现在的前后端分离项目&#xff0c;后端只管数据传递&#xff0c;视图跳转的活交由前端来干了&#xff0c;vue-router就是专门来干这个活的&#xff0c;它可以让页面跳转到指定组件 组件是可复用的 Vue 实例, 把一些公共的模块抽取出来&#xff0c;然后写成单独的的工具组件或者…

tdesign坑之EnhancedTable树形结构默认展开所有行

⚠️在官方实例中&#xff0c;树形结构的表格提供了2种方法控制展开全部节点&#xff1a; 一是通过配置属性tree.defaultExpandAll为true代表默认展开全部节点&#xff08;仅默认情况有效&#xff09;&#xff1b; 二是使用组件实例方法expandAll()可以自由控制树形结构的展开…

从零开始学Python(五)面向对象

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Python的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.类的定义 二.魔法方法 1.概念 2.常…

Bert基础(十二)--Bert变体之知识蒸馏原理解读

B站视频&#xff1a;https://www.bilibili.com/video/BV1nx4y1v7F5/ 白话知识蒸馏 在前面&#xff0c;我们了解了BERT的工作原理&#xff0c;并探讨了BERT的不同变体。我们学习了如何针对下游任务微调预训练的BERT模型&#xff0c;从而省去从头开始训练BERT的时间。但是&#…

物联网实验

实验1 基于ZStack光敏传感器实验 1.实验目的 我们通过上位机发指令给协调器&#xff0c;协调器把串口接收到的指令通过Zigbee协议无线发送给带有光敏传感器的终端节点&#xff0c;获取到数据以后把数据返回给上位机&#xff0c;实现无线获取数据的目的。 2.实验设备 硬件&a…

企业鸿蒙原生应用元服务备案实操包名公钥签名信息

一、鸿蒙应用/元服务如何查询包名&#xff1f; 登录 AppGallery Connect &#xff0c;点击“我的应用”&#xff0c;输入应用名称可查询到需要备案的鸿蒙应用/元服务包名。 二、鸿蒙应用/元服务如何获取公钥和签名信息&#xff1f; &#xff08;1&#xff09;登录 AppGaller…

【IC验证】类的一些问题

1、句柄悬空 在声明句柄和创建对象以后&#xff0c;句柄是悬空的&#xff0c;在仿真没开始时内容为null。但是对于结构体和module的例化&#xff0c;仿真开始之前变量就给了一个不确定的值&#xff08;32hxxxx&#xff09; 但是对于放在initial块里面的&#xff0c;在仿真之前…

龙蜥社区「人人都可以参与开源」——体验开源成为“开源人“

龙蜥社区「人人都可以参与开源」体验开源——让更多的人了解开源&#xff01; 龙蜥社区开源概述&#xff1a;龙蜥社区开源的探索过程:龙蜥社区收获总结:AtomGit评测:服务设计上:功能结构上:安全设计上: AtomGit测评总结: 龙蜥社区开源概述&#xff1a; 在追求技术的路上少不了…

【智能算法】人工电场算法(AEFA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2019年&#xff0c;A Yadav等人受库伦定律和运动定律启发&#xff0c;提出了人工电场算法&#xff08;Artificial Electric Field Algorithm&#xff0c;AEFA&#xff09;。 2.算法原理 2.1算法思…

二维相位解包理论算法和软件【全文翻译- DCT相位解包裹(5.3.2)】

5.3.2 基于 DCT 的方法 在本节中,我们将详细介绍如何通过 DCT 算法解决非加权最小二乘相位解缠问题,而不是通过FFT.我们将使用公式 5.53 所定义的二维余弦变换。我们开发的算法等同于 FFT 方法 2(第 5.3.1 节)。与 FFT 方法 I 等价的 DCT 算法也可以推导出来,但我们将其作…

selenium 如何获取 session 指定的数据

代码核心在于这几个部分&#xff1a; 其一&#xff1a;使用元素定位来获取页面上指定需要抓取的关键字&#xff1b; 其二&#xff1a;将页面上定位得到的数据永久存储到本地文件中。 具体来梳理一下从访问URL开始到爬取数据整个流程下来的各个节点我们都做了哪些工作。 我们来看…

C语言——详解字符函数和字符串函数(二)

Hi,铁子们好呀&#xff01;之前博主给大家简单地介绍了部分字符和字符串函数&#xff0c;那么这次&#xff0c;博主将会把这些字符串函数给大家依次讲完&#xff01; 今天讲的具体内容如下: 文章目录 6.strcmp函数的使用及模拟实现6.1 strcmp函数介绍和基本使用6.1.1 strcmp函…

Vulnhub靶机练习笔记-Os-hackNos-1

vulnhub靶机下载 https://www.vulnhub.com/entry/hacknos-os-hacknos,401/ 靶场环境&#xff1a; NAT模式 kali&#xff1a;192.168.242.131 靶机&#xff1a;192.168.242.142 渗透 nmap探测靶机 开放了80和22端口 dirsearch对80端口进行目录扫描&#xff0c;发现drupal…

nacos derby.log无法的读取+derby数据库启动失败分析解决

排查思路分析 日志报错&#xff1a; derby.log文件权限不够&#xff08;root权限&#xff09;&#xff0c;无法读取&#xff0c;我用普通用户启动。 使用命令chown xx:xx derby.log修改属主和属组为普通用户后&#xff0c;又报出其他错误。 数据库启动不了&#xff0c;无…

图片怎么批量改格式png改jpg?一键批量搞定方法

在创建幻灯片或演示文稿时&#xff0c;使用jpg格式可以减小文件大小&#xff0c;方便分享和传输。转换png格式的图片为jpg&#xff0c;可以确保文件大小的合理控制&#xff0c;同时保持图像的可视质量&#xff0c;当遇到需要批量处理的时候&#xff0c;许多小伙伴都不太懂图片怎…

鸿蒙OS开发学习:【尺寸适配实现】

概述 在鸿蒙开发中&#xff0c;尺寸适配是一个重要的概念&#xff0c;它可以帮助我们在不同屏幕尺寸的设备上正确显示和布局我们的应用程序。本文将介绍如何在鸿蒙开发中实现尺寸适配的方法。 流程图 详细步骤 1. 定义适配方案 在鸿蒙开发中&#xff0c;我们可以通过定义适…

基于springboot+vue实现的的成人教育教务系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

Windows Nginx 启动

先解压 nginx安装包&#xff0c;进入到安装目录下(配置环境变量没有用) 解压后的目录结构如上。 #启动服务 默认是80端口&#xff0c; #如果端口被占用&#xff0c;是启动不了的&#xff0c;会生成error log在log目录下 start nginx#停止nginx 服务 nginx -s stop#重新加载配置…