Dijkstra算法理解-无人机路径规划

news/2024/7/7 16:02:50/文章来源:https://www.cnblogs.com/Zhouce/p/18283354

1、理解

Dijkstra算法是路径规划算法中非常经典的一种算法,在很多地方都会用到,特别是在机器人的路径规划中,基本学习机器人运动相关的都会接触到该算法。

Dijkstra算法本身的原理是基于贪心思想实现的,首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的,所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,

如果更近了就更新距离,这样把所有的点找遍之后就存下了起点到其他所有点的最短距离。

2、代码

  1 import math
  2 import matplotlib.pyplot as plt
  3 
  4 min_set = 10
  5 show_animation = True         # 绘图
  6 
  7 # 创建一个类
  8 
  9 class Dijkstra:
 10     # 初始化
 11     def __init__(self, ox, oy, resolution, robot_radius, resource_points):
 12         # 属性分配  设置默认值或初始值
 13         self.min_x = None      # 地图的最小x坐标
 14         self.min_y = None      # 地图的最小y坐标
 15         self.max_x = None      # 地图的最大x坐标
 16         self.max_y = None      # 地图的最大y坐标
 17         self.x_width = None    # 地图的x方向网络宽度
 18         self.y_width = None    # 地图的y方向网格宽度
 19         self.obstacle_map = None     # 障碍物地图
 20 
 21         # 传递并存储参数
 22         self.resolution = resolution             # 网格大小(m)
 23         self.robot_radius = robot_radius         # 机器人半径
 24         self.resource_points = resource_points   # 资源点
 25 
 26         # 调用初始化方法
 27         self.calc_obstacle_map(ox, oy)           # 计算障碍物地图
 28         self.motion = self.get_motion_model()    # 机器人运动方式
 29 
 30     # Node 类定义了路径规划中的一个节点。每个节点包含坐标(x 和 y)、路径成本(cost)以及父节点索引(parent_index)。
 31     # 构建节点,每个网格代表一个节点
 32     class Node:
 33         # 初始化:通过 __init__ 方法初始化节点,确保每个节点都有定义的属性值。
 34         def __init__(self, x, y, cost, parent_index):
 35             self.x = x  # 网格索引
 36             self.y = y
 37             self.cost = cost  # 路径值
 38             self.parent_index = parent_index  # 该网格的父节点
 39 
 40         # 字符串表示:通过 __str__ 方法定义了节点的字符串表示形式,方便调试和日志记录
 41         def __str__(self):
 42             return str(self.x) + ',' + str(self.y) + ',' + str(self.cost) + ',' + str(self.parent_index)
 43 
 44     # 寻找最优路径,网格起始坐标(sx,sy),终点坐标(gx,gy)
 45     def planning(self, sx, sy, gx, gy):                    # planning方法接受起点坐标和终点坐标
 46         # 节点初始化
 47         # 将已知的起点和终点坐标形式转化为节点类型,0代表路径权重,-1代表无父节点
 48         # calc_xy_index方法将实际坐标转换为网格索引,Node的cost初始化为0.0,parent_index初始化为-1,表示起点没有父节点。Node的cost初始化为0.0,parent_index初始化为-1,表示起点没有父节点。
 49         start_node = self.Node(self.calc_xy_index(sx, self.min_x),
 50                                self.calc_xy_index(sy, self.min_y), 0.0, -1)
 51         # 终点
 52         goal_node = self.Node(self.calc_xy_index(gx, self.min_x),
 53                               self.calc_xy_index(gy, self.min_y), 0.0, -1)
 54 
 55         # 保存入库节点和待计算节点 使用dict()创建了两个空字典open_set和closed_set。
 56         open_set, closed_set = dict(), dict()  # 初始化两个集合:open_set用于保存待处理的节点,closed_set用于保存已处理的节点。
 57         # 先将起点入库,获取每个网格对应的key
 58         # 将起点节点加入到open_set中。self.calc_index(start_node)计算了起点节点的唯一索引值,并将其作为键,将起点节点对象作为值存储在open_set中。
 59         open_set[self.calc_index(start_node)] = start_node
 60 
 61         # 循环
 62         while 1:
 63             # 选择扩展点,添加了启发项,f(n)= g(n) + h(n) 计算了节点的总代价(f值),即实际代价和启发式代价的和。
 64             c_id = min(open_set,
 65                        key=lambda o: open_set[o].cost + \
 66                                      self.calc_heuristic(self,goal_node, open_set[o]))
 67             # 选择了 open_set 中代价最小的节点作为当前处理的节点。
 68             # 具体来说,这段代码选择了当前待处理节点中具有最小估计总代价(f值)的节点。f值是由当前节点的实际代价g值和启发式代价h值之和得到的。
 69             # key=lambda o: ...:这是一个匿名函数(lambda函数),用于计算每个节点的代价。
 70             # min()函数将使用这个函数来比较open_set中的每个节点。
 71             #  min(open_set, key=...):
 72             #      这部分代码使用min函数在open_set中查找具有最小代价的节点。min函数的key参数指定了用于比较的准则。
 73             #  o是open_set的一个键(节点的唯一索引)
 74             #  open_set[o]是对应于键o的节点对象。
 75             #  open_set[o].cost是节点的实际代价(从起点到该节点的代价,g值)。
 76             # self.calc_heuristic(goal_node, open_set[o])是节点的启发式代价(从该节点到终点的估计代价,h值)。
 77 
 78             current = open_set[c_id]  # 从字典中取出该节点
 79 
 80             # 绘图  如果启用了动画,绘制当前节点的位置。
 81             if show_animation:
 82                 # 网格索引转换为真实坐标
 83                 # self.calc_position(current.y, self.min_y)方法将网格索引转换为实际坐标(单位:米)
 84                 plt.plot(self.calc_position(current.x, self.min_x),
 85                          self.calc_position(current.y, self.min_y), 'xc')
 86                 plt.pause(0.0001)   # plt.pause函数可以刷新绘图窗口并暂时停止代码执行
 87 
 88             # 判断是否是终点,如果选出来的损失最小的点是终点
 89             if current.x == goal_node.x and current.y == goal_node.y:
 90                 # 更新终点的父节点
 91                 goal_node.cost = current.cost
 92                 # 更新终点的损失
 93                 goal_node.parent_index = current.parent_index
 94                 break
 95 
 96             # 在外库中删除该最小损失点,把它入库
 97             del open_set[c_id]                  # 这行代码将当前节点从开放集中删除。
 98             closed_set[c_id] = current          # 这行代码将当前节点添加到闭合集中。
 99             # 避免重复处理
