Bézier curve fitting with SciPy

后端 未结 7 618
醉酒成梦
醉酒成梦 2020-12-04 19:57

I have a set of points which approximate a 2D curve. I would like to use Python with numpy and scipy to find a cubic Bézier path which approximately fits the points, where I

7条回答
  •  遥遥无期
    2020-12-04 20:29

    @keynesiancross asked for "comments in [Roland's] code as to what the variables are" and others completely missed the stated problem. Roland started with a Bézier curve as input (to get a perfect match), which made it harder to understand both the problem and (at least for me) the solution. The difference from interpolation is easier to see for input that leaves residuals. Here is both paraphrased code and non-Bézier input -- and an unexpected outcome.

    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()
    

    This works well for fcn = np.cos but not for log. I kind of expected that the fit would use the t-component of the control points as additional degrees of freedom, as we would do by dragging the control points:

    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()
    

    The cause of failure, I guess, is that the norm measures the distance between points on the curves instead of the distance between a point on one curve to the nearest point on the other curve.

提交回复
热议问题