B-Spline曲线拟合 – Python实现

1.曲线定义

定义曲线为p阶样条曲线

给定n+1个控制点 P0,P1,...Pn

节点向量 U = {u0,u1....um},且m = n+p+1

B-Spline曲线定义如下:

在这里插入图片描述

N(i,p)为样条基函数

P(i)为控制顶点

参数u为参数节点,一般取0 ≤ u ≤ 1

在这里插入图片描述

上式为样条基函数的递推公式

2.De Boor 算法

参考文章:
https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/B-spline/single-insertion.html

根据链接中的描述,B-Spline公式可以简化为以下公式:

img

img

Pi为控制顶点

ai为比例系数,由当前节点t和节点u(i) u(i+p) 计算得到

在这里插入图片描述在这里插入图片描述

3.算法实现流程 – 3次B-Spline曲线

第一步:设置曲线次数为p=3,读取控制顶点P0,P1,...Pn,根据控制顶点得到节点向量U = {u0,u1....um},注意m = n+p+1.为了保证拟合的曲线经过第一个、最后一个控制点,节点向量首末重复度要设置为为p+1,即U = {0,0,0,0,u(4)...u(n),1,1,1,1}

第二步:按照本文描述的De Boor算法完成递推函数

第三步:在0 ≤ u ≤ 1取值,即可得到u对应的P(u)位置

4.Python 实现

下面贴出完整python代码,仅供参考

import matplotlib.pyplot as plt
import numpy as nmp
import math
#degree. 3次样条
knotType = 1   #knot的计算方式 0:平均取值  1:根据长度取值
CPointX = []
CPointY = []
#控制轮廓顶点

with open('data.txt', 'r') as f1:
    list1 = f1.readlines()

for i in range(len(list1)):
    str = list1[i]
    str1 = str.split(",")
    CPointX.append(float(str1[0]))
    CPointY.append(float(str1[1]))

n = len(CPointX)
knot = list(nmp.arange(n+3+1))
for i in range(0,4):
    knot[i] = 0.0
    knot[n+i] = 1.0

if knotType:
    L = list(nmp.arange(n-1))
    S = 0
    for i in range(n-1):
        L[i] = nmp.sqrt(pow(CPointX[i+1]- CPointX[i],2) + pow(CPointY[i+1]- CPointY[i],2))
        S = S + L[i]

    tmp = L[0]
    for i in range(4,n):
        tmp = tmp + L[i-3]
        knot[i] = tmp / S
else:
    tmp = 1 / (n-3)
    tmpS = tmp
    for i in range(4,n):
        knot[i] = tmpS
        tmpS = tmpS + tmp
print(knot)
#节点向量
#knot = [0,0,0,0,0.25,0.4,0.8,1,1,1,1]

def de_boor_cal(u,knot,X, Y):
    #判断u在哪个区间
    m = len(knot) #节点个数 且 m = n + k +1
    n = len(X)
    i = 3
    for i in range(3,m-3):
        if u>=knot[i] and u<knot[i+1]:
            break
    #递归计算
    if knot[i+3] - knot[i] == 0:
        a41 = 0
    else:
        a41 = (u - knot[i]) / (knot[i+3] - knot[i])
    if knot[i-1+3] - knot[i-1] == 0:
        a31 = 0
    else:
        a31 = (u - knot[i-1]) / (knot[i-1+3] - knot[i-1])
    if knot[i-2+3] - knot[i-2] == 0:
        a21 = 0
    else:
        a21 = (u - knot[i-2]) / (knot[i-2+3] - knot[i-2])

    XP40 = X[i]
    YP40 = Y[i]
    XP30 = X[i-1]
    YP30 = Y[i - 1]
    XP20 = X[i-2]
    YP20 = Y[i - 2]
    XP10 = X[i-3]
    YP10 = Y[i - 3]

    XP41 = (1 - a41) * XP30 + a41 * XP40
    YP41 = (1 - a41) * YP30 + a41 * YP40

    XP31 = (1 - a31) * XP20 + a31 * XP30
    YP31 = (1 - a31) * YP20 + a31 * YP30

    XP21 = (1 - a21) * XP10 + a21 * XP20
    YP21 = (1 - a21) * YP10 + a21 * YP20
    if knot[i+3-1] - knot[i] == 0:
        a42 = 0
    else:
        a42 = (u - knot[i]) / (knot[i+3-1] - knot[i])
    if knot[i-1+3-1] - knot[i-1] == 0:
        a32 = 0
    else:
        a32 = (u - knot[i-1]) / (knot[i-1+3-1] - knot[i-1])


    XP42 = (1 - a42) * XP31 + a42 * XP41
    YP42 = (1 - a42) * YP31 + a42 * YP41

    XP32 = (1 - a32) * XP21 + a32 * XP31
    YP32 = (1 - a32) * YP21 + a32 * YP31

    if knot[i+3-2] - knot[i] == 0:
        a43 = 0
    else:
        a43 = (u - knot[i]) / (knot[i+3-2] - knot[i])

    P43 = [(1 - a43) * XP32 + a43 * XP42,(1 - a43) * YP32 + a43 * YP42]
    return P43


plt.plot(CPointX,CPointY, '-ob', label="Waypoints")
plt.grid(True)
plt.legend()
plt.axis("equal")

Bspline = [[],[]]
for u in nmp.arange(0.0,1.0,0.0005):
    P = de_boor_cal(u,knot,CPointX,CPointY)
    Bspline[0].append(P[0])
    Bspline[1].append(P[1])

Bspline[0].append(CPointX[-1])
Bspline[1].append(CPointY[-1])
plt.plot(Bspline[0],Bspline[1], 'r')
plt.show()

运行效果:

在这里插入图片描述