28

2D曲線に近い点のセットがあります。Pythonをnumpyとscipyで使用して、ポイントにほぼ適合する3次ベジェパスを見つけたいと思います。ここで、2つの端点の正確な座標を指定すると、他の2つのコントロールポイントの座標が返されます。

最初はscipy.interpolate.splprep()自分のやりたいことができると思っていましたが、曲線が各データポイントを通過するように強制されているようです(補間が必要だと思います)。私はそれで間違った方向に進んでいたと思います。

私の質問はこれに似ています:ベジェ曲線をデータセットにどのように適合させることができますか?、彼らがnumpyを使いたくないと言ったことを除いて。私の好みは、scipyまたはnumpyのどこかにすでに実装されている必要があるものを見つけることです。それ以外の場合は、numpyを使用して、その質問に対する回答の1つからリンクされたアルゴリズムを実装する予定です。デジタル化された曲線を自動的にフィッティングするためのアルゴリズム(pdf.622ページ)。

提案ありがとうございます!

編集:3次ベジエ曲線がすべての点を通過することが保証されているわけではないことを理解しています。与えられた2つの端点を通過し、指定された内部点にできるだけ近いものが必要です。

4

8 に答える 8

24

numpy を使用してベジエ曲線を作成する方法は次のとおりです。

import numpy as np
from scipy.special import comb

def bernstein_poly(i, n, t):
    """
     The Bernstein polynomial of n, i as a function of t
    """

    return comb(n, i) * ( t**(n-i) ) * (1 - t)**i


def bezier_curve(points, nTimes=1000):
    """
       Given a set of control points, return the
       bezier curve defined by the control points.

       points should be a list of lists, or list of tuples
       such as [ [1,1], 
                 [2,3], 
                 [4,5], ..[Xn, Yn] ]
        nTimes is the number of time steps, defaults to 1000

        See http://processingjs.nihongoresources.com/bezierinfo/
    """

    nPoints = len(points)
    xPoints = np.array([p[0] for p in points])
    yPoints = np.array([p[1] for p in points])

    t = np.linspace(0.0, 1.0, nTimes)

    polynomial_array = np.array([ bernstein_poly(i, nPoints-1, t) for i in range(0, nPoints)   ])

    xvals = np.dot(xPoints, polynomial_array)
    yvals = np.dot(yPoints, polynomial_array)

    return xvals, yvals


if __name__ == "__main__":
    from matplotlib import pyplot as plt

    nPoints = 4
    points = np.random.rand(nPoints,2)*200
    xpoints = [p[0] for p in points]
    ypoints = [p[1] for p in points]

    xvals, yvals = bezier_curve(points, nTimes=1000)
    plt.plot(xvals, yvals)
    plt.plot(xpoints, ypoints, "ro")
    for nr in range(len(points)):
        plt.text(points[nr][0], points[nr][1], nr)

    plt.show()
于 2012-09-28T17:14:47.720 に答える
17

ポイントをフィッティングするためのPythonコードの一部を次に示します。

'''least square qbezier fit using penrose pseudoinverse
    >>> V=array
    >>> E,  W,  N,  S =  V((1,0)), V((-1,0)), V((0,1)), V((0,-1))
    >>> cw = 100
    >>> ch = 300
    >>> cpb = V((0, 0))
    >>> cpe = V((cw, 0))
    >>> xys=[cpb,cpb+ch*N+E*cw/8,cpe+ch*N+E*cw/8, cpe]            
    >>> 
    >>> ts = V(range(11), dtype='float')/10
    >>> M = bezierM (ts)
    >>> points = M*xys #produces the points on the bezier curve at t in ts
    >>> 
    >>> control_points=lsqfit(points, M)
    >>> linalg.norm(control_points-xys)<10e-5
    True
    >>> control_points.tolist()[1]
    [12.500000000000037, 300.00000000000017]

'''
from numpy import array, linalg, matrix
from scipy.misc import comb as nOk
Mtk = lambda n, t, k: t**(k)*(1-t)**(n-k)*nOk(n,k)
bezierM = lambda ts: matrix([[Mtk(3,t,k) for k in range(4)] for t in ts])
def lsqfit(points,M):
    M_ = linalg.pinv(M)
    return M_ * points

通常、ベジェ曲線については、 アニメーション化されたベジェbezierinfoを確認してください。

于 2013-01-02T16:48:55.540 に答える
7

