Unter bestimmten Umständen untersuchte ich die Teilung der Bezier-Kurve. Ich konnte nicht viele Websites finden, die erklären, wie die Bezier-Kurve ** $ n $ order ** geteilt wird.
Nun, da die Bezier-Kurve nur bis zur 4. Ordnung verwendet wird, spielt es keine Rolle, ob Sie die $ n $ -Ordnung nicht kennen. Ich wollte es wirklich ordentlich implementieren, also habe ich es untersucht, also werde ich es als Wissen belassen. Ich habe ein Implementierungsbeispiel in dem Artikel veröffentlicht, aber ich habe es auch auf GitHub veröffentlicht (siehe line_module.py).
Dies ist eine Methode namens De Casteljaus Algorithmus. Diese Methode verwendet die Wiederholung, um die Kontrollpunkte für die neue Bezier-Kurve nach der Division zu finden. Diese Methode ist ** einfach im Konzept und leicht zu implementieren **, hat jedoch den Nachteil, dass sie in eingebetteten Systemen mit einer kleinen Stapelgröße ** schwierig zu verwenden ist **. Nun, die Bezier-Kurve ist nur bis zur 4. Ordnung (weggelassen)
Teilen wir als Beispiel die unten gezeigte Bezier-Kurve. Sei $ t $ die Mediatorvariable der Bezier-Kurve am Teilungspunkt und sei $ P_i $ der anfängliche Kontrollpunkt der Bezier-Kurve.
** 1. Suchen Sie den Punkt, der zu $ t: 1-t $ wird, auf der Linie, die die Kontrollpunkte der Bezier-Kurve verbindet (Punkt $ Q $). ** ** **
** 2. Suchen Sie für die Verbindungslinie zwischen den erhaltenen Punkten den Punkt $ t: 1-t $ (Punkt $ R $). ** ** **
** 3. Wiederholen, bis es einen Punkt gibt (Punkt $ S $). ** ** **
** 4. An allen Punkten einschließlich der anfänglichen Kontrollpunkte sind die Punktmenge mit dem kleinsten Index und die Menge der Punkte mit dem größten Index die Kontrollpunkte nach der Division. ** ** **
Für den letzten Teil (Mindestindex) ist es einfacher zu verstehen, wenn Sie die folgende Baumstruktur schreiben. Die unten gezeigte Baumstruktur zeigt, aus welchem Liniensegment (welche zwei Punkte) ein neuer Punkt generiert wurde.
Die äußersten Punkte dieser Baumstruktur, nämlich die Punkte $ \ {P_0, Q_0, R_0, S_0 \} $ und die Punkte $ \ {S_0, R_1, Q_2, P_3 \} $, werden zu neuen Bezier-Kurven. Ich werde. Sie können sehen, dass der Start- und Endpunkt der ursprünglichen Bezier-Kurve gemeinsam sind und dass der Punkt (Teilungspunkt $ S_0 $) in der Mediatorvariablen $ t $ der Startpunkt (Endpunkt) der neuen Bezier-Kurve ist.
Ein Beispiel für die Implementierung eines Split-Programms mit Wiederholung ist unten dargestellt.
import numpy as np
from scipy.special import comb
import matplotlib.pyplot as plt
class Bezier:
def __init__(self, points):
points = [np.array(e).flatten() for e in points]
self._n = len(points)
self._points = points
@property
def dims(self):
return self._n - 1
@property
def points(self):
return self._points
@property
def points4matplot(self):
xs, ys = zip(*self.points)
return xs, ys
def _bernstein(self, n, i, t):
return comb(n, i) * t**i * (1 - t)**(n-i)
def bezier_point(self, t):
p = np.zeros(2)
for i, f in enumerate(self.points):
p += self._bernstein(self.dims, i, t) * f
return np.array(p).flatten()
def _de_casteljau_algorithm(self, points, t):
prev_p = None
q = []
for p in points:
if not prev_p is None:
## Split line into t:(1-t)
q.append(np.array((1-t) * prev_p + t * p).flatten())
prev_p = p
if len(q) == 1:
return [q]
return [q] + self._de_casteljau_algorithm(q, t)
def split_with_recursion(self, t):
ret = [self.points] + self._de_casteljau_algorithm(self.points, t)
lp = []
rp = []
for lst in ret:
## Fetch min and max point
lp.append(lst[0])
rp.append(lst[-1])
return Bezier(lp), Bezier(rp)
def plot(self, ax=None, with_ctrl_pt=True, bcolor="black", ccolor="gray", resolution=100):
if ax is None:
_, ax = plt.subplots()
prev_point = None
for t in np.linspace(0, 1, resolution):
bp = self.bezier_point(t)
if prev_point is None:
prev_point = (bp[0], bp[1])
xs, ys = zip(*(prev_point, (bp[0], bp[1])))
ax.plot(xs, ys, '-', color=bcolor)
prev_point = (bp[0], bp[1])
if with_ctrl_pt:
xs, ys = self.points4matplot
ax.plot(xs, ys, 'x--', lw=2, color=ccolor, ms=10)
return ax
def main():
bezier = Bezier([(0.3, 1), (0.2, 3), (0.4, 4), (0.5, 0)])
ax = bezier.plot()
left_bezier, right_bezier = bezier.split_with_recursion(0.3)
left_bezier.plot(ax, bcolor = "red")
right_bezier.plot(ax, bcolor = "blue")
plt.grid()
plt.show()
if __name__ == "__main__":
main()
Dies ist eine Methode, um mithilfe einer Matrix einen neuen Kontrollpunkt zu finden. Diese Methode ** spielt keine Rolle, wenn die Stapelgröße klein ist **, aber ** die Berechnung ist etwas kompliziert **. Nun, die Bezier-Kurve ist höchstens 4. Ordnung (weggelassen)
Als ersten Schritt zur Erklärung des Algorithmus werde ich die Matrixdarstellung der Bezier-Kurve erläutern. Die Bezier-Kurve kann durch die folgende Gleichung ausgedrückt werden.
\begin{align}
F(t) &= \sum_{i=0}^{n} P_i~B_{i}^{n}(t)
\end{align}
$ P_i $ repräsentiert den Kontrollpunkt und $ B_ {i} ^ {n} (t) $ repräsentiert die Bernstein-Basisfunktion. $ n $ repräsentiert die Reihenfolge der Bezier-Kurve plus $ 1 $ (die Anzahl der $ = $ Kontrollpunkte). Dies kann in Form eines Matrizenprodukts wie folgt ausgedrückt werden.
\begin{align}
F(t) &= \sum_{i=0}^{n} P_i~B_{i}^{n}(t) \\
&= \left(
\begin{array}{ccc}
B_{0}^{n} & B_{1}^{n} & \dots & B_{n}^{n}
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
\vdots \\
P_n
\end{array}
\right)
\end{align}
Zusätzlich kann die Formel der Bezier-Kurve wie folgt ausgedrückt werden, indem sie für $ t $ organisiert wird. Hier repräsentiert $ a_n $ einen geeigneten Koeffizienten.
\begin{align}
F(t) &= \sum_{i=0}^{n} P_i~B_{i}^{n}(t) \\
&= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)
\left(
\begin{array}{ccc}
a_0 \\
a_1 \\
\vdots \\
a_n
\end{array}
\right)
\end{align}
Aus dem Obigen kann die folgende Gleichung abgeleitet werden.
\begin{align}
F(t) &=
\left(
\begin{array}{ccc}
B_{0}^{n} & B_{1}^{n} & \dots & B_{n}^{n}
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
\vdots \\
P_n
\end{array}
\right)
=
\left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)
\left(
\begin{array}{ccc}
a_0 \\
a_1 \\
\vdots \\
a_n
\end{array}
\right) \\ \\
F(t) &= BP = XA
\end{align}
Es ist ziemlich sauber. Hier wissen wir, dass $ A $ durch eine untere Dreiecksmatrix $ U_t $ und einen Kontrollpunkt $ P $ wie folgt ausgedrückt werden kann.
\begin{align}
A &= U_tP \\ \\
&= \left(
\begin{array}{ccc}
{n \choose 0} {n \choose 0} (-1)^0 & 0 & \cdots & 0 \\
{n \choose 0} {n \choose 1} (-1)^1 & {n \choose 1} {n-1 \choose 0} (-1)^0 & \cdots & 0 \\
\vdots & \vdots & \cdots & \vdots \\
{n \choose 0} {n \choose n} (-1)^n & {n \choose 1} {n-1 \choose n-1} (-1)^{n-1} & \cdots & {n \choose n} {n-n \choose n-n} (-1)^{n-n} \\
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
\vdots \\
P_n
\end{array}
\right)
\end{align}
Daher kann schließlich die Bezier-Kurvengleichung unter Verwendung einer Matrix wie folgt ausgedrückt werden.
F(t) = BP = XU_tP
Der Algorithmus für die Bezier-Kurventeilung unter Verwendung einer Matrix ist wie folgt.
** 1. Ordnen Sie die Bezier-Kurve für $ t $ an und drücken Sie sie in Form einer Matrix aus. ** ** **
\begin{align}
F(t) &= XU_tP \\
&= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)U_tP \\
\end{align}
** 2. Trennen Sie den Teilungspunkt $ z ~ (z \ in t) $. ** ** **
\begin{align}
F(t) &= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)U_tP \\ \\
&= \left(
\begin{array}{ccc}
1 & (z\cdot t) & (z\cdot t)^2 & \dots & (z\cdot t)^n
\end{array}
\right)U_tP \\ \\
&= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & & & & 0 \\
& z & & & \\
& & z^2 & & \\
& & & \ddots & \\
0 & & & & z^n
\end{array}
\right)U_tP
\end{align}
** 3. Transformieren Sie die Formel wie folgt, um $ Q $ zu finden. ** ** **
\begin{align}
F(t) &= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & & & & 0 \\
& z & & & \\
& & z^2 & & \\
& & & \ddots & \\
0 & & & & z^n
\end{array}
\right)U_tP \\
&= X ~ Z U_t ~ P \\ \\
&= X ~ U_t U_t^{-1} Z U_t ~ P \\ \\
&= X ~ U_t Q ~P
\end{align}
Bei der obigen Berechnung wird die Position von $ U_t $ verschoben. Da $ U_t U_t ^ {-1} $ eine Einheitsmatrix ist, ändert sich das Berechnungsergebnis selbst nicht. $ Q = U_t ^ {-1} ZU_t $.
** 4. Berechnen Sie $ QP $, um den Kontrollpunkt der neuen Bezier-Kurve zu berechnen. ** ** **
\begin{align}
F(t) &= X ~ U_t Q ~P \\
&= X ~ U_t ~ P^{~\prime}
\end{align}
Wenn $ P ^ {~ \ prime} = QP $ ist, hat dies die Form eines Ausdrucks, der die Bezier-Kurve darstellt. Daher ist $ P ^ {~ \ prime} $ der Kontrollpunkt der Bezier-Kurve nach der Division.
Durch das obige Verfahren konnten wir die ** geteilte linke Bezier-Kurve ** finden. Um die Bezier-Kurve auf der rechten Seite zu finden, trennen Sie $ z $ in Schritt 2 wie folgt. Finden Sie die richtige Matrix $ Q ^ {~ \ prime} $.
\begin{align}
F(t) &= \left(
\begin{array}{ccc}
1 & t & t^2 & \dots & t^n
\end{array}
\right)U_tP \\ \\
&= \left(
\begin{array}{ccc}
1 & (z + (1-z)\cdot t) & (z + (1-z)\cdot t)^2 & \dots & (z + (1-z)\cdot t)^n
\end{array}
\right)U_tP
\end{align}
Sie können jedoch $ Q ^ {~ \ prime} $ mithilfe des berechneten $ Q $ finden, ohne es direkt berechnen zu müssen. Speziell,
Mach einfach die Operation. Da es schwierig ist, mathematische Formeln zu erklären, lesen Sie den Abschnitt des folgenden Berechnungsbeispiels.
Für diejenigen, die sagen: "$ n $ Ich verstehe nicht nur die nächste Geschichte!", Hier ist ein Beispiel für die Division durch Matrixberechnung.
Diesmal als Beispiel
Zunächst wird diese Bezier-Kurve in Form einer Matrix dargestellt und der Teilungspunkt $ z = 0,3 $ getrennt.
\begin{align}
F(t) &= XZU_tP \\
&=
\left(
\begin{array}{ccc}
1 & t & t^2
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0 & z & 0 \\
0 & 0 & z^2
\end{array}
\right)
\left(
\begin{array}{ccc}
{2 \choose 0}{2 \choose 0}(-1)^0 & 0 & 0 \\
{2 \choose 0}{2 \choose 1}(-1)^1 & {2 \choose 1}{1 \choose 0}(-1)^0 & 0 \\
{2 \choose 0}{2 \choose 2}(-1)^2 & {2 \choose 1}{1 \choose 1}(-1)^1 & {2 \choose 2}{0 \choose 0}(-1)^0
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
P_2
\end{array}
\right) \\ \\
&=
\left(
\begin{array}{ccc}
1 & t & t^2
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0 & 0.3 & 0 \\
0 & 0 & 0.09
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
P_2
\end{array}
\right)
\end{align}
Berechnen Sie nun $ Q = U_t ^ {-1} ZU_t $.
\begin{align}
Q &= U_t^{-1}ZU_t \\ \\
&=
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
1 & \frac{1}{2} & 0 \\
1 & 1 & 1
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0 & 0.3 & 0 \\
0 & 0 & 0.09
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{array}
\right) \\ \\
&=
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0.7 & 0.3 & 0 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\end{align}
Transformieren Sie die Formel mit $ Q $ in eine Formel.
\begin{align}
F(t) &= XZU_tP \\
&= XU_tU_t^{-1}ZU_tP \\
&= XU_tQP \\
&=
\left(
\begin{array}{ccc}
1 & t & t^2
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
-2 & 2 & 0 \\
1 & -2 & 1
\end{array}
\right)
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0.7 & 0.3 & 0 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
P_2
\end{array}
\right)
\end{align}
Wenn Sie hier für $ QP $ rechnen,
\begin{align}
QP &=
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0.7 & 0.3 & 0 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
P_2
\end{array}
\right) \\ \\
&=
\left(
\begin{array}{ccc}
P_0 \\
0.7P_0 + 0.3P_1 \\
0.49P_0 + 0.42P_1 + 0.09P_2
\end{array}
\right) \\ \\
&= P^{~\prime} \\ \\
\end{align}
\begin{align}
P^{~\prime}_x &=
\left(
\begin{array}{ccc}
0 \\
0.7\cdot 0 + 0.3 \cdot 1 \\
0.49\cdot 0 + 0.42\cdot 1 + 0.09\cdot 2
\end{array}
\right)
=
\left(
\begin{array}{ccc}
0 \\
0.3 \\
0.6
\end{array}
\right)
\end{align}
\begin{align}
P^{~\prime}_y &=
\left(
\begin{array}{ccc}
1 \\
0.7\cdot 1 + 0.3 \cdot 4 \\
0.49\cdot 1 + 0.42\cdot 4 + 0.09\cdot 0
\end{array}
\right)
=
\left(
\begin{array}{ccc}
1 \\
1.9 \\
2.17
\end{array}
\right) \\
\end{align}
Als nächstes finden wir die Bezier-Kurve auf der rechten Seite. $ U_t ^ {-1} Z ^ {~ \ prime} U_t = Q ^ {~ \ prime} $ für die rechte Seite kann wie folgt unter Verwendung des berechneten $ Q $ berechnet werden.
** 1. Packen Sie das $ Q $ -Element rechts um $ 0 $. ** ** **
\begin{align}
Q &=
\left(
\begin{array}{ccc}
1 & 0 & 0 \\
0.7 & 0.3 & 0 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\Rightarrow
\left(
\begin{array}{ccc}
0 & 0 & 1 \\
0 & 0.7 & 0.3 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\end{align}
** 2. Drehen Sie die nach rechts gepackten Elemente der Matrix um. ** ** **
\begin{align}
\left(
\begin{array}{ccc}
0 & 0 & 1 \\
0 & 0.7 & 0.3 \\
0.49 & 0.42 & 0.09
\end{array}
\right)
\Rightarrow
\left(
\begin{array}{ccc}
0.49 & 0.42 & 0.09 \\
0 & 0.7 & 0.3 \\
0 & 0 & 1
\end{array}
\right)
= Q^{~\prime}
\end{align}
Es ist einfach. Sie können das erhaltene $ Q ^ {~ \ prime} $ verwenden, um die ** geteilte rechte Bezier-Kurve ** zu finden.
\begin{align}
Q^{~\prime}P &=
\left(
\begin{array}{ccc}
0.49 & 0.42 & 0.09 \\
0 & 0.7 & 0.3 \\
0 & 0 & 1
\end{array}
\right)
\left(
\begin{array}{ccc}
P_0 \\
P_1 \\
P_2
\end{array}
\right) \\ \\
&=
\left(
\begin{array}{ccc}
0.49P_0 + 0.42P_1 + 0.09P_2 \\
0.7P_1 + 0.3P_2 \\
P_2
\end{array}
\right) \\ \\
&= P^{~\prime\prime} \\ \\
\end{align}
\begin{align}
P^{~\prime\prime}_x &=
\left(
\begin{array}{ccc}
0.49\cdot 0 + 0.42\cdot 1 + 0.09\cdot 2 \\
0.7\cdot 1 + 0.3 \cdot 2 \\
2
\end{array}
\right)
=
\left(
\begin{array}{ccc}
0.6 \\
1.3 \\
2
\end{array}
\right)
\end{align}
\begin{align}
P^{~\prime\prime}_y &=
\left(
\begin{array}{ccc}
0.49\cdot 1 + 0.42\cdot 4 + 0.09\cdot 0 \\
0.7\cdot 4 + 0.3 \cdot 0 \\
0
\end{array}
\right)
=
\left(
\begin{array}{ccc}
2.17 \\
2.8 \\
0
\end{array}
\right)
\end{align}
Da $ P_0 = P_0 ^ {~ \ prime} $, $ P_2 ^ {~ \ prime} = P_0 ^ {~ \ prime \ prime} $, $ P_2 = P_2 ^ {~ \ prime \ prime} $ Es scheint, dass Sie es gut teilen können. Wenn Sie die erhaltene Bezier-Kurve auf der linken Seite und die Bezier-Kurve auf der rechten Seite zeichnen, entspricht dies der Abbildung unten. Sie können es gut teilen.
Ein Beispiel für die Implementierung eines Teilungsprogramms unter Verwendung einer Matrix ist unten gezeigt.
import numpy as np
from scipy.special import comb
import matplotlib.pyplot as plt
class Bezier:
def __init__(self, points):
points = [np.array(e).flatten() for e in points]
self._n = len(points)
self._points = points
@property
def dims(self):
return self._n - 1
@property
def points(self):
return self._points
@property
def points4matplot(self):
xs, ys = zip(*self.points)
return xs, ys
def _bernstein(self, n, i, t):
return comb(n, i) * t**i * (1 - t)**(n-i)
def bezier_point(self, t):
p = np.zeros(2)
for i, f in enumerate(self.points):
p += self._bernstein(self.dims, i, t) * f
return np.array(p).flatten()
def split_with_matrix(self, t):
M = self.create_Ut()
iM = np.linalg.inv(M)
Z = np.eye(self.dims + 1)
for i in range(self.dims + 1):
Z[i] = Z[i] * t ** i
## Caluclate Q
Q = iM @ Z @ M
xs, ys = self.points4matplot
X = np.array(xs)
Y = np.array(ys)
left_bezier = Bezier(list(zip(Q @ X, Q @ Y)))
## Make Q'
_Q = np.zeros((self.dims + 1, self.dims + 1))
lst = []
for i in reversed(range(self.dims + 1)):
l = [-1] * i + list(range(self.dims + 1 - i))
lst.append(l)
for i, l in enumerate(lst):
mtx = [Q[i][e] if not e == -1 else 0 for e in l]
_Q[i] = np.array(mtx)
_Q = np.flipud(_Q)
right_bezier = Bezier(list(zip(_Q @ X, _Q @ Y)))
return left_bezier, right_bezier
def create_Ut(self, dims=None):
if dims is None:
dims = self.dims
U = np.zeros([dims + 1, dims + 1])
lmbd = lambda n, i, j: comb(n, j) * comb(n-j, i-j) * (-1)**(i - j)
for i in range(dims + 1):
lst = list(range(i+1)) + [-1]*(dims-i)
elems = [lmbd(dims, i, j) if j >= 0 else 0.0 for j in lst]
mtx = np.array(elems)
U[i] = mtx
return U
def plot(self, ax=None, with_ctrl_pt=True, bcolor="black", ccolor="gray", resolution=100):
if ax is None:
_, ax = plt.subplots()
prev_point = None
for t in np.linspace(0, 1, resolution):
bp = self.bezier_point(t)
if prev_point is None:
prev_point = (bp[0], bp[1])
xs, ys = zip(*(prev_point, (bp[0], bp[1])))
ax.plot(xs, ys, '-', color=bcolor)
prev_point = (bp[0], bp[1])
if with_ctrl_pt:
xs, ys = self.points4matplot
ax.plot(xs, ys, 'x--', lw=2, color=ccolor, ms=10)
return ax
def main():
bezier = Bezier([(0, 1), (1, 4), (2, 0)])
ax = bezier.plot()
left_bezier, right_bezier = bezier.split_with_matrix(0.3)
left_bezier.plot(ax, bcolor = "red")
right_bezier.plot(ax, bcolor = "blue")
plt.grid()
plt.show()
if __name__ == "__main__":
main()
In diesem Artikel habe ich die Division der Bezier-Kurve der Ordnung $ n $ vorgestellt. Es war vielleicht nicht sehr gut organisiert, aber bitte vergib mir. Ich hoffe, der Inhalt dieses Artikels wird jemandem nützlich sein.
Recommended Posts