赞
踩
本文中有很多名词都是译自论文中,很多都不知道用中文怎么表示。对这些名词后面都用了原文的词汇。论文主要是 Pivtoraiko M 两篇05年的论文,08年的那篇这位虎哥已经解析并注释讲解了PR的源码了,可以移步进行学习。
如之前的A*栅格地图的四连接/八连接,这是一种对控制空间的离散化,而对于PRM,在空间中进行点的采样,则是对状态空间进行离散化,但是他们都有缺陷,那就是当考虑真正的有数学描述的机器人模型的时候,机器人并不能视之为随意移动的质点了,因此,本文学习并总结了Lattice Planner.
“lattice” 译为晶格。晶格规划?听起来是一个很抽象的名字,不知道这个晶格具体指什么,和原来栅格化地图中的"grid"又有什么联系。我们引用Mihail Pivtoraiko的原话:
The state lattice that we develop here can be viewed as a generalization of a grid.
概括一下,使用晶格(lattice) 的目的就是为了简化多个网格(grid),使得原来的网格图演化成一个新的晶格图,如Fig.1[2]所示,红色点标出来的就是晶格,而连接他们之间的边则表示一个状态到另一个状态的转移,Fig.2中最浅的灰色部分就是我们建立完毕的 Lattice 地图。黑色实线和黑色加粗线代表什么,以及这个地图是如何生成的,详看后文,这里只需要记住 Lattice planning 就是在自己建立的新的 Lattice 地图上进行路径规划。
Lattice Planner主要分为在控制空间上进行晶格生成和在状态空间上进行晶格生成,称为 Control Lattice Planner(CLP), State Lattice Planner(SLP)。
由于控制空间的 Lattice Plan 比较简单,本文就简要带过,主要讲述 State Lattice Planning.
设机器人状态方程如下如下:
State:
s
=
(
x
y
z
x
˙
y
˙
z
˙
)
Input:
u
=
(
x
¨
y
¨
z
¨
)
s
˙
=
A
⋅
s
+
B
⋅
u
A
=
[
0
I
3
0
⋯
0
0
0
I
3
⋯
0
⋮
⋱
⋱
⋱
⋮
0
⋯
⋯
0
I
3
0
⋯
⋯
0
0
]
B
=
[
0
0
⋮
0
I
1
]
\text{State: }\mathbf{s}={\left(
我们固定输入量
u
u
u 和时间 T,然后我们进行前向积分,就可以知道T后的机器人状态,如Fig.2 所示。尤其是当机器人是线性系统的时候。在知道T时刻机器人的末状态之后,就可以通过状态转移方程:
s
(
t
)
=
e
A
t
s
0
+
[
∫
0
t
e
A
(
t
−
σ
)
B
d
σ
]
u
m
,
s(t) = e^{At}s_0+[\int_0^te^{A(t-\sigma)}Bd\sigma]u_m,
s(t)=eAts0+[∫0teA(t−σ)Bdσ]um,
来还原出过程之中的各个状态,其中矩阵A是状态转移矩阵。假设
t
=
τ
t=\tau
t=τ,此时我们想要求解
s
(
τ
)
s(\tau)
s(τ),但
e
A
t
e^{At}
eAt的求解是一件很麻烦的事,我们可以利用泰勒展开
e
A
t
=
I
+
A
t
1
!
+
(
A
t
)
2
2
!
+
(
A
t
)
3
3
!
+
⋯
+
(
A
t
)
k
k
!
+
⋯
e^{At}=I+\frac{At}{1!}+\frac{(At)^2}{2!}+\frac{(At)^3}{3!}+\cdots+\frac{(At)^k}{k!}+\cdots
eAt=I+1!At+2!(At)2+3!(At)3+⋯+k!(At)k+⋯
而且此时我们知道,A矩阵有一个性质,就是nilpotent,也就是说它在多次方之后会变为0,那高次项就都被消去了,这使得
e
A
t
e^{At}
eAt 在(n-1)次矩阵多项式中都具有封闭的表达式,更多的求解方式可参考现代控制理论。
但这种状态建图法有一点缺陷,和Dijkstra一样,比较盲目,你不知道自己是要往左边还是往右边,只是不断的尝试各种输入量 u u u的积分,所以导致在规划过程中的效率不够高。所以这时我们就提到我们这篇论文的核心部分,state lattice planner.
我们可以选择在状态空间采样,我直接在空间中采样一个机器人状态(包括位置与姿态),然后反解出路径,即机器人如何选定一个 u u u在T上积分,刚好可以达到我们采样的这个状态。优点显而易见,这使得算法具有启发性和贪心性,难点在于难以实现。
在 State Lattice中,我们需要根据采样点与当前点的c状态在他们之间生成符合运动学和车辆约束的路径。这个问题被称为Boundary Value Problem(BVP),它是是状态抽样格规划的基础。由于没有通解,它往往演变成一个复杂的数值优化问题。本文基于深蓝学院和AtsushiSakai/**PythonRobotics**总结了两种路径生成方式,分别为插值法和最优边界求解OBVP.
百度百科定义:一种以 可变样条 来作出一条经过一系列点的光滑曲线的数学方法。插值样条是由一些多项式组成的,每一个多项式都是由相邻的两个数据点决定的,这样,任意的两个相邻的多项式以及它们的导数在连接点处都是连续的。
有一篇博客写的很简单明了,大家可以直接跳转 -> https://www.cnblogs.com/yanshw/p/11194058.html
本文主要谈一下PythonRobotics中对样条插值的应用,让我们看下它的源码:
def generate_trajectory(s, km, kf, k0): ''' 根据参数 [s, km, kf, k0]前向积分得到轨迹 s: 弧长 km: 运行(time / 2.0)时的曲率 kf: 末端的曲率 k0: 初始的曲率 ''' n = s / ds # 生成多少个状态 time = s / v # 走这个弧长要多久 if isinstance(time, type(np.array([]))): time = time[0] if isinstance(km, type(np.array([]))): km = km[0] if isinstance(kf, type(np.array([]))): kf = kf[0] # 时间t为自变量,曲率为因变量,根据已知的样本做插值 tk = np.array([0.0, time / 2.0, time]) # 时间样本 kk = np.array([k0, km, kf]) # 曲率样本 t = np.arange(0.0, time, time / n) fkp = scipy.interpolate.interp1d(tk, kk, kind="quadratic") kp = [fkp(ti) for ti in t] # 得到时间轴上每个t对应的曲率k dt = float(time / n) # plt.plot(t, kp) # plt.show() state = State() x, y, yaw = [state.x], [state.y], [state.yaw] for ikp in kp: state = update(state, v, ikp, dt, L) x.append(state.x) y.append(state.y) yaw.append(state.yaw) return x, y, yaw
a) 我们可以在BVP问题中加入一个优化目标,确保我们生成成的状态曲线更加符合我们的工程实践,比如我们定一个Objective,要使得加速度的平方和最小,而且状态转移的过程要尽可能的快,如下所示:
J
=
∫
0
T
g
(
x
,
u
)
d
t
=
∫
0
T
(
1
+
u
T
R
u
)
d
t
=
∫
0
T
(
1
+
a
x
2
+
a
y
2
+
a
z
2
)
d
t
J=\int_0^T g(x,u)dt=\int_0^T(1+u^TRu)dt=\int_0^T(1+a_x^2+a_y^2+a_z^2)dt
J=∫0Tg(x,u)dt=∫0T(1+uTRu)dt=∫0T(1+ax2+ay2+az2)dt
b) 状态量,输入量和系统方程表征如下:
x
=
(
p
x
p
y
p
z
v
x
v
x
v
z
)
,
u
=
(
a
x
a
y
a
z
)
,
x
˙
=
f
(
x
,
u
)
=
(
v
x
v
y
v
z
a
x
a
y
a
z
)
x=
c) 边界条件表征如下
i
n
i
t
i
a
l
s
t
a
t
e
:
x
(
0
)
=
(
p
x
0
p
y
0
p
z
0
v
x
0
v
y
0
v
z
0
)
,
e
n
d
s
t
a
t
e
:
x
(
T
)
=
(
p
x
f
p
y
f
p
z
f
?
?
?
)
i n i t i a l~s t a t e:x(0)=\left(
问题解决求解
计算过程中的求导和合并同类型等操作可以通过Matlab设立符号来进行简化,如下所示:
clear;clc;
syms x pfx p0x v0x pfy p0y v0y pfz p0z v0z T % define variable
DPx = pfx - p0x - v0x*T;
DPy = pfy - p0y - v0y*T;
DPz = pfz - p0z - v0z*T;
f1 = simplify(DPx*DPx + DPy*DPy + DPz*DPz);
result1 = simplify(diff(f1, T)); % derivation
result2 = simplify(9 * (DPx*DPx + DPy*DPy + DPz*DPz));
result3 = simplify(T^4 - (result1 * result2))
根据Pontryain’s 的极小值原理:
写出汉密顿函数如下:
H
(
x
,
u
,
λ
)
=
g
(
x
,
u
)
+
λ
T
f
(
x
,
u
)
=
1
+
a
x
2
+
a
y
2
+
a
z
2
+
λ
1
v
x
+
λ
2
v
y
+
λ
3
v
z
+
λ
4
a
x
+
λ
5
a
y
+
λ
6
a
z
其中
λ
=
(
λ
1
,
λ
2
,
λ
3
,
λ
4
,
λ
5
,
λ
6
)
\lambda = (\lambda_1,\lambda_2,\lambda_3,\lambda_4,\lambda_5,\lambda_6)
λ=(λ1,λ2,λ3,λ4,λ5,λ6)为态变量(costate)
因此我们可以得到
λ
\boldsymbol{\lambda}
λ 的导数以及
λ
(
t
)
\boldsymbol{\lambda}(t)
λ(t) :
λ
˙
=
−
∇
H
(
x
∗
,
u
∗
,
λ
)
=
(
0
0
0
−
λ
1
−
λ
2
−
λ
3
)
T
\dot{\lambda}=-\nabla H(x^*,u^*,\lambda)=(0\quad0\quad0\quad-\lambda_1\quad-\lambda_2\quad-\lambda_3)^T
λ˙=−∇H(x∗,u∗,λ)=(000−λ1−λ2−λ3)T
λ
(
t
)
=
(
2
α
1
2
α
2
2
α
3
−
2
α
1
t
−
2
β
1
−
2
α
2
t
−
2
β
2
−
2
α
3
t
−
2
β
3
)
\lambda(t)=
所以我们可以得到我们的最优控制输入
u
∗
u^*
u∗
u
∗
=
arg
min
u
(
t
)
H
(
x
∗
(
t
)
,
u
(
t
)
,
λ
(
t
)
)
=
(
α
1
t
+
β
1
α
2
t
+
β
2
α
3
t
+
β
3
)
u^*=\arg\min\limits_{u(t)}H(x^*(t),u(t),\lambda(t))=
通过(2)式, 我们得到最优的状态量
x
∗
(
t
)
x^*(t)
x∗(t)为:
x
∗
=
(
1
6
α
1
t
3
+
1
2
β
1
t
2
+
v
x
0
t
+
p
x
0
1
6
α
2
t
3
+
1
2
β
2
t
2
+
v
y
0
t
+
p
y
0
1
6
α
3
t
3
+
1
2
β
3
t
2
+
v
z
0
t
+
p
z
0
1
2
α
1
t
2
+
β
1
t
+
v
x
0
1
2
α
2
t
2
+
β
2
t
+
v
y
0
1
2
α
3
t
2
+
β
3
t
+
v
z
0
)
,
x^{*}=\left(
在极小值原理中,我们有边界条件如下:
λ
(
T
)
=
−
∇
h
(
x
∗
(
T
)
)
\lambda(T)=-\nabla h(x^*(T))
λ(T)=−∇h(x∗(T))
因为末端速度这个维度未制定,因此
h
(
x
∗
(
T
)
)
h(x^*(T))
h(x∗(T)) 显然和变量
v
v
v没什么关系,因此求导为0,
(
λ
4
(
T
)
λ
5
(
T
)
λ
6
(
T
)
)
=
(
−
2
α
1
T
−
2
β
1
−
2
α
2
T
−
2
β
2
−
2
α
3
T
−
2
β
3
)
=
0
所以我们可以得到一个新的
λ
(
t
)
\lambda(t)
λ(t):
β
=
−
α
T
\boldsymbol{\beta}=-\boldsymbol{\alpha}T
β=−αT
然后我们再利用终末条件
x
(
T
)
x(T)
x(T) 来求解得到
α
\boldsymbol{\alpha}
α:
α
=
−
3
(
P
f
−
P
0
−
v
0
T
)
T
3
=
−
3
Δ
P
T
3
,
\boldsymbol{\alpha}=-\frac{3(\boldsymbol{P_f}-\boldsymbol{P_0}-\boldsymbol{v_0}T)}{T^3}=-\frac{3\Delta {\boldsymbol{P}}}{T^3},
α=−T33(Pf−P0−v0T)=−T33ΔP,
其中
P
f
\boldsymbol{P_f}
Pf 是末端的位置状态,
P
0
\boldsymbol{P_0}
P0 和
v
0
\boldsymbol{v_0}
v0 是起始的位置状态. 所以我们得到最优的控制输入为
u
∗
u^*
u∗:
u
∗
=
(
α
1
(
t
−
T
)
α
2
(
t
−
T
)
α
3
(
t
−
T
)
)
=
(
−
3
Δ
P
x
T
3
(
t
−
T
)
−
3
Δ
P
y
T
3
(
t
−
T
)
−
3
Δ
P
z
T
3
(
t
−
T
)
)
.
u^*=
J
=
∫
0
T
1
+
(
u
∗
(
t
)
T
u
∗
(
t
)
)
d
t
=
T
+
3
∥
Δ
P
∥
2
T
3
,
其中 ∥ Δ P ∥ 2 = Δ P x 2 + Δ P y 2 + Δ P z 2 {\lVert \boldsymbol{\Delta P} \rVert}^2 = \Delta P^2_x + \Delta P^2_y + \Delta P^2_z ∥ΔP∥2=ΔPx2+ΔPy2+ΔPz2.
根据式(3),我们知道 J J J 和 T T T 无关. 如果我们想要得到 J J J 的最小值,我们需要 J J J 的导数为0。
T
4
+
(
2
V
0
x
Δ
P
x
+
2
V
0
y
Δ
P
y
+
2
V
0
z
Δ
P
z
)
(
9
∥
Δ
P
∥
2
)
=
0
T^4+(2V_{0x}\Delta P_x + 2V_{0y}\Delta P_y + 2V_{0z}\Delta P_z)(9{\lVert \boldsymbol{\Delta P} \rVert}^2) = 0
T4+(2V0xΔPx+2V0yΔPy+2V0zΔPz)(9∥ΔP∥2)=0
我们使用两种方式来对生成的状态进行验证,如Fig.3所示,代码已提交至我的 GitHub.
在此项目中,每一个Lattice Node有6个维度,包括 3D position 和 3D velocity.
τ
1
,
τ
2
\tau_1,\tau_2
τ1,τ2是拥有相同端点的路径,当
τ
1
\tau_1
τ1包含在
τ
2
\tau_2
τ2的
Q
Q
Q区域中时,则令
τ
1
=
τ
2
\tau_1=\tau_2
τ1=τ2,判断条件如下所示:
∀
q
∈
τ
1
,
∀
q
′
∈
τ
2
,
Q
=
{
q
′
∣
ρ
(
q
′
,
q
)
<
δ
c
}
,
\forall q\in\tau_1,\forall q'\in\tau_2,Q=\{q'|\rho(q',q)<\delta_c\},
∀q∈τ1,∀q′∈τ2,Q={q′∣ρ(q′,q)<δc},
其中
δ
c
\delta_c
δc是用来判定
τ
1
\tau_1
τ1是否包含在
τ
2
\tau_2
τ2内的距离阈值。如 Fig.4 的阴影部分。
路径等效化是 Lattice Planner 中非常重要的核心做法,因为他在不损失路径表示的情况下大大简化了搜索空间[1]。
如同在原来的4-neighbor栅格地图中一样,我们可以分出最小的一个路径集, 即上下左右四,如Fig.5(a)所示。而且可以看到途中的灰色路径代表了所有可行路径,通过这个点的任何路径都可以用这个用最小路径集中的分量表示,如在Fig.5(a)中的分量就有向上,向左,向右,向下,这被命名为**“原义(primitives)”**,这在运动规划中是一个很常见的名词。让我们再看一个例子如Fig.5(b) [4] ,这种优势显而易见,我们如果能在 state lattice 中也定义这么一个原义,那我们就可以将无限多的路径表示为很多个这种有限小的原义集合。
要注意到,在lattice空间下,我们所生成的路径都是符合机器人运动学的,毕竟路径是代表状态的转移过程。所以为了满足我们上面的目标,我们再引入一个概念,那就是路径分解。当我们生成路径的时候,如果该路径经过了另外一个节点 B B B,且原点 A A A 到此点的路径 s u b p a t h 1 subpath1 subpath1 和此点到伪目标点 C C C 的路径 s u b p a t h 2 subpath2 subpath2 相加等于原点到伪目标点的路径 M a i n p a t h Mainpath Mainpath,那么说明该路径可以被分解。(至于这个伪目标点是什么,继续往下看),如 Fig.6 所示。
现在让我们来看下如何生成一个最小路径原义,具体算法如下所示:
结合算法流程我们很容易明白路径分解在生成最小运动原义过程中所起到的意义,也就是你
A
A
A的 ManhattanDistance
R
b
R_b
Rb 下生成的路径可由在点
A
A
A 的 ManhattanDistance
R
s
1
R_{s1}
Rs1,和在点
B
B
B 的ManhattanDistance
R
s
2
R_{s2}
Rs2
(
R
s
1
,
R
s
2
<
R
b
)
(R_{s1},R_{s2} < R_b)
(Rs1,Rs2<Rb)生成的路径组合代替,那我生成这个路径的意义就已经丧失了,因为我可以由当前已生成的最小原义集合在
A
,
B
A,B
A,B两点叠加生成。在不断的增加R,不断地加入新的
O
r
i
g
P
a
t
h
OrigPath
OrigPath到
P
r
i
m
s
e
t
Primset
Primset 中,直到找到的路径都可以被分解,也就是可以由当前已生成的最小原义集合表示的时候,说明我们已经生成了可以表示所有路径的最小原义集合,也就是我们的控制集(control set),然后将这个最小原义集应用在各个点上就可以了。如Fig.1所示。
给定一个起始状态
Q
i
=
(
x
i
,
y
i
,
θ
i
,
k
i
)
Q_i = (x_i, y_i, \theta_i, k_i)
Qi=(xi,yi,θi,ki),和终止状态
Q
i
=
(
x
f
,
y
f
,
θ
f
,
k
f
)
Q_i = (x_f, y_f, \theta_f, k_f)
Qi=(xf,yf,θf,kf),对于阿克曼等nonholonomic vechine 的启发函数估计中则需要包含平动的代价和旋转的代价。因此可以考虑欧式距离和带权重的航向误差,启发函数如下所示:
h
=
L
2
(
(
x
i
,
y
i
)
,
(
x
f
,
y
f
)
)
+
w
0
(
∣
θ
f
−
θ
i
∣
)
h = L_2((x_i,y_i),(x_f,y_f))+w_0(\lvert \theta_f - \theta_i \rvert)
h=L2((xi,yi),(xf,yf))+w0(∣θf−θi∣)
其中
w
0
w_0
w0为heading error的加权值,
∣
θ
f
−
θ
i
∣
\lvert \theta_f - \theta_i \rvert
∣θf−θi∣是航向误差。
当然,启发函数的制定是根据任务所定,多种多样,这只是[1]中提到的一种方式。但这种估计方式是有缺点的,如同A*算法中提到的一样,我们的启发式估计成本必须小于实际成本,也就是说 h ( t ) < h ∗ h(t) < h * h(t)<h∗,但是上述方式中不恰当的加权 w 0 w_0 w0可能会使得估计成本超出实际成本。那该采取怎么样的启发函数呢?[1]中给出了一种为Look_Up_Table(LUT)的启发函数设计方案。
这个Look_Up_Table其实就是预计算,举例: 将Lattice origin点到其附近的点(该点与Lattice origin的距离小于3)的成本预先计算好纳入这个查找表中,为什么是距离小于3呢?因为只有在接近机器人的时候,曲率和方向才会对路径产生较大的影响,对于超出这个距离的部分大豆都是直接冲的,可以直接使用简单的欧几里得距离作为路径启发代价的评判。
[1] Pivtoraiko M, Kelly A. Efficient constrained path planning via search in state lattices[C]//International Symposium on Artificial Intelligence, Robotics, and Automation in Space. Germany: Munich, 2005: 1-7.
[2] Pivtoraiko M, Knepper R A, Kelly A. Differentially constrained mobile robot motion planning in state lattices[J]. Journal of Field Robotics, 2009, 26(3): 308-333.
[3]Howard T M, Green C J, Kelly A, et al. State space sampling of feasible motions for high‐performance mobile robot navigation in complex environments[J]. Journal of Field Robotics, 2008, 25(6‐7): 325-345.
[4] Learning Control Sets for Lattice Planner – university of WATERLOO
[4]深蓝学院移动机器人Motion Planning
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。