@keynesiancross は、「[Roland の] コードで、変数が何であるかについてのコメント」を求めましたが、他の人は、述べられた問題を完全に見逃していました。Roland は (完全に一致させるために) ベジエ曲線を入力として使用し始めたため、問題と (少なくとも私にとっては) 解決策の両方を理解することが難しくなりました。補間との違いは、残差が残る入力の方が分かりやすいです。これは言い換えられたコードと非ベジエ入力の両方であり、予期しない結果です。

import matplotlib.pyplot as plt
import numpy as np
from scipy.special import comb as n_over_k
Mtk = lambda n, t, k: t**k * (1-t)**(n-k) * n_over_k(n,k)
BézierCoeff = lambda ts: [[Mtk(3,t,k) for k in range(4)] for t in ts]

fcn = np.log
tPlot = np.linspace(0. ,1. , 81)
xPlot = np.linspace(0.1,2.5, 81)
tData = tPlot[0:81:10]
xData = xPlot[0:81:10]
data = np.column_stack((xData, fcn(xData))) # shapes (9,2)

Pseudoinverse = np.linalg.pinv(BézierCoeff(tData)) # (9,4) -> (4,9)
control_points = Pseudoinverse.dot(data)     # (4,9)*(9,2) -> (4,2)
Bézier = np.array(BézierCoeff(tPlot)).dot(control_points)
residuum = fcn(Bézier[:,0]) - Bézier[:,1]

fig, ax = plt.subplots()
ax.plot(xPlot, fcn(xPlot),   'r-')
ax.plot(xData, data[:,1],    'ro', label='input')
ax.plot(Bézier[:,0],
        Bézier[:,1],         'k-', label='fit')
ax.plot(xPlot, 10.*residuum, 'b-', label='10*residuum')
ax.plot(control_points[:,0],
        control_points[:,1], 'ko:', fillstyle='none')
ax.legend()
fig.show()

これは ではうまく機能しますが、 では機能しfcn = np.cosませんlog。コントロール ポイントをドラッグして行うように、フィットではコントロール ポイントの t 成分を追加の自由度として使用することを期待していました。

manual_points = np.array([[0.1,np.log(.1)],[.27,-.6],[.82,.23],[2.5,np.log(2.5)]])
Bézier = np.array(BézierCoeff(tPlot)).dot(manual_points)
residuum = fcn(Bézier[:,0]) - Bézier[:,1]

fig, ax = plt.subplots()
ax.plot(xPlot, fcn(xPlot),   'r-')
ax.plot(xData, data[:,1],    'ro', label='input')
ax.plot(Bézier[:,0],
        Bézier[:,1],         'k-', label='fit')
ax.plot(xPlot, 10.*residuum, 'b-', label='10*residuum')
ax.plot(manual_points[:,0],
        manual_points[:,1],  'ko:', fillstyle='none')
ax.legend()
fig.show()

失敗の原因は、ノルムが一方の曲線上の点から他方の曲線上の最も近い点までの距離ではなく、曲線上の点間の距離を測定することだと思います。

于 2018-07-15T20:07:51.953 に答える
3

結果のプロット

@reptilicus と @Guillaume P. からの回答に基づいて構築された完全なコードは次のとおりです。

  • ポイントのリストからベジエ パラメータ、つまりコントロール ポイントを取得します。
  • ベジェ パラメータ、つまり制御点からベジェ曲線を作成します。
  • 元の点、制御点、および結果のベジエ曲線をプロットします。

ベジエ パラメーターを取得します。つまり、一連の X、Y ポイントまたは座標からコントロール ポイントを取得します。必要な他のパラメーターは近似の次数であり、結果のコントロール ポイントは (次数 + 1) になります。

import numpy as np
from scipy.special import comb

def get_bezier_parameters(X, Y, degree=3):
    """ Least square qbezier fit using penrose pseudoinverse.

    Parameters:

    X: array of x data.
    Y: array of y data. Y[0] is the y point for X[0].
    degree: degree of the Bézier curve. 2 for quadratic, 3 for cubic.

    Based on https://stackoverflow.com/questions/12643079/b%C3%A9zier-curve-fitting-with-scipy
    and probably on the 1998 thesis by Tim Andrew Pastva, "Bézier Curve Fitting".
    """
    if degree < 1:
        raise ValueError('degree must be 1 or greater.')

    if len(X) != len(Y):
        raise ValueError('X and Y must be of the same length.')

    if len(X) < degree + 1:
        raise ValueError(f'There must be at least {degree + 1} points to '
                         f'determine the parameters of a degree {degree} curve. '
                         f'Got only {len(X)} points.')

    def bpoly(n, t, k):
        """ Bernstein polynomial when a = 0 and b = 1. """
        return t ** k * (1 - t) ** (n - k) * comb(n, k)
        #return comb(n, i) * ( t**(n-i) ) * (1 - t)**i

    def bmatrix(T):
        """ Bernstein matrix for Bézier curves. """
        return np.matrix([[bpoly(degree, t, k) for k in range(degree + 1)] for t in T])

    def least_square_fit(points, M):
        M_ = np.linalg.pinv(M)
        return M_ * points

    T = np.linspace(0, 1, len(X))
    M = bmatrix(T)
    points = np.array(list(zip(X, Y)))
    
    final = least_square_fit(points, M).tolist()
    final[0] = [X[0], Y[0]]
    final[len(final)-1] = [X[len(X)-1], Y[len(Y)-1]]
    return final

