Skip to content

Commit c145793

Browse files
committed
Add comments + support for n point bezier curve
Add documentation for bezier curve + add computation of derivatives and curvature + display tanget, normal and radius of curvature
1 parent 7008ef8 commit c145793

File tree

1 file changed

+137
-43
lines changed

1 file changed

+137
-43
lines changed

PathPlanning/BezierPath/bezier_path.py

Lines changed: 137 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,203 @@
11
"""
22
3-
Path Planning with 4 point Beizer curve
3+
Path Planning with Bezier curve.
44
55
author: Atsushi Sakai(@Atsushi_twi)
66
77
"""
8+
from __future__ import division, print_function
89

910
import scipy.special
1011
import numpy as np
1112
import matplotlib.pyplot as plt
12-
import math
1313

1414
show_animation = True
1515

1616

17-
def calc_4point_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset):
18-
D = math.sqrt((sx - ex)**2 + (sy - ey)**2) / offset
19-
cp = np.array(
17+
def calc_4points_bezier_path(sx, sy, syaw, ex, ey, eyaw, offset):
18+
"""
19+
Compute control points and path given start and end position.
20+
21+
:param sx: (float) x-coordinate of the starting point
22+
:param sy: (float) y-coordinate of the starting point
23+
:param syaw: (float) yaw angle at start
24+
:param ex: (float) x-coordinate of the ending point
25+
:param ey: (float) y-coordinate of the ending point
26+
:param eyaw: (float) yaw angle at the end
27+
:param offset: (float)
28+
:return: (numpy array, numpy array)
29+
"""
30+
dist = np.sqrt((sx - ex) ** 2 + (sy - ey) ** 2) / offset
31+
control_points = np.array(
2032
[[sx, sy],
21-
[sx + D * math.cos(syaw), sy + D * math.sin(syaw)],
22-
[ex - D * math.cos(eyaw), ey - D * math.sin(eyaw)],
33+
[sx + dist * np.cos(syaw), sy + dist * np.sin(syaw)],
34+
[ex - dist * np.cos(eyaw), ey - dist * np.sin(eyaw)],
2335
[ex, ey]])
2436

37+
path = calc_bezier_path(control_points, n_points=100)
38+
39+
return path, control_points
40+
41+
42+
def calc_bezier_path(control_points, n_points=100):
43+
"""
44+
Compute bezier path (trajectory) given control points.
45+
46+
:param control_points: (numpy array)
47+
:param n_points: (int) number of points in the trajectory
48+
:return: (numpy array)
49+
"""
2550
traj = []
26-
for t in np.linspace(0, 1, 100):
27-
traj.append(bezier(3, t, cp))
28-
P = np.array(traj)
51+
for t in np.linspace(0, 1, n_points):
52+
traj.append(bezier(t, control_points))
2953

30-
return P, cp
54+
return np.array(traj)
3155

3256

33-
def bernstein(n, i, t):
34-
return scipy.special.comb(n, i) * t**i * (1 - t)**(n - i)
57+
def bernstein_poly(n, i, t):
58+
"""
59+
Bernstein polynom.
3560
61+
:param n: (int) polynom degree
62+
:param i: (int)
63+
:param t: (float)
64+
:return: (float)
65+
"""
66+
return scipy.special.comb(n, i) * t ** i * (1 - t) ** (n - i)
3667

37-
def bezier(n, t, q):
38-
p = np.zeros(2)
39-
for i in range(n + 1):
40-
p += bernstein(n, i, t) * q[i]
41-
return p
4268

69+
def bezier(t, control_points):
70+
"""
71+
Return one point on the bezier curve.
4372
44-
def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"):
45-
u"""
46-
Plot arrow
73+
:param t: (float) number in [0, 1]
74+
:param control_points: (numpy array)
75+
:return: (numpy array) Coordinates of the point
76+
"""
77+
n = len(control_points) - 1
78+
return np.sum([bernstein_poly(n, i, t) * control_points[i] for i in range(n + 1)], axis=0)
79+
80+
81+
def bezier_derivatives_control_points(control_points, n_derivatives):
4782
"""
83+
Compute control points of the successive derivatives of a given bezier curve.
4884
85+
A derivative of a bezier curve is a bezier curve.
86+
See https://pomax.github.io/bezierinfo/#derivatives
87+
for detailed explanations
88+
89+
:param control_points: (numpy array)
90+
:param n_derivatives: (int)
91+
e.g., n_derivatives=2 -> compute control points for first and second derivatives
92+
:return: ([numpy array])
93+
"""
94+
w = {0: control_points}
95+
for i in range(n_derivatives):
96+
n = len(w[i])
97+
w[i + 1] = np.array([(n - 1) * (w[i][j + 1] - w[i][j]) for j in range(n - 1)])
98+
return w
99+
100+
101+
def curvature(dx, dy, ddx, ddy):
102+
"""
103+
Compute curvature at one point given first and second derivatives.
104+
105+
:param dx: (float) First derivative along x axis
106+
:param dy: (float)
107+
:param ddx: (float) Second derivative along x axis
108+
:param ddy: (float)
109+
:return: (float)
110+
"""
111+
return (dx * ddy - dy * ddx) / (dx ** 2 + dy ** 2) ** (3 / 2)
112+
113+
114+
def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"):
115+
"""Plot arrow."""
49116
if not isinstance(x, float):
50117
for (ix, iy, iyaw) in zip(x, y, yaw):
51118
plot_arrow(ix, iy, iyaw)
52119
else:
53-
plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw),
120+
plt.arrow(x, y, length * np.cos(yaw), length * np.sin(yaw),
54121
fc=fc, ec=ec, head_width=width, head_length=width)
55122
plt.plot(x, y)
56123

57124

58125
def main():
126+
"""Plot an example bezier curve."""
59127
start_x = 10.0 # [m]
60128
start_y = 1.0 # [m]
61-
start_yaw = math.radians(180.0) # [rad]
129+
start_yaw = np.radians(180.0) # [rad]
62130

63131
end_x = -0.0 # [m]
64132
end_y = -3.0 # [m]
65-
end_yaw = math.radians(-45.0) # [rad]
133+
end_yaw = np.radians(-45.0) # [rad]
66134
offset = 3.0
67135

68-
P, cp = calc_4point_bezier_path(
136+
path, control_points = calc_4points_bezier_path(
69137
start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset)
70138

71-
assert P.T[0][0] == start_x, "path is invalid"
72-
assert P.T[1][0] == start_y, "path is invalid"
73-
assert P.T[0][-1] == end_x, "path is invalid"
74-
assert P.T[1][-1] == end_y, "path is invalid"
139+
# Note: alternatively, instead of specifying start and end position
140+
# you can directly define n control points and compute the path:
141+
# control_points = np.array([[5., 1.], [-2.78, 1.], [-11.5, -4.5], [-6., -8.]])
142+
# path = calc_bezier_path(control_points, n_points=100)
143+
144+
# Display the tangent, normal and radius of cruvature at a given point
145+
t = 0.86 # Number in [0, 1]
146+
x_target, y_target = bezier(t, control_points)
147+
derivatives_cp = bezier_derivatives_control_points(control_points, 2)
148+
point = bezier(t, control_points)
149+
dt = bezier(t, derivatives_cp[1])
150+
ddt = bezier(t, derivatives_cp[2])
151+
# Radius of curvature
152+
radius = 1 / curvature(dt[0], dt[1], ddt[0], ddt[1])
153+
# Normalize derivative
154+
dt /= np.linalg.norm(dt, 2)
155+
tangent = np.array([point, point + dt])
156+
normal = np.array([point, point + [- dt[1], dt[0]]])
157+
curvature_center = point + np.array([- dt[1], dt[0]]) * radius
158+
circle = plt.Circle(tuple(curvature_center), radius, color=(0, 0.8, 0.8), fill=False, linewidth=1)
159+
160+
assert path.T[0][0] == start_x, "path is invalid"
161+
assert path.T[1][0] == start_y, "path is invalid"
162+
assert path.T[0][-1] == end_x, "path is invalid"
163+
assert path.T[1][-1] == end_y, "path is invalid"
75164

76165
if show_animation:
77-
plt.plot(P.T[0], P.T[1], label="Bezier Path")
78-
plt.plot(cp.T[0], cp.T[1], '--o', label="Control Points")
166+
fig, ax = plt.subplots()
167+
ax.plot(path.T[0], path.T[1], label="Bezier Path")
168+
ax.plot(control_points.T[0], control_points.T[1], '--o', label="Control Points")
169+
ax.plot(x_target, y_target)
170+
ax.plot(tangent[:, 0], tangent[:, 1], label="Tangent")
171+
ax.plot(normal[:, 0], normal[:, 1], label="Normal")
172+
ax.add_artist(circle)
79173
plot_arrow(start_x, start_y, start_yaw)
80174
plot_arrow(end_x, end_y, end_yaw)
81-
plt.legend()
82-
plt.axis("equal")
83-
plt.grid(True)
175+
ax.legend()
176+
ax.axis("equal")
177+
ax.grid(True)
84178
plt.show()
85179

86180

87181
def main2():
182+
"""Show the effect of the offset."""
88183
start_x = 10.0 # [m]
89184
start_y = 1.0 # [m]
90-
start_yaw = math.radians(180.0) # [rad]
185+
start_yaw = np.radians(180.0) # [rad]
91186

92187
end_x = -0.0 # [m]
93188
end_y = -3.0 # [m]
94-
end_yaw = math.radians(-45.0) # [rad]
95-
offset = 3.0
189+
end_yaw = np.radians(-45.0) # [rad]
96190

97191
for offset in np.arange(1.0, 5.0, 1.0):
98-
P, cp = calc_4point_bezier_path(
192+
path, control_points = calc_4points_bezier_path(
99193
start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset)
100-
assert P.T[0][0] == start_x, "path is invalid"
101-
assert P.T[1][0] == start_y, "path is invalid"
102-
assert P.T[0][-1] == end_x, "path is invalid"
103-
assert P.T[1][-1] == end_y, "path is invalid"
194+
assert path.T[0][0] == start_x, "path is invalid"
195+
assert path.T[1][0] == start_y, "path is invalid"
196+
assert path.T[0][-1] == end_x, "path is invalid"
197+
assert path.T[1][-1] == end_y, "path is invalid"
104198

105199
if show_animation:
106-
plt.plot(P.T[0], P.T[1], label="Offset=" + str(offset))
200+
plt.plot(path.T[0], path.T[1], label="Offset=" + str(offset))
107201

108202
if show_animation:
109203
plot_arrow(start_x, start_y, start_yaw)

0 commit comments

Comments
 (0)