移动机器人路径规划:人工势场法[通俗易懂]

移动机器人路径规划:人工势场法[通俗易懂]人工势场法是一种原理比较简单的移动机器人路径规划算法,它将目标点位置视做势能最低点,将地图中的障碍物视为势能高点,计算整个已知地图的势场图,然后理想情况下,机器人就像一个滚落的小球,自动避开各个障碍物滚向目标点。

大家好,又见面了,我是你们的朋友全栈君。

人工势场法是一种原理比较简单的移动机器人路径规划算法,它将目标点位置视做势能最低点,将地图中的障碍物视为势能高点,计算整个已知地图的势场图,然后理想情况下,机器人就像一个滚落的小球,自动避开各个障碍物滚向目标点。

具体地,目标点的势能公式为:

e1
其中写道,为防止距离目标点较远时的速度过快,可以采用分段函数的形式描述,后文会进行展示。
而障碍物的势能表示为:

e2
即在障碍物周围某个范围内具有高势能,范围外视障碍物的影响为0。
最终将目标点和障碍物的势能相加,获得整张势能地图:
e3
以下是参考链接中的源代码,比较容易懂:

""" Potential Field based path planner author: Atsushi Sakai (@Atsushi_twi) Ref: https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf """
from collections import deque
import numpy as np
import matplotlib.pyplot as plt
# Parameters
KP = 5.0  # attractive potential gain
ETA = 100.0  # repulsive potential gain
AREA_WIDTH = 30.0  # potential area width [m]
# the number of previous positions used to check oscillations
OSCILLATIONS_DETECTION_LENGTH = 3
show_animation = True
def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy):
""" 计算势场图 gx,gy: 目标坐标 ox,oy: 障碍物坐标列表 reso: 势场图分辨率 rr: 机器人半径 sx,sy: 起点坐标 """
# 确定势场图坐标范围:
minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0
miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0
maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0
maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0
# 根据范围和分辨率确定格数:
xw = int(round((maxx - minx) / reso))
yw = int(round((maxy - miny) / reso))
# calc each potential
pmap = [[0.0 for i in range(yw)] for i in range(xw)]
for ix in range(xw):
x = ix * reso + minx   # 根据索引和分辨率确定x坐标
for iy in range(yw):
y = iy * reso + miny  # 根据索引和分辨率确定x坐标
ug = calc_attractive_potential(x, y, gx, gy)  # 计算引力
uo = calc_repulsive_potential(x, y, ox, oy, rr)  # 计算斥力
uf = ug + uo
pmap[ix][iy] = uf
return pmap, minx, miny
def calc_attractive_potential(x, y, gx, gy):
""" 计算引力势能:1/2*KP*d """
return 0.5 * KP * np.hypot(x - gx, y - gy)
def calc_repulsive_potential(x, y, ox, oy, rr):
""" 计算斥力势能: 如果与最近障碍物的距离dq在机器人膨胀半径rr之内:1/2*ETA*(1/dq-1/rr)**2 否则:0.0 """
# search nearest obstacle
minid = -1
dmin = float("inf")
for i, _ in enumerate(ox):
d = np.hypot(x - ox[i], y - oy[i])
if dmin >= d:
dmin = d
minid = i
# calc repulsive potential
dq = np.hypot(x - ox[minid], y - oy[minid])
if dq <= rr:
if dq <= 0.1:
dq = 0.1
return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2
else:
return 0.0
def get_motion_model():
# dx, dy
# 九宫格中 8个可能的运动方向
motion = [[1, 0],
[0, 1],
[-1, 0],
[0, -1],
[-1, -1],
[-1, 1],
[1, -1],
[1, 1]]
return motion
def oscillations_detection(previous_ids, ix, iy):
""" 振荡检测:避免“反复横跳” """
previous_ids.append((ix, iy))
if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH):
previous_ids.popleft()
# check if contains any duplicates by copying into a set
previous_ids_set = set()
for index in previous_ids:
if index in previous_ids_set:
return True
else:
previous_ids_set.add(index)
return False
def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr):
# calc potential field
pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy)
# search path
d = np.hypot(sx - gx, sy - gy)
ix = round((sx - minx) / reso)
iy = round((sy - miny) / reso)
gix = round((gx - minx) / reso)
giy = round((gy - miny) / reso)
if show_animation:
draw_heatmap(pmap)
# for stopping simulation with the esc key.
plt.gcf().canvas.mpl_connect('key_release_event',
lambda event: [exit(0) if event.key == 'escape' else None])
plt.plot(ix, iy, "*k")
plt.plot(gix, giy, "*m")
rx, ry = [sx], [sy]
motion = get_motion_model()
previous_ids = deque()
while d >= reso:
minp = float("inf")
minix, miniy = -1, -1
# 寻找8个运动方向中势场最小的方向
for i, _ in enumerate(motion):
inx = int(ix + motion[i][0])
iny = int(iy + motion[i][1])
if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0:
p = float("inf")  # outside area
print("outside potential!")
else:
p = pmap[inx][iny]
if minp > p:
minp = p
minix = inx
miniy = iny
ix = minix
iy = miniy
xp = ix * reso + minx
yp = iy * reso + miny
d = np.hypot(gx - xp, gy - yp)
rx.append(xp)
ry.append(yp)
# 振荡检测,以避免陷入局部最小值:
if (oscillations_detection(previous_ids, ix, iy)):
print("Oscillation detected at ({},{})!".format(ix, iy))
break
if show_animation:
plt.plot(ix, iy, ".r")
plt.pause(0.01)
print("Goal!!")
return rx, ry
def draw_heatmap(data):
data = np.array(data).T
plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues)
def main():
print("potential_field_planning start")
sx = 0.0  # start x position [m]
sy = 10.0  # start y positon [m]
gx = 30.0  # goal x position [m]
gy = 30.0  # goal y position [m]
grid_size = 0.5  # potential grid size [m]
robot_radius = 5.0  # robot radius [m]
# 以下障碍物坐标是我进行修改后的,用来展示人工势场法的困于局部最优的情况:
ox = [15.0, 5.0, 20.0, 25.0, 12.0, 15.0, 19.0, 28.0, 27.0, 23.0, 30.0, 32.0]  # obstacle x position list [m]
oy = [25.0, 15.0, 26.0, 25.0, 12.0, 20.0, 29.0, 28.0, 26.0, 25.0, 28.0, 27.0]  # obstacle y position list [m]
if show_animation:
plt.grid(True)
plt.axis("equal")
# path generation
_, _ = potential_field_planning(
sx, sy, gx, gy, ox, oy, grid_size, robot_radius)
if show_animation:
plt.show()
if __name__ == '__main__':
print(__file__ + " start!!")
main()
print(__file__ + " Done!!")