100             # 记录处理过的节点
101             # 确保算法终止:通过不断减少开放集中的节点数量,最终开放集会为空,算法会终止
102 
103             # 遍历邻接节点
104             for move_x, move_y, move_cost in self.motion:
105                 # self.motion:是一个列表,包含机器人可能的移动方式。
106                 # 每个元素都是一个三元组,表示移动的方向(move_x, move_y)和移动的代价(move_cost)。
107 
108                 # 获取每个邻接节点的节点坐标,到起点的距离,父节点
109                 node = self.Node(current.x + move_x,
110                                  current.y + move_y,
111                                  current.cost + move_cost, c_id)
112 
113                 #  current.x + move_x, current.y + move_y 计算邻接节点的坐标
114                 #  current.cost + move_cost:计算从起点到邻接节点的代价
115                 #  c_id:当前节点的索引,作为邻接节点的父节点索引
116 
117                 # 获取该邻居节点的key
118                 n_id = self.calc_index(node)
119                 # 计算邻接节点的唯一索引,用于在开放集和闭合集中进行查找
120 
121                 # 如果该节点入库了,就看下一个
122                 # 如果邻接节点已经在闭合集中,跳过这个节点(因为它已经被处理过)。
123                 if n_id in closed_set:
124                     continue
125 
126                 # 邻居节点是否超出范围了,是否在障碍物上
127                 if not self.verify_node(node):
128                     continue
129 
130                 # 如果该节点不在外库中,就作为一个新节点加入到外库
131                 if n_id not in open_set:
132                     open_set[n_id] = node
133                 # 节点在外库中时
134                 else:
135                     # 如果该点到起点的距离,要小于外库中该点的距离,就更新外库中的该点信息,更改路径
136                     # 如果邻接节点已经在开放集中,检查新的路径代价是否小于当前路径代价。
137                     # 如果新的路径代价较小,更新开放集中节点的信息,表示找到了更优的路径。
138                     if node.cost <= open_set[n_id].cost:
139                         open_set[n_id] = node
140 
141         # 找到终点
142         rx, ry = self.calc_final_path(goal_node, closed_set)
143         return rx, ry
144 
145     # ------------------------------ #
146     # A* 的启发函数
147     # ------------------------------ #
148     @staticmethod
149     def calc_heuristic(self,n1, n2):  # n1终点,n2当前网格
150         w = 1.0  # 单个启发函数的权重,如果有多个启发函数,权重可以设置的不一样
151         d = w * math.hypot(n1.x - n2.x, n1.y - n2.y)  # 当前网格和终点距离
152         resource_value = 0
153         for rx, ry in self.resource_points:
154             if n2.x == self.calc_xy_index(rx, self.min_x) and n2.y == self.calc_xy_index(ry, self.min_y):
155                 resource_value += 1
156         return d - resource_value
157 
158         # return d
159     # 这一行计算两个点 n1 和 n2 之间的欧几里得距离,并乘以权重 w。
160     # math.hypot 函数计算两个点在平面上的直线距离,公式为 sqrt((n1.x - n2.x)^2 + (n1.y - n2.y)^2)。
161 
162     # 机器人行走的方式,每次能向周围移动8个网格移动
163     @staticmethod
164     def get_motion_model():
165         # [dx, dy, cost]
166         motion = [[1, 0, 1],  # 右
167                   [0, 1, 1],  # 上
168                   [-1, 0, 1],  # 左
169                   [0, -1, 1],  # 下
170                   [-1, -1, math.sqrt(2)],  # 左下
171                   [-1, 1, math.sqrt(2)],  # 左上
172                   [1, -1, math.sqrt(2)],  # 右下
173                   [1, 1, math.sqrt(2)]]  # 右上
174         return motion
175 
176     # 绘制栅格地图
177     def calc_obstacle_map(self, ox, oy):
178         # 地图边界坐标
179         self.min_x = round(min(ox))  # 四舍五入取整
180         self.min_y = round(min(oy))
181         self.max_x = round(max(ox))
182         self.max_y = round(max(oy))
183         # 地图的x和y方向的栅格个数,长度/每个网格的长度=网格个数
184         self.x_width = round((self.max_x - self.min_x) / self.resolution)  # x方向网格个数
185         self.y_width = round((self.max_y - self.min_y) / self.resolution)  # y方向网格个数
186         # 初始化地图,二维列表,每个网格的值为False
187         self.obstacle_map = [[False for _ in range(self.y_width)]
188                              for _ in range(self.x_width)]
189         # 这一行代码初始化地图为一个二维列表,地图中每个网格的初始值为 False,表示没有障碍物
190         # [[False for _ in range(self.y_width)] for _ in range(self.x_width)] 是一个嵌套的列表推导式,用于生成二维列表。
191         # 外层列表推导式 for _ in range(self.x_width) 迭代 self.x_width 次,每次生成一行(即一个内层列表)。
192         # 内层列表推导式 for _ in range(self.y_width) 迭代 self.y_width 次,每次生成一个 False 值。
193         # 变量 _ 通常用于表示一个不需要使用的循环变量。在这种情况下,我们只关心生成的列表长度,而不关心循环变量的具体值。
194 
195         # 设置障碍物
196         for ix in range(self.x_width):  # 遍历x方向的网格 [0:x_width]
197             x = self.calc_position(ix, self.min_x)  # 根据网格索引计算x坐标位置
198             # 调用 self.calc_position 方法,根据当前网格索引 ix 和最小x值 self.min_x 计算出该网格在地图上的实际x坐标位置。
199             for iy in range(self.y_width):  # 遍历y方向的网格 [0:y_width]
200                 y = self.calc_position(iy, self.min_y)  # 根据网格索引计算y坐标位置
201                 # 调用 self.calc_position 方法,根据当前网格索引 iy 和最小y值 self.min_y 计算出该网格在地图上的实际y坐标位置。
202                 # 遍历障碍物坐标(实际坐标)
203                 for iox, ioy in zip(ox, oy):
204                     # 计算障碍物和网格点之间的距离
205                     d = math.hypot(iox - x, ioy - y)
206 
207                     # 膨胀障碍物,如果障碍物和网格之间的距离小,机器人无法通行,对障碍物膨胀
208                     if d <= self.robot_radius + self.resolution:  # 膨胀范围增加
209                         # 将障碍物所在网格设置为True
210                         self.obstacle_map[ix][iy] = True
211                         break  # 每个障碍物膨胀一次就足够了
212 
213     # 根据网格编号计算实际坐标
214     def calc_position(self, index, minp):
215         # minp代表起点坐标,左下x或左下y
216         pos = minp + index * self.resolution  # 网格点左下左下坐标
217         return pos
218 
219     # 位置坐标转为网格坐标
220     def calc_xy_index(self, position, minp):
221         # (目标位置坐标-起点坐标)/一个网格的长度==>目标位置的网格索引
222         return round((position - minp) / self.resolution)
223 
224     # 给每个网格编号,得到每个网格的key
225     def calc_index(self, node):
226         # 从左到右增大,从下到上增大
227         return node.y * self.x_width + node.x
228 
229     # 邻居节点是否超出范围
230     def verify_node(self, node):
231         # 根据网格坐标计算实际坐标
232         px = self.calc_position(node.x, self.min_x)
233         py = self.calc_position(node.y, self.min_y)
234         # 判断是否超出边界
235         if px < self.min_x:
236             return False
237         if py < self.min_y:
238             return False
239         if px >= self.max_x:
240             return False
241         if py >= self.max_y:
242             return False
243         # 节点是否在障碍物上,障碍物标记为True
244         if self.obstacle_map[node.x][node.y]:
245             return False
246         # 没超过就返回True
247         return True
248 
249     # 计算路径, parent属性记录每个节点的父节点
250     def calc_final_path(self, goal_node, closed_set):
251         # 先存放终点坐标(真实坐标)
252         rx = [self.calc_position(goal_node.x, self.min_x)]
253         ry = [self.calc_position(goal_node.y, self.min_y)]
254         # 获取终点的父节点索引
255         parent_index = goal_node.parent_index
256         # 起点的父节点==-1
257         while parent_index != -1:
258             n = closed_set[parent_index]  # 在入库中选择父节点
259             rx.append(self.calc_position(n.x, self.min_x))  # 节点的x坐标
260             ry.append(self.calc_position(n.y, self.min_y))  # 节点的y坐标
261             parent_index = n.parent_index  # 节点的父节点索引
262 
263         return rx, ry
264 
265 def draw_circle(cx, cy, radius):
266     ox, oy = [], []
267     for angle in range(0, 360, 5):  # 每隔5度取一个点
268         rad = math.radians(angle)
269         x = cx + radius * math.cos(rad)
270         y = cy + radius * math.sin(rad)
271         ox.append(x)
272         oy.append(y)
273     return ox, oy
274 
275 def main():
276     # 设置起点和终点
277     # sx = 10
278     sx = -5.0
279     sy = -5.0
280     gx = 50.0
281     gy = 39.0
282     # 网格大小
283     grid_size = 2.0
284     # 机器人半径
285     robot_radius = 1.0
286 
287     resource_points = [(-5,10),(10,30),(10,40)]
288 
289     # 设置障碍物位置
290     # 边界
291     ox, oy = [], []
292     for i in range(-10, 60):
293         ox.append(i)
294         oy.append(-10.0)  # 下边界
295     for i in range(-10, 60):
296         ox.append(60.0)
297         oy.append(i)  # 右边界
298     for i in range(-10, 61):
299         ox.append(i)
300         oy.append(60.0)  # 上边界
301     for i in range(-10, 61):
302         ox.append(-10.0)
303         oy.append(i)  # 左边界
304 
305     # 添加圆形障碍物
306     cx, cy, radius = 30, 30, 10
307     circle_ox, circle_oy = draw_circle(cx, cy, radius)
308     ox.extend(circle_ox)
309     oy.extend(circle_oy)
310 
311     cx1, cy1, radius1 = 10, 10, 5
312     circle_ox1, circle_oy1 = draw_circle(cx1, cy1, radius1)
313     ox.extend(circle_ox1)
314     oy.extend(circle_oy1)
315 
316     # 添加围栏
317     for i in range(-10, 15):
318         ox.append(20.0)
319         oy.append(i)  # 左围栏
320 
321     # 绘图
322     if show_animation:
323         plt.plot(ox, oy, '.k')  # 障碍物黑色
324         plt.plot(sx, sy, 'og')  # 起点绿色
325         plt.plot(gx, gy, 'xb')  # 终点蓝色
326         plt.grid(True)
327         plt.axis('equal')  # 坐标轴刻度间距等长
328         for rx, ry in resource_points:
329             plt.plot(rx, ry, '*r')
330 
331     # 实例化,传入障碍物,网格大小
332     dijkstra = Dijkstra(ox, oy, grid_size, robot_radius, resource_points)
333     # 求解路径,返回路径的 x 坐标和 y 坐标列表
334     rx, ry = dijkstra.planning(sx, sy, gx, gy)
335 
336     # 绘制路径经过的网格
337     if show_animation:
338         plt.plot(rx, ry, '-r')
339         plt.pause(0.01)
340         plt.show()
341 
342 if __name__ == '__main__':
343     main()

         (2)增加一个途经点,分段规划

  1 import math
  2 import matplotlib.pyplot as plt
  3 
  4 min_set = 10
  5 show_animation = True         # 绘图
  6 
  7 # 创建一个类
  8 
  9 class Dijkstra:
 10     # 初始化
 11     def __init__(self, ox, oy, resolution, robot_radius, resource_points):
 12         # 属性分配  设置默认值或初始值
 13         self.min_x = None      # 地图的最小x坐标
 14         self.min_y = None      # 地图的最小y坐标
 15         self.max_x = None      # 地图的最大x坐标
 16         self.max_y = None      # 地图的最大y坐标
 17         self.x_width = None    # 地图的x方向网络宽度
 18         self.y_width = None    # 地图的y方向网格宽度
 19         self.obstacle_map = None     # 障碍物地图
 20 
 21         # 传递并存储参数
 22         self.resolution = resolution             # 网格大小(m)
 23         self.robot_radius = robot_radius         # 机器人半径
 24         self.resource_points = resource_points   # 资源点
 25 
 26         # 调用初始化方法
 27         self.calc_obstacle_map(ox, oy)           # 计算障碍物地图
 28         self.motion = self.get_motion_model()    # 机器人运动方式
 29 
 30     # Node 类定义了路径规划中的一个节点。每个节点包含坐标(x 和 y)、路径成本(cost)以及父节点索引(parent_index)。
 31     # 构建节点,每个网格代表一个节点
 32     class Node:
 33         # 初始化:通过 __init__ 方法初始化节点,确保每个节点都有定义的属性值。
 34         def __init__(self, x, y, cost, parent_index):
 35             self.x = x  # 网格索引
 36             self.y = y
 37             self.cost = cost  # 路径值
 38             self.parent_index = parent_index  # 该网格的父节点
 39 
 40         # 字符串表示:通过 __str__ 方法定义了节点的字符串表示形式,方便调试和日志记录
 41         def __str__(self):
 42             return str(self.x) + ',' + str(self.y) + ',' + str(self.cost) + ',' + str(self.parent_index)
 43 
 44     # 寻找最优路径,网格起始坐标(sx,sy),终点坐标(gx,gy)
 45     def planning(self, sx, sy, gx, gy):                    # planning方法接受起点坐标和终点坐标
 46         # 节点初始化
 47         # 将已知的起点和终点坐标形式转化为节点类型,0代表路径权重,-1代表无父节点
 48         # calc_xy_index方法将实际坐标转换为网格索引,Node的cost初始化为0.0,parent_index初始化为-1,表示起点没有父节点。Node的cost初始化为0.0,parent_index初始化为-1,表示起点没有父节点。
 49         start_node = self.Node(self.calc_xy_index(sx, self.min_x),
 50                                self.calc_xy_index(sy, self.min_y), 0.0, -1)
 51         # 终点
 52         goal_node = self.Node(self.calc_xy_index(gx, self.min_x),
 53                               self.calc_xy_index(gy, self.min_y), 0.0, -1)
 54 
 55         # 保存入库节点和待计算节点 使用dict()创建了两个空字典open_set和closed_set。
 56         open_set, closed_set = dict(), dict()  # 初始化两个集合:open_set用于保存待处理的节点,closed_set用于保存已处理的节点。
 57         # 先将起点入库,获取每个网格对应的key
 58         # 将起点节点加入到open_set中。self.calc_index(start_node)计算了起点节点的唯一索引值,并将其作为键,将起点节点对象作为值存储在open_set中。
 59         open_set[self.calc_index(start_node)] = start_node
 60 
 61         # 循环
 62         while 1:
 63             # 选择扩展点,添加了启发项,f(n)= g(n) + h(n) 计算了节点的总代价(f值),即实际代价和启发式代价的和。
 64             c_id = min(open_set,
 65                        key=lambda o: open_set[o].cost + \
 66                                      self.calc_heuristic(self,goal_node, open_set[o]))
 67             # 选择了 open_set 中代价最小的节点作为当前处理的节点。
 68             # 具体来说,这段代码选择了当前待处理节点中具有最小估计总代价(f值)的节点。f值是由当前节点的实际代价g值和启发式代价h值之和得到的。
 69             # key=lambda o: ...:这是一个匿名函数(lambda函数),用于计算每个节点的代价。
 70             # min()函数将使用这个函数来比较open_set中的每个节点。
 71             #  min(open_set, key=...):
 72             #      这部分代码使用min函数在open_set中查找具有最小代价的节点。min函数的key参数指定了用于比较的准则。
 73             #  o是open_set的一个键(节点的唯一索引)
 74             #  open_set[o]是对应于键o的节点对象。
 75             #  open_set[o].cost是节点的实际代价(从起点到该节点的代价,g值)。
 76             # self.calc_heuristic(goal_node, open_set[o])是节点的启发式代价(从该节点到终点的估计代价,h值)。
 77 
 78             current = open_set[c_id]  # 从字典中取出该节点
 79 
 80             # 绘图  如果启用了动画,绘制当前节点的位置。
 81             if show_animation:
 82                 # 网格索引转换为真实坐标
 83                 # self.calc_position(current.y, self.min_y)方法将网格索引转换为实际坐标(单位:米)
 84                 plt.plot(self.calc_position(current.x, self.min_x),
 85                          self.calc_position(current.y, self.min_y), 'xc')
 86                 plt.pause(0.0001)   # plt.pause函数可以刷新绘图窗口并暂时停止代码执行
 87 
 88             # 判断是否是终点,如果选出来的损失最小的点是终点
 89             if current.x == goal_node.x and current.y == goal_node.y:
 90                 # 更新终点的父节点
 91                 goal_node.cost = current.cost
 92                 # 更新终点的损失
 93                 goal_node.parent_index = current.parent_index
 94                 break
 95 
 96             # 在外库中删除该最小损失点,把它入库
 97             del open_set[c_id]                  # 这行代码将当前节点从开放集中删除。
 98             closed_set[c_id] = current          # 这行代码将当前节点添加到闭合集中。
 99             # 避免重复处理
