SC-Lego-LOAM解析(中)

2023-05-16

上回说到经过连续帧间匹配,激光odo给出来一个位姿估计,
但是是存在不断的误差的积累的,需要与绝对的参考(地图)进行匹配,
以及进行回环检测和全局位姿优化。这也是正是mapOptmization这个node所做的事情。

mapOptmization

还是直接来到main函数:

int main(int argc, char **argv) {
    ros::init(argc, argv, "lego_loam");

    ROS_INFO("\033[1;32m---->\033[0m Map Optimization Started.");

    lego_loam::mapOptimization MO;

    //  两个线程分别进行回环检测和可视化
    std::thread loopthread(&lego_loam::mapOptimization::loopClosureThread, &MO);
    std::thread visualizeMapThread(&lego_loam::mapOptimization::visualizeGlobalMapThread, &MO);

    ros::Rate rate(200);
    while (ros::ok())
        // while ( 1 )
    {
        ros::spinOnce();

        // 循环进行scan-to-map的优化,保存关键帧,计算回环检测所需要的ScanContext
        MO.run();

        rate.sleep();
    }

    loopthread.join();
    visualizeMapThread.join();

    return 0;
}

首先又是构造了一个lego_loam::mapOptimization MO,然后开了两个新线程分别进行ScanContext回环检测和可视化,接下来又是一个死循环执行MO.run()。

先来看lego_loam::mapOptimization的构造函数:

mapOptimization::mapOptimization() : nh("~") {
        //  用于闭环图优化的参数设置,使用gtsam库
        ISAM2Params parameters;
        parameters.relinearizeThreshold = 0.01;
        parameters.relinearizeSkip = 1;
        isam = new ISAM2(parameters);

        InitParams();
        //  subscriber
        // jin: 以下几个回调函数,接收原始点云,面点,角点,离群点,以及里程计位姿保存在成员变量中
        // 这里的原始的点云,是没有经过分割和提取的,雷达直接发出来的
        subLaserCloudRaw = nh.subscribe<sensor_msgs::PointCloud2>(pointCloudTopic, 2,
                                                                  &mapOptimization::laserCloudRawHandler, this);
        subLaserCloudCornerLast = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_corner_last", 2,
                                                                         &mapOptimization::laserCloudCornerLastHandler,
                                                                         this);
        subLaserCloudSurfLast = nh.subscribe<sensor_msgs::PointCloud2>("/laser_cloud_surf_last", 2,
                                                                       &mapOptimization::laserCloudSurfLastHandler,
                                                                       this);
        subOutlierCloudLast = nh.subscribe<sensor_msgs::PointCloud2>("/outlier_cloud_last", 2,
                                                                     &mapOptimization::laserCloudOutlierLastHandler,
                                                                     this);
        // odo输出的位姿保存到transformSum中
        subLaserOdometry = nh.subscribe<nav_msgs::Odometry>("/laser_odom_to_init", 5,
                                                            &mapOptimization::laserOdometryHandler, this);
        // 保存IMU输出的时间/roll/pitch
        subImu = nh.subscribe<sensor_msgs::Imu>(imuTopic, 50, &mapOptimization::imuHandler, this);

        //  publisher
        pubKeyPoses = nh.advertise<sensor_msgs::PointCloud2>("/key_pose_origin", 2);
        pubLaserCloudSurround = nh.advertise<sensor_msgs::PointCloud2>("/laser_cloud_surround", 2);
        pubOdomAftMapped = nh.advertise<nav_msgs::Odometry>("/aft_mapped_to_init", 5);

        pubHistoryKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("/history_cloud", 2);
        pubIcpKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("/corrected_cloud", 2);
        pubRecentKeyFrames = nh.advertise<sensor_msgs::PointCloud2>("/recent_cloud", 2);
        pubRegisteredCloud = nh.advertise<sensor_msgs::PointCloud2>("/registered_cloud", 2);
        
        。。。。。。
 
    }

后端使用gtsam进行后端优化,首先进行参数的初始化,然后,请注意,定义了6个subscriber!!!
订阅的分别是最最原始的激光雷达的数据,featureAssociation提取并发布的角点,面点和离群点,激光里程计位姿以及IMU消息,回调函数基本都只是进行了格式转换和保存,注意对于IMU消息,只保存了俯仰和滚转,后面可以看出在这两个维度上只由IMU确定。

