|
1 | 1 | """
|
2 | 2 |
|
3 |
| -Path Planning with 4 point Beizer curve |
| 3 | +Path Planning with Bezier curve. |
4 | 4 |
|
5 | 5 | author: Atsushi Sakai(@Atsushi_twi)
|
6 | 6 |
|
7 | 7 | """
|
| 8 | +from __future__ import division, print_function |
8 | 9 |
|
9 | 10 | import scipy.special
|
10 | 11 | import numpy as np
|
11 | 12 | import matplotlib.pyplot as plt
|
12 |
| -import math |
13 | 13 |
|
14 | 14 | show_animation = True
|
15 | 15 |
|
16 | 16 |
|
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( |
20 | 32 | [[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)], |
23 | 35 | [ex, ey]])
|
24 | 36 |
|
| 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 | + """ |
25 | 50 | 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)) |
29 | 53 |
|
30 |
| - return P, cp |
| 54 | + return np.array(traj) |
31 | 55 |
|
32 | 56 |
|
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. |
35 | 60 |
|
| 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) |
36 | 67 |
|
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 |
42 | 68 |
|
| 69 | +def bezier(t, control_points): |
| 70 | + """ |
| 71 | + Return one point on the bezier curve. |
43 | 72 |
|
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): |
47 | 82 | """
|
| 83 | + Compute control points of the successive derivatives of a given bezier curve. |
48 | 84 |
|
| 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.""" |
49 | 116 | if not isinstance(x, float):
|
50 | 117 | for (ix, iy, iyaw) in zip(x, y, yaw):
|
51 | 118 | plot_arrow(ix, iy, iyaw)
|
52 | 119 | 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), |
54 | 121 | fc=fc, ec=ec, head_width=width, head_length=width)
|
55 | 122 | plt.plot(x, y)
|
56 | 123 |
|
57 | 124 |
|
58 | 125 | def main():
|
| 126 | + """Plot an example bezier curve.""" |
59 | 127 | start_x = 10.0 # [m]
|
60 | 128 | start_y = 1.0 # [m]
|
61 |
| - start_yaw = math.radians(180.0) # [rad] |
| 129 | + start_yaw = np.radians(180.0) # [rad] |
62 | 130 |
|
63 | 131 | end_x = -0.0 # [m]
|
64 | 132 | end_y = -3.0 # [m]
|
65 |
| - end_yaw = math.radians(-45.0) # [rad] |
| 133 | + end_yaw = np.radians(-45.0) # [rad] |
66 | 134 | offset = 3.0
|
67 | 135 |
|
68 |
| - P, cp = calc_4point_bezier_path( |
| 136 | + path, control_points = calc_4points_bezier_path( |
69 | 137 | start_x, start_y, start_yaw, end_x, end_y, end_yaw, offset)
|
70 | 138 |
|
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" |
75 | 164 |
|
76 | 165 | 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) |
79 | 173 | plot_arrow(start_x, start_y, start_yaw)
|
80 | 174 | 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) |
84 | 178 | plt.show()
|
85 | 179 |
|
86 | 180 |
|
87 | 181 | def main2():
|
| 182 | + """Show the effect of the offset.""" |
88 | 183 | start_x = 10.0 # [m]
|
89 | 184 | start_y = 1.0 # [m]
|
90 |
| - start_yaw = math.radians(180.0) # [rad] |
| 185 | + start_yaw = np.radians(180.0) # [rad] |
91 | 186 |
|
92 | 187 | end_x = -0.0 # [m]
|
93 | 188 | 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] |
96 | 190 |
|
97 | 191 | 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( |
99 | 193 | 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" |
104 | 198 |
|
105 | 199 | 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)) |
107 | 201 |
|
108 | 202 | if show_animation:
|
109 | 203 | plot_arrow(start_x, start_y, start_yaw)
|
|
0 commit comments