100             # 记录处理过的节点
101             # 确保算法终止:通过不断减少开放集中的节点数量,最终开放集会为空,算法会终止
102 
103             # 遍历邻接节点
104             for move_x, move_y, move_cost in self.motion:
105                 # self.motion:是一个列表,包含机器人可能的移动方式。
106                 # 每个元素都是一个三元组,表示移动的方向(move_x, move_y)和移动的代价(move_cost)。
107 
108                 # 获取每个邻接节点的节点坐标,到起点的距离,父节点
109                 node = self.Node(current.x + move_x,
110                                  current.y + move_y,
111                                  current.cost + move_cost, c_id)
112 
113                 #  current.x + move_x, current.y + move_y 计算邻接节点的坐标
114                 #  current.cost + move_cost:计算从起点到邻接节点的代价
115                 #  c_id:当前节点的索引,作为邻接节点的父节点索引
116 
117                 # 获取该邻居节点的key
118                 n_id = self.calc_index(node)
119                 # 计算邻接节点的唯一索引,用于在开放集和闭合集中进行查找
120 
121                 # 如果该节点入库了,就看下一个
122                 # 如果邻接节点已经在闭合集中,跳过这个节点(因为它已经被处理过)。
123                 if n_id in closed_set:
124                     continue
125 
126                 # 邻居节点是否超出范围了,是否在障碍物上
127                 if not self.verify_node(node):
128                     continue
129 
130                 # 如果该节点不在外库中,就作为一个新节点加入到外库
131                 if n_id not in open_set:
132                     open_set[n_id] = node
133                 # 节点在外库中时
134                 else:
135                     # 如果该点到起点的距离,要小于外库中该点的距离,就更新外库中的该点信息,更改路径
136                     # 如果邻接节点已经在开放集中,检查新的路径代价是否小于当前路径代价。
137                     # 如果新的路径代价较小,更新开放集中节点的信息,表示找到了更优的路径。
138                     if node.cost <= open_set[n_id].cost:
139                         open_set[n_id] = node
140 
141         # 找到终点
142         rx, ry = self.calc_final_path(goal_node, closed_set)
143         return rx, ry
144 
145     # ------------------------------ #
146     # A* 的启发函数
147     # ------------------------------ #
148     @staticmethod
149     def calc_heuristic(self,n1, n2):  # n1终点,n2当前网格
150         w = 1.0  # 单个启发函数的权重,如果有多个启发函数,权重可以设置的不一样
151         d = w * math.hypot(n1.x - n2.x, n1.y - n2.y)  # 当前网格和终点距离
152         resource_value = 0
153         for rx, ry in self.resource_points:
154             if n2.x == self.calc_xy_index(rx, self.min_x) and n2.y == self.calc_xy_index(ry, self.min_y):
155                 resource_value += 1
156         return d - resource_value
157 
158         # return d
159     # 这一行计算两个点 n1 和 n2 之间的欧几里得距离,并乘以权重 w。
160     # math.hypot 函数计算两个点在平面上的直线距离,公式为 sqrt((n1.x - n2.x)^2 + (n1.y - n2.y)^2)。
161 
162     # 机器人行走的方式,每次能向周围移动8个网格移动
163     @staticmethod
164     def get_motion_model():
165         # [dx, dy, cost]
166         motion = [[1, 0, 1],  # 右
167                   [0, 1, 1],  # 上
168                   [-1, 0, 1],  # 左
169                   [0, -1, 1],  # 下
170                   [-1, -1, math.sqrt(2)],  # 左下
171                   [-1, 1, math.sqrt(2)],  # 左上
172                   [1, -1, math.sqrt(2)],  # 右下
173                   [1, 1, math.sqrt(2)]]  # 右上
174         return motion
175 
176     # 绘制栅格地图
177     def calc_obstacle_map(self, ox, oy):
178         # 地图边界坐标
179         self.min_x = round(min(ox))  # 四舍五入取整
180         self.min_y = round(min(oy))
181         self.max_x = round(max(ox))
182         self.max_y = round(max(oy))
183         # 地图的x和y方向的栅格个数,长度/每个网格的长度=网格个数
184         self.x_width = round((self.max_x - self.min_x) / self.resolution)  # x方向网格个数
185         self.y_width = round((self.max_y - self.min_y) / self.resolution)  # y方向网格个数
186         # 初始化地图,二维列表,每个网格的值为False
187         self.obstacle_map = [[False for _ in range(self.y_width)]
188                              for _ in range(self.x_width)]
189         # 这一行代码初始化地图为一个二维列表,地图中每个网格的初始值为 False,表示没有障碍物
190         # [[False for _ in range(self.y_width)] for _ in range(self.x_width)] 是一个嵌套的列表推导式,用于生成二维列表。
191         # 外层列表推导式 for _ in range(self.x_width) 迭代 self.x_width 次,每次生成一行(即一个内层列表)。
192         # 内层列表推导式 for _ in range(self.y_width) 迭代 self.y_width 次,每次生成一个 False 值。
193         # 变量 _ 通常用于表示一个不需要使用的循环变量。在这种情况下,我们只关心生成的列表长度,而不关心循环变量的具体值。
194 
195         # 设置障碍物
196         for ix in range(self.x_width):  # 遍历x方向的网格 [0:x_width]
197             x = self.calc_position(ix, self.min_x)  # 根据网格索引计算x坐标位置
198             # 调用 self.calc_position 方法,根据当前网格索引 ix 和最小x值 self.min_x 计算出该网格在地图上的实际x坐标位置。
199             for iy in range(self.y_width):  # 遍历y方向的网格 [0:y_width]
200                 y = self.calc_position(iy, self.min_y)  # 根据网格索引计算y坐标位置
201                 # 调用 self.calc_position 方法,根据当前网格索引 iy 和最小y值 self.min_y 计算出该网格在地图上的实际y坐标位置。
202                 # 遍历障碍物坐标(实际坐标)
203                 for iox, ioy in zip(ox, oy):
204                     # 计算障碍物和网格点之间的距离
205                     d = math.hypot(iox - x, ioy - y)
206 
207                     # 膨胀障碍物,如果障碍物和网格之间的距离小,机器人无法通行,对障碍物膨胀
208                     if d <= self.robot_radius + self.resolution:  # 膨胀范围增加
209                         # 将障碍物所在网格设置为True
210                         self.obstacle_map[ix][iy] = True
211                         break  # 每个障碍物膨胀一次就足够了
212 
213     # 根据网格编号计算实际坐标
214     def calc_position(self, index, minp):
215         # minp代表起点坐标,左下x或左下y
216         pos = minp + index * self.resolution  # 网格点左下左下坐标
217         return pos
218 
219     # 位置坐标转为网格坐标
220     def calc_xy_index(self, position, minp):
221         # (目标位置坐标-起点坐标)/一个网格的长度==>目标位置的网格索引
222         return round((position - minp) / self.resolution)
223 
224     # 给每个网格编号,得到每个网格的key
225     def calc_index(self, node):
226         # 从左到右增大,从下到上增大
227         return node.y * self.x_width + node.x
228 
229     # 邻居节点是否超出范围
230     def verify_node(self, node):
231         # 根据网格坐标计算实际坐标
232         px = self.calc_position(node.x, self.min_x)
233         py = self.calc_position(node.y, self.min_y)
234         # 判断是否超出边界
235         if px < self.min_x:
236             return False
237         if py < self.min_y:
238             return False
239         if px >= self.max_x:
240             return False
241         if py >= self.max_y:
242             return False
243         # 节点是否在障碍物上,障碍物标记为True
244         if self.obstacle_map[node.x][node.y]:
245             return False
246         # 没超过就返回True
247         return True
248 
249     # 计算路径, parent属性记录每个节点的父节点
250     def calc_final_path(self, goal_node, closed_set):
251         # 先存放终点坐标(真实坐标)
252         rx = [self.calc_position(goal_node.x, self.min_x)]
253         ry = [self.calc_position(goal_node.y, self.min_y)]
254         # 获取终点的父节点索引
255         parent_index = goal_node.parent_index
256         # 起点的父节点==-1
257         while parent_index != -1:
258             n = closed_set[parent_index]  # 在入库中选择父节点
259             rx.append(self.calc_position(n.x, self.min_x))  # 节点的x坐标
260             ry.append(self.calc_position(n.y, self.min_y))  # 节点的y坐标
261             parent_index = n.parent_index  # 节点的父节点索引
262 
263         return rx, ry
264 
265 def draw_circle(cx, cy, radius):
266     ox, oy = [], []
267     for angle in range(0, 360, 5):  # 每隔5度取一个点
268         rad = math.radians(angle)
269         x = cx + radius * math.cos(rad)
270         y = cy + radius * math.sin(rad)
271         ox.append(x)
272         oy.append(y)
273     return ox, oy
274 
275 def main():
276     # 设置起点和终点
277     # sx = 10
278     sx = -5.0
279     sy = -5.0
280     gx = 50.0
281     gy = 39.0
282     # 网格大小
283     grid_size = 2.0
284     # 机器人半径
285     robot_radius = 1.0
286 
287     resource_points = [(-5,10),(10,30),(10,40)]
288     resource_x = 10
289     resource_y = 30
290 
291 
292     # 设置障碍物位置
293     # 边界
294     ox, oy = [], []
295     for i in range(-10, 60):
296         ox.append(i)
297         oy.append(-10.0)  # 下边界
298     for i in range(-10, 60):
299         ox.append(60.0)
300         oy.append(i)  # 右边界
301     for i in range(-10, 61):
302         ox.append(i)
303         oy.append(60.0)  # 上边界
304     for i in range(-10, 61):
305         ox.append(-10.0)
306         oy.append(i)  # 左边界
307 
308     # 添加圆形障碍物
309     cx, cy, radius = 30, 30, 10
310     circle_ox, circle_oy = draw_circle(cx, cy, radius)
311     ox.extend(circle_ox)
312     oy.extend(circle_oy)
313 
314     cx1, cy1, radius1 = 10, 10, 5
315     circle_ox1, circle_oy1 = draw_circle(cx1, cy1, radius1)
316     ox.extend(circle_ox1)
317     oy.extend(circle_oy1)
318 
319     # 添加围栏
320     for i in range(-10, 15):
321         ox.append(20.0)
322         oy.append(i)  # 左围栏
323 
324     # 绘图
325     if show_animation:
326         plt.plot(ox, oy, '.k')  # 障碍物黑色
327         plt.plot(sx, sy, 'og')  # 起点绿色
328         plt.plot(gx, gy, 'xb')  # 终点蓝色
329         plt.grid(True)
330         plt.axis('equal')  # 坐标轴刻度间距等长
331         for rx, ry in resource_points:
332             plt.plot(rx, ry, '*r')
333 
334     # 实例化,传入障碍物,网格大小
335     dijkstra = Dijkstra(ox, oy, grid_size, robot_radius, resource_points)
336     # 求解路径,返回路径的 x 坐标和 y 坐标列表
337     rx1, ry1 = dijkstra.planning(sx, sy, resource_x, resource_y)
338     rx2, ry2 = dijkstra.planning(resource_x,resource_y,gx,gy)
339 
340     # 合并路径
341     rx = rx1[:-1] + rx2
342     ry = ry1[:-1] + ry2
343 
344 
345     # 绘制路径经过的网格
346     if show_animation:
347         plt.plot(rx1, ry1, '-r')
348         plt.plot(rx2, ry2, '-r')
349         plt.pause(0.01)
350         plt.show()
351 
352 if __name__ == '__main__':
353     main()

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/738011.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Three.js

