B样条曲线的公式推导及代码实现

本文仅简述B样条曲线的公式推导,并给出了一种代码实现。在阅读本文之前,请确保你已经对B样条曲线的背景知识有所了解。相关知识可以通过以下课程进行学习:MOOC-计算机图形学-中国农业大学-赵明或者观看B站搬运版

公式定义

给定如下参数:

  • n + 1 n+1 n+1个控制点 P i ( i = 0 , 1 , ⋯   , n ) P_i(i=0,1,\cdots,n) Pi(i=0,1,,n)
  • n + k + 1 ( 2 ⩽ k ⩽ n + 1 ) n+k+1(2\leqslant k\leqslant n+1) n+k+1(2kn+1)个参数: { u i ∣ i = 0 , 1 , ⋯   , n + k } \{u_i|i=0,1,\cdots,n+k\} {uii=0,1,,n+k}

构造多项式函数
P ( u ) = ∑ i = 0 n P i B i , k ( u ) , u ∈ [ u k − 1 , u n + 1 ] (1) \mathcal{P}(u)=\sum_{i=0}^{n}P_i B_{i,k}(u), \quad u\in[u_{k-1},u_{n+1}] \tag{1} P(u)=i=0nPiBi,k(u),u[uk1,un+1](1)来逼近曲线。其中 B i , k ( u ) B_{i,k}(u) Bi,k(u)称为 k k k阶( k − 1 k-1 k1次)B样条基函数,它是由DeBoor-Cox递推公式来定义的:
B i , 1 ( u ) = { 1 , u i ⩽ u < u i + 1 0 , o t h e r w i s e   , B i , k ( u ) = u − u i u i + k − 1 − u i B i , k − 1 ( u ) + u i + k − u u i + k − u i + 1 B i + 1 , k − 1 ( u )   。 (2) \begin{alignedat}{3} & B_{i,1}(u) = \begin{cases} 1, & u_i\leqslant u < u_{i+1} \\ 0, & otherwise \end{cases} \thinspace, \\ & B_{i,k}(u) = \frac{u-u_i}{u_{i+k-1}-u_i} B_{i,k-1}(u) + \frac{u_{i+k}-u}{u_{i+k}-u_{i+1}} B_{i+1,k-1}(u) \thinspace。 \end{alignedat} \tag{2} Bi,1(u)={1,0,uiu<ui+1otherwiseBi,k(u)=ui+k1uiuuiBi,k1(u)+ui+kui+1ui+kuBi+1,k1(u)(2)递推式中若遇到 0 0 0除以 0 0 0,则除法结果取值为 0 0 0

B i , k ( u ) B_{i,k}(u) Bi,k(u)可以看作是控制点 P i P_i Pi u u u下的权值,因为B样条基函数具有权性:
∑ i = 0 n B i , k ( u ) ≡ 1 , ∀ u   。 (3) \sum_{i=0}^{n} B_{i,k}(u) \equiv 1, \quad \forall u \thinspace。 \tag{3} i=0nBi,k(u)1,u(3)

对于某个 u ∈ [ u j , u j + 1 ) u\in [u_j, u_{j+1}) u[uj,uj+1),运用公式(2)递推 B i , k ( u ) B_{i,k}(u) Bi,k(u),使得最终的结果中只含 B x , 1 ( u ) B_{x,1}(u) Bx,1(u),我们可以推出 x = i , i + 1 , ⋯   , i + k − 1 x=i,i+1,\cdots,i+k-1 x=i,i+1,,i+k1。又根据 B x , 1 ( u ) B_{x,1}(u) Bx,1(u)的定义式(2),我们发现仅当 j ∈ { i , i + 1 , ⋯ i + k − 1 } j\in\{i,i+1,\cdots i+k-1\} j{i,i+1,i+k1},即 i ∈ { j − ( k − 1 ) , ⋯   , j } i\in\{j-(k-1),\cdots,j\} i{j(k1),,j}时, B i , k ( u ) B_{i,k}(u) Bi,k(u)有可能不为0。

举个例子来解释一下。以 i = 7 , k = 3 i=7,k=3 i=7,k=3为例,运用公式(2)递推 B 7 , 3 ( u ) B_{7,3}(u) B7,3(u)
B 7 , 3 ( u ) = c 0 B 7 , 2 ( u ) + c 1 B 8 , 2 ( u ) = c 2 B 7 , 1 ( u ) + c 3 B 8 , 1 ( u ) + c 4 B 9 , 1 ( u ) \begin{aligned} B_{7,3}(u) &= c_0B_{7,2}(u)+c_1B_{8,2}(u) \\ &= c_2B_{7,1}(u)+c_3B_{8,1}(u)+c_4B_{9,1}(u) \end{aligned} B7,3(u)=c0B7,2(u)+c1B8,2(u)=c2B7,1(u)+c3B8,1(u)+c4B9,1(u)(其中 c 0 , c 1 , ⋯   , c 4 c_0,c_1,\cdots,c_4 c0,c1,,c4均为系数),我们发现最终的结果中只含 B 7 , 1 ( u ) , B 8 , 1 ( u ) , B 9 , 1 ( u ) B_{7,1}(u), B_{8,1}(u),B_{9,1}(u) B7,1(u),B8,1(u),B9,1(u)。对于某个 u ∈ [ u j , u j + 1 ) u\in [u_j, u_{j+1}) u[uj,uj+1),仅当 j ∈ { 7 , 8 , 9 } j\in\{7,8,9\} j{7,8,9} B 7 , 1 ( u ) , B 8 , 1 ( u ) , B 9 , 1 ( u ) B_{7,1}(u), B_{8,1}(u),B_{9,1}(u) B7,1(u),B8,1(u),B9,1(u)不全为0,从而使得 B 7 , 3 ( u ) B_{7,3}(u) B7,3(u)有可能不为0。

算法原理

对于某个 u ∈ [ u j , u j + 1 ] u\in [u_j, u_{j+1}] u[uj,uj+1],我们定义
d i , 0 = P i , i = j − ( k − 1 ) , ⋯   , j   , (4) d_{i,0} = P_i,\quad i = j-(k-1),\cdots,j \thinspace,\tag{4} di,0=Pi,i=j(k1),,j(4) α i , r = u − u i u i + k − r − u i , d i , r = ( 1 − α i , r ) d i − 1 , r − 1 + α i , r d i , r − 1 ( r = 1 , 2 , ⋯   , k − 1 i = j − ( k − 1 ) + r , ⋯   , j )   。 (5) \begin{alignedat}{3} \alpha_{i,r} &= \frac{u-u_i}{u_{i+k-r}-u_i}, \\ d_{i,r} &= (1-\alpha_{i,r})d_{i-1,r-1}+\alpha_{i,r}d_{i,r-1} \end{alignedat} \left(\begin{array}{l} r = 1,2,\cdots,k-1 \\ i = j-(k-1)+r,\cdots,j \end{array}\right) \thinspace。 \tag{5} αi,rdi,r=ui+kruiuui,=(1αi,r)di1,r1+αi,rdi,r1(r=1,2,,k1i=j(k1)+r,,j)(5)

这里对公式(5)中的 i = j − ( k − 1 ) + r , ⋯   , j i = j-(k-1)+r,\cdots,j i=j(k1)+r,,j做出解释:递推 d i , r d_{i,r} di,r使最终结果中只含 d x , 0 d_{x,0} dx,0,得到 x = i − r , ⋯   , i x=i-r,\cdots,i x=ir,,i。由 d x , 0 d_{x,0} dx,0的定义式(4)知, ∀ x , x ∈ { j − ( k − 1 ) , ⋯   , j } \forall x, x\in\{j-(k-1),\cdots,j\} x,x{j(k1),,j}。因此有 i − r ∈ { j − ( k − 1 ) , ⋯   , j } , ⋯   , i ∈ { j − ( k − 1 ) , ⋯   , j } i-r\in\{j-(k-1),\cdots,j\},\cdots,i\in\{j-(k-1),\cdots,j\} ir{j(k1),,j},,i{j(k1),,j},从而得到 i ∈ { j − ( k − 1 ) + r , ⋯   , j } i\in\{j-(k-1)+r,\cdots,j\} i{j(k1)+r,,j}

可以证明
P ( u ) = d j , k − 1   。 (6) \mathcal{P}(u) = d_{j,k-1} \thinspace。 \tag{6} P(u)=dj,k1(6)

于是得到算法逻辑如下(假定输入只提供了 n + 1 n+1 n+1个控制点,参数 u i ( i ∈ { 0 , 1 , ⋯   , n + k } ) u_i(i\in\{0,1,\cdots,n+k\}) ui(i{0,1,,n+k})由我们自定义选取):

  • 在实数域上任取一段区间,不妨取 [ 0 , 1 ] [0,1] [0,1]
  • [ 0 , 1 ] [0,1] [0,1]均匀分隔为 n + k n+k n+k段(控制点个数= n + 1 n+1 n+1,曲线阶数= k k k)。
  • 分隔之后产生了 n + k + 1 n+k+1 n+k+1个分隔点,依次记为 u 0 , u 1 , ⋯   , u n + k u_0,u_1,\cdots,u_{n+k} u0,u1,,un+k。于是有 u j = j / ( n + k ) u_j=j/(n+k) uj=j/(n+k)
  • 在区间 [ u k − 1 , u n + 1 ] [u_{k-1},u_{n+1}] [uk1,un+1]上大量采样。对于每个样本 u u u,计算 P ( u ) \mathcal{P}(u) P(u)
  • 根据公式(6),我们可以通过计算 d j , k − 1 d_{j,k-1} dj,k1来计算 P ( u ) \mathcal{P}(u) P(u)
  • d j , k − 1 d_{j,k-1} dj,k1可以通过公式(5)进行递归计算。

算法实现

def draw_curve(p_list, k: int = 4):
	"""
    :param p_list: (list of list of int: [[x0, y0], [x1, y1], [x2, y2], ...]) 曲线的控制点坐标列表
    :param k: B样条曲线的阶数,默认为4阶,即3次均匀B样条曲线
    :return: (list of list of int: [[x_0, y_0], [x_1, y_1], [x_2, y_2], ...]) 绘制结果的像素点坐标列表
    """
    n = len(p_list) - 1
    if n == 0:
        return p_list
    elif n == 1:
        return draw_line(p_list) # 调用绘制直线的算法
        
    assert 2 <= k <= n + 1
    freq = 500
    result = []
    
    for j in range(k - 1, n + 1):
        seg = n + k
        u_j = j / seg
        for u in range(freq + 1):
            u = u / (freq * seg) + u_j
            tmp = [
                [p_list[i + j - (k-1)][0],
                 p_list[i + j - (k-1)][1]
                ] for i in range(k)
            ]
            for r in range(1, k):
                for i in range(k - 1, r - 1, -1):
                    ii = i + j - (k - 1)
                    u_i = ii / seg
                    alpha = (u - u_i) / ((ii+k-r)/seg - u_i)
                    tmp[i] = [
                        (1-alpha)*tmp[i-1][0] + alpha*tmp[i][0],
                        (1-alpha)*tmp[i-1][1] + alpha*tmp[i][1]
                    ]
            result.append(
                [round(tmp[k-1][0]), round(tmp[k-1][1])]
            )
    return result

补充: P ( u ) \mathcal{P}(u) P(u)定义域的推导

在公式(1)中,我们直接定义变量 u u u的取值范围为 [ u k − 1 , u n + 1 ] [u_{k-1},u_{n+1}] [uk1,un+1]。这一节中我们将详细说明其原理。

根据递推公式(2),我们可以推知:

  • B i , 1 B_{i,1} Bi,1涉及1个区间( [ u i , u i + 1 ] [u_i,u_{i+1}] [ui,ui+1])2个控制点( u i u_i ui u i + 1 u_{i+1} ui+1
  • B i , 2 B_{i,2} Bi,2 B i , 1 B_{i,1} Bi,1 B i + 1 , 1 B_{i+1,1} Bi+1,1组成,涉及2个区间3个控制点
  • B i , 3 B_{i,3} Bi,3 B i , 2 B_{i,2} Bi,2 B i + 1 , 2 B_{i+1,2} Bi+1,2组成,涉及3个区间4个控制点
  • ⋯ \cdots
  • B i , k B_{i,k} Bi,k涉及 k k k个区间 k + 1 k+1 k+1个控制点

从而可以定义 B i , k B_{i,k} Bi,k支承区间 [ u i , u i + k ] [u_i,u_{i+k}] [ui,ui+k]

控制点-支承区间
举个例子,如上图,其中 n = 4 , k = 4 n=4,k=4 n=4,k=4。根据支承区间的定义,我们有:

  • P 0 B 0 , 4 P_0B_{0,4} P0B0,4涉及节点 u 0 u_0 u0 u 4 u_4 u4
  • P 1 B 1 , 4 P_1B_{1,4} P1B1,4涉及节点 u 1 u_1 u1 u 5 u_5 u5
  • ⋯ \cdots
  • P 4 B 4 , 4 P_4B_{4,4} P4B4,4涉及节点 u 4 u_4 u4 u 8 u_8 u8

对于每个区间 [ u i , u i + 1 ] [u_i,u_{i+1}] [ui,ui+1],它至多包含于 k k k个不同的支承区间中,也就是说,它至多与 k k k个控制点相关。下图形象地展示了这种关系。
在这里插入图片描述
我们的目标是,对于 P ( u ) \mathcal{P}(u) P(u)的定义域中的每个区间,要使它与尽可能多的控制点相关。于是我们最终选择了 [ u k − 1 , u n + 1 ] [u_{k-1},u_{n+1}] [uk1,un+1]。可以证明, ∀ [ u i , u i + 1 ] ⊆ [ u k − 1 , u n + 1 ] \forall [u_i,u_{i+1}]\subseteq [u_{k-1},u_{n+1}] [ui,ui+1][uk1,un+1] [ u i , u i + 1 ] [u_i,u_{i+1}] [ui,ui+1]都与 k k k个控制点相关。

更多细节

由于篇幅原因,文中有些证明并未详细展开叙述。更多的证明细节请参阅相关论文。例如C. DE BOOR, On calculating with B-splines, 1972等。

<think>好的,我现在需要帮助用户了解B样条曲线的矩阵表示方法以及实现计算。首先,我需要回顾一下B样条曲线的基本概念,确保自己理解正确。B样条曲线是一种参数曲线,广泛应用于计算机图形学和CAD领域,因为它具有局部控制和灵活性的优点。 用户提到了矩阵表示方法,这可能涉及到将B样条的基函数用矩阵形式表达,从而方便计算。根据引用[1],四阶三次B样条曲线的矩阵形式被推导出来,并且在MATLAB中实现了坐标的计算。四阶三次应该对应的是三次B样条,四阶可能指的是次数加一,即k=3时,阶数为4。三次B样条的基函数通常由四个控制点决定,每个区间的曲线段由四个控制点生成。 接下来,我需要回忆B样条曲线的矩阵形式是如何推导的。一般来说,B样条的基函数可以通过递归公式定义,但矩阵表示可能将基函数展开为多项式形式,并用矩阵乘法来简化计算。例如,三次均匀B样条的矩阵形式可能涉及到将基函数表示为参数t的多项式,并组合成矩阵,与控制点的坐标相乘得到曲线上的点。 根据引用[3],B样条基函数Bi,k(t)是关键,其中k是次数。对于三次B样条,k=3,基函数由四个控制点影响。每个曲线段的参数t在某个区间内,比如[0,1],通过基函数和控制点的线性组合得到曲线上的点。 现在,用户可能想知道如何具体构造这个矩阵。例如,三次B样条的标准矩阵可能是从基函数的展开式中得到的。例如,均匀三次B样条的矩阵形式通常是这样的: $$ \mathbf{M} = \frac{1}{6}\begin{bmatrix} -1 & 3 & -3 & 1 \\ 3 & -6 & 3 & 0 \\ -3 & 0 & 3 & 0 \\ 1 & 4 & 1 & 0 \end{bmatrix} $$ 这个矩阵与控制点的坐标相乘,再与参数向量[ t^3, t^2, t, 1 ]相乘,得到曲线上的点坐标。不过,需要确认这个矩阵是否正确,或者是否适用于特定类型的B样条,比如均匀或非均匀的。 根据引用[1],用户可能对四阶三次的矩阵形式感兴趣,可能对应上述矩阵。在实现时,每一段曲线需要四个控制点,通过矩阵乘法计算每个t对应的x和y坐标。例如,每一段曲线对应参数t从0到1的变化,将参数向量与矩阵相乘,再乘以四个控制点的坐标矩阵,得到该点的位置。 接下来,我需要考虑如何在代码实现这一点。MATLAB中的实现可以参考引用[1]和[2]。比如,使用循环遍历每一段曲线,将四个控制点代入矩阵计算,生成该段的坐标点,并存储到结果矩阵中。对于每个t值(比如等间距采样),计算对应的多项式值。 可能的步骤包括: 1. 确定控制点数量,计算曲线段数。对于三次B样条,每段需要四个控制点,段数等于总控制点数减3。 2. 对于每个曲线段i,取控制点P_i, P_{i+1}, P_{i+2}, P_{i+3}。 3. 对于每个参数t(比如0到1,步长0.01),计算参数向量T = [t^3, t^2, t, 1]。 4. 计算T * M,得到四个基函数的系数,然后与控制点矩阵相乘,得到该点的坐标。 在代码实现中,矩阵乘法可能需要将控制点按行或列排列,注意矩阵的维度。例如,控制点可能是4x2的矩阵(假设二维),而基函数的权重是1x4,相乘后得到点的坐标。 另外,引用[2]提到MATLAB中的spmak、spcrv等函数可以生成B样条,但用户可能需要手动实现矩阵形式,所以可能需要避开这些内置函数,直接根据矩阵公式计算坐标。 关于均匀和非均匀B样条的区别,均匀的情况下节点向量是等距的,而非均匀的情况下节点可以不等距。矩阵形式可能仅适用于均匀B样条的情况,非均匀可能需要不同的处理方式,比如使用de Boor算法。 现在,用户的问题涉及矩阵表示的实现方法,因此需要明确是否是均匀B样条,并且如何构造矩阵。在回答中需要详细说明矩阵的推导或直接给出标准矩阵形式,并解释如何应用该矩阵进行计算。 此外,用户可能想知道如何将矩阵方法与控制点结合,得到最终的曲线坐标。例如,对于每一段,如何将四个控制点的坐标与矩阵相乘,参数化t的取值,以及如何将这些结果组合成完整的曲线。 总结步骤: 1. 确定B样条的次数(三次对应k=3,四阶)。 2. 获取控制点序列。 3. 对于每个曲线段,提取对应的四个控制点。 4. 构造参数t的采样点。 5. 使用矩阵乘法计算每个t对应的坐标。 6. 存储所有段的结果,绘制曲线。 在代码示例中,可以使用MATLAB或Python。例如,在MATLAB中,可以预计算矩阵M,然后循环处理每个段,对每个t计算T*M*P,其中P是当前段的四个控制点组成的4x2矩阵(假设二维)。 可能的错误点包括矩阵的构造是否正确,参数t的范围是否合适,以及控制点的索引是否正确。例如,第三段中的控制点索引可能应该是i, i+1, i+2, i+3,其中i从0到n-4(假设控制点从0开始编号)。 最后,用户可能需要了解这种方法的优缺点,比如矩阵形式在计算上的效率,或者是否适用于实时计算等。同时,可能还需要提到如何处理端点条件,比如是否闭合曲线,或者是否需要重复控制点等。 总的来说,回答需要涵盖矩阵的构造、控制点的处理、参数的计算、代码实现的步骤,以及可能的MATLAB或Python示例。同时,结合引用的资料,说明如何将矩阵形式应用于实际计算中。</think>### B样条曲线矩阵表示与实现方法 #### 一、B样条曲线的矩阵表示 **三次均匀B样条曲线**的标准矩阵形式为: $$ \mathbf{M} = \frac{1}{6}\begin{bmatrix} -1 & 3 & -3 & 1 \\ 3 & -6 & 3 & 0 \\ -3 & 0 & 3 & 0 \\ 1 & 4 & 1 & 0 \end{bmatrix} $$ 该矩阵将基函数多项式化,结合参数向量 $[t^3, t^2, t, 1]$ 和控制点坐标,可计算曲线点的位置[^1]。 #### 二、实现步骤(以四阶三次B样条为例) 1. **分段处理** 若控制点数为 $n$,则曲线段数为 $n-3$。每段由相邻的4个控制点定义,例如第 $i$ 段对应 $P_i, P_{i+1}, P_{i+2}, P_{i+3}$。 2. **参数化计算** 对每段曲线,参数 $t \in [0,1]$ 均匀采样。曲线点坐标计算为: $$ Q(t) = \begin{bmatrix} t^3 & t^2 & t & 1 \end{bmatrix} \cdot \mathbf{M} \cdot \begin{bmatrix} P_i \\ P_{i+1} \\ P_{i+2} \\ P_{i+3} \end{bmatrix} $$ 3. **MATLAB实现示例** ```matlab function [xx, yy] = cubic_bspline_matrix(control_points, step) M = (1/6) * [-1, 3, -3, 1; 3, -6, 3, 0; -3, 0, 3, 0; 1, 4, 1, 0]; n = size(control_points, 1); t = 0:step:1; T = [t.^3; t.^2; t; ones(1, length(t))]'; xx = zeros(n-3, length(t)); yy = zeros(n-3, length(t)); for i = 1:n-3 P = control_points(i:i+3, :); Q = T * M * P; xx(i,:) = Q(:,1)'; yy(i,:) = Q(:,2)'; end end ``` 此代码逐段计算所有曲线点坐标,结果存储在 `xx` 和 `yy` 矩阵中[^2]。 #### 三、关键特性 1. **局部支持性**:单段曲线仅受4个控制点影响。 2. **连续性**:三次B样条天然满足 $C^2$ 连续性。 3. **均匀性**:矩阵形式仅适用于节点均匀分布的B样条[^3]。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值