然后来看main函数中死循环执行的函数MO.run():

void mapOptimization::run() {
        // 有新数据进来,才执行后续
        // jin: 如果成员变量里接收到了新的数据
        if (newLaserCloudCornerLast && std::abs(timeLaserCloudCornerLast - timeLaserOdometry) < 0.005 &&
            newLaserCloudSurfLast && std::abs(timeLaserCloudSurfLast - timeLaserOdometry) < 0.005 &&
            newLaserCloudOutlierLast && std::abs(timeLaserCloudOutlierLast - timeLaserOdometry) < 0.005 &&
            newLaserOdometry) {

            newLaserCloudCornerLast = false;
            newLaserCloudSurfLast = false;
            newLaserCloudOutlierLast = false;
            newLaserOdometry = false;

            // jin: 互斥锁
            std::lock_guard<std::mutex> lock(mtx);// jin: 和回环检测不同时进行
            // jin: 距离上一次进行scan-to-map优化足够久了
            if (timeLaserOdometry - timeLastProcessing >= mappingProcessInterval) {
                timeLastProcessing = timeLaserOdometry;

                // jin: 应该是根据当前的odo pose,以及上一次进行map_optimation前后的pose(即漂移),计算目前最优的位姿估计
                // 保存到transformTobeMapped
                transformAssociateToMap(); 

                // jin: 确定周围的关键帧的索引,点云保存到recentCorner等,地图拼接保存到laserCloudCornerFromMap等
                extractSurroundingKeyFrames();

                // jin: 对当前帧原始点云,角点,面点,离群点进行降采样
                downsampleCurrentScan();

                // jin: 进行scan-to-map位姿优化,并为下一次做准备
                // 最优位姿保存在和transformAftMapped中,同时transformBfeMapped中保存了优化前的位姿,两者的差距就是激光odo和最优位姿之间偏移量的估计
                scan2MapOptimization();
                // 到这里,虽然在scan-to-scan之后,又进行了scan-to-map的匹配,但是并未出现回环检测和优化,
                // 所以依然是一个误差不断积累的里程计的概念

                // jin: 如果距离上一次保存的关键帧欧式距离最够大,需要保存当前关键帧
                // 计算与上一关键帧之间的约束,这种约束可以理解为局部的小回环,加入后端进行优化,
                // 将优化的结果保存作为关键帧位姿,同步到scan-to-map优化环节
                // 为了检测全局的大回环,还需要生成当前关键帧的ScanContext
                saveKeyFramesAndFactor();

                // jin: 如果另一个线程中isam完成了一次全局位姿优化,那么对关键帧中cloudKeyPoses3D/6D的位姿进行修正
                correctPoses();

                // jin: 发布优化后的位姿,及tf变换
                publishTF();

                // 发布所有关键帧位姿,当前的局部面点地图及当前帧中的面点/角点
                publishKeyPosesAndFrames();

                clearCloud();
            }
        }
    }

如果接收到了所有订阅的话题所发布的消息,且距离上一次进行scan-to-map优化足够久了,那么需要进行新的一次scan-to-map优化:

  1. transformAssociateToMap:根据odo与map之间的漂移(由上一次scan-to-map的解雇确定),对该帧的odo位姿进行补偿,保存到transformTobeMapped,这个变量也是后面一直被优化的变量; extractSurroundingKeyFrames:以时间顺序,选择最近的一定数量的关键帧点云,组成局部地图laserCloudCornerFromMap等,并进行一定的降采样;

  2. downsampleCurrentScan:对当前帧原始点云,角点,面点,离群点进行降采样;

  3. scan2MapOptimization:将步骤2提取出来的局部地图构建kd-tree,方便用来搜索最近邻,基于当前的最优位姿估计transformTobeMapped,对于当前帧的每一个点在 kd-tree中查找最近邻,建立约束,对transformTobeMapped的位姿进行优化,使得总体残差最小,连续循环优化多次,再使用IMU消息回调所确定的roll和pitch对该位姿进行修正。最终,最优位姿保存在和transformAftMapped中,同时transformBfeMapped中保存了优化前的位姿即激光里程计位姿,两者的差距就是激光odo和最优位姿之间偏移量的估计,这也是步骤1所需要的;

  4. saveKeyFramesAndFactor:如果距离上一次保存的关键帧欧式距离最够大,需要保存当前关键帧,计算与上一关键帧之间的约束,这种约束可以理解为局部的小回环,加入后端进行优化。将优化的结果保存作为关键帧位姿(保存当前关键帧的3维和6维位姿)和点云,同步到scan-to-map优化环节(即修改transformAftMapped和transformTobeMapped)。为了检测全局的大回环,还需要生成当前关键帧的ScanContext,即scManager.makeAndSaveScancontextAndKeys(*thisRawCloudKeyFrame),这里暂且不表;

  5. correctPoses:如果ScanContext回环检测(即std::thread loopthread)对全局位姿进行了优化,需要同步下来优化后的关键帧所在的位姿; publishTF:发布优化后的位姿和tf变换;

  6. publishKeyPosesAndFrames:发布所有关键帧位姿,当前的局部面点地图及当前帧中的面点/角点。

