三次B样条曲线核心特性与C++实现详解

张开发
2026/4/11 0:08:01 15 分钟阅读

分享文章

三次B样条曲线核心特性与C++实现详解
为了深入理解三次B样条曲线我们将从特点、公式推导、C代码实现三个维度进行阐述并通过表格和代码示例展示其对应关系。一、 三次B样条曲线的核心特点三次p3是B样条中最常用的次数在计算效率、连续性与灵活性之间取得了良好平衡。其核心特点如下表所示特性三次B样条的具体表现与优势连续性在内部非重节点处曲线自然保持C² 连续即位置、一阶导数、二阶导数均连续。这种二阶连续性保证了曲线段之间过渡极为平滑没有尖锐的转折这是其优于低次贝塞尔曲线的重要特点 。局部控制性每个控制点Pᵢ仅影响长度为4p1的节点区间[uᵢ, uᵢ₊₄)上的曲线形状 。移动一个控制点只会改变与之相关的4段曲线其余部分保持不变非常便于交互式编辑。凸包性曲线段C(u), u ∈ [uᵢ, uᵢ₊₁)必然位于定义它的4个控制点{Pᵢ₋₃, Pᵢ₋₂, Pᵢ₋₁, Pᵢ}所形成的凸包内部。这保证了曲线的可预测性和稳定性不会产生意外的震荡 。强仿射不变性对曲线进行仿射变换如平移、旋转、缩放等价于对其所有控制点进行相同的仿射变换后再计算曲线。这是所有参数曲线的重要特性便于几何操作 。二、 核心公式的推演过程三次B样条曲线的定义由通用B样条公式 $C(u) \sum_{i0}^{n} N_{i,3}(u) P_i$ 给出其推演关键在于基函数 $N_{i,3}(u)$的构造这依赖于节点向量 U和Cox-de Boor 递归公式。推导过程遵循以下清晰步骤1. 设定基础参数假设我们有n1个控制点P₀, P₁, ..., Pₙ。为了构造三次B样条我们选择一个节点向量U [u₀, u₁, ..., uₘ]其中m n p 1 n 4。节点向量必须是非递减序列 。一个典型的均匀三次B样条节点向量为U [0, 0, 0, 0, 1, 2, 3, ..., n-3, n-3, n-3, n-3]两端节点重复4次。2. 从零次基函数开始递归基函数的递归定义是核心 。零次基函数p0定义在节点区间上的“脉冲”函数。$N_{i,0}(u) \begin{cases} 1 \text{if } u_i \le u u_{i1} \ 0 \text{otherwise} \end{cases}$这构成了递归的“地基”。3. 递推至三次基函数根据Cox-de Boor 公式$N_{i,p}(u) \frac{u - u_i}{u_{ip} - u_i} N_{i, p-1}(u) \frac{u_{ip1} - u}{u_{ip1} - u_{i1}} N_{i1, p-1}(u)$我们可以手动推演到三次一次基函数p1由两个相邻的零次基函数线性混合得到形状为三角形或梯形在[uᵢ, uᵢ₊₂)上非零。二次基函数p2由两个一次基函数混合是光滑的二次曲线在[uᵢ, uᵢ₊₃)上非零。三次基函数p3由两个二次基函数混合最终形成三次多项式段在[uᵢ, uᵢ₊₄)上非零。这是一个分段三次多项式保证了 C² 连续性 。这个递推过程确保了每个基函数是局部支撑、非负且具有单位分解性对于定义域内的任何u所有非零基函数值之和为1。三、 C代码实现与公式的对应关系及示例以下我们将使用C代码来具象化上述理论并清晰展示代码与数学公式和特性的对应关系。1. 数据结构定义对应节点向量与控制点代码首先定义了B样条的核心数据结构。#include vector #include cmath #include iostream // 使用二维点作为示例可扩展至三维 struct Point2D { double x, y; Point2D(double x_ 0, double y_ 0) : x(x_), y(y_) {} Point2D operator(const Point2D other) const { return Point2D(x other.x, y other.y); } Point2D operator*(double scalar) const { return Point2D(x * scalar, y * scalar); } }; class CubicBSpline { public: // 控制点序列和节点向量在构造时传入 CubicBSpline(const std::vectorPoint2D ctrlPts, const std::vectordouble knots) : controlPoints(ctrlPts), knotVector(knots) { if (knotVector.size() ! controlPoints.size() 4) { // 检查 m n p 1 (ctrlPts.size()-1) 3 1 std::cerr Error: Invalid knot vector size for cubic B-spline. std::endl; } } private: std::vectorPoint2D controlPoints; std::vectordouble knotVector; // 非递减序列长度 controlPoints.size() 4 };此代码中的controlPoints对应公式中的 $P_i$knotVector对应 $U$其长度关系knots.size() ctrlPts.size() 4直接体现了公式 $m n p 1$ 在p3时的具体形态 。2. 核心算法实现对应公式推演与特性我们实现两个核心函数递归计算基函数展示公式和 de Boor 算法高效求值并体现局部性。a) 基函数递归计算直接对应Cox-de Boor公式double CubicBSpline::basisFunction(int i, int p, double u) const { const std::vectordouble U knotVector; // --- 对应公式零次基函数定义 --- if (p 0) { if ((U[i] u u U[i 1]) || (std::abs(u - U.back()) 1e-9 i U.size() - p - 2)) { return 1.0; } return 0.0; } // --- 对应公式高次基函数的递归混合 --- double leftTerm 0.0, rightTerm 0.0; double denomLeft U[i p] - U[i]; double denomRight U[i p 1] - U[i 1]; // 公式第一部分 (u - U[i]) / (U[ip] - U[i]) * N_{i,p-1}(u) if (std::abs(denomLeft) 1e-9) { leftTerm ((u - U[i]) / denomLeft) * basisFunction(i, p - 1, u); } // 公式第二部分 (U[ip1] - u) / (U[ip1] - U[i1]) * N_{i1,p-1}(u) if (std::abs(denomRight) 1e-9) { rightTerm ((U[i p 1] - u) / denomRight) * basisFunction(i 1, p - 1, u); } return leftTerm rightTerm; // N_{i,p}(u) }此函数是Cox-de Boor 公式的逐字翻译。递归调用栈精确模拟了从零次基函数线性混合至三次基函数的过程。分母为零的检查对应了节点重复的情况这是实现C²或更低连续性的关键 。b) de Boor 算法求值对应局部控制性与高效计算这是计算曲线点最推荐的方法它直接操作控制点隐含了基函数的计算。Point2D CubicBSpline::evaluateByDeBoor(double u) const { const std::vectordouble U knotVector; int p 3; // 三次 // --- 步骤1: 找到 u 所在的节点区间 [U[k], U[k1]) --- // 这直接对应了基函数的“局部支撑”特性因为只有涉及该区间的p1个基函数非零。 int k p; while (k U.size() - p - 1 u U[k 1]) { k; } // u的有效范围应为 [U[p], U[controlPoints.size()]] if (u U[p] || u U[controlPoints.size()]) { std::cerr Parameter u out of valid domain. std::endl; return Point2D(); } // --- 步骤2: 取出局部相关的 p1 个控制点 --- // 这体现了局部性曲线点 C(u) 仅由 P_{k-p}, ..., P_k 这4个控制点决定。 std::vectorPoint2D d; for (int i k - p; i k; i) { d.push_back(controlPoints[i]); } // --- 步骤3: de Boor 递推 (对应基函数的线性组合过程) --- for (int r 1; r p; r) { for (int i p; i r; --i) { int index k - p i; // 计算混合系数 alpha它本质上是由节点向量和参数u决定的权重。 // 这个线性插值过程等价于逐步构造更高次数的基函数。 double alpha (u - U[index]) / (U[index p 1 - r] - U[index]); d[i] d[i - 1] * (1.0 - alpha) d[i] * alpha; // 线性插值 } } // --- 步骤4: 递推完成后的 d[p] 即为曲线点 C(u) --- return d[p]; }对应关系解析k的查找确定了参数u位于哪个节点区间[U[k], U[k1])。这立即界定了哪些控制点有影响直观体现了局部控制性。数组d初始化d[0..p]初始化为P_{k-p}, ..., P_k。这对应了 de Boor 算法的起点即受影响的控制点子集。双层循环递推这是算法的核心。外层循环r从1到p3代表递推的“层数”对应从一次线性到三次的构造过程。内层循环的线性插值d[i] ...直接对应了Cox-de Boor 公式中基函数的线性混合。最终d[3]就是所有相关基函数加权控制点之和的结果即 $C(u)$。高效性该算法仅需O(p²)次线性插值远比显式计算所有基函数再求和O(n*p)高效且数值稳定性更好 。3. 完整示例与应用演示int main() { // 1. 定义控制点形成一个大致的“S”形 std::vectorPoint2D cpts { {0, 0}, {1, 3}, {3, 1}, {5, 4}, {7, 0}, {9, 2}, {11, 1} }; // n 6 (7个控制点) // 2. 构造一个准均匀三次B样条的节点向量 // 两端节点重复4次内部节点均匀分布。这保证了曲线穿过首尾控制点。 std::vectordouble knots; int p 3; // 首端重复 p1 次 for (int i 0; i p; i) knots.push_back(0.0); // 内部均匀节点 int numInternal cpts.size() - p; // 用于均匀化的段数 for (int i 1; i numInternal; i) { knots.push_back(static_castdouble(i) / numInternal); } // 末端重复 p1 次 for (int i 0; i p; i) knots.push_back(1.0); std::cout Knot Vector: ; for (double k : knots) std::cout k ; std::cout std::endl; // 3. 创建三次B样条对象 CubicBSpline spline(cpts, knots); // 4. 均匀采样并计算曲线点验证平滑性 std::cout Sampled Curve Points (using de Boor algorithm): std::endl; double u_start knots[p]; // 有效参数域起点 U[3] 0.0 double u_end knots[cpts.size()]; // 有效参数域终点 U[7] 1.0 for (int i 0; i 10; i) { double u u_start (u_end - u_start) * i / 10.0; Point2D pt spline.evaluateByDeBoor(u); std::cout u u ( pt.x , pt.y ) std::endl; } // 5. 演示局部性移动一个控制点观察仅部分曲线改变 std::cout --- Demonstrating Local Control --- std::endl; std::vectorPoint2D modifiedPts cpts; modifiedPts[2] {3, 4}; // 将第三个控制点(3,1)上移到(3,4) CubicBSpline modifiedSpline(modifiedPts, knots); // 计算相同参数点观察变化。受影响的u区间约为 [U[2], U[6]) [0, 0.75) double u_test 0.5; // 在受影响区间内 Point2D origPt spline.evaluateByDeBoor(u_test); Point2D modPt modifiedSpline.evaluateByDeBoor(u_test); std::cout At u u_test : std::endl; std::cout Original: ( origPt.x , origPt.y ) std::endl; std::cout After moving P2: ( modPt.x , modPt.y ) std::endl; std::cout Point has changed significantly. std::endl; u_test 0.9; // 在受影响区间外 origPt spline.evaluateByDeBoor(u_test); modPt modifiedSpline.evaluateByDeBoor(u_test); std::cout At u u_test (outside influenced segment): std::endl; std::cout Original: ( origPt.x , origPt.y ) std::endl; std::cout After moving P2: ( modPt.x , modPt.y ) std::endl; std::cout Point is (almost) unchanged, demonstrating locality. std::endl; return 0; }此示例完整展示了从定义到计算的过程节点向量构造准均匀向量使曲线端点与首尾控制点重合是一种常用配置 。曲线采样通过在有效定义域[U[3], U[7]]内采样获得平滑的离散点序列可用于绘图或路径规划 。局部性演示通过修改控制点P₂并对比参数u0.5和u0.9处的曲线点变化直观验证了局部控制特性。u0.5位于P₂的影响区间内点坐标显著变化而u0.9位于影响区间外点坐标几乎不变 。综上所述三次B样条曲线通过其节点向量和递归定义的基函数构成了核心数学框架。C代码中的knotVector和basisFunction/evaluateByDeBoor函数分别与此框架精确对应。de Boor算法不仅是高效的实现其递推过程本身也深刻地揭示了基函数如何混合控制点以生成曲线并生动地体现了局部控制性和C²连续性这两大核心优势 。参考来源【计算几何】贝塞尔曲线 B样条曲线简介及其离散化 Python C 代码实现三次B样条曲线拟合技术详解与实战掌握三次B样条曲线绘制及应用B样条曲线实现代码与原理详解探索B样条曲线原理、实现与应用曲线生成 | 图解B样条曲线生成原理(附ROS C/Python/Matlab仿真)

更多文章