CE7453 Numerical Algorithms 期末高分超详细攻略(第三章:Bezier/B-spline/Interpolation)
本章内容:Bezier 曲线、B-spline 曲线、插值方法
适用对象:零基础/考前冲刺/快速查漏补缺
内容特色:详细原理、公式推导、算法流程、例题全解、英文关键词
1. 基本概念与背景
- Bezier 曲线:由一组控制点定义的参数曲线,广泛用于计算机图形、字体、动画等。
- B-spline 曲线:Bezier 曲线的推广,支持更多控制点和更高阶的连续性,局部性更好。
- 插值(Interpolation):通过已知数据点,构造通过这些点的函数(曲线/多项式)。
2. Bezier 曲线
2.1 定义与公式
- n阶 Bezier 曲线公式:
其中 为控制点,, 为 Bernstein 基函数:
2.2 主要性质
- 端点插值(Endpoint interpolation):,
- 共线性(Co-tangency):曲线在端点处与首末两段控制多边形共线
- 凸包性(Convex hull property):曲线始终在控制多边形的凸包内
- 仿射不变性(Affine invariance):对控制点做仿射变换,曲线同样变换
2.3 de Casteljau 算法(数值稳定的递归算法)
算法流程
- 设 个控制点 ,参数
- 递归计算:
- 最终 即为
步骤解释
- 每一层递归都在相邻点之间做线性插值(Lerp),逐步逼近曲线上的点。
- 该算法数值稳定,适合实际计算。
例题
已知控制点 , , ,求 时的 Bezier 曲线点。
解答:
- , ,
- 答:
Python代码示例 (de Casteljau):
import numpy as np
def de_casteljau(control_points, t):
"""使用 de Casteljau 算法计算 Bezier 曲线上一点"""
points = np.array(control_points, dtype=float)
n = len(points) - 1 # 曲线阶数
# P_i^(0) = P_i
current_points = points.copy()
for r in range(1, n + 1): # 从 r=1 到 n
new_points = []
for i in range(n - r + 1): # P_i^(r) 的 i 从 0 到 n-r
p_r_i = (1 - t) * current_points[i] + t * current_points[i+1]
new_points.append(p_r_i)
current_points = np.array(new_points)
# print(f"r={r}, points={current_points}") # 可选:打印中间步骤
return current_points[0] # P_0^(n)
# 例题数据
P_bezier = [[0, 0], [1, 2], [3, 3]]
t_bezier = 0.5
# 计算
point_on_curve = de_casteljau(P_bezier, t_bezier)
print(f"例题 (Bezier de Casteljau): P({t_bezier}) = {point_on_curve}")
# 验证 (可选)
# expected_point = np.array([1.25, 1.75])
# assert np.allclose(point_on_curve, expected_point)
3. B-spline 曲线
3.1 定义与公式
-
B-spline 曲线公式:
其中 为 阶 B-spline 基函数, 为 de Boor 控制点, 为参数。 -
B-spline 基函数递归定义:
3.2 主要性质
- 局部性(Local support):每个控制点只影响相邻 个区间
- 高阶连续性:可实现 连续
- 可调节点向量(Knot vector):灵活控制曲线形状
3.3 de Boor 算法
算法流程
- 给定节点向量 ,控制点 ,阶数 ,参数
- 找到 所在区间
- 递归线性插值,最终得到
例题
已知三次 B-spline 曲线,控制点 , , , ,节点向量 ,求 处的曲线点。
解答:
- 节点向量为 ,阶数 (因为节点向量长度为 )。
- 参数 落在区间 ,但由于节点向量重复,实际有效区间需要考虑 属于 ,因此 。
- 影响 的控制点为 (因为 到 ,即控制点索引 到 )。
- 计算基函数 :
- 先计算 :
- (因为 不在 )
- (因为 不在 )
- (因为 不在 )
- (因为 在 )
- (因为 不在 )
- 递归计算 :
- 递归计算 :
- (类似计算)
- 递归计算 :
- 先计算 :
- 最终曲线点
- 答:
Python代码示例 (手动计算基函数):
import numpy as np
# 例题数据
control_points_bspline = np.array([[0, 0], [1, 2], [3, 3], [4, 0]])
knots_bspline = np.array([0,0,0,0,1,2,2,2,2])
u_bspline = 1
# 按照文档手动计算的基函数值
N14_at_1 = 0.25
N24_at_1 = 0.5
N34_at_1 = 0.25
# 影响 u=1 的控制点是 P1, P2, P3 (索引1, 2, 3)
point_on_bspline = N14_at_1 * control_points_bspline[1] + \
N24_at_1 * control_points_bspline[2] + \
N34_at_1 * control_points_bspline[3]
print(f"例题 (B-spline 手动基函数): r({u_bspline}) = {point_on_bspline}")
# 验证 (可选)
# expected_point = np.array([2.75, 2.0])
# assert np.allclose(point_on_bspline, expected_point)
# 注意: 实际应用中会使用递归函数计算基函数或直接使用 scipy.interpolate
# from scipy.interpolate import BSpline
# k = 3 # B样条阶数 (degree), k=order-1. Order k=4 for cubic.
# tck = (knots_bspline, control_points_bspline.T, k)
# point_scipy = BSpline(*tck)(u_bspline)
# print(f"例题 (B-spline Scipy): r({u_bspline}) = {point_scipy}")
新增例题:计算给定控制点 ,, 的二次Bezier曲线在 处的点
解答步骤:
- 使用de Casteljau算法:
- 第一层:
- 第二层:
- 结果:曲线点为 。
Python代码示例 (使用上面的 de_casteljau 函数):
# 新增例题数据
P_bezier_add = [[0, 0], [1, 1], [2, 0]]
t_bezier_add = 0.5
# 计算
point_on_curve_add = de_casteljau(P_bezier_add, t_bezier_add)
print(f"新增例题 (Bezier de Casteljau): P({t_bezier_add}) = {point_on_curve_add}")
# 验证 (可选)
# expected_point = np.array([1, 0.5])
# assert np.allclose(point_on_curve_add, expected_point)
4. 插值方法(Interpolation)
4.1 Lagrange 插值
- 公式:
- 优缺点:公式显式,适合手算,但高阶时数值不稳定。
例题
已知点 , , ,用 Lagrange 插值求 处的函数值。
解答:
- 基函数:
- 插值多项式:
- 代入 :
- 答:
Python代码示例 (Lagrange 插值):
import numpy as np
from scipy import interpolate # 用于验证
def lagrange_basis(x_data, i, x):
"""计算第 i 个 Lagrange 基函数 l_i(x)"""
n = len(x_data)
li = 1.0
for j in range(n):
if i != j:
li *= (x - x_data[j]) / (x_data[i] - x_data[j])
return li
def lagrange_interpolation(x_data, y_data, x_interp):
"""使用 Lagrange 插值计算 x_interp 处的函数值"""
n = len(x_data)
result = 0.0
for i in range(n):
result += y_data[i] * lagrange_basis(x_data, i, x_interp)
return result
# 例题数据
x_lagrange = np.array([0, 1, 2])
y_lagrange = np.array([1, 2, 0])
x_interp_lagrange = 1.5
# 计算
interp_value_lagrange = lagrange_interpolation(x_lagrange, y_lagrange, x_interp_lagrange)
print(f"例题 (Lagrange 插值): L({x_interp_lagrange}) = {interp_value_lagrange}")
# 验证 (可选)
# expected_value = 1.375
# assert np.isclose(interp_value_lagrange, expected_value)
# lagrange_poly = interpolate.lagrange(x_lagrange, y_lagrange)
# scipy_value = lagrange_poly(x_interp_lagrange)
# assert np.isclose(scipy_value, expected_value)
4.2 Newton 插值
- 公式:
其中 为差商(divided differences)
例题
已知点 , , ,用 Newton 插值求 处的函数值。
解答:
- 差商表:
- 插值多项式:
- 代入 :
- 答:
Python代码示例 (Newton 插值):
import numpy as np
def divided_differences(x_data, y_data):
"""计算差商表,返回 Newton 多项式系数"""
n = len(x_data)
coef = np.zeros([n, n])
coef[:, 0] = y_data # 第一列是 y 值
for j in range(1, n): # 列
for i in range(n - j): # 行
coef[i, j] = (coef[i + 1, j - 1] - coef[i, j - 1]) / (x_data[i + j] - x_data[i])
return coef[0, :] # 返回第一行作为系数 a0, a1, a2, ...
def newton_polynomial(coefficients, x_data, x):
"""根据差商系数计算 Newton 插值多项式在 x 处的值"""
n = len(coefficients)
result = coefficients[0]
term = 1.0
for i in range(1, n):
term *= (x - x_data[i - 1]) # 计算 (x-x0), (x-x0)(x-x1), ...
result += coefficients[i] * term
return result
# 例题数据
x_newton = np.array([0, 1, 2])
y_newton = np.array([1, 2, 0])
x_interp_newton = 1.5
# 计算
coeffs_newton = divided_differences(x_newton, y_newton)
interp_value_newton = newton_polynomial(coeffs_newton, x_newton, x_interp_newton)
print(f"例题 (Newton 插值) 差商系数: {coeffs_newton}")
print(f"例题 (Newton 插值): N({x_interp_newton}) = {interp_value_newton}")
# 验证 (可选)
# expected_value = 1.375
# assert np.isclose(interp_value_newton, expected_value)
4.3 样条插值(Spline Interpolation)
- 三次样条:分段三次多项式,保证 连续,常用于平滑曲线拟合。
例题
已知点 , , ,构造自然三次样条插值(边界条件为二阶导数为0)。
解答:
- 设区间 和 上的三次多项式为:
- 条件:
- 插值条件:, , ,
- 一阶导数连续:
- 二阶导数连续:
- 自然边界条件:,
- 解方程组(详细计算略):
- 答:自然三次样条插值多项式如上。
Python代码示例 (使用 Scipy):
import numpy as np
from scipy.interpolate import CubicSpline
# 例题数据
x_spline = np.array([0, 1, 2])
y_spline = np.array([1, 2, 0])
# 计算自然三次样条 (bc_type='natural')
cs = CubicSpline(x_spline, y_spline, bc_type='natural')
# 打印样条系数 (可选)
# print("样条系数 (分段):")
# for i in range(len(cs.c[0, :])):
# print(f" 区间 {i}: {cs.c[:, i]}")
# 在 x=1.5 处插值 (虽然例题只要求构造)
x_interp_spline = 1.5
interp_value_spline = cs(x_interp_spline)
print(f"例题 (自然三次样条插值): S({x_interp_spline}) = {interp_value_spline}")
# 验证 x=0, 1, 2 处的插值结果
print(f"验证: S(0)={cs(0)}, S(1)={cs(1)}, S(2)={cs(2)}")
assert np.isclose(cs(0), 1)
assert np.isclose(cs(1), 2)
assert np.isclose(cs(2), 0)
# 验证边界条件 S''(0)=0, S''(2)=0
print(f"验证: S''(0)={cs(0, 2)}, S''(2)={cs(2, 2)}")
assert np.isclose(cs(0, 2), 0)
assert np.isclose(cs(2, 2), 0)
5. 常见考点与易错点
- Bezier 曲线的端点、切线、凸包等性质
- de Casteljau 算法步骤与递归思想
- B-spline 节点向量与控制点影响范围
- 插值多项式的数值稳定性与误差
- 英文术语拼写与公式记忆
6. 英文关键词与表达
- Bezier curve, control point, Bernstein polynomial, de Casteljau algorithm, subdivision, convex hull, affine invariance
- B-spline, knot vector, de Boor point, local support, continuity, degree, order
- interpolation, Lagrange interpolation, Newton divided difference, spline, cubic spline
如需更详细例题推导或某一算法的代码实现,可随时补充!