简单总结一下,这个函数对回调函数所保存的各种数据进行了处理,主要是进行scan-to-map匹配,如果需要还增加了新的关键帧进行优化,同时,对该关键帧生成ScanContext信息。另外的一些内容就是,如果后端回环检测后对关键帧的位姿进行了优化,这里需要做同步,以及发布了地图,所有关键帧位姿和当前点云等信息。

那么接下来的重点就是,ScanContext是怎么生成的?以及怎么用的?(肯定是在std::thread loopthread用的)

参考:
SC-Lego-LOAM解析(上)
SC-Lego-LOAM解析(中)
SC-Lego-LOAM解析(下)

本文经允许后转自知乎:https://zhuanlan.zhihu.com/p/348281520

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SC-Lego-LOAM解析(中) 的相关文章

  • 论文阅读 | LOAM:实时Lidar里程计和建图

    Zhang J Singh S LOAM Lidar odometry and mapping in real time C Robotics Science and Systems 2014 2 9 1 9 64 inproceeding
  • LEGO-LOAM(LOAM)部分公式推导---未完待续

    一 featureAssociation相关推导 1 xff09 帧间匹配雅可比矩阵推导 首先明确LEGO LOAM中 xff0c 运动坐标系 xff08 符合右手系 xff09 的设置为 xff1a 因此对于平面运动来说 xff0c 影响
  • SLAM会议笔记(一)LOAM

    LOAM Lidar Odometry and Mapping in Real time ABSTRACT 将复杂的SLAM问题分离为两个算法 xff0c 一个高频低精度的运动估计 xff0c 另一个低一个数量级的点云匹配和配准算法 REL
  • SLAM会议笔记(三)V-LOAM

    Visual lidar Odometry and Mapping Low drift Robust and Fast Abstract 提出了一种新的使用激光雷达里程计和视觉里程计的框架 xff0c 提升了表现 xff0c 特别是在剧烈运
  • SLAM会议笔记(四)Lego-LOAM

    LeGO LOAM Lightweight and Ground Optimized Lidar Odometry and Mapping on Variable Terrain Abstract 提出一种轻量级的ground optimi
  • 激光雷达 LOAM 论文 解析

    注意 xff1a 本人实验室买的是Velodyne VLP 16激光雷和 LOAM 论文中作者用的不一样 xff0c 在介绍论文之前先介绍一下激光雷达的工作原路 xff0c 这样更容易理解激光雷达的工作过程 xff0c 其实物图如下图1所示
  • A-LOAM源码阅读

    LOAM 论文地址 xff1a https www ri cmu edu pub files 2014 7 Ji LidarMapping RSS2014 v8 pdf A LOAM地址 xff1a https github com HKU
  • LeGo-LOAM 跑通与源码学习

    论文链接 xff1a https www researchgate net LeGO LOAM 源码仓库 xff1a https github com RobustFieldAutonomyLab LeGO LOAM 本人注释 xff1a
  • velodyne运行Loam_velodyne过程记录

    刚拿到手的3D激光雷达 xff0c 运行一下试试 xff08 1 xff09 loam velodyne环境配置 cd catkin ws src git clone https github com laboshinl loam velo
  • A-LOAM学习

    A LOAM学习 一 复现1 1 Ubuntu 和 ROS1 2 Ceres Solver1 3 PCL 二 下载A LOAM三 下数据集 一 复现 1 1 Ubuntu 和 ROS A LOAM 1 2 Ceres Solver span
  • A-LOAM学习

    A LOAM学习 一 kittiHelper cpp二 scanRegistration cpp三 laserOdometry cpp四 laserMapping cpp 一 kittiHelper cpp 本代码旨在实现 将kitti数据
  • LOAM进行点云地图创建

    3D激光点云数据处理入门 xff08 一 xff09 使用LOAM进行点云地图创建 LOAM 原理简述topic关系算法分析算法伪代码 LOAM 建图实践创建你的 ROS Workspace下载LOAM Package下载数据包运行 LOA
  • SC-Lego-LOAM解析(上)

    文章目录 正文imageProjectionfeatureAssociationFeature Extraction 正文 SC Lego LOAM实际上应该并不对应某一篇特定的论文 xff0c 而是韩国KAIST在github开源的代码
  • SC-Lego-LOAM解析(中)

    上回说到经过连续帧间匹配 xff0c 激光odo给出来一个位姿估计 xff0c 但是是存在不断的误差的积累的 xff0c 需要与绝对的参考 xff08 地图 xff09 进行匹配 xff0c 以及进行回环检测和全局位姿优化 这也是正是map
  • SC-Lego-LOAM解析(下)

    回环检测是一个相对独立的模块 xff0c 这里再开一篇专门说明 前面两篇已经说过 xff0c 先对点云做了预处理 xff0c 然后进行连续帧之间的匹配即激光odom xff0c 然后是scan to map匹配 xff0c 并保存关键帧的位
  • 保存并查看Lego-Loam的三维点云地图

    Loam的安装及运行方法可以参考 https blog csdn net qq 36396941 article details 82973772 本文提供ROS wiki http wiki ros org loam velodyne上无
  • LOAM_velodyne学习(三)

    终于到第三个模块了 我们先来回顾下之前的工作 点云数据进来后 经过前两个节点的处理可以完成一个完整但粗糙的里程计 可以概略地估计出Lidar的相对运动 如果不受任何测量噪声的影响 这个运动估计的结果足够精确 没有任何漂移 那我们可以直接利用
  • 使用C/C++编程控制LEGO EV3

    环境搭建 1 安装Eclipse 选择Eclipse IDE for C C Developers 网址 http www eclipse org downloads 2 安装c4ev3 网址 https c4ev3 github io 该
  • 作为科技迷,你必须要了解的乐高机器人常识!

    Source by Fans 主要材料 乐高机器人常识 所需工具 乐高机器人常识 制作步骤 第1步 从今天起 给大家盘点一下主流的机器人开发套件 谈及机器人套件 乐高是回避不掉的 既然这样 那我们索性从乐高机器人套件开始说起 第2步 乐高
  • 利用python语言编程控制LEGO EV3

    1 环境搭建 安装WinSCP 网址 https sourceforge net projects winscp 下载一个自己喜欢的python IDE 例如pycharm 将EV3的系统换为ev3dev 参考网址 https www ev

