Bug 算法路径规划:原理、推导与实现
文章目录
- [Bug 算法路径规划:原理、推导与实现](#Bug 算法路径规划:原理、推导与实现)
一、引言
在机器人导航与自主移动领域,路径规划是实现智能行为的关键技术。它决定了机器人如何在复杂环境中从起始点安全、高效地移动到目标点。Bug 算法作为经典的局部路径规划方法,具有简单性、实时性和无需全局地图的显著特性。这使得它在未知环境探索、工业机器人避障等场景中具有重要的应用价值。例如,在未知环境探索中,机器人可以利用 Bug 算法实时规划路径,而无需预先了解整个环境的地图信息;在工业机器人避障场景中,Bug 算法能够快速响应,帮助机器人避开突然出现的障碍物。本文将从算法原理、数学推导、实现步骤及代码解析三个维度,系统阐述 Bug0、Bug1、Bug2 三种核心算法的技术细节,并提供完整的 Python 实现方案。
二、算法核心原理
2.1 基本范式
所有 Bug 算法均遵循统一的执行范式,主要包含以下三个部分:
直线趋近模式(Motion to Goal)
机器人沿目标方向直线移动,直至遭遇障碍物。在二维平面中,假设当前时刻 t t t 机器人的位置为 ( x t , y t ) (x_t,y_t) (xt,yt),目标点的坐标为 ( g x , g y ) (g_x, g_y) (gx,gy)。机器人在 x x x 和 y y y 方向上的位移增量 Δ x \Delta x Δx 和 Δ y \Delta y Δy 可以通过以下公式计算:
Δ x = sign ( g x − x t ) Δ y = sign ( g y − y t ) \begin{aligned} \Delta x &= \text{sign}(g_x - x_t)\\ \Delta y &= \text{sign}(g_y - y_t) \end{aligned} ΔxΔy=sign(gx−xt)=sign(gy−yt)
其中, sign \text{sign} sign 是符号函数,其作用是返回参数的符号。当参数大于 0 时,返回 1;小于 0 时,返回 -1;等于 0 时,返回 0。通过这种方式,机器人可以朝着目标点直线移动。例如,如果目标点在机器人的右侧,那么 g x − x t > 0 g_x-x_t>0 gx−xt>0, Δ x = 1 \Delta x = 1 Δx=1,机器人在 x x x 方向上向右移动。
边界跟随模式(Boundary Following)
当机器人在直线趋近模式下遇到障碍物时,会切换到边界跟随模式。在这个模式下,机器人会沿着障碍物的轮廓移动,直至满足离开条件。离开条件因不同的 Bug 算法而异。例如,在 Bug0 算法中,只要有机会再次朝着目标点直线前进,机器人就会离开障碍物;在 Bug1 算法中,机器人需要绕障碍物一圈并找到距离目标点最近的点后才会离开;在 Bug2 算法中,机器人需要再次遇到从起点到目标点的参考线且距离目标点更近时才会离开。
模式切换机制
机器人会根据当前的情况动态切换直线趋近模式和边界跟随模式,直到抵达目标点。这种模式切换机制使得机器人能够在复杂的环境中灵活地避开障碍物,朝着目标前进。例如,当机器人在直线趋近模式下遇到障碍物时,会自动切换到边界跟随模式;当在边界跟随模式下满足离开条件时,又会切换回直线趋近模式。
2.2 算法变体对比
算法特性 | Bug0 | Bug1 | Bug2 |
---|---|---|---|
绕障策略 | 简单绕行 | 完全环绕+最近点选择 | 参考线跟踪+最短路径判断 |
路径质量 | 可能次优 | 大部分情况下较优 | 大部分情况下最优 |
计算复杂度 | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( n log n ) O(n\log n) O(nlogn) |
适用场景 | 简单障碍 | 复杂障碍 | 长距离导航 |
Bug0 算法
Bug0 算法采用简单绕行的绕障策略。当遇到障碍物时,机器人会沿着障碍物的边界绕行,一旦有机会再次朝着目标点直线前进,就会切换回直线趋近模式。这种策略简单直接,但可能会导致路径质量次优,因为它没有考虑到障碍物周围的全局信息。例如,在一些复杂的障碍物环境中,Bug0 算法可能会多次绕圈,导致路径长度增加。其计算复杂度为 O ( n ) O(n) O(n),适用于简单障碍的场景,因为在简单障碍环境中,机器人不需要进行复杂的计算就可以找到绕过障碍物的路径。
Bug1 算法
Bug1 算法在遇到障碍物时,会完全环绕障碍物一圈,并记录下距离目标点最近的点。绕完一圈后,机器人会回到最初遇到障碍物的点,然后从记录的最近点出发继续朝着目标点前进。这种策略能够找到相对较优的路径,因为它考虑了障碍物周围的全局信息,通过比较所有边界点到目标点的距离,选择距离最近的点作为出口。但计算复杂度较高,为 O ( n 2 ) O(n^2) O(n2),因为需要遍历障碍物的所有边界点来找到最近点。适用于复杂障碍的场景,在复杂障碍环境中,这种策略可以帮助机器人找到更优的路径。
Bug2 算法
Bug2 算法引入了参考线的概念。在遇到障碍物时,机器人会沿着障碍物的边界绕行,同时关注与从起点到目标点的参考线的交点。当再次遇到这条参考线且距离目标点更近时,就会离开障碍物继续朝着目标点前进。这种策略能够找到最优路径,因为它通过参考线的约束,使得机器人在绕障碍物时能够朝着更接近目标点的方向移动。计算复杂度为 O ( n log n ) O(n\log n) O(nlogn),适用于长距离导航的场景。在长距离导航中,Bug2 算法可以通过优化路径,减少机器人的移动距离。
三、数学建模与算法推导
3.1 环境建模
设机器人工作空间为二维离散网格:
W = { ( x , y ) ∣ x , y ∈ Z } \mathcal{W} = \{(x,y) | x,y \in \mathbb{Z}\} W={(x,y)∣x,y∈Z}
这个工作空间表示机器人可以在二维平面上的整数坐标点上移动。在实际应用中,这种离散化的表示方式可以简化机器人的路径规划问题,因为机器人的位置可以用整数坐标来表示,便于计算和处理。
障碍物集合:
O = { ( o x i , o y i ) } i = 1 N \mathcal{O} = \{(o_x^i, o_y^i)\}_{i=1}^N O={(oxi,oyi)}i=1N
其中, N N N 表示障碍物的数量, ( o x i , o y i ) (o_x^i,o_y^i) (oxi,oyi) 表示第 i i i 个障碍物的坐标。通过定义障碍物集合,我们可以明确机器人在工作空间中需要避开的区域。
边界点集合通过 Moore 邻域生成:
∂ O = { ( o x ± 1 , o y ± 1 ) ∖ O } \partial\mathcal{O} = \{(o_x\pm1, o_y\pm1) \setminus \mathcal{O}\} ∂O={(ox±1,oy±1)∖O}
Moore 邻域是指一个点周围的 8 个相邻点,通过对障碍物的每个点取其 Moore 邻域,并排除那些本身就是障碍物的点,就可以得到障碍物的边界点集合。这些边界点对于机器人在边界跟随模式下的移动非常重要,因为机器人需要沿着这些边界点来绕过障碍物。
3.2 Bug0 算法数学描述
直线趋近模式
在直线趋近模式下,机器人会直接朝着目标点移动。假设当前时刻 t 机器人的位置为 ( x t , y t ) (x_t, y_t) (xt,yt),目标点的坐标为 ( g x , g y ) (g_x, g_y) (gx,gy)。机器人在 x x x 和 y y y 方向上的位移增量 Δ x \Delta x Δx 和 Δ y \Delta y Δy 分别为:
Δ x = sign ( g x − x t ) Δ y = sign ( g y − y t ) \Delta x = \text{sign}(g_x - x_t) \\ \Delta y = \text{sign}(g_y - y_t) Δx=sign(gx−xt)Δy=sign(gy−yt)
其中, sign \text{sign} sign 是符号函数,其定义如下:
- 如果 g x − x t > 0 g_x - x_t > 0 gx−xt>0,则 Δ x = 1 \Delta x = 1 Δx=1,表示机器人在 x x x 方向上向右移动;
- 如果 g x − x t < 0 g_x - x_t < 0 gx−xt<0,则 Δ x = − 1 \Delta x = -1 Δx=−1,表示机器人在 x x x 方向上向左移动;
- 如果 g x − x t = 0 g_x - x_t = 0 gx−xt=0,则 Δ x = 0 \Delta x = 0 Δx=0,表示机器人在 x x x 方向上不移动。
同理, Δ y \Delta y Δy 的含义类似,表示机器人在 y y y 方向上的移动方向。
通过这种方式,机器人可以朝着目标点逐步接近。例如,如果目标点在机器人右上方,那么 Δ x = 1 \Delta x = 1 Δx=1 和 Δ y = 1 \Delta y = 1 Δy=1,机器人会同时向右和向上移动。
边界跟随模式
当机器人在直线趋近模式下遇到障碍物时,会切换到边界跟随模式。机器人会在障碍物的边界上移动,直到找到可以再次朝着目标点直线移动的机会。边界点通过 Moore 邻域计算得到,即机器人可以在上下左右以及四个对角线方向上移动,但只能选择不在障碍物集合中的点。
模式切换机制
机器人会在两种模式之间切换:
- 从直线趋近模式切换到边界跟随模式:当机器人在直线趋近模式下移动到下一个位置时,如果该位置是障碍物的边界点,则切换到边界跟随模式。
- 从边界跟随模式切换回直线趋近模式:在边界跟随模式下,机器人会持续检查下一个位置是否可以直接朝向目标点移动。如果可以(即目标点方向上没有障碍物),则切换回直线趋近模式。
算法流程
- 初始化:机器人从起点出发,目标是到达终点。
- 直线移动 :计算 Δ x \Delta x Δx 和 Δ y \Delta y Δy 并移动到下一个位置。
- 检查障碍物 :
- 如果下一个位置不在障碍物边界上,则继续直线移动。
- 如果下一个位置是障碍物边界,则切换到边界跟随模式。
- 边界跟随:在障碍物边界上移动,直到可以返回直线趋近模式。
- 重复:重复上述步骤,直到机器人到达目标点。
3.3 Bug1 算法数学描述
接触点记录
首次碰撞点 p h = ( x h , y h ) p_h = (x_h, y_h) ph=(xh,yh) 表示机器人在直线趋近模式下第一次遇到障碍物的点。这个点是 Bug1 算法中一个重要的参考点,当机器人绕完障碍物一圈后,需要回到这个点。通过记录接触点,机器人可以知道自己是从哪里开始绕障碍物的,并且在绕完一圈后可以准确地回到这个点,然后从最近点出发继续前进。
最近点求解
在绕障碍物的过程中,机器人需要找到距离目标点最近的点 p e x i t p_{exit} pexit,可以通过以下公式计算:
p e x i t = arg min ( x , y ) ∈ ∂ O ∣ ( x , y ) − ( g x , g y ) ∣ 2 p_{exit} = \arg\min_{(x,y)\in\partial\mathcal{O}} |(x,y)-(g_x,g_y)|_2 pexit=arg(x,y)∈∂Omin∣(x,y)−(gx,gy)∣2
其中, ∣ ⋅ ∣ 2 |\cdot|2 ∣⋅∣2 表示欧几里得距离,即两点之间的直线距离。通过遍历障碍物的边界点集合,找到距离目标点欧几里得距离最小的点,即为 p e x i t p{exit} pexit。
环绕条件
为了判断机器人是否绕完障碍物一圈,需要满足以下环绕条件:
∃ t > t h , ∣ p ( t ) − p h ∣ < ϵ (闭合环路检测) \exists t > t_h,\ |p(t)-p_h| < \epsilon \quad \text{(闭合环路检测)} ∃t>th, ∣p(t)−ph∣<ϵ(闭合环路检测)
其中, t h t_h th 是机器人首次遇到障碍物的时间, p ( t ) p(t) p(t) 是机器人在时间 t t t 的位置, ϵ \epsilon ϵ 是一个很小的正数,用于判断机器人是否回到了接触点附近。当机器人在绕障碍物的过程中,再次回到接触点附近(即满足上述条件)时,说明机器人已经绕完一圈。
3.4 Bug2 参考线理论
定义参考直线
定义参考直线 L \mathcal{L} L 为从起点 ( s x , s y ) (s_x,s_y) (sx,sy) 到目标点 ( g x , g y ) (g_x, g_y) (gx,gy) 的直线,其方程为:
L : y = g y − s y g x − s x ( x − s x ) + s y \mathcal{L}: y = \frac{g_y - s_y}{g_x - s_x}(x - s_x) + s_y L:y=gx−sxgy−sy(x−sx)+sy
这条参考直线为机器人在绕障碍物时提供了一个方向参考。例如,当机器人在绕障碍物时,它可以通过比较自己与参考直线的位置关系,来判断是否应该离开障碍物继续朝着目标点前进。
路径优化条件
为了优化路径,Bug2 算法引入了路径优化条件。当机器人在绕障碍物的过程中,需要满足以下条件才会离开障碍物:
∣ p n e w − L ∣ < ∣ p c u r r e n t − L ∣ |p_{new}-\mathcal{L}| < |p_{current}-\mathcal{L}| ∣pnew−L∣<∣pcurrent−L∣
其中, p n e w p_{new} pnew 是机器人的下一个可能位置, p c u r r e n t p_{current} pcurrent 是机器人的当前位置, ∣ ⋅ ∣ | \cdot | ∣⋅∣ 表示点到直线的距离。当机器人找到一个新的位置,使得该位置到参考直线的距离小于当前位置到参考直线的距离时,说明朝着这个新位置移动可以让机器人更接近参考直线,也就更有可能朝着目标点前进,因此可以离开障碍物继续朝着目标点前进。
四、算法实现
python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
"""
主类:路径规划器
实现三种 Bug 算法(Bug0、Bug1、Bug2)来进行路径规划
"""
class BugPlanner:
def __init__(self, start_x, start_y, goal_x, goal_y, obs_x, obs_y):
"""
初始化路径规划器
:param start_x: 起始点 x 坐标
:param start_y: 起始点 y 坐标
:param goal_x: 目标点 x 坐标
:param goal_y: 目标点 y 坐标
:param obs_x: 障碍物 x 坐标列表
:param obs_y: 障碍物 y 坐标列表
"""
self.goal_x = goal_x # 目标点 x 坐标
self.goal_y = goal_y # 目标点 y 坐标
self.obs_x = obs_x # 障碍物 x 坐标列表
self.obs_y = obs_y # 障碍物 y 坐标列表
self.r_x = [start_x] # 机器人路径的 x 坐标(起点)
self.r_y = [start_y] # 机器人路径的 y 坐标(起点)
self.out_x = [] # 障碍物边界点的 x 坐标
self.out_y = [] # 障碍物边界点的 y 坐标
# 生成障碍物边界点
# 遍历障碍物的每个点,生成其周围的8个方向的边界点
for o_x, o_y in zip(obs_x, obs_y):
for add_x, add_y in zip([1, 0, -1, -1, -1, 0, 1, 1],
[1, 1, 1, 0, -1, -1, -1, 0]):
cand_x, cand_y = o_x + add_x, o_y + add_y # 候选点
valid_point = True # 假设候选点有效
for _x, _y in zip(obs_x, obs_y):
if cand_x == _x and cand_y == _y:
valid_point = False # 如果候选点是障碍物本身,无效
break
if valid_point:
self.out_x.append(cand_x) # 添加有效的边界点
self.out_y.append(cand_y)
self.path_frames = [] # 用于存储路径动画的帧
def mov_normal(self):
"""
正常移动:计算机器人朝着目标点移动的下一个坐标
:return: 计算后的下一个 x 和 y 坐标
"""
# 使用 sign 函数确定移动方向(正或负)
next_x = self.r_x[-1] + np.sign(self.goal_x - self.r_x[-1])
next_y = self.r_y[-1] + np.sign(self.goal_y - self.r_y[-1])
return next_x, next_y
def mov_to_next_obs(self, visited_x, visited_y):
"""
绕行障碍物:在障碍物边界上寻找下一个移动点
:param visited_x: 已访问点的 x 坐标列表
:param visited_y: 已访问点的 y 坐标列表
:return: 下一个候选点的 x、y 坐标,以及是否回到起点(True/False)
"""
# 尝试四个方向(右、上、左、下)
for add_x, add_y in zip([1, 0, -1, 0], [0, 1, 0, -1]):
c_x = self.r_x[-1] + add_x # 候选点 x
c_y = self.r_y[-1] + add_y # 候选点 y
use_pt = False # 初始化标志位
# 检查候选点是否在障碍物边界上,且未被访问过
for _x, _y in zip(self.out_x, self.out_y):
if c_x == _x and c_y == _y:
use_pt = True
# 检查是否已访问过该点
for v_x, v_y in zip(visited_x, visited_y):
if c_x == v_x and c_y == v_y:
use_pt = False
break
if use_pt:
return (c_x, c_y, False) # 返回有效点
break # 退出障碍物边界检查循环
if use_pt:
break # 退出方向循环
# 如果没有找到有效点,返回当前位置并标记为回到起点
return (self.r_x[-1], self.r_y[-1], True)
def bug0(self):
"""
Bug0 算法实现
简单的环绕障碍物移动策略
"""
mov_dir = 'normal' # 初始移动方向为正常模式
visited_x, visited_y = [], [] # 记录已访问的障碍物边界点
self.path_frames = [] # 重置路径帧
# 检查起始点是否位于障碍物边界上
for x_ob, y_ob in zip(self.out_x, self.out_y):
if self.r_x[-1] == x_ob and self.r_y[-1] == y_ob:
mov_dir = 'obs' # 如果起始点在障碍物边界上,切换到障碍物模式
break
while True:
# 判断是否到达目标点
if self.r_x[-1] == self.goal_x and self.r_y[-1] == self.goal_y:
break
# 根据移动方向计算候选点
if mov_dir == 'normal':
cand_x, cand_y = self.mov_normal()
else:
cand_x, cand_y, _ = self.mov_to_next_obs(visited_x, visited_y)
# 更新路径
if mov_dir == 'normal':
found_obstacle = False
# 检查候选点是否位于障碍物边界上
for x_ob, y_ob in zip(self.out_x, self.out_y):
if cand_x == x_ob and cand_y == y_ob:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x = [] # 重置已访问点
visited_y = []
visited_x.append(cand_x)
visited_y.append(cand_y)
mov_dir = 'obs' # 切换到障碍物模式
found_obstacle = True
break
if not found_obstacle:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
else:
# 检查是否可以移动到目标方向
can_move_directly = True
for x_ob, y_ob in zip(self.obs_x, self.obs_y):
if (self.mov_normal()[0] == x_ob and
self.mov_normal()[1] == y_ob):
can_move_directly = False
break
if can_move_directly:
mov_dir = 'normal' # 切换回正常模式
else:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x.append(cand_x)
visited_y.append(cand_y)
# 保存当前路径帧
self.path_frames.append((self.r_x.copy(), self.r_y.copy()))
def bug1(self):
"""
Bug1 算法实现
环绕障碍物时记录距离目标最近的点,并在绕行完成后从该点出发
"""
mov_dir = 'normal' # 初始移动方向为正常模式
visited_x, visited_y = [], [] # 记录已访问的障碍物边界点
exit_x, exit_y = -np.inf, -np.inf # 离目标最近的点
dist = np.inf # 初始距离为无穷大
back_to_start = False # 标记是否回到起点
second_round = False # 标记是否开始第二轮绕行
self.path_frames = [] # 重置路径帧
# 检查起始点是否位于障碍物边界上
for x_ob, y_ob in zip(self.out_x, self.out_y):
if self.r_x[-1] == x_ob and self.r_y[-1] == y_ob:
mov_dir = 'obs' # 如果起始点在障碍物边界上,切换到障碍物模式
break
while True:
# 判断是否到达目标点
if self.r_x[-1] == self.goal_x and self.r_y[-1] == self.goal_y:
break
# 根据移动方向计算候选点
if mov_dir == 'normal':
cand_x, cand_y = self.mov_normal()
else:
cand_x, cand_y, back_to_start = self.mov_to_next_obs(visited_x, visited_y)
# 更新路径
if mov_dir == 'normal':
found_obstacle = False
for x_ob, y_ob in zip(self.out_x, self.out_y):
if cand_x == x_ob and cand_y == y_ob:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x = [] # 重置已访问点
visited_y = []
visited_x.append(cand_x)
visited_y.append(cand_y)
mov_dir = 'obs' # 切换到障碍物模式
dist = np.inf # 重置距离
back_to_start = False # 重置回到起点标记
second_round = False # 重置第二轮绕行标记
found_obstacle = True
break
if not found_obstacle:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
else:
# 计算当前点到目标的距离
current_dist = np.linalg.norm(np.array([cand_x, cand_y]) -
np.array([self.goal_x, self.goal_y]))
# 更新距离目标最近的点
if current_dist < dist and not second_round:
exit_x, exit_y = cand_x, cand_y
dist = current_dist
# 检查是否回到起点
if back_to_start and not second_round:
second_round = True
# 删除第二轮绕行的路径
del self.r_x[-len(visited_x):]
del self.r_y[-len(visited_y):]
visited_x = []
visited_y = []
# 更新路径
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x.append(cand_x)
visited_y.append(cand_y)
# 检查是否回到最近点
if cand_x == exit_x and cand_y == exit_y and second_round:
mov_dir = 'normal' # 切换回正常模式
# 保存当前路径帧
self.path_frames.append((self.r_x.copy(), self.r_y.copy()))
def bug2(self):
"""
Bug2 算法实现
环绕障碍物时,当距离目标开始增加时,切换回正常模式
"""
mov_dir = 'normal' # 初始移动方向为正常模式
self.path_frames = [] # 重置路径帧
# 生成从起点到目标点的直线路径,并记录障碍物碰撞点
straight_x, straight_y = [self.r_x[-1]], [self.r_y[-1]]
hit_x, hit_y = [], []
while True:
cx = straight_x[-1] + np.sign(self.goal_x - straight_x[-1])
cy = straight_y[-1] + np.sign(self.goal_y - straight_y[-1])
for x_ob, y_ob in zip(self.out_x, self.out_y):
if cx == x_ob and cy == y_ob:
hit_x.append(cx)
hit_y.append(cy)
break
straight_x.append(cx)
straight_y.append(cy)
if cx == self.goal_x and cy == self.goal_y:
break
# 检查起始点是否位于障碍物边界上
on_obstacle = False
for x_ob, y_ob in zip(self.out_x, self.out_y):
if self.r_x[-1] == x_ob and self.r_y[-1] == y_ob:
on_obstacle = True
break
visited_x, visited_y = [], [] # 记录已访问的障碍物边界点
while True:
# 判断是否到达目标点
if self.r_x[-1] == self.goal_x and self.r_y[-1] == self.goal_y:
break
if mov_dir == 'normal':
cand_x, cand_y = self.mov_normal()
found_obstacle = False
# 检查候选点是否位于障碍物边界上
for x_ob, y_ob in zip(self.out_x, self.out_y):
if cand_x == x_ob and cand_y == y_ob:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x, visited_y = [], []
visited_x.append(cand_x)
visited_y.append(cand_y)
# 删除已处理的碰撞点
try:
hit_x.pop(0)
hit_y.pop(0)
except IndexError:
pass
mov_dir = 'obs' # 切换到障碍物模式
found_obstacle = True
break
if not found_obstacle:
self.r_x.append(cand_x)
self.r_y.append(cand_y)
else:
cand_x, cand_y, _ = self.mov_to_next_obs(visited_x, visited_y)
self.r_x.append(cand_x)
self.r_y.append(cand_y)
visited_x.append(cand_x)
visited_y.append(cand_y)
# 检查是否到达碰撞点
for i in range(len(hit_x)):
if cand_x == hit_x[i] and cand_y == hit_y[i]:
del hit_x[i]
del hit_y[i]
mov_dir = 'normal' # 切换回正常模式
break
# 保存当前路径帧
self.path_frames.append((self.r_x.copy(), self.r_y.copy()))
def main():
"""
主函数:定义障碍物位置、起始点和目标点,并运行三种算法生成路径规划动画
"""
# 定义障碍物位置(多个矩形区域)
o_x, o_y = [], []
# 障碍物 1
for i in range(20, 40):
for j in range(20, 40):
o_x.append(i)
o_y.append(j)
# 障碍物 2
for i in range(70, 100):
for j in range(40, 80):
o_x.append(i)
o_y.append(j)
# 障碍物 3
for i in range(120, 140):
for j in range(80, 100):
o_x.append(i)
o_y.append(j)
# 障碍物 4
for i in range(80, 140):
for j in range(0, 20):
o_x.append(i)
o_y.append(j)
# 障碍物 5
for i in range(120, 160):
for j in range(40, 60):
o_x.append(i)
o_y.append(j)
# 起始点和目标点
s_x = 0.0
s_y = 0.0
g_x = 167.0
g_y = 50.0
# 定义要运行的算法
algorithms = [
{"name": "Bug0", "method": "bug0"},
{"name": "Bug1", "method": "bug1"},
{"name": "Bug2", "method": "bug2"}
]
# 依次运行每种算法并生成动画
for algo in algorithms:
my_Bug = BugPlanner(s_x, s_y, g_x, g_y, o_x, o_y) # 创建规划器实例
method = getattr(my_Bug, algo["method"]) # 获取算法方法
method() # 执行算法
# 创建动画图形
fig, ax = plt.subplots()
# 绘制障碍物、起点、目标点和障碍物边界
ax.plot(o_x, o_y, ".k", label="Obstacles") # 障碍物
ax.plot(s_x, s_y, "og", label="Start") # 起始点
ax.plot(g_x, g_y, "xb", label="Goal") # 目标点
ax.plot(my_Bug.out_x, my_Bug.out_y, ".", label="Obstacle Boundaries") # 障碍物边界
# 初始化动画
def init():
ax.set_xlim(-10, 170)
ax.set_ylim(-10, 110)
ax.set_aspect('equal')
ax.legend()
return []
# 动画函数:绘制每一帧
def animate(i):
ax.clear()
# 绘制静态元素
ax.plot(o_x, o_y, ".k", label="Obstacles")
ax.plot(s_x, s_y, "og", label="Start")
ax.plot(g_x, g_y, "xb", label="Goal")
ax.plot(my_Bug.out_x, my_Bug.out_y, ".", label="Obstacle Boundaries")
# 绘制当前路径
ax.plot(my_Bug.path_frames[i][0], my_Bug.path_frames[i][1], "-r", label="Path")
# 添加算法名称
ax.set_title(f"Algorithm: {algo['name']}", fontsize=12, fontweight='bold')
# 设置坐标轴限制
ax.set_xlim(-10, 170)
ax.set_ylim(-10, 110)
ax.set_aspect('equal')
ax.legend()
return [ax]
# 创建动画
ani = animation.FuncAnimation(
fig, animate,
frames=range(0, len(my_Bug.path_frames), 5),
init_func=init,
interval=100,
blit=False
)
# 显示动画
plt.show()
# 保存动画为 GIF 文件
ani.save(f"{algo['name']}_compressed.gif",
writer="pillow",
fps=2,
metadata={'loop': 0}) # 替代extra_args参数
plt.close(fig) # 关闭图形以释放内存
if __name__ == '__main__':
main()
运行效果如下:

五、应用与展望
典型应用场景
- 家庭服务机器人:在动态家居环境中导航,如扫地机器人、送餐机器人等。这些机器人需要在家庭环境中实时规划路径,避开家具、人员等障碍物,完成清洁、送餐等任务。
- 工业 AGV:仓库物料运输路径规划。AGV(Automated Guided Vehicle)在仓库中需要根据货物的存储位置和运输需求,规划最优的路径,提高物流效率。
- 无人机巡检:复杂地形避障飞行。无人机在巡检过程中,需要在复杂的地形环境中避开山脉、建筑物等障碍物,完成巡检任务。
未来发展方向
- 混合算法:与 A*、RRT 等全局规划算法结合。全局规划算法可以提供更优的全局路径,而 Bug 算法可以在局部环境中快速响应,两者结合可以提高路径规划的效率和质量。
- 机器学习增强:利用强化学习优化绕障策略。通过强化学习,机器人可以在不断的试错过程中学习到最优的绕障策略,提高在复杂环境中的导航能力。
- 多机器人协同:扩展至多智能体系统。多个机器人可以协同工作,共同完成任务,如多个 AGV 在仓库中协同运输货物,多个无人机在巡检中协同作业等。
参考文献
PythonRobotics-Bug planner