bezier_curve.py
Go to the documentation of this file.
1 # Copyright (c) 2016-2019 The UUV Simulator Authors.
2 # All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import numpy as np
17 try:
18  from scipy.misc import factorial
19 except ImportError as error:
20  # factorial has been removed from scipy.misc in version 1.3.0.
21  from scipy.special import factorial
22 
23 
24 
25 class BezierCurve(object):
26  """
27  Implementation of [Bezier curves](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
28  of orders 3, 4 and 5 based on [1].
29 
30  > *Input arguments*
31 
32  * `pnts` (*type:* `list`): List of 3D points as a vector (example: `[[0, 0, 0], [0, 1, 2]]`)
33  * `order` (*type:* `int`): Order of the Bezier curve, options are 3, 4 or 5
34  * `tangents` (*type:* `list`, *default:* `None`): Optional input of the tangent
35  vectors for each of the input points. In case only two points are provided,
36  the tangents have to be provided, too. Otherwise, the tangents will be calculated.
37  * `normals` (*type:* `list`, *default:* `None`): Optional input of the normal
38  vectors for each of the input points. In case only two points are provided,
39  the normals have to be provided, too. Otherwise, the normals will be calculated.
40 
41  !!! note
42 
43  [1] Biagiotti, Luigi, and Claudio Melchiorri. Trajectory planning for
44  automatic machines and robots. Springer Science & Business Media, 2008.
45  """
46  def __init__(self, pnts, order, tangents=None, normals=None):
47  assert order in [3, 4, 5], 'Invalid Bezier curve order'
48  assert type(pnts) == list and len(pnts) >= 2, 'At least two points are needed to calculate the curve'
49 
50  self._pnts = list()
51  for pnt in pnts:
52  if type(pnt) == list:
53  assert len(pnt) == 3, 'Point must have three elements'
54  self._pnts.append(np.array(pnt))
55  elif type(pnt) == np.ndarray:
56  assert pnt.size == 3, 'Point must have three elements'
57  self._pnts.append(pnt)
58  else:
59  raise TypeError('Point in list is neither a list or an array')
60 
61  if tangents is not None:
62  assert type(tangents) == list and len(tangents) == 2, 'Tangent vectors must be provided'
63  for t in tangents:
64  if type(t) == list:
65  assert len(t) == 3, 'Tangent vector must have three elements'
66  elif type(t) == np.ndarray:
67  assert t.size == 3, 'Tangent vector must have three elements'
68  else:
69  raise TypeError('Tangent vector is neither a list or an array')
70 
71  self._control_pnts = [np.zeros(3) for _ in range(order + 1)]
72 
73  self._order = order
74  if self._order == 3:
75  assert len(self._pnts) == 2, 'Two points are needed for the curve to be computed'
76  # Setting initial and last control points
77  self._control_pnts[0] = self._pnts[0]
78  self._control_pnts[3] = self._pnts[1]
79  # Compute alpha
80  a = 16 - np.linalg.norm(tangents[0] + tangents[1])**2
81  b = 12 * np.dot(self._control_pnts[3] - self._control_pnts[0], tangents[0] + tangents[1])
82  c = -36 * np.linalg.norm(self._control_pnts[3] - self._control_pnts[0])**2
83  alpha = np.roots([a, b, c]).max()
84 
85  # Compute the rest of the control points
86  self._control_pnts[1] = self._control_pnts[0] + (1.0 / 3) * alpha * tangents[0]
87  self._control_pnts[2] = self._control_pnts[3] - (1.0 / 3) * alpha * tangents[1]
88  elif self._order == 4:
89  assert len(self._pnts) == 3, 'Three points are needed for the curve to be computed'
90  # Setting initial control points
91  self._control_pnts[0] = self._pnts[0]
92  self._control_pnts[2] = self._pnts[1]
93  self._control_pnts[4] = self._pnts[2]
94 
95  radius = np.linalg.norm(self._pnts[0] - self._pnts[1])
96  tangents = list()
97  tangents.append((self._pnts[1] - self._pnts[0]) / radius)
98  tangents.append((self._pnts[2] - self._pnts[1]) / radius)
99 
100  # Compute alpha
101  a = 4 - (1.0 / 4) * np.linalg.norm(tangents[0] + tangents[1])**2
102  b = 3 * np.dot(self._control_pnts[4] - self._control_pnts[0], tangents[0] + tangents[1])
103  c = -9 * np.linalg.norm(self._control_pnts[4] - self._control_pnts[0])**2
104  alpha = np.roots([a, b, c]).max()
105 
106  # Compute the rest of the control points
107  self._control_pnts[1] = self._control_pnts[0] + 0.25 * alpha * tangents[0]
108  self._control_pnts[3] = self._control_pnts[4] - 0.25 * alpha * tangents[1]
109  elif self._order == 5:
110  if len(self._pnts) == 3:
111  # Setting initial control points
112  self._control_pnts[0] = self._pnts[0]
113  self._control_pnts[5] = self._pnts[2]
114 
115  radius = np.linalg.norm(self._pnts[0] - self._pnts[1])
116  tangents = list()
117  tangents.append((self._pnts[1] - self._pnts[0]) / radius)
118  tangents.append((self._pnts[2] - self._pnts[1]) / radius)
119 
120  # Compute alpha
121  a = 256 - 49 * np.linalg.norm(tangents[0] + tangents[1])**2
122  b = 420 * np.dot(self._control_pnts[5] - self._control_pnts[0], tangents[0] + tangents[1])
123  c = -900 * np.linalg.norm(self._control_pnts[5] - self._control_pnts[0])**2
124  alpha = np.roots([a, b, c]).max()
125 
126  # Compute the rest of the control points
127  self._control_pnts[1] = self._control_pnts[0] + 0.2 * alpha * tangents[0]
128  self._control_pnts[2] = 2 * self._control_pnts[1] - self._control_pnts[0]
129  self._control_pnts[4] = self._control_pnts[5] - 0.2 * alpha * tangents[1]
130  self._control_pnts[3] = 2 * self._control_pnts[4] - self._control_pnts[5]
131  elif len(self._pnts) == 2:
132  assert tangents is not None and normals is not None
133  assert isinstance(tangents, list) and len(tangents) == 2, 'Tangent vectors must be provided'
134  assert isinstance(normals, list) and len(normals) == 2, 'Normal vectors must be provided'
135 
136  beta_hat = 0.51
137 
138  a = beta_hat**2 * np.linalg.norm(normals[1] - normals[0])**2
139  b = -28 * beta_hat * np.dot((tangents[0] + tangents[1]), normals[1] - normals[0])
140  c = 196 * np.linalg.norm(tangents[0] + tangents[1])**2 + 120 * beta_hat * np.dot(self._pnts[1] - self._pnts[0], normals[1] - normals[0]) - 1024
141  d = -1680 * np.dot(self._pnts[1] - self._pnts[0], tangents[0] + tangents[1])
142  e = 3600 * np.linalg.norm(self._pnts[1] - self._pnts[0])**2
143 
144  alpha_k = np.real(np.roots([a, b, c, d, e])).max()
145 
146  # Setting initial control points
147  self._control_pnts[0] = self._pnts[0]
148  self._control_pnts[5] = self._pnts[1]
149 
150  self._control_pnts[1] = self._control_pnts[0] + alpha_k / 5.0 * tangents[0]
151  self._control_pnts[2] = 2.0 * self._control_pnts[1] - self._control_pnts[0] + beta_hat * alpha_k**2 / 20.0 * normals[0]
152  self._control_pnts[4] = self._control_pnts[5] - alpha_k / 5.0 * tangents[1]
153  self._control_pnts[3] = 2.0 * self._control_pnts[4] - self._control_pnts[5] + beta_hat * alpha_k**2 / 20.0 * normals[1]
154 
155  @staticmethod
156  def distance(p1, p2):
157  """Compute the distance between two 3D points.
158 
159  > *Input arguments*
160 
161  * `p1` (*type:* list of `float` or `numpy.array`): Point 1
162  * `p2` (*type:* list of `float` or `numpy.array`): Point 2
163 
164  > *Returns*
165 
166  Distance between points as a `float`
167  """
168  p1 = np.array(p1)
169  p2 = np.array(p2)
170 
171  assert p1.size == 3 and p2.size == 3, \
172  'Both input points must be three elements'
173  return np.sqrt(np.sum((p2 - p1)**2))
174 
175  @staticmethod
177  """Generate cubic Bezier curve segments from a list of points.
178 
179  > *Input arguments*
180 
181  * `pnts` (*type:* list of `float` or of `numpy.array`): List of points
182 
183  > *Returns*
184 
185  List of `BezierCurve` segments
186  """
187  assert isinstance(pnts, list), 'List of points is invalid'
188  tangents = [np.zeros(3) for _ in range(len(pnts))]
189 
190  lengths = [BezierCurve.distance(pnts[i + 1], pnts[i]) for i in range(len(pnts) - 1)]
191  lengths = [0] + lengths
192 
193  # Initial vector of parametric variables for the curve
194  u = [l / np.sum(lengths) for l in np.cumsum(lengths)]
195  delta_u = lambda k: u[k] - u[k - 1]
196  delta_q = lambda k: pnts[k] - pnts[k - 1]
197  lamb_k = lambda k: delta_q(k) / delta_u(k)
198  alpha_k = lambda k: delta_u(k) / (delta_u(k) + delta_u(k + 1))
199 
200  for i in range(1, len(u) - 1):
201  tangents[i] = (1 - alpha_k(i)) * lamb_k(i) + alpha_k(i) * lamb_k(i + 1)
202  if i == 1:
203  tangents[0] = 2 * lamb_k(i) - tangents[1]
204 
205  tangents[-1] = 2 * lamb_k(len(u) - 1) - tangents[-2]
206 
207  # Normalize tangent vectors
208  for i in range(len(tangents)):
209  tangents[i] = tangents[i] / np.linalg.norm(tangents[i])
210 
211  segments = list()
212  # Generate the cubic Bezier curve segments
213  for i in range(len(tangents) - 1):
214  segments.append(BezierCurve([pnts[i], pnts[i + 1]], 3, [tangents[i], tangents[i + 1]]))
215 
216  return segments, tangents
217 
218  @staticmethod
220  """Generate quintic Bezier curve segments from a list of points.
221 
222  > *Input arguments*
223 
224  * `pnts` (*type:* list of `float` or of `numpy.array`): List of points
225 
226  > *Returns*
227 
228  List of `BezierCurve` segments
229  """
230  assert isinstance(pnts, list), 'List of points is invalid'
231  tangents = [np.zeros(3) for _ in range(len(pnts))]
232  normals = [np.zeros(3) for _ in range(len(pnts))]
233 
234  lengths = [BezierCurve.distance(pnts[i + 1], pnts[i]) for i in range(len(pnts) - 1)]
235  lengths = [0] + lengths
236  # Initial vector of parametric variables for the curve
237  u = np.cumsum(lengths) / np.sum(lengths)
238 
239  delta_u = lambda k: u[k] - u[k - 1]
240  delta_q = lambda k: pnts[k] - pnts[k - 1]
241  lamb_k = lambda k: delta_q(k) / delta_u(k)
242  alpha_k = lambda k: delta_u(k) / (delta_u(k) + delta_u(k + 1))
243  normal_k = lambda k: ( ((pnts[k + 1] - pnts[k]) / (u[k + 1] - u[k])) - ((pnts[k] - pnts[k - 1]) / (u[k] - u[k - 1])) ) / (u[k + 1] - u[k - 1])
244 
245  for i in range(1, len(u) - 1):
246  tangents[i] = (1 - alpha_k(i)) * lamb_k(i) + alpha_k(i) * lamb_k(i + 1)
247  normals[i] = normal_k(i)
248  if i == 1:
249  tangents[0] = 2 * lamb_k(i) - tangents[1]
250  normals[0] = normal_k(i)
251 
252  tangents[-1] = 2 * lamb_k(len(u) - 1) - tangents[-2]
253  normals[-1] = normal_k(len(u) - 3)
254 
255  # Normalize tangent vectors
256  for i in range(len(tangents)):
257  tangents[i] /= np.linalg.norm(tangents[i])
258  normals[i] /= np.linalg.norm(normals[i])
259 
260  segments = list()
261  # Generate the cubic Bezier curve segments
262  for i in range(len(tangents) - 1):
263  segments.append(BezierCurve([pnts[i], pnts[i + 1]], 5,
264  [tangents[i], tangents[i + 1]],
265  [normals[i], normals[i + 1]]))
266 
267  return segments
268 
269  def control_pnts(self):
270  """Return the list of control points of the Bezier curve.
271 
272  > *Returns*
273 
274  List of 3D points as `list`
275  """
276  return self._control_pnts
277 
278  def interpolate(self, u):
279  """Interpolate the Bezier curve using the input parametric variable `u`.
280 
281  > *Input arguments*
282 
283  * `u` (*type:* `float`): Curve parametric input in the interval `[0, 1]`
284 
285  > *Returns*
286 
287  3D point from the Bezier curve as `numpy.array`
288  """
289  u = max(u, 0)
290  u = min(u, 1)
291 
292  b = np.zeros(3)
293  for i in range(len(self._control_pnts)):
294  b += self.compute_polynomial(self._order, i, u) * self._control_pnts[i]
295  return b
296 
297  def get_derivative(self, u, order=1):
298  """Compute the derivative of the Bezier curve using the input parametric
299  variable `u`.
300 
301  > *Input arguments*
302 
303  * `u` (*type:* `float`): Curve parametric input in the interval `[0, 1]`
304  * `order` (*type:* `int`, *default:* `1`): Order of the derivative
305 
306  > *Returns*
307 
308  `numpy.array`: 3D derivative value from the Bezier curve
309  """
310  u = max(u, 0)
311  u = min(u, 1)
312 
313  b = np.zeros(3)
314  for i in range(len(self._control_pnts) - order):
315  b = b + self._order * self.compute_polynomial(self._order - order, i, u) * \
316  (self._control_pnts[i + 1] - self._control_pnts[i])
317  return b
318 
319  def get_length(self):
320  """Get length of the Bezier curve segment.
321 
322  > *Returns*
323 
324  `float`: Length of the curve
325  """
326  return self._order * np.linalg.norm(self._control_pnts[1] - self._control_pnts[0])
327 
328  def compute_polynomial(self, n, i, u):
329  """Compute the Bernstein polynomial
330 
331  $$
332  \mathbf{B} = {n\choose i} (1 - u)^{(n - i)} u^{i}
333  $$
334 
335  > *Input arguments*
336 
337  * `n` (*type:* `int`): Degree of the Bezier curve
338  * `i` (*type:* `int`): Index of the control point
339  * `u` (*type:* `float`): Parametric input of the curve in interval [0, 1]
340 
341  > *Returns*
342 
343  `float`: Bernstein polynomial result
344  """
345  return self._get_binomial(n, i) * (1 - u)**(n - i) * u**i
346 
347  @staticmethod
348  def _get_binomial(n, i):
349  """Compute binomial function $\binom{n}{i}$
350 
351  > *Input arguments*
352 
353  * `n` (*type:* `int`)
354  * `i` (*type:* `int`)
355  """
356  return factorial(n) / (factorial(i) * factorial(n - i))
357 
def __init__(self, pnts, order, tangents=None, normals=None)
Definition: bezier_curve.py:46


uuv_trajectory_control
Author(s):
autogenerated on Thu Jun 18 2020 03:28:42