随机推荐

  • 使用cmake构建一个大型项目框架

    文章目录 使用CMake构建一个大型项目工程1 大型工程目录结构介绍1 1 工程目录结构介绍1 2 工程目录说明 xff08 我是这样设计的 xff0c 你们也可以参考类似这样设计 xff09 1 3 最外层CMakeLists txt说明
  • github下载加速的几种方法

    文章目录 1 github加速的几种办法1 1 把github的代码 xff0c 转到码云上1 2 有人做了github的代下载网站 xff0c 可以从上面进行下载1 3 使用cnpmjs镜像进行加速1 4 使用国外服务器进行搭桥 2 总结
  • EasyLogger的代码解读和移植(linux和stm)

    文章目录 1 EasyLogger目录结构分析 2 EasyLogger之docs查看总结 2 1 EasyLogger之docs查看 2 1 2 api gt kernel md文档 2 1 3 port gt kernel md文档 2
  • C++之socket.io编译使用

    文章目录 1 什么是socket io2 开发环境配置2 1 获取socket io的源码2 2 cmake安装2 3 boost安装2 3 1 获取源码2 3 2 解压编译下载 2 4 rapidjson下载2 5 websocketpp
  • github push的改版

    1 记录一次github 推送时的错误 错误如下 xff1a remote Please see https github blog 2020 12 15 token authentication requirements for git
  • Frp内网穿透

    Frp内网穿透 所有经过服务器的内网穿透都是有一个服务端和客户端 因为都需要借助服务器的公网ip来访问进而达到内网穿透的效果 frp的github开源地址 https github com fatedier frp frp的说明文档 htt
  • linux常见的几种排序方法

    我们以数组a 61 2 6 8 9 1 2 进行排序输出作为列子 xff1a 下面我来总结几种方法来帮助大家学习 1 xff1a 常规排序 首先2和6对比 xff0c 2不比6大不因此不交换 xff0c 所以还是268912 xff0c 然
  • 值得推荐的C/C++框架和库

    值得学习的C语言开源项目 Libevent libev是一个开源的事件驱动库 xff0c 基于epoll xff0c kqueue等OS提供的基础设施 其以高效出名 xff0c 它可以将IO事件 xff0c 定时器 xff0c 和信号统一起
  • cmake教程4(find_package使用)

    本文主要内容如下 xff1a 1 cmake find package的基本原理 2 如何编写自己的 cmake module模块 3 使用cmake find package 使用不同版本的opencv lib问题 xff08 openc
  • A*算法

    如此好贴 xff0c 不能不转 xff01 原文地址 xff1a http dev gameres com Program Abstract Arithmetic AmitAStar mht 本文版权归原作者 译者所有 xff0c 我只是转
  • 3.5RC_Channel 和 SRV_channel

    前言 这部分是之前在折腾ROVER的时候梳理了一遍 xff0c 但是没有形成记录 xff0c 现在从新从对象的角度来分析一遍 xff0c 包括映射 转换 数据结构 等方面进行梳理 xff1b 数据结构 首先分析的入口是从Rover read
  • CAN通信详解

    本章我们将向大家介绍如何使用STM32自带的CAN控制器来实现两个开发板之间的CAN通讯 xff0c 并将结果显示在TFTLCD模块上 本章分为如下几个部分 xff1a 30 1 CAN简介 30 2 硬件设计 30 3 软件设计 30 4
  • VC中出现的一些小问题的解决办法

    本人用的是vc6 0的 xff0c 在实际调试过程中出现一些小的问题 xff0c 通过网上查询资料得以解决 xff0c 特此在此整理下 xff0c 方便后者参考 第一问题 xff1a 程序源码编写完成后编译没有问题 xff0c 而链接时出现
  • C++编译报错`XXX‘被多次定义总结;未定义的引用等等

    1 C 43 43 编译报错 96 XXX 被多次定义总结 报错原因 xff1a 诸如类似的报错都是因为可能在两个或者多个 cpp文件 h文件定义该全局变量 xff0c 属于重复定义问题 解决办法是 xff1a 在VScode全局搜索该变量
  • C++必备万能头文件“#include<bits/stdc++.h>”

    c 43 43 代码一个简单的头文件 xff1a span class token macro property span class token directive hash span span class token directive
  • Livox SLAM(带LIO+闭环检测优化)

    主题 xff1a Livox雷达LIO 43 闭环检测优化 开源地址 xff1a LiDAR SLAM 该开源为 Livox雷达实现了一个一体化且即用型的 LiDAR 惯性里程计系统 前端基于基于开源里程计框架LIO Livox获取里程计信
  • 大疆livox定制的格式CustomMsg格式转换pointcloud2

    官方livox driver驱动livox雷达发出的点云topic有两种 xff0c 一种是大疆览沃定制的格式CustomMsg格式 xff0c 另一种是将CustomMsg格式 转换过的pointcloud2格式 xff0c 参见 Liv
  • paddlepaddle

    项目用到了paddlespeech2 xff0c 学了几天paddlepaddle xff0c 简单记录一下 文章目录 1 手写数字识别任务2 极简方案构建手写数字识别模型模型设计训练配置训练过程模型测试 3 手写数字识别 之数据处理4 手
  • SC-Lego-LOAM解析(上)

    文章目录 正文imageProjectionfeatureAssociationFeature Extraction 正文 SC Lego LOAM实际上应该并不对应某一篇特定的论文 xff0c 而是韩国KAIST在github开源的代码
  • SC-Lego-LOAM解析(中)

    上回说到经过连续帧间匹配 xff0c 激光odo给出来一个位姿估计 xff0c 但是是存在不断的误差的积累的 xff0c 需要与绝对的参考 xff08 地图 xff09 进行匹配 xff0c 以及进行回环检测和全局位姿优化 这也是正是map