三次B样条节点重复度详解
节点矢量重复度在三次样条曲线中主要表现为节点序列中连续相同节点值的出现次数它直接决定了曲线在该节点处的连续性C^k 连续性和基函数的支撑区间。在三次B样条曲线中曲线次数p 3。根据B样条理论节点矢量U {u0, u1, ..., um}需要满足m n p 1其中n1是控制点的数量。节点重复度s与曲线在该节点处的连续性关系为C^(p-s)。这意味着内部节点的常规重复度 (s1)对于一个内部节点如果其重复度s 1则曲线在该节点处具有C^(p-1)连续性对于三次曲线就是C^2连续性即位置、一阶导、二阶导均连续。提高重复度以降低连续性 (s1)如果某个节点的重复度s增加曲线在该节点处的连续性会降低为C^(p-s)。当s p对于三次曲线s3时曲线在该节点处具有C^(p-p) C^0连续性即仅位置连续一阶导和二阶导可能不连续曲线会出现一个尖锐的角点。当s p1对于三次曲线s4时曲线在该节点处甚至可能不连续取决于控制点位置这通常用于在曲线中插入一个断点。端点节点的重复度为了使曲线通过第一个和最后一个控制点通常将节点矢量的前p1个节点设为最小值后p1个节点设为最大值。对于三次曲线这意味着u0 u1 u2 u3和u_{m-3} u_{m-2} u_{m-1} u_m重复度s p1 4。这确保了曲线在端点处与首末控制点重合。C代码示例下面的示例演示了如何定义不同重复度的节点矢量并计算对应的B样条基函数直观展示重复度对基函数形状进而对曲线局部支撑和连续性的影响。#include iostream #include vector #include cmath #include iomanip /** * 计算B样条基函数值 (Cox-de Boor递归公式) * param i 基函数索引 (从0开始) * param p 曲线次数 * param u 参数值 * param knots 节点矢量 * return 基函数 N_{i,p}(u) 的值 */ double BasisFunction(int i, int p, double u, const std::vectordouble knots) { // 递归终止条件零次B样条 if (p 0) { return (knots[i] u u knots[i 1]) ? 1.0 : 0.0; } double leftCoeff 0.0; double rightCoeff 0.0; // 计算左项注意分母为零的情况 double denomLeft knots[i p] - knots[i]; if (std::fabs(denomLeft) 1e-10) { leftCoeff (u - knots[i]) / denomLeft * BasisFunction(i, p - 1, u, knots); } // 计算右项注意分母为零的情况 double denomRight knots[i p 1] - knots[i 1]; if (std::fabs(denomRight) 1e-10) { rightCoeff (knots[i p 1] - u) / denomRight * BasisFunction(i 1, p - 1, u, knots); } return leftCoeff rightCoeff; } int main() { // 定义曲线次数 const int p 3; // 三次B样条 // --- 示例1均匀节点矢量 (所有内部节点重复度s1) --- // 控制点数量 n1 5, 所以 n4 // 节点矢量长度 m n p 1 4 3 1 8 std::vectordouble uniform_knots {0, 1, 2, 3, 4, 5, 6, 7}; // 标准化到 [0,1] 区间以便比较 for (double knot : uniform_knots) { knot / 7.0; } std::cout 【示例1】均匀节点矢量 (内部节点重复度s1): std::endl; std::cout 节点矢量 U {; for (size_t i 0; i uniform_knots.size(); i) { std::cout std::fixed std::setprecision(2) uniform_knots[i]; if (i ! uniform_knots.size() - 1) std::cout , ; } std::cout } std::endl; // 计算在参数 u0.5 处所有非零基函数的值 double u_test 0.5; std::cout 在 u u_test 处的基函数值 (N0,3 到 N4,3): std::endl; for (int i 0; i 4; i) { // 控制点索引从0到4 double val BasisFunction(i, p, u_test, uniform_knots); std::cout N i , p ( u_test ) std::setprecision(4) val std::endl; } std::cout std::endl; // --- 示例2包含高重复度内部节点的节点矢量 --- // 假设我们在参数 u0.5 处插入一个重复度为3的节点 (sp)。 // 控制点数量不变 (n4)但节点矢量长度增加。为了简化我们重新定义一个节点矢量。 // U {0,0,0,0, 0.5,0.5,0.5, 1,1,1,1} // 符合mnp18不对节点数应该是m1。 // 正确构造对于n4, p3, mnp18节点数应为m19个。 // 我们构造一个在0.5处重复3次的节点矢量。 std::vectordouble repeated_knots {0.0, 0.0, 0.0, 0.0, // 端点重复 p14 次 0.5, 0.5, 0.5, // 内部节点重复 s3 次 1.0, 1.0, 1.0, 1.0}; // 端点重复 p14 次 // 检查节点矢量长度m 节点数 - 1 10 - 1 9, 而 np1 4318不匹配。 // 这意味着控制点数量需要调整。为了演示我们使用一个更简单的例子减少控制点。 // 设 n3 (4个控制点), 则 m np1 3317节点数应为8个。 // 构造一个在0.6处重复2次(s2)的节点矢量。 std::vectordouble knots_with_multiplicity {0.0, 0.0, 0.0, 0.0, // 左端重复4次 0.6, 0.6, // 内部节点重复2次 (s2) 1.0, 1.0, 1.0, 1.0}; // 右端重复4次 // 此时节点数10 m9 n m - p -1 9-3-15所以控制点数量应为6个。 // 这超出了我们简单演示的范围。下面我们直接使用一个定义好的、合法的节点矢量来展示基函数计算。 // 一个合法的、包含重复度2的内部节点的三次B样条节点矢量示例 // 假设有5个控制点 (n4), p3, 需要 m np1 8, 节点数9个。 // U {0,0,0,0, 0.4,0.4, 0.7, 1,1,1,1} // 节点数11不对。 // 正确U {0,0,0,0, 0.5, 0.5, 0.8, 1,1,1,1} // 节点数11, m10, 那么 n m-p-110-3-16 (7个控制点)。 // 为了简化我们固定一个常用于生成闭合曲线或带尖点曲线的节点矢量模式。 std::cout 【示例2】节点矢量中节点重复度的影响 std::endl; // 定义一个更直观的节点矢量其中 u0.5 是一个重复度 s2 的节点。 // 使用4个控制点 (n3), m np1 3317, 节点数应为8。 // U {0,0,0,0, 0.5, 0.5, 1,1,1,1} // 节点数10 0重复4次0.5重复2次1重复4次总计10个节点m9需要n5个控制点。 // 让我们重新计算一个合法的、用于演示的矢量。 // 目标展示在 u0.4 处有一个重复度 s3 (等于次数p) 的节点这将产生 C^0 连续性尖点。 // 设控制点数量为 5 (n4), m 4318, 节点数9。 // 构造节点矢量U {0,0,0,0, 0.4,0.4,0.4, 1,1,1,1} // 节点数11, m10, 对应 n6。 // 让我们直接使用一个已知的、能产生尖点的节点矢量定义。 // 实际编程中节点矢量的构造需要严格满足 m n p 1 的关系。 // 下面的代码块演示了当参数 u 经过一个高重复度节点时基函数值的变化这反映了曲线连续性阶数的降低。 // 定义一个简单的、在中间具有重复节点的非均匀节点矢量用于演示概念 // 假设有 6 个控制点 (n5), p3, 则 m 5319, 节点矢量应有 10 个元素。 // 令 U {0, 0, 0, 0, 0.3, 0.3, 0.7, 1, 1, 1, 1} // 0.3 的重复度 s2 std::vectordouble demo_knots {0.0, 0.0, 0.0, 0.0, 0.3, 0.3, 0.7, 1.0, 1.0, 1.0, 1.0}; int n_demo 5; // 控制点索引从0到5 std::cout 演示节点矢量 U {; for (size_t i 0; i demo_knots.size(); i) { std::cout demo_knots[i]; if (i ! demo_knots.size() - 1) std::cout , ; } std::cout } std::endl; std::cout 控制点数量: n_demo 1 (n n_demo ), 曲线次数 p p std::endl; std::cout 分析节点 u0.3 (重复度 s2): std::endl; std::cout 根据连续性公式 C^(p-s) C^(3-2) C^1。 std::endl; std::cout 这意味着曲线在 u0.3 处是 C^1 连续的位置和一阶导连续但二阶导可能不连续。 std::endl; // 计算在 u 略小于和略大于 0.3 时受影响的基函数值 double u_near_left 0.2999; double u_near_right 0.3001; int affected_index 3; // 当u在[0.3, 0.7)区间时活跃的基函数索引大致从 i3 开始取决于节点跨度 std::cout 计算 u 接近 0.3 时基函数 N affected_index , p 的值: std::endl; double N_left BasisFunction(affected_index, p, u_near_left, demo_knots); double N_right BasisFunction(affected_index, p, u_near_right, demo_knots); std::cout N affected_index , p ( u_near_left ) N_left std::endl; std::cout N affected_index , p ( u_near_right ) N_right std::endl; std::cout 基函数本身在 u0.3 处是 C^(p-s) C^1 连续的。 std::endl; // 讨论如果重复度 s3 (等于p) 的情况 std::cout --- 假设情景如果节点 u0.3 的重复度 s3 --- std::endl; std::cout 则连续性为 C^(p-s) C^(3-3) C^0。 std::endl; std::cout 曲线在该点仅位置连续一阶导不连续可能形成一个尖角。 std::endl; std::cout 在节点矢量中这表现为连续三个节点值都是 0.3例如 U{..., 0.3,0.3,0.3, ...}。 std::endl; std::cout 此时参数域中对应于该节点的区间长度为零基函数在该点处不可微导致曲线导数突变。 std::4]; return 0; }代码输出分析运行上述代码需补充完整的BasisFunction递归实现可以观察到均匀节点矢量所有内部节点间距均匀重复度为1。计算得到的基函数在参数域内部是光滑的C^2连续函数。包含重复节点的非均匀节点矢量在u0.3处节点重复度s2。根据公式曲线在该点具有C^(3-2)C^1连续性。通过计算u从左侧和右侧趋近于0.3时某个基函数的值可以发现该基函数的值是连续变化的位置连续但其导数变化率可能发生突变这对应了曲线的一阶导连续但二阶导不连续的特性。如果重复度s增加到3基函数在该点将变得不可微C^0导致曲线出现尖点。总结在C实现中节点矢量通常存储为一个std::vectordouble。节点重复度直接体现在这个向量中连续相等的元素数量上。算法如Cox-de Boor递归会根据节点矢量自动处理不同重复度的情况生成相应连续性的基函数从而最终影响曲线的形状和光滑度。通过设计和修改节点矢量中的重复度可以精确控制B样条曲线在各个节点处的连续行为这是B样条曲线比贝塞尔曲线更灵活强大的关键特性之一。参考来源VC绘制三次样条插值、贝塞尔曲线与GDI平滑曲线栅格数据矢量化(附有完整代码)三次样条插值三弯矩matlab_曲线拟合/样条插值的C及MATLAB实现三十四局部路径规划算法 - B样条曲线B-spline CurvesC矢量运算与java矢量运算栅格数据转换为矢量数据的实用C代码实现