写在前面
我们之前使用gmapping绘制了一张完整的地图,但是在大多数情况下,我们对机器人的需求都是让机器人在已知的场景做导航(例如送餐和扫地机器人)。这就需要我们在一张“已知”的地图上进行定位。
但是“已知”的地图又不会一成不变,这就需要我们能够在已知地图的基础上对局部做一些重建和修改。例如一个机器人在美食街中导航,而美食街有总会增添一些临时的展台
这个教程尝试将ros中的gmapping,地图服务器以及amcl做一个结合,实现在一张已知地图的导航,同时针对已知地图的变化进行实时修正~
今天来介绍第一节,我们首先使用map server发布一张地图,然后使用gmapping在生成一张地图,为其配置坐标系,使他们能够共存在同一个坐标系里面。
使用ROS节点发布一张静态的地图
如果我们想实现机器人在一张已知地图上的导航,首先就要发布一张静态的地图:
- 发布一个类型为nav_msg/OccupancyGrid的消息(这个消息中有一个代表着地图图片的矩阵):
- 配置相关的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变换体系,使他们能够共存在同一个坐标系里面。
评论(0)
您还未登录,请登录后发表或查看评论