Hybrid Astar 算法剖析和实现(二)

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的无效信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

0 前言

本篇承接上篇,主要对状态空间栅格作一个介绍,并讨论如何将理论落地为代码。

1 什么是状态空间栅格

状态空间栅格是对状态空间的离散化。

下面对状态空间和离散化分别作一个简要说明。

  1. 状态空间

状态空间就是以单位状态向量为基底的所有向量构成的抽象空间,可以类比笛卡尔坐标系。那什么又是状态向量呢?

如果将车辆看成一个质点,那么车辆的笛卡尔坐标(x,y)就可以看作是车辆的状态向量。此时,使用Astar算法就绰绰有余了,因为当车辆被看作一个质点时,不存在运动学约束(可以简单理解为转弯半径的约束)。

但真实的车辆一般被抽象为一个刚体,刚体有大小,也就有朝向,因此需要新增一个维度 θ \theta θ 来对该状态进行描述。那么车辆此时的状态向量就是 ( x , y , θ ) (x,y,\theta) (x,y,θ) 了。

  1. 离散化

离散化就是针对状态向量的每一个维度值进行切分,切分的间隔就是离散的分辨率 r e s o l u t i o n resolution resolution ,离散之后相当于将连续的状态向量 ( x , y , θ ) (x,y,\theta) (x,y,θ) 映射到了离散空间当中,对应离散空间中的离散状态向量 ( x i n d e x , y i n d e x , θ i n d e x ) (x_{index},y_{index},\theta_{index}) (xindex,yindex,θindex)