人工势场法的一项主要缺点就是可能会落入局部最优解,下图是源代码运行后的结果:
p1
下面是在我添加了一些障碍物后,人工势场法困于局部最优解的情况:虽然还没有到达目标点,但势场决定了路径无法再前进。
p2
需要注意的是,源代码在计算目标点势场的时候,使用的是某x,y位置距离目标点的距离的一次项,并未如课件中所示使用二次项,也是为了使势场变化没有那么快。下面是按照课件中所说,使用距离的二次项运行的结果,我们可以看到,为运行正常,KP需要调得很低:

KP = 0.1
def calc_attractive_potential(x, y, gx, gy):
""" 计算引力势能:1/2*KP*d^2 """
return 0.5 * KP * np.hypot(x - gx, y - gy)**2

正常运行:
p3
困在局部最优点:
p4
可以从势场图中看到,引力变化较上一个例子快得多。

最后,我们将程序修改成上面课件截图中所示的分段函数:

KP = 0.25
dgoal = 10
def calc_attractive_potential(x, y, gx, gy):
""" 计算引力:如课件截图 """
dg = np.hypot(x - gx, y - gy)
if dg<=dgoal:
U = 0.5 * KP * np.hypot(x - gx, y - gy)**2
else:
U = dgoal*KP*np.hypot(x - gx, y - gy) - 0.5*KP*dgoal
return U

正常运行:
p5
困于局部最优:
p6
可以看到引力势场分段的效果。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148611.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • ftp常用命令详解_ospf生成路由表的过程

    ftp常用命令详解_ospf生成路由表的过程在window下按window+r可打开DOS命令窗口,然后就可以输入FTP命令了。1.登录FTP服务器方法一:直接输入ftp加ip地址ftp192.168.10.xxx方法二:直接输入ftp,进入ftp服务后输入open加ip地址open192.168.10.xxx当连接成功后会让你进行身份验证,在输入密码时屏幕上没有任何显示,不用管,直接…

  • Mit6.S081-实验1-Xv6 and Unix utilities

    Mit6.S081-实验1-Xv6 and Unix utilitiesMit6.S081-实验1-Xv6andUnixutilities前言一、Bootxv61,实验目的2,操作流程1)切换到xv6-labs-2020代码库的lab1分支2)启动xv63)测试xv64)过程分析5)其他操作二、在xv6中添加一个自己编写的程序1,源码准备2,编译配置3,测试添加程序4,过程分析三、xv6中shell简析前言一、Bootxv61,实验目的利用qemu启动xv62,操作流程1)切换到xv6-labs-2020代码库的lab1分支gitcheckoutut

  • goland2021.3.26激活破解方法

    goland2021.3.26激活破解方法,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • Linux 简单命令操作 笔记

    Linux 简单命令操作 笔记

  • 数据库 — char、varchar、varchar2区别

    数据库 — char、varchar、varchar2区别char、varchar、varchar2区别char是定长的,varchar是变长的。varchar2应该是varchar的升级,只有ORACLE才有,这里不作讨论。char定长存储,速度快,但是存在一定的空间浪费,适用于字段不是很大,对速度要求高的场合。速度快是因为其在物理上是按定长存储的,这样,就可以根据偏移址一次取出固定长度的字符。varchar变长存储,效率不如char。…

  • 使用fiddler对手机APP进行抓包

    使用fiddler对手机APP进行抓包在做手机或移动端APP的接口测试时,需要从开发人员那里获取接口文档,接口文档应该包括完整的功能接口、接口请求方式、接口请求URL、接口请求参数、接口返回参数。如果当前项目没有接口文档,则可以使用fiddler对APP进行抓包确认。在手机上对APP进行操作,然后在Fiddler中可以抓取对应的网络交互信息(一个功能中可能设计多个接口的交互)。在抓取的信息中可以看到接口请求方式、接口请求URL、接口请

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号