热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

GoogleCartographerROS源码解析

GoogleCartographerROSoverviewCartographer主要可分为两个子系统:localSLAM(frontendorlocaltraje

Google Cartographer ROS overview在这里插入图片描述

Cartographer主要可分为两个子系统:local SLAM(frontend or local trajectory builder), 和global SLAM (backend)。

Local SLAM构建一系列的submaps,每一个submaps具有局部一致性,可理解为其submaps内部是进行了匹配,但是各个submaps之间随着时间会产生漂移。

Global SLAM background运行,利用scan-matching scans找到loop closure constraints,然后利用pose graph优化找到全局一致的地图。

W. Hess, D. Kohler, H. Rapp, and D. Andor, Real-Time Loop Closure in 2D LIDAR SLAM, in Robotics and Automation (ICRA), 2016 IEEE International Conference on. IEEE, 2016. pp. 1271–1278

优点:算法低功耗、实时、不追求高精度(5cm);算法实现完美值得学习、易于扩展、维护;开发依赖库很少,主要包括Boost,Eigen3,Lua,Ceres,Protobuf,产品级的嵌入式机器人系统。

应用场景:计算资源有限、对精度要求不高、且需要实时避障的、规划、导航的应用,如室内服务机器人、无人机、以及物流、餐饮、安防、医疗等等。

Cartographer ROS Code Explanation

把代码主要分为两个部分,即cartographer_ros (ROS接口)和cartographer (Local SLAM和Global SLAM).

本文旨在以最简洁的方式理清代码运行的思路。

cartographer_ros (ROS接口)

cartographer_ros这个package是在ROS下面运行的,可以以ROS消息的方式接受各种传感器数据,在处理过后又以消息的形式publish出去,便于调试和可视化。

main():
整个程序的入口main函数在cartographer_ros/cartographer_ros/cartographer_ros/node_main.cc文件中,通过void Run()来启动整个程序,包括:

  • 加载参数:NodeOptions,TrajectoryOptions;
  • 定义了map_builder变量:请注意此时的变量类型已经是cartographer::mapping::MapBuilder了;
  • Node类:所有的一切都送入了Node这个类。

class Node:
而在Node类的构造函数中,包括:

  • 发布topic

    • kSubmapListTopic
    • kTrajectoryNodeListTopic
    • kLandmarkPosesListTopic
    • kConstraintListTopic
    • kScanMatchedPointCloudTopic (匹配上的点云数据)
  • 服务service

    • kSubmapQueryServiceName(查询Submap)
    • kStartTrajectoryServiceName(开始一段trajectory)
    • kFinishTrajectoryServiceName(结束一段trajectory)
    • kWriteStateServiceName

开始运行构建地图,都需要调用一个重要的函数int Node::AddTrajectory(const TrajectoryOptions& options,const cartographer_ros_msgs::SensorTopics& topics),而这个函数可以通过两种方式调用,一是通过调用service函数,启动bool Node::HandleStartTrajectory();另一个是在void Run()中使用默认topics直接调用void Node::StartTrajectoryWithDefaultTopics(const TrajectoryOptions& options)

函数int Node::AddTrajectory()开始一段trajectory,添加了

  • PoseExtrapolator 估计pose
  • SensorSamplers 处理传感器
  • LaunchSubscribers 消息订阅

另外,map_builder_bridge_变量也同时调用了AddTrajectory()函数,而其是在Node构建函数参数列表初始化map_builder_bridge_(node_options_, std::move(map_builder), tf_buffer),由main()传入。

订阅传感器发布的消息:Laser, MultiEchoLaser, PointCloud2, IMU, Odometry, NavSatFixMessage, Landmark等,并调用了相应的处理函数,最后都以map_builder_bridge_.sensor_bridge->HandleLaserScanMessage的形式进行了调用,此处需要注意的是map_builder_bridge_sensor_bridge都是桥接cartographer的关键。

class MapBuilderBridge:
class Node类主要是通过class MapBuilderBridge来实现再次调用

  • Node::HandleSubmapQuery(){map_builder_bridge_.HandleSubmapQuery(request, response);}
  • Node::PublishSubmapList(){map_builder_bridge_.GetSubmapList();}
  • Node::PublishTrajectoryStates(){map_builder_bridge_.GetTrajectoryStates();}
  • Node::PublishTrajectoryNodeList(){map_builder_bridge_.GetTrajectoryNodeList();}
  • Node::PublishLandmarkPosesList)(){map_builder_bridge_.GetLandmarkPosesList();}
  • Node::PublishConstraintList(){map_builder_bridge_.GetConstraintList();}

