使用Numpy擬合貝塞爾曲線

貝塞爾曲線: 通過起點、終點以及多個控制點繪製得到的曲線,Photoshop中的鋼筆工具就是貝塞爾曲線。另外,CSS中動畫的計時函數也有三次貝塞爾曲線,例如常用的ease - cubic-bezier(0.25,0.1,0.25,1), ease-in-out - cubic-bezier(0.42,0,0.58,1)。貝塞爾曲線的通用計算公式爲B(t)=Σi=0n(ni)Pi(1t)niti,0t1B(t)=\Sigma_{i=0}^{n}\binom{n}{i}Pi(1-t)^{n-i}t^i, 0 \le t \ge 1,其中t可以理解爲時刻,Pi表示控制點(包括起終點)。

本文使用Numpy擬合三次貝塞爾曲線cubic-bezier(0.3,0,0,1),其圖像如下:
三次貝塞爾曲線

擬合後的結果如下:
擬合後的曲線

可以使用geogebra查看擬合後的曲線在整個座標軸上的結果,本文中擬合後的結果爲:5634657.187995095x16+45207123.80406813x15+164321160.9410473x14+357735878.3266866x13+519499735.0536763x12+530305734.45764804x11+390543484.20302945x10+209498881.24963355x9+81592649.14667341x8+22708417.585277304x7+4379479.970961253x6+556579.9351615013x5+43295.904223406396x4+1880.8424521764407x3+32.99834229194805x2+0.19434715012662382x1+0.0003451401468581472x-5634657.187995095x^{16}+45207123.80406813x^{15}+-164321160.9410473x^{14}+357735878.3266866x^{13}+-519499735.0536763x^{12}+530305734.45764804x^{11}+-390543484.20302945x^{10}+209498881.24963355x^{9}+-81592649.14667341x^{8}+22708417.585277304x^{7}+-4379479.970961253x^{6}+556579.9351615013x^{5}+-43295.904223406396x^{4}+1880.8424521764407x^{3}+-32.99834229194805x^{2}+0.19434715012662382x^{1}+0.0003451401468581472x,挺長的emmm

擬合過程:

  1. 以間隔0.001計算每個t時對應的座標(x,y)
  2. 使用pyplot繪製曲線
  3. 使用np.polyfit(x, y, deg)擬合曲線
  4. 繪製擬合後的曲線,根據結果調整deg參數(即多項式的最高次)

代碼如下

from matplotlib import pyplot as plt
import numpy as np

p0 = (0, 0)
p1 = (0, 0)
p2 = (1, 1)
p3 = (1, 1)

def calculateP(t: float):
    """ 
    根據p0~p3計算時刻t曲線的座標,0 <= t <= 1,曲線: cubic-bezier(.4,0,0,1)  
    p0      p1          p2      p3  
    (0, 0)  (0.4, 0)    (0, 1)  (1, 1)

    返回一個(x, y)
    """
    tmp = 1 - t
    x = p0[0] * pow(tmp, 3) + 3 * p1[0] * t * pow(tmp, 2) + 3 * \
        p2[0] * pow(t, 2) * tmp + 1 * p3[0] * pow(t, 3)
    y = p0[1] * pow(tmp, 3) + 3 * p1[1] * t * pow(tmp, 2) + 3 * \
        p2[1] * pow(t, 2) * tmp + 1 * p3[1] * pow(t, 3)
    return (x, y)

def getPoints(dis, calc: callable):
    """ 
    獲取三次貝塞爾曲線的離散點,參數 dis 指定離散點的間距,calc 指定計算函數

    返回一個座標列表[(x,y)] 
    """
    if dis <= 0:
        return [(0, 0)]
    t = 0
    res = []
    while t < 1:
        res.append(calc(t))
        t += dis
    return res

def showCurve(points):
    """ 繪製 points 中的點 """
    # plt.figure(figsize=(5, 5))
    plt.subplot()
    plt.grid()
    plt.plot([p[0] for p in points], [p[1] for p in points])
    plt.show()

def work():
    global p1
    global p2
    dis = 0.001
    p1 = (0.3, 0)
    p2 = (0, 1)
    points = getPoints(dis, calculateP)
    showCurve(points)
    f = np.polyfit([p[0] for p in points], [p[1] for p in points], 16)
    print([x for x in f])
    f = np.poly1d(f)
    npoints = getPoints(dis, lambda x: (x, f(x)))
    showCurve(npoints)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章