连续状态向量到离散状态向量的转换关系如下式:
{ x i n d e x = ⌊ x − x m i n r e s o l u t i o n x ⌋ y i n d e x = ⌊ y − y m i n r e s o l u t i o n y ⌋ θ i n d e x = ⌊ θ − θ m i n r e s o l u t i o n θ ⌋ (1) \begin{cases} x_{index} = \lfloor{\frac{x-x_{min}}{resolution_x}}\rfloor\\[3ex] y_{index} = \lfloor{\frac{y-y_{min}}{resolution_y}}\rfloor\\[3ex] \theta_{index} = \lfloor{\frac{\theta-\theta_{min}}{resolution_\theta}}\rfloor \end{cases}\tag1 xindex=resolutionxxxminyindex=resolutionyyyminθindex=resolutionθθθmin(1)
离散状态向量到连续状态向量的转换关系如下式:
{ x = x m i n + x i n d e x × r e s o l u t i o n x y = y m i n + y i n d e x × r e s o l u t i o n y θ = θ m i n + θ i n d e x × r e s o l u t i o n θ (2) \begin{cases} x = x_{min} + {x_{index} \times resolution_x}\\[3ex] y = y_{min} + {y_{index} \times resolution_y}\\[3ex] \theta = \theta_{min} + {\theta_{index} \times resolution_\theta} \end{cases}\tag2 x=xmin+xindex×resolutionxy=ymin+yindex×resolutionyθ=θmin+θindex×resolutionθ(2)

2 状态空间栅格的作用

Astar算法使用状态空间栅格对状态空间进行离散化,而后依据状态空间栅格进行 探索 。这里的探索就是子节点的拓展,所以,Astar算法使用状态空间栅格的本质就是为了离散迭代——一步步从局部最优解逼近全局最优解。

Hybrid Astar也是借助状态空间栅格进行子节点的拓展的,但仅仅是借助。

Hybrid Astar子节点拓展最根本的驱动力来自于对车辆控制的采样或者说是对输入的采样。将对输入的采样进行前向模拟(基于车辆运动学方程,后面的文章会详细介绍),此时将车辆的状态映射到状态空间栅格中,并把落入的栅格状态作为拓展之后的节点状态。再根据该状态进行后续的扩展。

有人可能会有一个疑问:为什么要将前向模拟出来的状态映射到状态空间栅格后才作为拓展后的子节点状态呢?直接用前向模拟之后的连续状态作为子节点状态不可以么?个人认为是可以的。

所以,Hybrid Astar算法使用状态空间栅格的作用仅仅是限制了连续状态向量在状态空间的落点;目前,我个人认为在Hybrid Astar算法中可以不使用状态空间栅格。

3 与地图栅格的区别和联系

占据栅格地图中一般使用地图栅格来表示障碍物。和状态空间栅格没有直接关系。

地图栅格在碰撞检测中才会使用,这里暂时不做介绍。

4 代码实现

代码实现参考公式(1)和公式(2)实现。

4.1 连续状态转离散状态

#include <Eigen/Core>

Eigen::Vector3i HybridAStar::State2Index(const Eigen::Vector3d &state) const {
    Eigen::Vector3i index;

	index[0] = (state[0] - x_min_) / STATE_GRID_RESOLUTION;
    index[1] = (state[1] - y_min_) / STATE_GRID_RESOLUTION;
    index[2] = ((state[2] - (-M_PI)) / ANGULAR_RESOLUTION;
    return index;
}

4.2 离散状态转连续状态

#include <Eigen/Core>

Eigen::Vector3i HybridAStar::Index2State(const Eigen::Vector3i &index) const {
    Eigen::Vector3d state;

	state[0] = index[0] * STATE_GRID_RESOLUTION + x_min_;
    state[1] = index[1] * STATE_GRID_RESOLUTION + y_min_;
    state[2] = index[2] * ANGULAR_RESOLUTION + (-M_PI);
    state[2] = NormalizeAngle(state[2]); //对角度做“归一化”
    return index;
}

Tips:代码中NormalizeAngle的实现请参考角度归一化实现_穿越临界点的博客-CSDN博客

5 状态空间栅格的精度该如何选取

有一个很重要的点就是如何对状态空间栅格的精度进行选取。

该精度的选取需要参照下述原则:

  1. 一个状态栅格中保证只有一个状态节点落入。
  2. 状态栅格分辨率一般要高于对控制采样的精度。
  3. 如果使用状态栅格做碰撞检测则状态栅格的分辨率要不低于地图栅格分辨率(强烈建议不要混用这两种栅格,也就避免了考虑本条原则)。

6 总结

本篇介绍了状态空间栅格,以及它的作用。为后续Hybrid Astar算法的剖析和实现打下了空间基础,不然后面遇到各种坐标和空间变换很容易迷失方向。

下一篇我们一起探索Hybrid Astar的核心逻辑——迭代搜索。

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
AStar算法是一种常用的路径规划算法,其主要思路是在图或网格中找到一条最短路径。 具体实现思路如下: 1. 初始化起点和终点,并将起点加入open列表中。 2. 从open列表中选取f值最小的节点作为当前节点,并将其加入close列表中。 3. 根据当前节点,找到其相邻的节点,并计算它们的f值(f = g + h),其中g表示从起点到当前节点的距离,h表示从当前节点到终点的估算距离。 4. 对于每个相邻节点,如果它已经在close列表中,或者障碍物阻挡了它,或者它已经在open列表中但是新的路径比原路径更长,则忽略它。 5. 否则,将当前节点作为该相邻节点的父节点,更新该相邻节点的g值和f值,并将其加入open列表中。 6. 重复执行步骤2-5,直到终点被加入close列表中或者open列表为空。 7. 如果终点被加入close列表中,则说明找到了一条最短路径,根据每个节点的父节点可以回溯得到该路径。 以下是Python的示例代码: ```python import heapq def astar(start, end, obstacles, grid_size): """ A*算法实现 :param start: 起点坐标 (x, y) :param end: 终点坐标 (x, y) :param obstacles: 障碍物坐标列表 [(x1, y1), (x2, y2), ...] :param grid_size: 网格大小 (width, height) :return: 最短路径坐标列表 [(x1, y1), (x2, y2), ...] """ # 计算启发式距离 def heuristic(a, b): return abs(b[0] - a[0]) + abs(b[1] - a[1]) # 判断坐标是否在网格内 def in_grid(x, y): return 0 <= x < grid_size[0] and 0 <= y < grid_size[1] # 判断坐标是否为障碍物 def is_obstacle(x, y): return (x, y) in obstacles # 获取周围相邻的坐标 def get_neighbors(x, y): neighbors = [(x+1, y), (x, y+1), (x-1, y), (x, y-1)] return filter(lambda p: in_grid(*p) and not is_obstacle(*p), neighbors) # 初始化起点和终点 start_node = (0, start, None) end_node = (heuristic(start, end), end, None) # 初始化open列表和close列表 open_list = [start_node] close_list = set() # 循环直到找到终点或open列表为空 while open_list: # 选取f值最小的节点作为当前节点 current_node = heapq.heappop(open_list) if current_node[1] == end: # 找到终点,回溯得到路径 path = [] while current_node: path.append(current_node[1]) current_node = current_node[2] return path[::-1] # 将当前节点加入close列表中 close_list.add(current_node[1]) # 处理相邻节点 for neighbor in get_neighbors(*current_node[1]): # 如果相邻节点已经在close列表中,忽略它 if neighbor in close_list: continue # 计算相邻节点的f值 g = current_node[0] + 1 h = heuristic(neighbor, end) f = g + h # 如果相邻节点已经在open列表中,更新它的f值和父节点 for node in open_list: if node[1] == neighbor: if f < node[0]: open_list.remove(node) node = (f, neighbor, current_node) heapq.heappush(open_list, node) break else: # 否则,将相邻节点加入open列表中 node = (f, neighbor, current_node) heapq.heappush(open_list, node) # open列表为空,无法找到路径 return [] ``` 通过调用`astar`函数,传入起点、终点、障碍物和网格大小,即可得到最短路径坐标列表。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值