class MapBuilderBridge中对象map_builder_由类cartographer::mapping::MapBuilderInterface定义,这使得接下来的功能转到MapBuilderInterface

class MapBuilderBridge中定义了对象sensor_bridges_来使用class SensorBridge,在处理各种传感信息时,都调用了trajectory_builder_->AddSensorData(),通过不同的sensor_id和变量类型添加不同的消息,Laser, MultiEchoLaser, PointCloud2, IMU, Odometry, NavSatFixMessage, Landmark等,而这些由cartographer::mapping::TrajectoryBuilderInterface类接口连接。

ros部分脉络:cartographer_ros这部分帮助更加方便的接受各种消息,方便开发调试。系统初始化流程:从node_main.cc文件中void Run()->class Node构造函数定义发布topics和service->Node::AddTrajectory()订阅各种传感器消息->class MapBuilderBridgeclass SensorBridge实现与cartographer的桥接->调用cartographer::mapping::MapBuilderInterfacecartographer::mapping::TrajectoryBuilderInterface接口类。

cartographer

cartographer源码主要包括文件夹:cloud、common、ground_truth、io、mapping、metrics、sensor、transform,

  • mapping: 算法的核心部分,包括ROS调用部分的接口、submap的构建、位姿优化接口;
  • common:基本数据结构、工具接口;
  • sensor: 传感器数据结构;
  • transform: 位姿数据结构、位姿变换;
  • io: 存读取数据、日志;
  • cloud: 点云处理;

configuration_files文件定义了map_builder,pose_graph,trajectory_builder的配信信息,Lua是一种非常轻量的脚本语言,主要用来做Configuration。

接口class MapBuilderInterface:接口MapBuilderInterface具体由MapBuilder继承并实现,MapBuildercartographer算法的顶层类,包括两个部分:TrajectoryBuilder类建立与维护Local Submap(Local SLAM),PoseGraph全局pose Loop Closure(Global SLAM)。

class MapBuilder:在/mapping/map_builder.h中定义了一个PoseGraph的智能指针std::unique_ptr pose_graph_,一个收集传感器数据的指针std::unique_ptr sensor_collator_,所有trajectory的管理向量std::vector> trajectory_builders_及其相应的Configuratonstd::vector all_trajectory_builder_options_

MapBuilderTrajectoryBuilder根据trajectory上收集到的传感器数据构建出栅格地图submap,submap会随着时间进行累积,超过阈值就会新增一个submap;PoseGraph则根据loop closure的constrants将所有的submaps进行全局优化,构建Global SLAM。

Local SLAM

TrajectoryBuilder自然是用来创建trajectory的,通过抽取trajectory上采集的数据获得关键帧并对应到trajectory的node上,trajectory就是一串node;同时需要建立栅格地图submaps,以便进行做scanmatch,形成global map。

/mapping/trajectory_builder_interface.hstruct InsertionResult{}用于保存Local SLAM的一个节点的数据结构,包括NodeId,TrajectoryNode::Data,Submap。
NodeId在/mapping/id.h中定义,由两部分组成:一个int型trajectory_id和一个int型node_index。而TrajectoryNode包括时间、传感器数据、节点在Local SLAM中的Pose、点云、节点在世界坐标系下的位姿。
/mapping/submaps.h中Submap不停的将range data加入其中,当达到一定阈值,submap完成,就要寻找constrains执行loop closure。

LocalSlamResultCallback函数是在local SLAM处理accumulated Rangedata 之后调用,插入到Local Slam中的pose,RangeData数据和插入结果InsertionResult。

virtual void AddSensorData()处理传感器数据的5个纯虚函数。

TrajectoryBuilderInterface会在其继承类CollatedTrajectoryBuilder (/mapping/internal/collated_trajectory_builder.h)继承并实现具体方法。

