在计算机图形学和几何建模领域,B样条曲线因其出色的局部控制能力和灵活的连续性而广受欢迎。与Bezier曲线相比,B样条允许我们独立控制曲线的阶数和控制点数量,这种解耦带来了极大的设计自由度。本文将带你从零开始实现de Boor-Cox递推公式,并通过Python动态可视化不同阶数的B样条基函数,直观理解其数学本质。
在开始编码之前,我们需要明确几个关键术语:
安装必要的Python库:
bash复制pip install numpy matplotlib
典型的均匀节点向量生成方法:
python复制def uniform_knots(n, k):
"""生成均匀节点向量"""
return np.linspace(0, 1, n+k+1)
提示:节点向量的长度必须满足
len(U) = n + k + 1,其中n+1是控制点数量,k是阶数。
de Boor-Cox公式的精妙之处在于用递归方式构建高阶基函数:
code复制B_{i,1}(u) = { 1 if u_i ≤ u < u_{i+1}
{ 0 otherwise
B_{i,k}(u) = ω_{i,k}(u)B_{i,k-1}(u) + (1-ω_{i+1,k}(u))B_{i+1,k-1}(u)
其中权重系数ω的计算:
python复制def omega(u, i, k, U):
"""计算递推权重系数"""
denom = U[i+k-1] - U[i]
return (u - U[i]) / denom if denom != 0 else 0
完整的基础函数实现:
python复制def basis_function(i, k, u, U):
"""递归计算B样条基函数"""
if k == 1:
return 1.0 if U[i] <= u < U[i+1] else 0.0
left = omega(u, i, k, U) * basis_function(i, k-1, u, U)
right = (1 - omega(u, i+1, k, U)) * basis_function(i+1, k-1, u, U)
return left + right
注意:这个实现虽然直观,但存在重复计算问题。实际应用时应使用动态规划优化。
让我们用Matplotlib观察k=1到k=4时的基函数形态差异:
python复制def plot_basis_functions(n=5, k_max=4):
U = uniform_knots(n, k_max)
u_vals = np.linspace(U[0], U[-1], 500)
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
for k in range(1, k_max+1):
ax = axes[(k-1)//2, (k-1)%2]
for i in range(n+k):
y = [basis_function(i, k, u, U) for u in u_vals]
ax.plot(u_vals, y, label=f'B_{i},{k}')
ax.set_title(f'{k}阶基函数(k={k})')
ax.legend()
plt.tight_layout()
plt.show()
运行结果将显示:
| 阶数 | 连续性 | 支撑区间宽度 | 典型应用场景 |
|---|---|---|---|
| k=1 | C⁰ | 1个区间 | 分段常数函数 |
| k=2 | C¹ | 2个区间 | 折线平滑 |
| k=3 | C² | 3个区间 | 汽车设计 |
| k=4 | C³ | 4个区间 | 高精度曲面 |
节点向量的分布方式会显著改变基函数行为。我们比较三种典型配置:
[0,1,2,3,4,5,6][0,0,0,1,2,3,4,4,4][0,1,2,3,3,4,5]实现节点向量生成:
python复制def quasi_uniform_knots(n, k):
"""生成准均匀节点向量"""
internal = np.linspace(0, 1, n-k+2)
return np.concatenate([
np.zeros(k-1),
internal,
np.ones(k-1)
])
比较不同节点向量的代码结构:
python复制def compare_knot_vectors(n=4, k=3):
knots = {
'均匀': uniform_knots(n, k),
'准均匀': quasi_uniform_knots(n, k),
'非均匀': [0,1,2,3,3,4,5]
}
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for (name, U), ax in zip(knots.items(), axes):
u_vals = np.linspace(U[0], U[-1], 500)
for i in range(len(U)-k):
y = [basis_function(i, k, u, U) for u in u_vals]
ax.plot(u_vals, y)
ax.set_title(f'{name}节点向量\n{U}')
plt.show()
关键观察点:
原始递归实现的时间复杂度为O(2^k),我们可以通过以下方式优化:
动态规划版本:
python复制def basis_function_dp(i, k, u, U, memo=None):
"""带记忆化的B样条基函数计算"""
if memo is None:
memo = {}
key = (i, k, u)
if key in memo:
return memo[key]
if k == 1:
val = 1.0 if U[i] <= u < U[i+1] else 0.0
else:
left = omega(u, i, k, U) * basis_function_dp(i, k-1, u, U, memo)
right = (1-omega(u, i+1, k, U)) * basis_function_dp(i+1, k-1, u, U, memo)
val = left + right
memo[key] = val
return val
向量化计算:对于需要批量计算的情况:
python复制def basis_vectorized(u_vals, i, k, U):
"""向量化计算基函数值"""
results = np.zeros_like(u_vals)
for j, u in enumerate(u_vals):
results[j] = basis_function_dp(i, k, u, U)
return results
实际项目中还需要考虑:
有了基函数后,B样条曲线的计算就水到渠成:
python复制def bspline_curve(u_vals, control_points, k, U):
"""计算B样条曲线点"""
n = len(control_points) - 1
curve = np.zeros((len(u_vals), 2))
for j, u in enumerate(u_vals):
for i in range(n+1):
basis = basis_function_dp(i, k, u, U)
curve[j] += control_points[i] * basis
return curve
绘制曲线的完整示例:
python复制def plot_bspline_example():
control_points = np.array([[0,0], [1,3], [2,-2], [3,4], [4,1]])
k = 3
U = quasi_uniform_knots(len(control_points)-1, k)
u_vals = np.linspace(U[k-1], U[-k], 100)
curve = bspline_curve(u_vals, control_points, k, U)
plt.figure(figsize=(10, 6))
plt.plot(control_points[:,0], control_points[:,1], 'ro--', label='控制多边形')
plt.plot(curve[:,0], curve[:,1], 'b-', linewidth=2, label='B样条曲线')
plt.legend()
plt.title(f'{k}阶准均匀B样条曲线')
plt.show()
调试技巧:
len(U)=n+k+1[U[k-1], U[-k]]内