B样条曲线——de Boor递推算法实现

版权声明:欢迎转载和交流。 https://blog.csdn.net/Hachi_Lin/article/details/89812126

B样条曲线——de Boor递推算法实现

1. 定义

  为保留Bezier方法的优点,B样条曲线的方程定义为
P ( t ) = i = 0 n P i N i , k ( t ) P(t)=\sum_{i=0}^n P_i N_{i,k}(t)
其中, P i ( i = 0 , 1 , , n ) P_i(i=0,1,\dots,n) 是控制多边形的顶点, N i , k ( t ) ( i = 0 , 1 , , n ) N_{i,k}(t)(i=0,1,\dots,n) 称为 k k 阶( k 1 k-1 次)B样条基函数,其中每一个称为B样条,它是一个由称为节点矢量的费递增参数 t t 的序列 T T t 0 t 1 t n + k t_0 \le t_1 \le \dots \le t_{n+k} 所决定的 k k 阶分段多项式,即为 k k 阶( k 1 k-1 次)B多项式样条。
  下面是基函数的递推公式,也称为de Boor-Cox公式:
{ N i , 1 ( t ) = { 1 , t i t t i + 1 0 , t < t i 或者 t t i + k N i , k ( t ) = t t i t i + k 1 t i N i , k 1 ( t ) + t i + k t t i + k t i + 1 N i + 1 , k 1 ( t ) . k 2 \begin{cases} N_{i,1}(t) = \begin{cases}&1 ,t_i \le t \le t_{i+1} \\ & 0, t < t_i \text{或者} t \ge t_{i+k} \end{cases} \\ N_{i,k}(t)=\frac{t-t_i}{t_{i+k-1}-t_i}N_{i,k-1}(t)+\frac{t_{i+k}-t}{t_{i+k}-t_{i+1}}N_{i+1,k-1}(t).\quad k \ge 2 \end{cases}
并约定 0 0 = 0 \frac{0}{0}=0
  递推公式表明:欲确定第 i i k k 阶B样条 N i , k ( t ) N_{i,k}(t) 需要用到 t i , t i + 1 , , t i + k t_i,t_{i+1},\dots,t_{i+k} k + 1 k+1 个节点,称区间 [ t i , t i + k ] [t_i,t_{i+k}] N i , k ( t ) N_{i,k}(t) 的支撑区间。曲线方程中, n + 1 n+1 个控制节点 P i P_i 要用到 n + 1 n+1 k k 阶B样条基 N i , k ( t ) N_{i,k}(t) 。它们的支撑区间的并集定义了这一组B样条基的节点矢量 T = [ t 0 , t 1 , , t n + k ] T=[t_0,t_1,\dots,t_{n+k}]

2. 性质

2.1 局部性

移动该曲线的第 i i 个控制顶点 P i P_i 至多影响定义在区间 ( t i , t i + k ) (t_i,t_{i+k}) 上那部分曲线的形状,对曲线的其余部分不产生影响。

2.2 连续性

P ( t ) P(t) r r 重节点 t i ( k i n ) t_i(k \le i \le n) 处的连续阶不低于 k 1 r k-1-r ;整条曲线 P ( t ) P(t) 的连续阶不低于 k 1 r max k-1-r_{\max} ,其中 r max r_{\max} 表示位于区间 ( t k 1 , t n + 1 ) (t_{k-1},t_{n+1}) 内节点的最大重数。

2.3 凸包性

P ( t ) P(t) 在区间 ( t i , t i + 1 ) (t_i,t_{i+1}) k 1 i n k-1 \le i \le n 上的部分位于 k k 个点 P i k + 1 , , P i P_{i-k+1},\dots,P_i 的凸包 C i C_i 内,整条曲线则位于各凸包 C i C_i 的并集 i = k 1 n C i \bigcup_{i=k-1}^n C_i 内。

3. B样条曲线类型的划分

3.1 均匀B样条曲线

节点矢量中节点为沿参数轴均匀或等距分布,所有节点区间长度 Δ i = t i + 1 t i = > 0 ( i = 0 , 1 , , n + k 1 ) \Delta_i = t_{i+1}-t_i=常数 >0(i=0,1,\dots,n+k-1) ,这样的节点矢量定义了均匀B样条基。

3.2 准均匀B样条曲线

准均匀B样条与均匀B样条曲线的差别在于两端点具有重复度k,这样的节点矢量定义了准均匀的B样条曲线。

3.3 分段Bezier曲线

节点矢量中两端点具有重复度 k k ,所有内节点重复度为k-1,这样的节点矢量定义了分段的Bernstein基。

3.4 非均匀B样条曲线

在这种类型里,任意分布的节点矢量 T = [ t 0 , t 1 , , t n + k ] T=[t_0,t_1,\dots,t_{n+k}] ,只要在数学上成立(节点序列递增,两端节点的重复度 k \le k ,内部节点重复度 k 1 \le k-1 )都可以取。这样的节点矢量定义了非均匀B样条基。

4. de Boor算法

先将 t t 固定在区间 [ t j , t j + 1 ) ( k 1 j n ) [t_j,t_{j+1})(k-1 \le j \le n) 上,由de Boor-Cox公式有
P ( t ) = i = 0 n P i N i , k ( t ) = i = j k + 1 j P i N i , k ( t ) = i = j k + 1 j P i [ ( t t i t i + k 1 t i ) N i , k 1 ( t ) + ( t i + k t t i + k t i + 1 ) N i + 1 , k 1 ( t ) ] = i = j k + 1 j [ ( t t i t i + k 1 t i ) P i + ( t i + k 1 t t i + k 1 t i + 1 ) P i 1 ] N i , k 1 ( t ) \begin{aligned} P(t) &=\sum_{i=0}^{n} P_{i} N_{i, k}(t) = \sum_{i=j-k+1}^{j}P_i N_{i,k}(t) \\ &= \sum_{i=j-k+1}^{j} P_{i}\left[\left(\frac{t-t_{i}}{t_{i+k-1}-t_{i}}\right) N_{i, k-1}(t)+\left(\frac{t_{i+k}-t}{t_{i+k}-t_{i+1}}\right) N_{i+1, k-1}(t)\right] \\ & = \sum_{i=j-k+1}^{j} \left[\left(\frac{t-t_{i}}{t_{i+k-1}-t_{i}}\right)P_i+\left(\frac{t_{i+k-1}-t}{t_{i+k-1}-t_{i+1}}\right) P_{i-1}\right]N_{i,k-1}(t) \end{aligned}
现令
P i ( r ) ( t ) = { ( 1 τ i j ) P i 1 ( r 1 ) ( t ) + τ i j P i ( r 1 ) ( t )  if  r = 1 , 2 , , k 1 P i  if  r = 0 \mathbf{P}_{i}^{(r)}(t)=\left\{\begin{array}{ll}{\left(1-\tau_{i}^{j}\right) \mathbf{P}_{i-1}^{(r-1)}(t)+\tau_{i}^{j} \mathbf{P}_{i}^{(r-1)}(t)} & {\text { if } r=1,2,\dots,k-1} \\ {\mathbf{P}_{i}} & {\text { if } r=0}\end{array}\right.
其中
τ i r = t t i t i + k r t i \tau_{i}^{r}=\frac{t-t_{i}}{t_{i+k-r}-t_{i}}
最终得到
P ( t ) = P j [ k 1 ] ( t ) P(t) = P_j^{[k-1]}(t)

5. python程序实现均匀B样条曲线

# -*- coding: utf-8 -*-
import numpy as np
from scipy.special import comb, perm
from matplotlib import pyplot as plt

class MyB:
    def __init__(self, line):
        self.line = line
        self.index_02 = None #保存拖动的这个点的索引
        self.press = None # 状态标识,1为按下,None为没按下
        self.pick = None # 状态标识,1为选中点并按下,None为没选中
        self.motion = None #状态标识,1为进入拖动,None为不拖动
        self.xs = list() # 保存点的x坐标
        self.ys = list() # 保存点的y坐标
        self.cidpress = line.figure.canvas.mpl_connect('button_press_event', self.on_press) # 鼠标按下事件
        self.cidrelease = line.figure.canvas.mpl_connect('button_release_event', self.on_release) # 鼠标放开事件
        self.cidmotion = line.figure.canvas.mpl_connect('motion_notify_event', self.on_motion) # 鼠标拖动事件
        self.cidpick = line.figure.canvas.mpl_connect('pick_event', self.on_picker) # 鼠标选中事件

    def on_press(self, event): # 鼠标按下调用
        if event.inaxes!=self.line.axes: return
        self.press = 1
        
    def on_motion(self, event): # 鼠标拖动调用
        if event.inaxes!=self.line.axes: return
        if self.press is None: return
        if self.pick is None: return
        if self.motion is None: # 整个if获取鼠标选中的点是哪个点
            self.motion = 1
            x = self.xs
            xdata = event.xdata
            ydata = event.ydata
            index_01 = 0
            for i in x:
                if abs(i - xdata) < 0.02: # 0.02 为点的半径
                    if abs(self.ys[index_01] - ydata) < 0.02:break
                index_01 = index_01 + 1
            self.index_02 = index_01
        if self.index_02 is None: return
        self.xs[self.index_02] = event.xdata # 鼠标的坐标覆盖选中的点的坐标
        self.ys[self.index_02] = event.ydata
        self.draw_01()

    def on_release(self, event): # 鼠标按下调用
        if event.inaxes!=self.line.axes: return
        if self.pick == None: # 如果不是选中点,那就添加点
            self.xs.append(event.xdata)
            self.ys.append(event.ydata)
        if self.pick == 1 and self.motion != 1: # 如果是选中点,但不是拖动点,那就降阶
            x = self.xs
            xdata = event.xdata
            ydata = event.ydata
            index_01 = 0
            for i in x:
                if abs(i - xdata) < 0.02:
                    if abs(self.ys[index_01] - ydata) < 0.02: break
                index_01 = index_01 + 1
            self.xs.pop(index_01)
            self.ys.pop(index_01)
        self.draw_01()
        self.pick = None # 所有状态恢复,鼠标按下到稀放为一个周期
        self.motion = None
        self.press = None
        self.index_02 = None

    def on_picker(self, event): # 选中调用
        self.pick = 1

    def draw_01(self): # 绘图
        self.line.clear() # 不清除的话会保留原有的图
        self.line.axis([0,1,0,1]) # x和y范围0到1
        self.b(self.xs,self.ys) # B样条曲线
        self.line.scatter(self.xs, self.ys,color='b',s=200, marker="o",picker=5) # 画点
        self.line.plot(self.xs, self.ys,color='r') # 画线
        self.line.figure.canvas.draw() # 重构子图

    def b(self,*args): # Bezier曲线公式转换,获取x和y
        k = 3 # 阶数
        n = len(args[0])-1 # 顶点的个数-1
        T = np.linspace(1,10,n+k+1) # T 范围1到10,均匀B样条曲线
        # if n >= k-1:
        #     T = [1]*k+(np.linspace(2,9,n-k+1)).tolist()+[10]*k # 准均匀样条
        x,y = [],[]
        # 递推公式
        # def de_Boor(r,t,i):
        #     if r == 0:
        #         return [args[0][i],args[1][i]]
        #     else:
        #         return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor(r-1,t,i-1)
        def de_Boor_x(r,t,i):
            if r == 0:
                return args[0][i]
            else:
                if T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] != 0:
                    return ((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i-1)
                elif T[i+k-r]-T[i] != 0 and T[i+k-r]-T[i] == 0:
                    return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i)
                elif T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] == 0:
                    return 0
                return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_x(r-1,t,i-1)
        def de_Boor_y(r,t,i):
            if r == 0:
                return args[1][i]
            else:
                if T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] != 0:
                    return ((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i-1)
                elif T[i+k-r]-T[i] != 0 and T[i+k-r]-T[i] == 0:
                    return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i)
                elif T[i+k-r]-T[i] == 0 and T[i+k-r]-T[i] == 0:
                    return 0
                return ((t-T[i])/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i)+((T[i+k-r]-t)/(T[i+k-r]-T[i]))*de_Boor_y(r-1,t,i-1)
        def plot(x,y):
            for j in range(k-1,n+1):
                for t in np.linspace(T[j],T[j+1]):
                    x.append(de_Boor_x(k-1,t,j))
                    y.append(de_Boor_y(k-1,t,j))
                #print(x,y)
            self.line.plot(x,y)
        if n >= k-1:
            plot(x,y)

fig = plt.figure(2,figsize=(12,6)) #创建第2个绘图对象,1200*600像素
ax = fig.add_subplot(111) #一行一列第一个子图
ax.set_title('My B')

myBezier = MyB(ax)
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

效果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Hachi_Lin/article/details/89812126