Eigen样条插值:端点导数为零但插值不经过给定点问题问询
问题分析与解决方案
你遇到的问题是Eigen的InterpolateWithDerivatives函数在自动生成节点向量时的一个边界情况bug:当插值点数量n小于等于2*degree时(你的情况是n=5,degree=3,2*3=6,5<=6),自动生成的节点向量会错误覆盖中间节点,导致插值矩阵构造异常,最终生成的样条无法经过给定的插值点。
为什么会这样?
Eigen的KnotAveraging::compute_knot_vector函数在生成clamped样条的节点向量时,会先重复端点degree+1次,然后通过平均插值点参数生成中间节点。但当n <= 2*degree时,生成中间节点的循环会和后续设置端点重复的代码重叠,导致中间节点被错误覆盖。比如你的情况,自动生成的节点向量会变成[0,0,0,0,0.25,1,1,1,1](而不是正确的[0,0,0,0,0.25,0.5,0.75,1,1,1,1]),这直接破坏了插值的条件。
解决方法:手动构造节点向量
绕过自动生成节点的bug,手动构造符合要求的节点向量,然后使用InterpolateWithDerivatives的另一个重载版本(接受节点向量作为参数)。
修改后的代码如下:
typedef Eigen::Spline<double,1> Spline1d; typedef Eigen::SplineFitting<Spline1d> Spline1dFitting; void test_spline() { Eigen::VectorXd x(5); Eigen::VectorXd y(5); x << 0.0, 0.25, 0.5, 0.75, 1.0; y << 0.0, 0.5, 1.0, 0.5, 0.0; Eigen::VectorXd derivatives(2); derivatives << 0., 0.; Eigen::VectorXi indices(2); indices << 0, x.size() - 1; // 手动构造clamped三次样条的节点向量 // 节点向量长度 = 控制点数 + degree + 1 // 控制点数 = 插值点数量 + 导数约束数量 = 5 + 2 =7 // 所以节点向量长度 =7+3+1=11 Eigen::VectorXd knots(11); knots.head(4).setConstant(0.0); // 端点0重复4次(degree+1=3+1) knots.segment(4, 3) = x.segment(1, 3); // 中间节点用x的内部点:0.25,0.5,0.75 knots.tail(4).setConstant(1.0); // 端点1重复4次 Spline1d spline = Spline1dFitting::InterpolateWithDerivatives( y.transpose(), derivatives.transpose(), indices, knots); for (int i = 0; i < 5; ++ i) std::cout << "must be 0: " << spline(x(i)) - y(i) << std::endl; }
关键说明
- 节点向量构造规则:对于clamped B样条(端点导数连续的样条),端点需要重复
degree+1次;中间节点使用插值点的内部参数,保证样段的划分和插值点对应。 - 控制点数匹配:总约束数(插值点数量+导数约束数量)必须等于控制点数,而控制点数 = 节点向量长度 - degree -1。这里5个点+2个导数=7个约束,对应7个控制点,节点向量长度为11(7+3+1)。
- 避免临时对象引用问题:虽然C++允许将临时对象绑定到const引用,但直接赋值给
Spline1d对象更清晰,也避免潜在的生命周期问题。
运行修改后的代码,你会看到输出的差值都接近0,说明样条正确经过了所有给定的点,同时满足端点导数为0的约束。
内容的提问来源于stack exchange,提问作者Maksim Surov
相关产品推荐
相关产品推荐