右手坐标系 //每秒转一圈 const clock = new THREE.Clock() function tick() {const time = clock.getElapsedTime()mesh.rotation.y = time * Math.PI * 2 //一秒转一圈renderer.render(scene, camera)window.requestAnimationFrame(tick) } tick()const client = {x: 0,y: 0 …

关于古书介绍上“单鱼尾”是什么?

在阅读一些古书时,读的往往时某些版本的综合刊定版,而介绍有关原版时,会出现这个词语“单鱼尾”或者双鱼尾,这是什么意思呢? 搜索发现,原来古书也不是一页页,单页的,而是双页印刷,然后中间折叠,装订成册的。而折叠的具体位置在哪呢?就由鱼尾型标志标明,显然鱼尾最凹…

数据特征采样在 MySQL 同步一致性校验中的实践

本文介绍了当前DTS应用中,MySQL数据同步使用到的数据一致性校验工具,并对它的实现思路进行分享。作者:vivo 互联网存储研发团队 - Shang Yongxing本文介绍了当前DTS应用中,MySQL数据同步使用到的数据一致性校验工具,并对它的实现思路进行分享。 一、背景 在 MySQL 的使用过…

什么是容器镜像?

镜像是容器的模板,容器运行需要借助镜像来装载环境。镜像描述了容器所需的运行时环境,我们以Docker镜像为例来了解镜像到底是什么。Docker镜像实际上是由一层一层的文件系统构成,这种层级的文件系统称为UnionFS。UnionFS文件系统是一种分层、轻量级并且高性能的文件系统,它…