class MapBuilder中,有一个对象trajectory_builders_(接口std::vector> trajectory_builders_),在int MapBuilder::AddTrajectoryBuilder()
函数中进行了赋值,指针类行为CollatedTrajectoryBuilder,在类CollatedTrajectoryBuilder的构造函数中,利用GlobalTrajectoryBuilder中的函数CreateGlobalTrajectoryBuilder2D()提供输入。在同样LocalTrajectoryBuilder2DPoseGraph2D也为CreateGlobalTrajectoryBuilder2D()提供输入,用于创建GlobalTrajectoryBuilder,最后使得三个类绑在一起,具体的实现例子如下(必须贴出来,以免混乱):

int MapBuilder::AddTrajectoryBuilder(){}

std::unique_ptr local_trajectory_builder;
...
trajectory_builders_.push_back(common::make_unique(sensor_collator_.get(), trajectory_id, expected_sensor_ids,CreateGlobalTrajectoryBuilder2D(std::move(local_trajectory_builder), trajectory_id,static_cast(pose_graph_.get()),local_slam_result_callback)));

所以MapBuilder的相关类之间的调用关系应该是:MapBuilderInterface->MapBuilder->TrajectoryBuilderInterface->CollatedTrajectoryBuilder->GlobalTrajectoryBuilder->CreateGlobalTrajectoryBuilder2D->LocalTrajectoryBuilder2D

MapBuilder初始化:
cartographer_ros利用MapBuilderBridge调用MapBuilderInterface->MapBuilder->int Mapuilder::AddTrajectoryBuilder(),而在继承实现TrajectoryBuilderInterface的类CollatedTrajectoryBuilder的构造函数输入参数中,将类GlobalTrajectoryBuilderLocalTrajectoryBuilder2D传入,取名为wrapped_trajectory_builder(取名很讲究)。

传感器Topic消息接收:在ROS订阅topic的函数中,利用SensorBridge来实现桥接,以laser为例,SensorBridge::HandleLaserScanMessage,最终还是需要调用trajectory_builder_->AddSensorData(),由CollatedTrajectoryBuilderGlobalTrajectoryBuilder继承TrajectoryBuilder实现AddSensorData()函数,最后在LocalTrajectoryBuilder2D类中实现PoseExtrapolatorScan Matching等核心算法。

在接收到IMUodometry消息后由PoseExtrapolator压入队列作为插入激光雷达的辅助,而当LocalTrajectoryBuilder2D::AddRangeData()时,则结合起来处理核心激光点云的数据。

Global SLAM

当local SLAM生成一串连续的submaps,全局优化在后台运行,重新排列submaps,使得形成全局一致的地图。pose graph优化通过建立nodes和submaps之间的constraints,然后优化pose graph。

PoseGraph先由接口PoseGraphInterface((/mapping/pose_graph_interface.h中))定义,然后由PoseGraph (/mapping/pose_graph.h)来继承,再区分不同的2D和3D的情况由PoseGraph2D (/mapping/internal/2d/pose_graph_2d.h)PoseGraph3D (/mapping/internal/3d/pose_graph_3d.h)实现。

PoseGraphInterface中定义了约束的数据结构Constraint,LandmarkNode,Submap,TrajectoryData,以及全局优化的回调函数GlobalSlamOptimizationCallback。而在PoseGraph (/mapping/pose_graph.h)中又定义了大量的虚函数,最后在PoseGraph2D (/mapping/internal/2d/pose_graph_2d.h)中实现。

enum class SubmapState { kActive, kFinished }描述了当前submap的状态,而当状态转换到kFinished的时候,所有的nodes都要跟submap做一次匹配;同样的,当trajetory中由新增的nodes时,新的nodes也需要跟finished submaps全部做一次匹配。
利用AddNode()函数,PoseGraph需要不断的将trajectory上新增的TrajectoryNode添加到其中;PoseGraph检查insertion_submaps中Old Submap状态是否为finished,如是则需要进行一次Loop Closure。

