目前打算使用python写出一个Astar的全局路径算法,总体分为三个部分:接收地图数据,设计路径(当然是顺过来的),发布路径。
一、建立功能包
新建功能包,并导入依赖: gmapping map_server amcl move_base roscpp rospy std_msgs 在功能包中新建一个python文件,运行这个文件就可以生成相应的路径。(相关配置略去)
二、接受地图数据(处理上游)
查看地图发布的话题和发布者
首先我们要查一下,地图数据是从哪发布的,使用什么类型发布的:
rostopic list
rostopic info /map
rosmsg info nav_msgs/OccupancyGrid
在同一个代码中接收和发布消息
下面我们先接受数据: 正常来说代码如下:
import rospy
from nav_msgs.msg import OccupancyGrid
def doMsg(msg):
rospy.loginfo(msg.data)
if __name__ == "__main__":
rospy.init_node("Astar_globel_path_planning",anonymous=True)
sub = rospy.Subscriber("/map",OccupancyGrid,doMsg,queue_size=10)
rospy.spin()
这么写虽然能成功获取消息,但是有一个问题,就是在rospy.spin()之后的代码不会进行了,说白了在ros中没有C++中的spinOnce。如果想要结束spin,就要关闭节点,这样我就没有办法在一个代码中又接受消息,又发布消息。
解决办法: 使用rospy.wait_for_message 这个函数,功能类似于spinOnce()
import rospy
from nav_msgs.msg import OccupancyGrid
if __name__ == "__main__":
rospy.init_node("Astar_globel_path_planning",anonymous=True)
OGmap = rospy.wait_for_message("/map",OccupancyGrid,timeout=None)
print(OGmap)
print("成功")
这样就能成功接受消息并且对数据进行处理发布。
python+ros在类中建立节点
秉着面向成双成对编程的思想,当然想把这个功能编成一个类来写,而且写成一个类更为方便,所以尝试将其放入类中。 在if __name__ == "__main__": 里面使用类,将执行类的__init__(self) 函数 比如:
class Test():
def __init__(self):
print("在__init__里面")
def _print(self):
print("使用_print")
if __name__ == '__main__':
a = Test()
会输出 ‘在__init__里面’ 如果想运行其他函数,只需要将其写进__init__(self) 函数里面
PS: 说一下if __name__ == "__main__": 的作用:
我们写的.py文件有的时候是直接运行,有的时候是当做一个功能,导入到其他的.py文件中。在if __name__ == "__main__": 里面的代码只有文件是直接运行的时候才会运行,如果是当做功能包引入到其他文件是不会运行的。
我们将上一节代码修改如下:
import rospy
from nav_msgs.msg import OccupancyGrid
class pathPlanning():
def __init__(self):
rospy.init_node("Astar_globel_path_planning",anonymous=True)
self.doMap()
def doMap(self):
'''
获取数据
将数据处理成一个矩阵(未知:-1,可通行:0,不可通行:1)
'''
self.OGmap = rospy.wait_for_message("/map",OccupancyGrid,timeout=None)
if __name__ == "__main__":
getmap = pathPlanning()
理解地图数据
先了解一下OccupancyGrid这个数据类型
std_msgs/Header header
uint32 seq
time stamp
string frame_id
nav_msgs/MapMetaData info
time map_load_time
float32 resolution
uint32 width
uint32 height
geometry_msgs/Pose origin
geometry_msgs/Point position
float64 x
float64 y
float64 z
geometry_msgs/Quaternion orientation
float64 x
float64 y
float64 z
float64 w
int8[] data
里面最重要的是int8[] data这个数据,放置了地图信息,可走区域的数值为0,障碍物数值为100,未知领域数值为-1,但是坑爹的是他是一个列表,不是矩阵,所以我们需要把他变成矩阵再处理数据。 OccupancyGrid将矩阵的宽高放在了上面,我们使用numpy进行变换:
def doMap(self):
'''
获取数据
将数据处理成一个矩阵(未知:-1,可通行:0,不可通行:1)
'''
self.OGmap = rospy.wait_for_message("/map",OccupancyGrid,timeout=None)
self.width = self.OGmap.info.width
self.height = self.OGmap.info.height
self.resolution = self.OGmap.info.resolution
mapdata = np.array(self.OGmap.data,dtype=np.int8)
self.map = mapdata.reshape((self.height,self.width))
self.map[self.map == 100] = 1
一般的算法是将0表示为可行区域,1表示为不可通行,所以末尾需要加一行变换,将矩阵内所有100变成1 然后我们看一下地图是什么样子的:
plt.matshow(self.map, cmap=plt.cm.gray)
plt.show()
结果如下: 但是我建立的地图在rviz中是这样的:
也就是正好倒过来了,所以将矩阵也翻一下才能变成我们想要的结果,优化doMap函数:
def doMap(self):
'''
获取数据
将数据处理成一个矩阵(未知:-1,可通行:0,不可通行:1)
'''
self.OGmap = rospy.wait_for_message("/map",OccupancyGrid,timeout=None)
self.width = self.OGmap.info.width
self.height = self.OGmap.info.height
self.resolution = self.OGmap.info.resolution
mapdata = np.array(self.OGmap.data,dtype=np.int8)
self.map = mapdata.reshape((self.height,self.width))
self.map[self.map == 100] = 1
self.map = self.map[:,::-1]
这样就变成我们想要的数据格式了。
地图膨胀
因为路径规划的时候我们都是把机器人当成一个质点,所以有必要进行地图的膨胀,防止其撞到周边。 制地图膨胀的思路如下:
- 首先找到矩阵中所有1的位置
- 遍历矩阵,如果当前位置和1的位置距离在设置的膨胀距离以内,那么就将其设置为1
在类中编写一个函数:
def obstacleMap(self,obsize):
indexList = np.where(self.map == 1)
for x in range(self.map.shape[0]):
for y in range(self.map.shape[1]):
if self.map[x][y] == 0:
for ox,oy in zip(indexList[0],indexList[1]):
distance = math.sqrt((x-ox)**2+(y-oy)**2)
if distance <= obsize:
self.map[x][y] = 1
使用numba加速
但是当我运行这段代码的时候发现,他竟然运行了快3分钟,多运行几次,我的暗影精灵就开始咆哮了,之前知道python比较慢,但是这也太慢了,在网上查资料,发现有个不错的加速循环的包叫numba 使用很简单:
from numba import jit
@jit(nopython=True)
def fun():
pass
这样就能对其加速,但是还有一个问题,我发现他不能在类中使用,只能单独对函数使用,所以无奈要把这个函数单独拿出来对其加速
@jit(nopython=True)
def _obstacleMap(map,obsize):
'''
给地图一个膨胀参数
'''
indexList = np.where(map == 1)
for x in range(map.shape[0]):
for y in range(map.shape[1]):
if map[x][y] == 0:
for ox,oy in zip(indexList[0],indexList[1]):
distance = math.sqrt((x-ox)**2+(y-oy)**2)
if distance <= obsize:
map[x][y] = 1
class pathPlanning():
def __init__(self):
rospy.init_node("Astar_globel_path_planning",anonymous=True)
self.doMap()
self.obsize=7
print("现在进行地图膨胀")
ob_time = time.time()
_obstacleMap(self.map,self.obsize)
print("膨胀地图所用时间是:{:.3f}".format(time.time()-ob_time))
从三分钟提速到了1.045s!!!!人间奇迹
PS: 关于ros没有numba包的问题
在一开始我也有这个问题,因为我不知道numba应该下载到哪里,直接使用
pip install numba
一个是网络问题,其次换了源导入的时候也是识别不到,所以应该查询ros使用的python包都在什么位置:
在vscode中有一个setting.json文件,那里给出了python包的地址:
/opt/ros/noetic/lib/python3/dist-packages
或者在终端中查看一下:
pip show rospy
也能查询到该地址
然后就可以从国内源下载numba到对应地址,在ros中使用numba了
sudo pip install --target=/opt/ros/noetic/lib/python3/dist-packages numba -i https://pypi.tuna.tsinghua.edu.cn/simple
|