字符串相似度算法完全指南:编辑、令牌与序列三类算法的全面解析与深入分析

在自然语言处理领域,人们经常需要比较字符串,这些字符串可能是单词、句子、段落甚至是整个文档。如何快速判断两个单词或句子是否相似,或者相似度是好还是差。这类似于我们使用手机打错一个词,但手机会建议正确的词来修正它,那么这种如何判断字符串相似度呢?本文将详细介…

Avalonia应用在基于Linux的国产操作deepin上运行

本文介绍了Avalonia应用如何在基于Linux的国产操作deepin上运行。deepin系统介绍 deepin(原名Linux Deepin)致力于为全球用户提供美观易用,安全可靠的 Linux发行版。deepin项目于2008年发起,并在2009年发布了以 linux deepin为名称的第一个版本。2014年4月更名为 deepin,在中…

java中的测试片段和include控制器使用

1.作用 可复用,提高测试计划的可维护性和效率。 2.具体使用 2.1在jmeter中将写好的一组请求保存为测试片段,保存到一个jmx文件 2.2新建一个线程组,添加include控制器,选择刚刚保存的测试片段文件即可将刚刚的测试片段重用。

splay-前驱后继

在平衡树中,经常会让我们查一下一个值的前驱或后继是谁,写两个函数就非常麻烦好吧,所以这里咱们用一点小技巧来让他变 成一个函数(这里的前驱后继定义时包括与本身相等的值) 代码点击查看代码 int nxt(int k){if(!m[rt].size) return 0;int root=rt;while(k!=m[root].val&…

softlockup detector

1 简介 从内核稳定性问题的角度来看内核安全,是基础,也是必备技能。很多时候,一个内核稳定性问题,就是造成系统安全的罪魁祸首。 当出现异常死锁、Hang up、死机等问题时,watchdog的作用就很好的体现出来。Watchdog主要用于监测系统运行情况,一旦出现以上异常情况,就会重…

drduino串口通信中文乱码

​ 第一次使用 Arduino 板学习时,遇到了串口工具接收乱码的问题。 最初认为可能是数据位或停止位设置不正确。当前设置如下:波特率:9600 数据位:8 校验位:无 停止位:1在 Arduino 代码中使用 Serial.begin(9600) 进行初始化。 Serial.begin(9600):- 波特率:9600- 数据位…

合理利用符号,用负反馈解决正反馈系统

首先,系统框图:此处的Kyrg当成是1,aircraft的状态方程系数为:A=[-0.0558 -0.9968 0.0802 0.0415; 0.598 -0.115 -0.0318 0; -3.05 0.388 -0.4650 0; 0 0.0805 1 0]; B=[0.00729 -0.475 0.153 0]; C=[0 1 0 0]; D=0;使用函数ss2tf和tf求出系统的传递函数为:sysGs =-0.475 …

若依框架前端表格自适应

1. 背景 问题描述: 如图,若依前端表格高度都是固定写死的,因此会出现底部空一部分,现在希望自适应表格,使得表格一屏展示,且在隐藏查询条件等操作,导致屏幕大小变化时,表格可以同步自适应。问题现状:很多时候,前端开发都是尽可能本机调整到刚刚好的高度,但不同用户不…

CentOS 7 升级 OpenSSH 9.8p1

背景 OpenSSH 官方发布安全通告,披露CVE-2024-6387 OpenSSH Server远程代码执行漏洞。 环境操作系统 内核 openssh 版本 openssl 版本7.8.2003 3.10.0-1127 7.4p1 1.0.2k-fips安装编译软件和下载 OpenSSH9.8p1 软件包 mkdir -p /data/software cd /data/software/yum install…

ComfyUI基础篇:为什么要学 ComfyUI?

前言:在AI生成图像领域,有许多产品,例如 Midjourney 和 Stability AI 等。为什么要学习 ComfyUI 呢?我斗胆带大家一起分析一下。目录1.Midjourney VS Stable Diffusion2.SD Web UI VS ComfyUI3.总结Midjourney VS Stable Diffusion在回答这个问题之前,我觉得有必要介绍下目…

功能齐全!一套基于AGPL3开源协议开源的智慧物业社区系统!!

ejyy —— 一套开源的智慧物业解决方案。实现了微信公众号、小程序、PC、H5、智能硬件多端打通,旨在提升物业公司效率、规范物业服务流程、提升物业服务满意度、加强小区智慧化建设、便捷业主服务。大家好,我是 Java陈序员。 今天,给大家介绍一套开源的物业社区管理系统,涵…

关于搭建可商用AI平台的小感想

AI时代已经到来,建立自己的AI平台不仅能提升技能,还能带来更多职业和商业机会。我们的开源AI平台解决了技术难题,并提供全套商业化解决方案,让你专注于业务拓展。想学习从0-1搭建AI平台的朋友,欢迎加入我们的开发者交流群,一起交流学习。前言 AI时代已经到来,从智能客服…

QChartView显示实时更新的温度曲线图(二)

目录参考图说明1. 项目结构2. TempChartView.pro3. main.cpp4. TemperatureSeries.qml5. main.qml详细说明 参考图说明Qt Charts 提供了一系列使用图表功能的简单方法。它使用Qt Graphics View Framework 图形视图框架,因此可以很容易集成到用户界面。可以使用Qt Charts作为QW…

程序人生日记20240704|工作零食:米饭+十分米莲藕汁+饼干(减脂记录)

程序员的工作饮食减脂记录打卡 餐别:早餐 零食详情:(同事给的不算统计内) 零食名称:十分米莲藕汁配饼干 其他选择:米饭+海盐饼干。 大致热量估算: 莲藕汁约50卡,低脂全麦饼干2片约80卡,米饭约500卡,总计约630卡。 初始数据: 体重:90公斤 目标:80公斤 完成情况:完…

开发一个题库系统App和小程序的心得

开发一个题库系统App和小程序的心得序言 对于一名开发者来说,独自开发一款小程序与App,也许总会有一些疑问:1. 需要掌握哪些技术? 答:java、vue、及常规Linux命令2. 需要多少成本? 答:服务器购买,云服务器新人50多三年; 域名购买,10块的域名够用,后续每年30左右的续…

SFE人才需要具备哪些能力

SFE(销售队伍效力)人才在企业中扮演着至关重要的角色,他们需要具备一系列的能力来确保销售队伍的高效运作和业绩提升。关于SFE的角色和能力,可以从业务理解、数据洞察、向上管理以及效率提升等几个方面来通俗地解释。01 懂业务 SFE人才首先需要深入了解公司的业务,像医药企…