AddNode()调用了一个重要的函数PoseGraph2D::ComputeConstraintsForNode(),用于计算新增节点与所有submaps的相对位姿约束。
void ComputeConstraint()计算新的节点与旧的submaps之间的关系,而void ComputeConstraintsForOldNodes()是当一个新的submap finished的时候,计算新submaps与旧节点之间的关系。

Non-global constraints在traject上节点之间的约束(即内部submaps的约束),保持了trajectory局部结构的一致性。Global constraints(即loop closure constraints)由新的submap和之前旧的nodes进行搜索匹配得到。首先采用FastCorrelativeScanMatcher,能够实时的loop closures scan matching,采用“Branch and bound” 的机制,高效消除不正确的匹配。当找到合适的备选项之后,在采用Ceres Scan Matcher来优化pose。

optimization旨在重新调整submaps,根据多个不同权重的残差,包括:global (loop closure) constraints,non-global (matcher) constraints,IMU acceleration及rotation measurements,local SLAM rough pose estimations,an odometry source,a fixed frame (such as a GPS system)。具体的处理在2D::HandleWorkQueue()(/mapping/internal/2d/pose_graph_2d.h))中,根据ConstraintBuilder2D输入的约束关系。其由PoseGraph2D中constraint_builder_(constraints::ConstraintBuilder2D)在加入新的Node时,建立新Node与submap之间的约束关系,存于vector constraints_,最后调用RunOptimization()函数进行全局优化。

Reference


  1. Cartographer (Hess, W., Kohler, D., Rapp, H., & Andor, D. (2016, May). Real-time loop closure in 2D LIDAR SLAM. In 2016 IEEE International Conference on Robotics and Automation (ICRA) (pp. 1271-1278). IEEE.)

  2. Sparse Pose Adjustment (Konolige, K., Grisetti, G., Kümmerle, R., Burgard, W., Limketkai, B., & Vincent, R. (2010, October). Efficient sparse pose adjustment for 2D mapping. In 2010 IEEE/RSJ International Conference on Intelligent Robots and Systems (pp. 22-29). IEEE.)

  3. Branch-and-bound scan matching (Olson, E. B. (2009, May). Real-time correlative scan matching. In 2009 IEEE International Conference on Robotics and Automation (pp. 4387-4393). IEEE.)

  4. Cartographer ROS Integration

  5. cartographer源码详细解读-April Lee


推荐阅读
  • 移动传感器扫描覆盖摘要:关于传感器网络中的地址覆盖问题,已经做过很多尝试。他们通常归为两类,全覆盖和栅栏覆盖,统称为静态覆盖 ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • ICRA2019最佳论文  Making Sense of Vision and Touch: SelfSupervised Learning of Multimodal Representatio
    文章目录摘要模型架构模态编码器自监督预测控制器设计策略学习控制器设计实验结论和展望会议:ICRA2019标题:《MakingSenseofVision ... [详细]
  • 四月份NFT优质榜单
    四月份NFT优质榜单 ... [详细]
  • 机器视觉在中国的发展已有十余个年头。过去十年是机器视觉产业在中国市场发展最快的十年,经过一定时期的普及与推广,机器视觉已逐渐为广大客户所熟知࿰ ... [详细]
  • Ubuntu18.04 安装ROS Melodic && Ros2 Dashing
    https:blog.csdn.netqq_44717317articledetails104547474一、Ubuntu18.04的安装ubuntu2go的制作关于Ubuntu2 ... [详细]
  • 2017亚马逊人工智能奖公布:他们的AI有什么不同?
    事实上,在我们周围,“人工智能”让一切都变得更“智能”极具讽刺意味。随着人类与机器智能之间的界限变得模糊,我们的世界正在变成一个机器 ... [详细]
  • cocos2dx-lua使用UIListView制作二级折叠菜单
    折叠菜单,用过jqueryaccordion的同学都知道是啥玩艺儿~,图片效果就是介样:cocos2dx不带有此控件,因此我们动手来实现一个。原理很简单,展开的时候往listview里i ... [详细]
  • 单目标应用:最有价值球员算法(Most Valuable Player Algorithm,MVPA)求解旅行商问题TSP
    一、最有价值球员算法最有价值球员算法(MostValuablePlayerAlgorithm,MVPA)由Bouchekara等人于20 ... [详细]
author-avatar
任乔彦
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有