Bezier パラメータ、つまり制御点を指定して Bezier 曲線を作成します。

def bernstein_poly(i, n, t):
    """
     The Bernstein polynomial of n, i as a function of t
    """
    return comb(n, i) * ( t**(n-i) ) * (1 - t)**i


def bezier_curve(points, nTimes=50):
    """
       Given a set of control points, return the
       bezier curve defined by the control points.

       points should be a list of lists, or list of tuples
       such as [ [1,1], 
                 [2,3], 
                 [4,5], ..[Xn, Yn] ]
        nTimes is the number of time steps, defaults to 1000

        See http://processingjs.nihongoresources.com/bezierinfo/
    """

    nPoints = len(points)
    xPoints = np.array([p[0] for p in points])
    yPoints = np.array([p[1] for p in points])

    t = np.linspace(0.0, 1.0, nTimes)

    polynomial_array = np.array([ bernstein_poly(i, nPoints-1, t) for i in range(0, nPoints)   ])

    xvals = np.dot(xPoints, polynomial_array)
    yvals = np.dot(yPoints, polynomial_array)

    return xvals, yvals

使用したサンプル データ (任意のデータに置き換えることができます。これは GPS データです)。

points = []
xpoints = [19.21270, 19.21269, 19.21268, 19.21266, 19.21264, 19.21263, 19.21261, 19.21261, 19.21264, 19.21268,19.21274, 19.21282, 19.21290, 19.21299, 19.21307, 19.21316, 19.21324, 19.21333, 19.21342]
ypoints = [-100.14895, -100.14885, -100.14875, -100.14865, -100.14855, -100.14847, -100.14840, -100.14832, -100.14827, -100.14823, -100.14818, -100.14818, -100.14818, -100.14818, -100.14819, -100.14819, -100.14819, -100.14820, -100.14820]
for i in range(len(xpoints)):
    points.append([xpoints[i],ypoints[i]])

元の点、制御点、および結果のベジエ曲線をプロットします。

import matplotlib.pyplot as plt
# Plot the original points
plt.plot(xpoints, ypoints, "ro",label='Original Points')
# Get the Bezier parameters based on a degree.
data = get_bezier_parameters(xpoints, ypoints, degree=4)
x_val = [x[0] for x in data]
y_val = [x[1] for x in data]
print(data)
# Plot the control points
plt.plot(x_val,y_val,'k--o', label='Control Points')
# Plot the resulting Bezier curve
xvals, yvals = bezier_curve(data, nTimes=1000)
plt.plot(xvals, yvals, 'b-', label='B Curve')
plt.legend()
plt.show()
于 2021-10-04T15:01:17.850 に答える
2

短い答え: ベジエ曲線が機能しないため、必要ありません。より長い答え: 代わりに Catmull-Rom スプラインを見てください。それらは非常に簡単に形成でき (始点と終点を除く任意の点 P での接線ベクトルは線 {P-1,P+1} に平行であるため、プログラムも簡単です)、常に通過します。すべての制御点によって設定された凸包内の「どこか」を補間するベジエ曲線とは異なり、それらを定義する点。

于 2013-04-09T11:19:34.020 に答える
1

ベジエ曲線は、指定したすべての点を通過するとは限りません。制御点は任意であり (それらを見つけるための特定のアルゴリズムがないという意味で、自分で選択するだけです) 、曲線をある方向に引っ張るだけです。

提供するすべてのポイントを通過する曲線が必要な場合は、自然な 3 次スプラインのようなものが必要であり、それらの制限のために (増加する x 座標でそれらを提供する必要があります。そうしないと、無限になる傾向があります)。 、おそらくパラメトリックな自然な 3 次スプラインが必要になるでしょう。

ここに素晴らしいチュートリアルがあります:

3 次スプライン

パラメトリック 3 次スプライン

于 2012-09-28T15:47:36.883 に答える