封闭B-Spline Python实现异常:不改动节点与控制点的修复需求
问题:封闭2D B-Spline曲线异常修复
我编写了一段实现封闭2D B-Spline的Python代码,运行后生成的曲线不符合预期,需要在不改变节点向量(knots)和控制点规模的前提下修复问题。当前代码尝试用模运算处理节点与控制点的索引越界,但仍存在错误。
原代码:
import numpy as np import matplotlib.pyplot as plt class BSpline2D: def __init__(self, degree, knots, control_points): self.degree = degree self.knots = np.array(knots) self.control_points = np.array(control_points) def get_knot_at(self, i): n = len(self.knots) return self.knots[(i % n + n) % n] def evaluate(self, t): span = self.find_span(t) coeff = self.basis_functions(span, t) point = np.zeros(2) # Assuming 2D control points for i in range(self.degree + 1): point += coeff[i] * self.get_control_point_at(span - self.degree + i) return point def find_span(self, u): return np.searchsorted(self.knots, u) - 1 def basis_functions(self, i, u): coeff = [1.0] left = [0.0] * (self.degree + 1) right = [0.0] * (self.degree + 1) for j in range(1, self.degree + 1): left[j] = u - self.get_knot_at(i + 1 - j) right[j] = self.get_knot_at(i + j) - u saved = 0.0 for r in range(j): temp = coeff[r] / (right[r + 1] + left[j - r]) coeff[r] = saved + temp * right[r + 1] saved = temp * left[j - r] coeff.append(saved) return coeff def get_control_point_at(self, i): n = len(self.control_points) return self.control_points[(i % n + n) % n] def plot_bspline_2d(spline, resolution=100): t_values = np.linspace(spline.knots[0], spline.knots[-1], resolution) curve_points = [spline.evaluate(t) for t in t_values] # Extract x, y for plotting curve_points = np.array(curve_points) x, y = curve_points[:, 0], curve_points[:, 1] # Extract control points control_points = np.array(spline.control_points) cx, cy = control_points[:, 0], control_points[:, 1] # Plot plt.figure() plt.plot(x, y, label='B-Spline Curve', color='blue') plt.scatter(cx, cy, color='red', label='Control Points') plt.plot(cx, cy, linestyle='dotted', color='red', label='Control Polygon') plt.legend() plt.xlabel("X") plt.ylabel("Y") plt.title("2D B-Spline Curve") plt.axis("equal") plt.show() # Example usage: degree = 2 knots = [1, 2, 3, 4, 5] control_points = [ [0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0] ] spline = BSpline2D(degree, knots, control_points) plot_bspline_2d(spline)
错误分析
- 节点周期处理错误:原
get_knot_at单纯用索引模运算,忽略了节点是递增的周期序列(周期为knots[-1] - knots[0]),导致计算基函数时节点差值错误。 - Span查找逻辑错误:原
find_span未将参数t映射到基础周期区间,导致接近节点末尾时span定位错误。 - 参数范围错误:绘制时
t_values取了整个节点区间,而封闭B样条的有效参数范围应为[knots[degree], knots[-degree]],否则会出现曲线首尾不闭合或异常段。
修复后的代码
import numpy as np import matplotlib.pyplot as plt class BSpline2D: def __init__(self, degree, knots, control_points): self.degree = degree self.knots = np.array(knots) self.control_points = np.array(control_points) self.n_knots = len(self.knots) self.n_ctrl = len(self.control_points) # 计算节点周期 self.knot_period = self.knots[-1] - self.knots[0] def get_knot_at(self, i): # 计算周期偏移量,返回周期化后的节点值 idx = i % self.n_knots cycles = i // self.n_knots return self.knots[idx] + cycles * self.knot_period def evaluate(self, t): # 将t映射到基础周期区间 [knots[0], knots[-1]) t_normalized = t - self.knot_period * np.floor((t - self.knots[0]) / self.knot_period) span = self.find_span(t_normalized) coeff = self.basis_functions(span, t) point = np.zeros(2) for i in range(self.degree + 1): ctrl_idx = span - self.degree + i point += coeff[i] * self.get_control_point_at(ctrl_idx) return point def find_span(self, u): # 在基础节点区间内查找span span = np.searchsorted(self.knots, u) - 1 # 处理u等于最后一个节点的情况 if span >= self.n_knots - 1: span = self.n_knots - 2 return span def basis_functions(self, i, u): coeff = [1.0] left = [0.0] * (self.degree + 1) right = [0.0] * (self.degree + 1) for j in range(1, self.degree + 1): left[j] = u - self.get_knot_at(i + 1 - j) right[j] = self.get_knot_at(i + j) - u saved = 0.0 for r in range(j): temp = coeff[r] / (right[r + 1] + left[j - r]) coeff[r] = saved + temp * right[r + 1] saved = temp * left[j - r] coeff.append(saved) return coeff def get_control_point_at(self, i): # 控制点循环索引,保持原逻辑但确保索引正确 return self.control_points[i % self.n_ctrl] def plot_bspline_2d(spline, resolution=100): # 封闭B样条的有效参数范围是 [knots[degree], knots[-degree]] t_start = spline.knots[spline.degree] t_end = spline.knots[-spline.degree] t_values = np.linspace(t_start, t_end, resolution) curve_points = [spline.evaluate(t) for t in t_values] # 提取曲线点坐标 curve_points = np.array(curve_points) x, y = curve_points[:, 0], curve_points[:, 1] # 提取控制点坐标 cx, cy = spline.control_points[:, 0], spline.control_points[:, 1] # 绘图 plt.figure() plt.plot(x, y, label='封闭B-Spline曲线', color='blue') plt.scatter(cx, cy, color='red', label='控制点') plt.plot(np.append(cx, cx[0]), np.append(cy, cy[0]), linestyle='dotted', color='red', label='控制多边形') plt.legend() plt.xlabel("X") plt.ylabel("Y") plt.title("2D封闭B-Spline曲线") plt.axis("equal") plt.show() # 示例使用:保持原节点和控制点不变 degree = 2 knots = [1, 2, 3, 4, 5] control_points = [ [0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0] ] spline = BSpline2D(degree, knots, control_points) plot_bspline_2d(spline)
关键修改说明
- 节点周期化:
get_knot_at不再单纯模索引,而是计算周期偏移,返回符合递增周期的节点值,确保基函数计算时的节点差值正确。 - 参数归一化:
evaluate中将参数t映射到基础周期区间,避免span查找越界。 - 有效参数范围:绘图时使用封闭B样条的有效参数区间
[knots[degree], knots[-degree]],确保曲线首尾闭合且无异常段。 - 控制多边形闭合:绘图时将控制多边形首尾相连,更直观展示封闭特性。
内容的提问来源于stack exchange,提问作者Jakab Martin
相关产品推荐
相关产品推荐

