w# DubinsPath
简介
Dubins曲线是在满足曲率约束和规定的始端和末端的切线的条件下,连接两个二维平面的最短路径,限制目标只能向前行进。
Dubins曲线的目的是规划一条满足转弯半径、前进方向、初始相对位置和速度方向的最短曲线。
Dubins曲线分类
在运动方向已知和转向半径最小的情况下,从初始向量到终止向量的最短的路径是由直线和最小半径转向圆弧组成的。
计算
-
输入参数
出发点
P
i
:
(
x
1
,
y
1
,
α
)
P_i:(x_1,y_1,\alpha)
Pi?:(x1?,y1?,α)
终止点
P
f
:
(
x
2
,
y
2
,
β
)
P_f:(x_2,y_2,\beta)
Pf?:(x2?,y2?,β)
转弯半径
r
r
r
-
输出
Dubins路径的长度和路径点
-
坐标系转换 将
P
i
P_i
Pi?变为新坐标系的原点,旋转坐标系使
P
f
P_f
Pf?在
X
X
X轴上。
P
i
:
(
x
1
,
y
1
,
α
)
?
>
P
i
′
:
(
0
,
0
,
α
?
θ
(
α
′
)
)
P_i:(x_1,y_1,\alpha)->P_i':(0,0,\alpha-\theta(\alpha'))
Pi?:(x1?,y1?,α)?>Pi′?:(0,0,α?θ(α′))
P
f
:
(
x
2
,
y
2
,
β
)
?
>
P
f
′
:
(
d
,
0
,
β
?
θ
(
β
′
)
)
P_f:(x_2,y_2,\beta)->P_f':(d,0,\beta-\theta(\beta'))
Pf?:(x2?,y2?,β)?>Pf′?:(d,0,β?θ(β′)) 其中:
{
θ
=
arctan
?
[
(
y
2
?
y
1
)
/
(
x
2
?
x
1
)
]
∣
P
i
P
f
?
∣
=
(
x
1
?
x
2
)
2
+
(
y
1
?
y
2
)
2
d
=
∣
P
i
P
f
?
∣
/
r
\begin{cases} \theta=\arctan{[(y_2-y_1)/(x_2-x_1)]}\\ |\vec{P_iP_f}|=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\\ d=|\vec{P_iP_f}|/r\\ \end{cases}
??????θ=arctan[(y2??y1?)/(x2??x1?)]∣Pi?Pf?
?∣=(x1??x2?)2+(y1??y2?)2
?d=∣Pi?Pf?
?∣/r?
转弯半径被归一化为1,可以直接使用弧度来表示路径长度。 -
计算Dubins路径长度
-
RSR型路径
- 根据起始点的坐标和方向推算出圆心的坐标
- 找到两圆的公切线中满足逆时针方向的那一条
-
LSL型路径 -
LSR型路径
- 坐标系转换
python代码实现
文字部分后续补全,代码如下:
import numpy as np
import matplotlib.pyplot as plt
from math import pi, sin, cos, acos, atan2, sqrt
def Dubins_path(Pi, Pf, alpha, beta, ratio, tp="RSR", show_path=True):
assert len(Pi) == 2 and len(Pf) == 2
assert tp in ["RSR", "LSL", "RSL", "LSR"]
assert ratio > 0.
Vec_Move = np.matrix([[1., 0., -Pi[0]], [0., 1., -Pi[1]], [0., 0., 1.]])
temp_theta = -(atan2(Pf[1] - Pi[1], Pf[0] - Pi[0]) + pi)
alpha_ = alpha + temp_theta
beta_ = beta + temp_theta
Vec_Rotate = np.matrix([[cos(temp_theta), -sin(temp_theta), 0.],
[sin(temp_theta), cos(temp_theta), 0.],
[0., 0., 1.]])
Vec_Length = np.matrix([[1. / ratio, 0., 0.],
[0., 1. / ratio, 0.],
[0., 0., 1.]])
Vec_Trans = Vec_Length * Vec_Rotate * Vec_Move
temp_res = Vec_Trans * [[Pi[0]], [Pi[1]], [1]]
Pi_ = [temp_res[0, 0], temp_res[1, 0]]
temp_res = Vec_Trans * [[Pf[0]], [Pf[1]], [1]]
Pf_ = [temp_res[0, 0], temp_res[1, 0]]
if tp == "RSR":
O1_x, O1_y = sin(alpha_), -cos(alpha_)
O2_x, O2_y = Pf_[0] + sin(beta_), -cos(beta_)
theta = atan2(O2_y - O1_y, O2_x - O1_x)
pho1 = (theta - alpha_) % (2.0 * pi)
pho2 = sqrt((O1_x - O2_x)**2 + (O1_y - O2_y)**2)
pho3 = (beta_ - pho1 - pho2 - alpha_) % (2.0 * pi)
A_x, A_y = sin(alpha_) - sin(alpha_ + pho1), -cos(alpha_) + cos(alpha_ + pho1)
B_x, B_y = Pf_[0] + sin(beta_) - sin(alpha_ + pho1), -cos(beta_) + cos(alpha_ + pho1)
elif tp == "LSL":
O1_x, O1_y = -sin(alpha_), cos(alpha_)
O2_x, O2_y = Pf_[0] - sin(beta_), cos(beta_)
theta = atan2(O2_y - O1_y, O2_x - O1_x)
pho1 = (theta - alpha_) % (2.0 * pi)
pho2 = sqrt((O1_x - O2_x)**2 + (O1_y - O2_y)**2)
pho3 = (beta_ - pho1 - pho2 - alpha_) % (2.0 * pi)
A_x, A_y = -sin(alpha_) + sin(alpha_ + pho1), cos(alpha_) - cos(alpha_ + pho1)
B_x, B_y = Pf_[0] - sin(beta_) + sin(alpha_ + pho1), cos(beta_) - cos(alpha_ + pho1)
elif tp == "RSL":
O1_x, O1_y = sin(alpha_), -cos(alpha_)
O2_x, O2_y = Pf_[0] - sin(beta_), cos(beta_)
theta = atan2(O2_y - O1_y, O2_x - O1_x)
dis_O1O2 = sqrt((O2_y - O1_y)**2 + (O2_x - O1_x)**2)
if dis_O1O2 < 2.0:
print("The geometry constraint is not satisfied!")
return None
else:
theta_O1A = acos(2. / dis_O1O2)
A_x, A_y = sin(alpha_) + cos(theta + theta_O1A), -cos(alpha_) + sin(theta + theta_O1A)
B_x, B_y = Pf_[0] - sin(beta_) - cos(theta + theta_O1A), cos(beta_) - sin( theta + theta_O1A)
else:
O1_x, O1_y = -sin(alpha_), cos(alpha_)
O2_x, O2_y = Pf_[0] + sin(beta_), -cos(beta_)
theta = atan2(O2_y - O1_y, O2_x - O1_x)
dis_O1O2 = sqrt((O2_y - O1_y)**2 + (O2_x - O1_x)**2)
if dis_O1O2 < 2.0:
print("The geometry constraint is not satisfied!")
return None
else:
theta_O1A = acos(2. / dis_O1O2)
A_x, A_y = -sin(alpha_) + cos(theta - theta_O1A), cos(alpha_) + sin(theta - theta_O1A)
B_x, B_y = Pf_[0] + sin(beta_) - cos(theta - theta_O1A), -cos(beta_) - sin(theta - theta_O1A)
Vec_Trans_INV = Vec_Trans.I
temp_res = Vec_Trans_INV * [[A_x], [A_y], [1]]
A_x, A_y = (temp_res[0, 0], temp_res[1, 0])
temp_res = Vec_Trans_INV * [[B_x], [B_y], [1]]
B_x, B_y = (temp_res[0, 0], temp_res[1, 0])
temp_res = Vec_Trans_INV * [[O1_x], [O1_y], [1]]
O1_x, O1_y = (temp_res[0, 0], temp_res[1, 0])
temp_res = Vec_Trans_INV * [[O2_x], [O2_y], [1]]
O2_x, O2_y = (temp_res[0, 0], temp_res[1, 0])
dis_lst = []
ati1, ati2 = atan2(Pi[1] - O1_y, Pi[0] - O1_x), atan2(A_y - O1_y, A_x - O1_x)
if tp[0] == "R" and ati1 < ati2:
ati1 += 2.0 * pi
elif tp[0] == "L" and ati1 > ati2:
ati1 -= 2.0 * pi
dis_lst.append(ratio * abs(ati1 - ati2))
dis_lst.append(sqrt((A_x - B_x)**2 + (A_y - B_y)**2))
atf1 = atan2(Pf[1] - O2_y, Pf[0] - O2_x)
atf2 = atan2(B_y - O2_y, B_x - O2_x)
if tp[2] == "R" and atf2 < atf1:
atf2 += 2.0 * pi
elif tp[2] == "L" and atf2 > atf1:
atf2 -= 2.0 * pi
dis_lst.append(ratio * abs(atf1 - atf2))
if show_path:
plt.figure(figsize=(12, 12))
arrow_length = 0.75
plt.arrow(Pi[0],
Pi[1],
arrow_length * cos(alpha),
arrow_length * sin(alpha),
width=0.01,
length_includes_head=True,
head_width=0.05,
head_length=0.1,
fc='b',
ec='b')
plt.arrow(Pf[0],
Pf[1],
arrow_length * cos(beta),
arrow_length * sin(beta),
width=0.01,
length_includes_head=True,
head_width=0.05,
head_length=0.1,
fc='r',
ec='r')
plt.scatter(np.array([Pi[0], A_x, B_x, Pf[0]]), np.array([Pi[1], A_y, B_y, Pf[1]]))
plt.scatter(np.array([O1_x, O2_x]), np.array([O1_y, O2_y]), c='r')
the = np.linspace(-pi, pi, 200)
plt.scatter((O1_x + ratio * np.cos(the)), (O1_y + ratio * np.sin(the)), s=0.1, c='r')
plt.scatter((O2_x + ratio * np.cos(the)), (O2_y + ratio * np.sin(the)), s=0.1, c='r')
the = np.linspace(ati2, ati1, 200)
plt.plot((O1_x + ratio * np.cos(the)), (O1_y + ratio * np.sin(the)), c='b')
plt.plot([A_x, B_x], [A_y, B_y], c='b')
the = np.linspace(atf1, atf2, 200)
plt.plot((O2_x + ratio * np.cos(the)), (O2_y + ratio * np.sin(the)), c='b')
ax = plt.gca()
ax.set_aspect(1)
plt.title("Dubins Path")
plt.grid(True)
plt.show()
return ([A_x, A_y], [B_x, B_y], dis_lst)
if __name__ == "__main__":
print("Dubins Path")
path_point = Dubins_path(Pi=(-4., 1.), Pf=(5., -2.), alpha=-1, beta=1, ratio=2, tp="RSL")
print(path_point)
效果
Julia代码实现
##########################################################
# 功能:
# 输入起止点的坐标,方向,转弯半径,曲线的种类,计算Dubins曲线
#
# 输入参数:
# 起点坐标:
# Pi(x,y)
# 终点坐标:
# Pf(x,y)
# 起点角度:
# alpha(弧度)
# 终点角度:
# beta(弧度)
# 转弯半径:
# ratio
# 曲线类型:
# tp "RSR","LSL","RSL","LSR"
# ( R顺时针 L逆时针 S直线 default:"RSR" )
#
# 输出结果:三元组
# 切出点坐标posiA:[A_x,A_y]
# 切入点坐标posiB:[B_x,B_y]
# 三段距离dist lst:[dist1出发圆弧,dist2直线,dist3到达圆弧
#
# Author: jia-yf19@mails.tsinghua.edu.cn
# Date : 2021.10.29
#
##########################################################
function Dubins_path(Pi,Pf,α,β,r,tp="SRS")
# 坐标系变化
Mtr_Move = [1. 0. -Pi[1]; 0. 1. -Pi[2]; 0. 0. 1.] # 平移
temp_θ = -(atan(Pf[2] - Pi[2], Pf[1] - Pi[1]) + π)
α_ = α + temp_θ
β_ = β + temp_θ
Mtr_Rotate = [cos(temp_θ) -sin(temp_θ) 0.; sin(temp_θ) cos(temp_θ) 0.; 0. 0. 1.] # 旋转
Mtr_Length = [1. / r 0. 0.; 0. 1. / r 0.; 0. 0. 1.] # 拉伸
Mtr_Trans = Mtr_Length * Mtr_Rotate *Mtr_Move
temp_res = Mtr_Trans * [Pi[1]; Pi[2]; 1]
Pi_ = [temp_res[1, 1], temp_res[2, 1]]
temp_res = Mtr_Trans * [Pf[1]; Pf[2]; 1]
Pf_ = [temp_res[1, 1], temp_res[2, 1]]
# 曲线计算
if tp == "RSR" # 双逆时针路线
Φ1_x, Φ1_y = sin(α_), -cos(α_) # 起点圆
Φ2_x, Φ2_y = Pf_[1] + sin(β_), -cos(β_) # 终点圆
θ = atan(Φ2_y - Φ1_y, Φ2_x - Φ1_x) # 圆心夹角
ρ1 = (θ - α_) % (2.0 * π) # 起点圆弧度
ρ2 = hypot((Φ1_x - Φ2_x), (Φ1_y - Φ2_y)) # 直线长度
ρ3 = (β_ - ρ1 - ρ2 - α_) % (2.0 * π) # 终点圆弧度
A_x, A_y = sin(α_) - sin(α_ + ρ1), -cos(α_) + cos(α_ + ρ1) # 切出点
B_x, B_y = Pf_[1] + sin(β_) - sin(α_ + ρ1), -cos(β_) + cos(α_ + ρ1) # 切入点
elseif tp == "LSL" # 双顺时针路线
Φ1_x, Φ1_y = -sin(α_), cos(α_) # 起点圆
Φ2_x, Φ2_y = Pf_[1] - sin(β_), cos(β_) # 终点圆
θ = atan(Φ2_y - Φ1_y, Φ2_x - Φ1_x) # 圆心夹角
ρ1 = (θ - α_) % (2.0 * π) # 起点圆弧度
ρ2 = hypot((Φ1_x - Φ2_x), (Φ1_y - Φ2_y)) # 直线长度
ρ3 = (β_ - ρ1 - ρ2 - α_) % (2.0 * π) # 终点圆弧度
A_x, A_y = -sin(α_) + sin(α_ + ρ1), cos(α_) - cos(α_ + ρ1) # 切出点
B_x, B_y = Pf_[1] - sin(β_) + sin(α_ + ρ1), cos(β_) - cos(α_ + ρ1) # 切入点
elseif tp == "RSL" # 八字交叉路线
Φ1_x, Φ1_y = sin(α_), -cos(α_) # 起点圆
Φ2_x, Φ2_y = Pf_[1] - sin(β_), cos(β_) # 终点圆
θ = atan(Φ2_y - Φ1_y, Φ2_x - Φ1_x) # 圆心夹角
dis_Φ1Φ2 = hypot((Φ2_y - Φ1_y), (Φ2_x - Φ1_x))
if dis_Φ1Φ2 < 2.0
println("The geometry constraint is not satisfied!")
return Nothing
else
θ_Φ1A = acos(2. / dis_Φ1Φ2) # 圆1圆心和A夹角
A_x, A_y = sin(α_) + cos(θ + θ_Φ1A), -cos(α_) + sin(θ + θ_Φ1A) # 切出点
B_x, B_y = Pf_[1] - sin(β_) - cos(θ + θ_Φ1A), cos(β_) - sin(θ + θ_Φ1A) # 切入点
end
else # "LSR" # 另一种八字交叉路线
Φ1_x, Φ1_y = -sin(α_), cos(α_) # 起点圆
Φ2_x, Φ2_y = Pf_[1] + sin(β_), -cos(β_) # 终点圆
θ = atan(Φ2_y - Φ1_y, Φ2_x - Φ1_x) # 圆心夹角
dis_Φ1Φ2 = hypot((Φ2_y - Φ1_y), (Φ2_x - Φ1_x))
if dis_Φ1Φ2 < 2.0
println("The geometry constraint is not satisfied!")
return Nothing
else
θ_Φ1A = acos(2. / dis_Φ1Φ2) # 圆1圆心和A夹角
A_x, A_y = -sin(α_) + cos(θ - θ_Φ1A), cos(α_) + sin(θ - θ_Φ1A) # 切出点
B_x, B_y = Pf_[1] + sin(β_) - cos(θ - θ_Φ1A), -cos(β_) - sin(θ - θ_Φ1A) # 切入点
end
end
# 逆变换
Mtr_Trans_INV = inv(Mtr_Trans)
temp_res = Mtr_Trans_INV * [A_x; A_y; 1]
A_x, A_y = (temp_res[1, 1], temp_res[2, 1])
temp_res = Mtr_Trans_INV * [B_x; B_y; 1]
B_x, B_y = (temp_res[1, 1], temp_res[2, 1])
temp_res = Mtr_Trans_INV * [Φ1_x; Φ1_y; 1]
Φ1_x, Φ1_y = (temp_res[1, 1], temp_res[2, 1])
temp_res = Mtr_Trans_INV * [Φ2_x; Φ2_y; 1]
Φ2_x, Φ2_y = (temp_res[1, 1], temp_res[2, 1])
dis_lst = [0., 0., 0.] # 每一段的距离
ati1, ati2 = atan(Pi[2] - Φ1_y, Pi[1] - Φ1_x), atan(A_y - Φ1_y, A_x - Φ1_x)
if tp[1] == 'R' && ati1 < ati2
ati1 += 2.0 * π
elseif tp[1] == 'L' && ati1 > ati2
ati1 -= 2.0 * π
end
dis_lst[1] = r * abs(ati1 - ati2) # 切出弧长度
dis_lst[2] = hypot((A_x - B_x), (A_y - B_y)) # 直线段距离
atf1 = atan(Pf[2] - Φ2_y, Pf[1] - Φ2_x)
atf2 = atan(B_y - Φ2_y, B_x - Φ2_x)
if tp[end] == 'R' && atf2 < atf1
atf2 += 2.0 * π
elseif tp[end] == 'L' && atf2 > atf1
atf2 -= 2.0 * π
end
dis_lst[3] = (r * abs(atf1 - atf2)) # 切入弧长度
return ([A_x, A_y], [B_x, B_y], dis_lst) # 切出点,切入点,每一段的距离
end
@time path_point = Dubins_path((2., 1.), (3., 2.), -1, 1, 3, "RSL")
println(path_point)
相关链接
|