一、局部曲线插值预备知识
给定 { Q k } ( k = 0 , 1 , ⋯ , n ) \{\pmb Q_k\}(k=0,1,\cdots,n) {Qk}(k=0,1,⋯,n),局部曲线插值是指创建 n n n 条多项式或有理曲线段: C i ( u ) ( i = 0 , 1 , ⋯ , n − 1 ) \pmb C_i(u)(i=0,1,\cdots,n-1) Ci(u)(i=0,1,⋯,n−1),要求相邻曲线段在连接点处满足事先指定的连续阶,采用多项式构造每个曲线段,然后选择一个合适的节点矢量得到 B 样条曲线。
为了生成 B 样条曲线段 C i ( u ) \pmb C_i(u) Ci(u),需要计算内控制点:二次的有一个;三次的有两个。这些控制点都在与曲线相切于 Q k \pmb Q_k Qk 点的直线上,因此我们需要知道每一个 Q k \pmb Q_k Qk 处的切向矢量 T k \pmb T_k Tk。
q k = Q k − Q k − 1 V k = ( 1 − α k ) q k + α k q k + 1 T k = V k ∣ V k ∣ (1) \begin{aligned} &\pmb{q}_k=\pmb Q_k-\pmb Q_{k-1}\\[2ex] &\pmb{V}_k=(1-\alpha_k)\pmb q_k+\alpha_k\pmb q_{k+1}\\[1ex] &\pmb T_k = \frac{\pmb V_k}{\mid\pmb V_k\mid} \end{aligned}\tag{1} qk=Qk−Qk−1Vk=(1−αk)qk+αkqk+1Tk=∣Vk∣Vk(1)
其中, T \pmb T T 表示单位长度切矢量。插值参数 α k \alpha_k αk 的计算方法如下(五点法):
α k = ∣ q k − 1 × q k ∣ ∣ q k − 1 × q k ∣ + ∣ q k + 1 × q k + 2 ∣ , k = 2 , ⋯ , n − 2 (2) \alpha_k=\frac{\mid\pmb q_{k-1}\times\pmb q_k\mid}{\mid\pmb q_{k-1}\times\pmb q_k\mid+\mid\pmb q_{k+1}\times\pmb q_{k+2}\mid},\quad k=2,\cdots,n-2\tag{2} αk=∣qk−1×qk∣+∣qk+1×qk+2∣∣qk−1×qk∣,k=2,⋯,n−2(2)
需要注意的是:当(2)式分母为 0 意味着要么 Q k \pmb Q_k Qk 是一个角点,要么 Q k − 2 \pmb Q_{k-2} Qk−2 到 Q k + 2 \pmb Q_{k+2} Qk+2 是一条直线段。在这种情况下, α k \alpha_k αk 的值有多种选择方式,可以按如下方式选取:
- α k = 1 \alpha_k=1 αk=1,这时有 V k = q k + 1 \pmb V_k=\pmb q_{k+1} Vk=qk+1;它会在 Q k \pmb Q_k Qk 处产生一个角点(当希望在 Q k \pmb Q_k Qk 处保留角点时);
- α k = 1 2 \alpha_k=\dfrac{1}{2} αk=21,有 V k = 1 2 ( q k + q k + 1 ) \pmb V_k=\dfrac{1}{2}(\pmb q_k+\pmb q_{k+1}) Vk=21(qk+qk+1);这种选择将会消除掉角点(使其变光滑)(当不希望保留角点时)。
需要对端点处进行特殊的处理:
q 0 = 2 q 1 − q 2 , q − 1 = 2 q 0 − q 1 , q n + 1 = 2 q n − q n − 1 , q n + 2 = 2 q n + 1 − q n (3) \pmb q_0=2\pmb q_1-\pmb q_2,\quad \pmb q_{-1}=2\pmb q_0-\pmb q_1,\quad \pmb q_{n+1}=2\pmb q_n-\pmb q_{n-1},\quad\pmb q_{n+2}=2\pmb q_{n+1}-\pmb q_n\tag{3} q0=2q1−q2,q−1=2q0−q1,qn+1=2qn−qn−1,qn+2=2qn+1−qn(3)
将(3)式代入(1)式和(2)式,即可得到端点处的切矢 T 0 , T 1 \pmb T_0,\pmb T_1 T0,T1 和 T n − 1 , T n \pmb T_{n-1},\pmb T_n Tn−1,Tn。
def TangentialVector(Q, flag):"""计算数据点 Q 每一点处的切矢:param Q: 数据点:param flag: 0:保留角点;1:消除角点:return: 切矢 T""""q_k = Q_k - Q_{k-1} q 的下标为 -1 ~ n+2"n = Q.shape[1] - 1 # 数据点 Q 的最大下标q = np.zeros((Q.shape[0], n + 3)) # q 应该有 n + 4 个列元素,缺少一个 q[-1] 为了统一下标,后续会额外增加一个 q_neg"计算 q[1]~q[n]"for i in range(1, n + 1): # 计算q[:, i] = Q[:, i] - Q[:, i - 1]q[:, 0] = 2 * q[:, 1] - q[:, 2] # q[0]q[:, n + 1] = 2 * q[:, n] - q[:, n - 1] # q[n+1]q[:, n + 2] = 2 * q[:, n + 1] - q[:, n] # q[n+2]q_neg = 2 * q[:, 0] - q[:, 1] # q[-1]"五点法计算插值参数 alpha[k]"V = np.zeros((Q.shape[0], n + 1))T = np.zeros((Q.shape[0], n + 1))alpha = np.zeros(n + 1)for k in range(1, n + 1):down = np.linalg.norm(np.cross(q[:, k - 1], q[:, k])) + np.linalg.norm(np.cross(q[:, k + 1], q[:, k + 2]))up = np.linalg.norm(np.cross(q[:, k - 1], q[:, k]))if down == 0: # Q[k] 是一个角点,或者,Q[k-2] 到 Q[k+2] 是一条直线段if flag == 0: # 保留角点V[:, k] = q[:, k + 1]T[:, k] = V[:, k] / np.linalg.norm(V[:, k])elif flag == 1: # 消除角点V[:, k] = (q[:, k] + q[:, k + 1]) / 2T[:, k] = V[:, k] / np.linalg.norm(V[:, k])else:print("参数 flag 输入错误")else:alpha[k] = up / downV[:, k] = (1 - alpha[k]) * q[:, k] + alpha[k] * q[:, k + 1]T[:, k] = V[:, k] / np.linalg.norm(V[:, k])parm_up = np.linalg.norm(np.cross(q_neg, q[:, 0]))parm_down = (np.linalg.norm(np.cross(q_neg, q[:, 0])) + np.linalg.norm(np.cross(q[:, 1], q[:, 2])))if parm_down == 0: # Q[k] 是一个角点,或者,Q[k-2] 到 Q[k+2] 是一条直线段if flag == 0: # 保留角点V[:, 0] = q[:, 1]T[:, 0] = V[:, 0] / np.linalg.norm(V[:, 0])elif flag == 1: # 消除角点V[:, 0] = (q[:, 0] + q[:, 1]) / 2T[:, 0] = V[:, 0] / np.linalg.norm(V[:, 0])else:print("参数 flag 输入错误")else:alpha[0] = parm_up / parm_downV[:, 0] = (1 - alpha[0]) * q[:, 0] + alpha[0] * q[:, 1]T[:, 0] = V[:, 0] / np.linalg.norm(V[:, 0])return T
二、局部抛物线插值
给定 x y xy xy 平面上的点 { Q k } , k = 0 , 1 , ⋯ , n \{\pmb Q_k\},k=0,1,\cdots,n {Qk},k=0,1,⋯,n,用上一节的方法来计算相应的 { T k } \{\pmb T_k\} {Tk}。用 L k L_k Lk 表示由 ( Q k , T k ) (\pmb Q_k,\pmb T_k) (Qk,Tk) 定义的有向直线,用 R k \pmb R_k Rk 表示 L k − 1 L_{k-1} Lk−1 和 L k L_k Lk 的交点
R k = Q k − 1 + γ k − 1 T k − 1 , R k = Q k + γ k T k (4) \pmb R_k=\pmb Q_{k-1}+\gamma_{k-1}\pmb T_{k-1},\pmb R_k=\pmb Q_k+\gamma_k\pmb T_k\tag{4} Rk=Qk−1+γk−1Tk−1,Rk=Qk+γkTk(4)
这里需要保证
γ k − 1 > 0 , γ k < 0 (5) \gamma_{k-1}>0,\quad\gamma_k<0\tag{5} γk−1>0,γk<0(5)
这样才能保证沿着切矢方向上存在交点。(稍后会补充不满足该条件时如何求 R k \pmb R_k Rk)
若 p p p 次贝塞尔曲线是定义在 U = { 0 , ⋯ , 0 , 1 , ⋯ , 1 } U=\{0,\cdots,0,1,\cdots,1\} U={0,⋯,0,1,⋯,1}(没有内节点)上的 B 样条曲线,则端点处的一阶导矢计算公式如下所示:
C ′ ( 0 ) = Q 0 = p u p + 1 ( P 1 − P 0 ) C ′ ( 1 ) = Q n − 1 = p 1 − u m − p − 1 ( P n − P n − 1 ) (6) \begin{aligned} &\pmb C^\prime(0)=\pmb Q_0=\frac{p}{u_{p+1}}(\pmb P_1-\pmb P_0)\\ &\pmb C^\prime(1)=\pmb Q_{n-1}=\frac{p}{1-u_{m-p-1}}(\pmb P_n-\pmb P_{n-1}) \end{aligned}\tag{6} C′(0)=Q0=up+1p(P1−P0)C′(1)=Qn−1=1−um−p−1p(Pn−Pn−1)(6)