ROS Navigation Stack是ROS提供的一个二维的导航功能包集合,通过输入里程计、传感器信息和目标位姿,输出控制机器人到达目标状态的安全速度指令。
ROS Navigation Stack为移动机器人的导航规划提供了比较好的参考,通过实现功能包集合提供的接口,也可以比较容易地将自己的算法应用到移动机器人上。本文将帮助大家理解ROS Navigation Stack的设计思路,并对各个功能包进行讲解。
通过ROS wiki上的这张图,我们可以比较清楚的看到ROS Navigation Stack的整体设计思路:整个功能包集合以move_base为核心,将里程计信息、传感器信息、定位信息、地图以及目标点输入给move_base,move_base经过规划后会输出速度指令。move_base包括三个关键部分:global_planner(全局规划器)、local_planner(局部规划器)和recovery_behaviors(恢复行为)。
这三个部分都是以插件的形式实现的,通过插件机制可以方便地切换不同算法实现的规划器。恢复行为会在机器人移动过程中出现了异常状态时被触发,目的是帮助机器人摆脱异常状态。另外,move_base还包括了global_costmap(全局代价地图)和local_costmap(局部代价地图),规划器需要在代价地图上进行导航规划。下面我们来详细看一下上面提到的各部分内容。
简单来说,里程计的作用就是估计机器人运动的距离和速度。通过阅读源码可以得知,在ROS Navigation Stack中,里程计信息有两个作用,一个作用是提供给局部规划器,当局部规划器选取最优路径和判断机器人是否停止的时候会使用到里程计的速度信息,另一个作用就是将估计位姿信息用于定位。
里程计信息一般从机器人底盘的轮式编码器获取,当然根据不同的机器人也可以选择使用视觉里程计,还可以使用扩展卡尔曼滤波对轮式里程计和IMU进行数据融合,得到更加准确的位姿估计。消息类型nav_msgs/Odometry中包括了机器人的位姿和速度以及各自的协方差。
nav_msgs/Odometry.msg
传感器数据一般来自于激光雷达、IMU和深度相机,可以用于定位和避障。使用传感器需要设定传感器参考系与机器人参考系之间的坐标变换关系,也就是常说的tf变换,这样做是为了表示传感器感知到的环境与机器人参考系之间的关系。如果使用amcl算法,激光雷达数据会用来与静态地图进行匹配,修正机器人的位姿,得到更加准确的定位。
激光雷达也可以感知到环境中障碍物的位置,通过将障碍物加入到代价地图中,实现避障。具体使用哪几种传感器,依赖于使用的机器人平台。理论上来说,使用的传感器种类越多,定位和避障的效果更好。
tf是一个让用户随时间跟踪多个参考系的功能包,它使用一种树型数据结构,根据时间缓存并维护多个参考系之间的坐标变换关系,可以帮助用户在任意时间,将点、向量等数据的坐标,在两个参考系中完成坐标变换。
机器人系统通常有许多随时间变化的三维参考系,例如世界参考系和机器人参考系。tf会随着时间的变化跟踪这些参考系。基于ROS Navigation Stack实现移动机器人的自主导航,必须维护一棵完整的tf树,即map->odom->base_link->sensor_link。
实际上,我们所说的定位,就是维护map->base_link之间关系的过程。tf树中记录了机器人参考系与地图参考系之间的关系,也就得到了机器人在地图中的哪个位置,tf树也记录了传感器参考系与机器人参考系的关系,也就得到了感知的数据与机器人之间的关系。
点击可查看大图
从上面这张图我们就可以直观地理解什么是tf变换了,由tf树帮助我们管理激光雷达与机器人底盘之间的坐标变换关系,当激光雷达感知到某个位置存在障碍物时,通过tf变换就能得到障碍物与机器人底盘之间的距离。
map_server在ROS Navigation Stack中是可选的,其主要的作用就是给机器人导航提供地图。提供地图有两种方式,一种方式是通过SLAM提供实时地图,另一种方式是提供SLAM提前建立并保存或者通过其他方式制作的地图,常用的SLAM算法有gmapping和hector_slam等。
一般在比较规则的场景,可以制作高精度地图提供给机器人,定位和规划都会有比较好的效果。通过SLAM提供实时地图,需要把实时地图以话题的形式发布。通过map_server提供的地图为pgm格式,通过加载yaml配置文件,将地图以话题的形式加载到系统中。在yaml文件中可以配置地图的分辨率,原点以及表示占据/空闲的概率。下面为yaml配置文件的内容:
·
地图的默认参考系为map。占据的概率occ = (255 – color_avg) / 255.0,其中color_avg为像素RGB的平均值。
amcl是ROS Navigation Stack中唯一指定的定位算法,全称为自适应蒙特卡洛定位,它是一种用于在二维环境中移动的机器人的概率定位系统。简单概括一下它的原理就是通过在全局地图中撒粒子,粒子可以理解为机器人的可能位姿。
按照评价标准,例如激光雷达数据与地图的匹配程度给粒子打分,分数越高代表机器人在这个位置的可能性越大,经过粒子滤波器以后留下的就是分数高的粒子了。
经过多次撒粒子,粒子就会集中到机器人位置可能性高的地方,称之为粒子收敛。自适应其实简单理解就是会根据粒子的平均分数或者粒子是否收敛来增加或减少粒子数,它能够有效地解决机器人绑架问题和粒子数固定问题。
上图中红色的粒子簇即为amcl在全局地图中撒下的粒子,可以看到一开始的粒子簇集中在给定的起始位
姿
周围,此时的粒子簇还是比较分散的,随着机器人的移动,粒子簇逐渐收敛,定位效果还是不错的。
点击可查看大图
amcl在ROS Navigation Stack中的作用就是输出map->odom的tf变换,来弥补里程计的漂移误差,它要求在机器人的定位系统中要存在里程计位姿估计,即odom->base_link的tf变换,并给定起始位姿和输入传感器数据。
costmap_2d功能包提供了一种二维代价地图的实现方案,该方案从实际环境中获取传感器数据,构建二维或三维的栅格占用地图,以及基于占用栅格地图和用户定义膨胀半径的二维代价地图。该包也支持基于map_server初始化代价地图,基于滚动窗口的代价地图,以及订阅和配置传感器话题。
在ROS Navigation Stack中,代价地图分为全局代价地图和局部代价地图,全局代价地图使用基于map_server初始化代价地图,也就是Static Map Layer(静态地图层),局部代价地图为基于滚动窗口的代价地图。
代价地图还包括Obstacle Map Layer(障碍物层)和Inflation Layer(膨胀层),有时候根据应用场景的需要也可以加入用户自定义层,用户自定义层可以用插件来实现。障碍物层是将传感器感知到的障碍物加入到代价地图中。
在规划的时候,我们会把机器人看做是一个质点,并没有考虑机器人的实际模型,因此在代价地图中需要膨胀层,来尽量保证规划出的路径不会使机器人和障碍物发生碰撞。
点击可查看大图
上面这张图为ROS wiki上的一个示例,可以看到图中灰色部分即为静态地图,红色部分为传感器感知到的障碍物,蓝色部分为膨胀层。红色多边形表示机器人的形状,为了避免碰撞,机器人形状不应该和红色部分相交,机器人中心点不应该和蓝色部分相交。
move_base是整个ROS Navigation Stack的最顶层,它将各个功能模块组合起来,通过SimpleActionServer接收目标点并完成导航任务。move_base支持任何遵循在nav_core包中指定的nav_core::BaseGlobalPlanner接口的全局规划器和任何遵循在nav_core包中指定的 nav_core::BaseLocalPlanner 接口的局部规划器。
move_base执行的流程是通过状态机来控制是执行规划行为还是恢复行为,当规划失败就会执行恢复行为,如果所有恢复行为也都失败就会退出导航并报告问题。规划的流程是通过回调函数接收目标点,同时会有一个线程不断地跑全局规划器,然后局部规划器以一定频率对规划出来的路径进行跟踪,计算出速度指令,最终到达目标点就重置规划状态。
nav_core功能包为用于导航的机器人行为提供了通用的接口,其中定义了三个抽象类,分别是BaseGlobalPlanner、BaseLocalPlanner和RecoveryBehavior。
点击可查看大图
从上图我们也可以清楚地看到move_base的整个导航流程以及各部分使用的nav_core功能包定义的接口。通过阅读源码我们可以知道,BaseGlobalPlanner中定义了纯虚函数makePlan(),makePlan()用于实现规划全局路径。
BaseLocalPlanner中定义了纯虚函数setPlan()、isGoalReached()和computeVelocityCommands(),setPlan()用于实现设定局部规划器跟踪的路径,isGoalReached()用于实现判断是否到达目标点,computeVelocityCommands()用于实现计算安全的速度指令。
RecoveryBehavior定义了纯虚函数runBehavior(),runBehavior()用于实现执行恢复行为。
对于move_base的全局规划器,我们主要了解global_planner功能包。global_planner功能包实现了两种路径规划算法:A*和dijkstra,并实现了OrientationFilter类,用于对路径进行优化。