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


uuv_trajectory_control
Author(s):
autogenerated on Mon Jul 1 2019 19:39:29