写在前面

我们之前使用gmapping绘制了一张完整的地图,但是在大多数情况下,我们对机器人的需求都是让机器人在已知的场景做导航(例如送餐和扫地机器人)。这就需要我们在一张“已知”的地图上进行定位。

但是“已知”的地图又不会一成不变,这就需要我们能够在已知地图的基础上对局部做一些重建和修改。例如一个机器人在美食街中导航,而美食街有总会增添一些临时的展台

这个教程尝试将ros中的gmapping,地图服务器以及amcl做一个结合,实现在一张已知地图的导航,同时针对已知地图的变化进行实时修正~

今天来介绍第一节,我们首先使用map server发布一张地图,然后使用gmapping在生成一张地图,为其配置坐标系,使他们能够共存在同一个坐标系里面。

使用ROS节点发布一张静态的地图

如果我们想实现机器人在一张已知地图上的导航,首先就要发布一张静态的地图:

  1. 发布一个类型为nav_msg/OccupancyGrid的消息(这个消息中有一个代表着地图图片的矩阵):
  2. 配置相关的TF坐标系,将发布地图消息的frame_id配置为一个指定的字符串,让ROS中的坐标变换系统能够找到它。

我们可以使用ros自带的map_server来发布一张地图,但是这种发布出来的地图灵活性比较低。我们也可以选择自己来写一个地图发布的节点。下面我们分别对两种方法进行介绍

使用map_server读取文件系统存储的地图

为了导入地图,我们首先要对存储的地图进行读取。ros自带的map_server提供了yaml格式的地图配置文件以及pgm格式的地图文件接口,如果我们使用map_server直接载入地图,首先我们要先写一个地图配置文件:

 

image: [地图的路径和名称]

resolution: [地图一个像素代表的实际距离,例如0.1就代表一个像素为10cm 10cm的方格]

origin: 一个三元素矩阵,代表了地图左下角的实际坐标系x,y,z。例如[0.0, 0.0, 0.0]即代表区地图左下角为原点

occupied_thresh:  占用栅格最小灰度值,大于这个值即默认这个像素区域是障碍

free_thresh:  空白区域最大灰度值,小于这个值即默认这个像素区域是空白的

negate: 0  是否反转色度(一般用不到,设置为0就可以)

写好的yaml以及地图的存储路径如下:

 

这些配置内容写好后,存入一个后缀名为.yaml的文件。然后使用以下代码:

rosrun map_server map_server [配置文件名]

 

就可以把地图发出来了,打开rviz,添加地图的topic,就可以看到发布的地图(不知道怎么看可以发布我之前的博客):

 低成本3D空间导航/测绘机器人(5)——上位机软件框图以及初步2D-SLAM模块配置 - 古月居 (guyuehome.com)

关于map server更多的信息可以参考http://wiki.ros.org/map_server

自己写一个节点发布地图

ROS自带的map server是有很大问题的,其中一个比较严重的问题是它不太实用,例如它无法实现地图切换的功能,而且它发布的地图参数配置也比较死板,没法动态变化。

我们可以利用opencv,配合ros-publisher打造一个自己的地图发布节点,使它能够基于jpeg图像发布ROS地图。

使用opencv读取地图矩阵

使用opencv的前提是导入opecv以及numpy两个python包:

import cv2

import numpy as np

导入这两个包后,使用以下两行简单的代码就可以将地图的文件转换为numpy矩阵:

  self.map=np.zeros((100,100))   构建一个空矩阵

        name=self.local+map_name+'.jpg'    获取地图的全路径

        self.map=cv2.imread(name,cv2.IMREAD_GRAYSCALE)    self.map即为地图的numpy矩阵

基于OccupancyGrid发布地图

我们首先来看一下机器人地图消息的结构体:

 

在机器人的地图中,比较关键的三册参数是:

  • header,包含了地图的基本信息,尤其是frame_id(坐标系名称)
  • 地图信息info,包含了地图载入的时间(load_time),地图每个像素代表的大小(resolution),以及地图左下角像素在实际场景中所对应的位置信息(position)
  • 地图序列data,这个数据比较关键,data的本质是一个数组,它的长度为:地图宽(像素)x地图高(像素);例如一个200x300的地图,data的长度就是60000。

在data的数据中,0代表白色(即空旷区域)100代表占用(即墙壁,物体等),-1代表不确定。

我们可以通过下面的代码,把一个图片的矩阵给传到地图里去:

 

然后直接使用一个Publisher就能发布地图了:

rospy.Publisher('map',OccupancyGrid,queue_size=1,latch=True).publish(self.map)

载入的地图与map_server生成的地图共存

map_server和gmapping同时发布地图

我们在之前的教程中写过怎么开启gmapping;当gmapping开启以后,我们就能够实时绘制一张地图。如果对这个部分有问题,可以看我之前的博客:

https://www.guyuehome.com/26367

当gmapping开启后,机器人就能够实现实时绘制一张地图,它的默认消息名称为:/map,默认坐标系原点为/map。

我们也可以同时开启 map server,但是不要忘了,我们目前的map server发布的消息,坐标系原点跟gmapping完全相同!

 

也就是说,如果我们同时开启了map server和给mapping,他们会同时发布一样的消息,由于map server发布的时间只有加载的时候,而gmapping的发布是连续的,这就会造成map server发布的地图无效。

通过差异化的地图名称和坐标系原点实现gmapping和map server共存

  我们知道任何一个地图都应当对应着唯一的坐标系“frame_id”,所以我们可以在map server的地图发布中将地图消息的名字以及坐标系原点都稍稍做变换,比如:

 

这样我们就在ROS的框架内得到了两个“图层”。但是由于我们没有发布相关的TF坐标系变换,所以从某种意义上,这两幅地图是并没有什么关系的。

 

下节预告

本节我们讲了一些基础的关于“地图”的概念,我们首先使用map server发布一张地图,然后使用gmapping在生成一张地图,为其配置了坐标系frame_id。

下一节我们将使用TF变换体系,使他们能够共存在同一个坐标系里面。