正确实现三次样条插值

时间:2013-11-30 17:13:28

标签: java c++ interpolation spline cubic

我正在使用其中一种提议的算法,但结果非常糟糕。

我实施了wiki algorithm

Java中的

(下面的代码)。 x(0)points.get(0)y(0)values[points.get(0)]αalfaμmi。其余部分与wiki伪代码相同。

    public void createSpline(double[] values, ArrayList<Integer> points){
    a = new double[points.size()+1];

    for (int i=0; i <points.size();i++)
    {
        a[i] = values[points.get(i)];

    }

    b = new double[points.size()];
    d = new double[points.size()];
    h = new double[points.size()];

    for (int i=0; i<points.size()-1; i++){
        h[i] = points.get(i+1) - points.get(i);
    }

    alfa = new double[points.size()];

    for (int i=1; i <points.size()-1; i++){
        alfa[i] = (double)3 / h[i] * (a[i+1] - a[i]) - (double)3 / h[i-1] *(a[i+1] - a[i]);
    }

    c = new double[points.size()+1];
    l = new double[points.size()+1];
    mi = new double[points.size()+1];
    z = new double[points.size()+1];

    l[0] = 1; mi[0] = z[0] = 0;

    for (int i =1; i<points.size()-1;i++){
        l[i] = 2 * (points.get(i+1) - points.get(i-1)) - h[i-1]*mi[i-1];
        mi[i] = h[i]/l[i];
        z[i] = (alfa[i] - h[i-1]*z[i-1])/l[i];
    }

    l[points.size()] = 1;
    z[points.size()] = c[points.size()] = 0;

    for (int j=points.size()-1; j >0; j--)
    {
        c[j] = z[j] - mi[j]*c[j-1];
        b[j] = (a[j+1]-a[j]) - (h[j] * (c[j+1] + 2*c[j])/(double)3) ;
        d[j] = (c[j+1]-c[j])/((double)3*h[j]);
    }


    for (int i=0; i<points.size()-1;i++){
        for (int j = points.get(i); j<points.get(i+1);j++){
            //                fk[j] = values[points.get(i)];
            functionResult[j] = a[i] + b[i] * (j - points.get(i)) 
                                + c[i] * Math.pow((j - points.get(i)),2)
                                + d[i] * Math.pow((j - points.get(i)),3);
        }
    }

}

我得到的结果如下:

enter image description here

但它应该类似于:

enter image description here


我还试图按照另一种方式实现该算法:http://www.geos.ed.ac.uk/~yliu23/docs/lect_spline.pdf

首先,他们展示了如何进行线性样条,这很容易。我创建了计算AB系数的函数。然后,他们通过添加二阶导数来扩展线性样条。 CD系数也很容易计算。

但是当我尝试计算二阶导数时问题就出现了。我不明白他们是如何计算的。

所以我只实现了线性插值。结果是:

enter image description here

有谁知道如何解决第一个算法或解释我如何计算第二个算法中的二阶导数?

4 个答案:

答案 0 :(得分:7)

最近,Unser,Thévenaz等人的一系列论文中描述了立方b样条,等等

  

微米。 Unser,A。Aldroubi,M。Eden,“用于连续图像表示和插值的快速B样条变换”, IEEE Trans。模式肛门。 Machine Intell。,vol。 13,n。 3,pp.277-285,1991年3月。

     

微米。 Unser,“Splines,非常适合信号和图像处理”, IEEE Signal Proc。 Mag。,pp.22-38,1999年11月。

  

P上。 Thévenaz,T。Blu,M。Unser,“Interpolation Revisited”, IEEE Trans。关于医学影像,第一卷。 19,没有。 7,pp.739-758,2000年7月。

以下是一些指导原则。

什么是样条线?

样条曲线是平滑连接在一起的分段多项式。对于度n的样条线,每个线段是度n的多项式。连接的部分使得样条连续到 knots n-1度的导数,即多项式部分的连接点。

如何构建样条线?

零次样条曲线如下

enter image description here

所有其他样条线都可以构造为

enter image description here

进行卷积n-1次。

立方样条

最流行的样条曲线是三次样条曲线,其表达式为

enter image description here

样条插值问题

给定在离散整数点f(x)处采样的函数k,样条插值问题是确定以下列方式表示的近似s(x)f(x)

enter image description here

其中ck是插值系数和s(k) = f(k)

样条预过滤

不幸的是,从n=3开始,

enter image description here

因此ck不是插值系数。它们可以通过求解通过强制s(k) = f(k)获得的线性方程组来确定,即

enter image description here

这样的方程式可以以卷积形式重铸,并在转换后的z - 空间中求解

enter image description here

,其中

enter image description here

因此,

enter image description here

以这种方式进行总是优于通过例如LU分解提供线性方程组的解。

可以通过注意到

来确定上述等式的解

enter image description here

,其中

enter image description here

第一部分代表因果过滤器,而第二部分代表反因果过滤器。它们都在下图中说明。

因果过滤器

enter image description here

Anticausal过滤器

enter image description here

在上图中,

enter image description here

滤波器的输出可以用以下递归方程式表示

enter image description here

可以通过首先确定c-c+的“初始条件”来解决上述等式。在假定周期性镜像输入序列fk

enter image description here

然后可以证明c+的初始条件可以表示为

enter image description here

虽然c+的初始条件可以表示为

enter image description here

答案 1 :(得分:5)

抱歉,但您的源代码对我来说真的是一个难以理解的混乱,所以我坚持理论。以下是一些提示:

  1. SPLINE cubics

    SPLINE不是插值而是近似来使用它们,您不需要任何推导。如果你有十个点:p0,p1,p2,p3,p4,p5,p6,p7,p8,p9那么三次样条曲线以三重点开始/结束。如果你创建了'绘制' SPLINE 三次曲线补丁的函数,那么为了确保连续性,调用序列将是这样的:

        spline(p0,p0,p0,p1);
        spline(p0,p0,p1,p2);
        spline(p0,p1,p2,p3);
        spline(p1,p2,p3,p4);
        spline(p2,p3,p4,p5);
        spline(p3,p4,p5,p6);
        spline(p4,p5,p6,p7);
        spline(p5,p6,p7,p8);
        spline(p6,p7,p8,p9);
        spline(p7,p8,p9,p9);
        spline(p8,p9,p9,p9);
    

    不要忘记p0,p1,p2,p3的SPLINE曲线仅绘制'p1,p2之间的曲线'

  2. BEZIER cubics

    4点 BEZIER 系数可以这样计算:

        a0=                              (    p0);
        a1=                    (3.0*p1)-(3.0*p0);
        a2=          (3.0*p2)-(6.0*p1)+(3.0*p0);
        a3=(    p3)-(3.0*p2)+(3.0*p1)-(    p0);
    
  3. <强>插值

    要使用插值,必须使用插值多项式。那里有很多,但我更喜欢使用立方体......类似于 BEZIER / SPLINE / NURBS ...... 所以

    • p(t) = a0+a1*t+a2*t*t+a3*t*t*t其中t = <0,1>

    唯一要做的就是计算a0,a1,a2,a3。您有2个方程(p(t)及其由t推导)和数据集中的4个点。您还必须确保连续性......因此,对于相邻曲线(t=0,t=1),连接点的首次推导必须相同。这导致4个线性方程组(使用t=0t=1)。如果你得到它,它将创建一个仅取决于输入点坐标的简单方程:

        double  d1,d2;
        d1=0.5*(p2-p0);
        d2=0.5*(p3-p1);
        a0=p1;
        a1=d1;
        a2=(3.0*(p2-p1))-(2.0*d1)-d2;
        a3=d1+d2+(2.0*(-p2+p1));
    

    通话顺序与 SPLINE

  4. 相同

    [注意]

    1. 插值和近似之间的区别在于:

      插值曲线始终通过控制点,但高阶多项式倾向于振荡,近似接近控制点(在某些情况下可以穿过它们,但通常不会)。

    2. <强>变量:

      • a0,... p0,...是向量(维度数必须与输入点匹配)
      • t是标量
    3. 从系数a0,..a3

      中绘制立方体

      做这样的事情:

          MoveTo(a0.x,a0.y);
          for (t=0.0;t<=1.0;t+=0.01)
           {
           tt=t*t;
           ttt=tt*t;
           p=a0+(a1*t)+(a2*tt)+(a3*ttt);
           LineTo(p.x,p.y);
           }
      

答案 2 :(得分:1)

请参阅spline interpolation,尽管它们只提供了一个可用的3x3示例。对于更多的样本点,比如N + 1枚举x[0..N],其值为y[0..N],必须为未知k[0..N]

解决以下系统
              2*    c[0]    * k[0] + c[0] * k[1] == 3*   d[0];
c[0] * k[0] + 2*(c[0]+c[1]) * k[1] + c[1] * k[2] == 3*(d[0]+d[1]);
c[1] * k[1] + 2*(c[1]+c[2]) * k[2] + c[2] * k[3] == 3*(d[1]+d[2]);
c[2] * k[2] + 2*(c[2]+c[3]) * k[3] + c[3] * k[4] == 3*(d[2]+d[3]);
...
c[N-2]*k[N-2]+2*(c[N-2]+c[N-1])*k[N-1]+c[N-1]*k[N] == 3*(d[N-2]+d[N-1]);
c[N-2]*k[N-1] + 2*c[N-1]        *  k[N]          == 3*   d[N-1];

其中

c[k]=1/(x[k+1]-x[k]); d[k]=(y[k+1]-y[k])*c[k]*c[k];

这可以通过Gauß-Seidel迭代(这不是完全针对这个系统发明的吗?)或你最喜欢的Krylov空间求解器来解决。

答案 3 :(得分:0)

//文件:cbspline-test.cpp

// C++ Cubic Spline Interpolation using the Boost library.
// Interpolation is an estimation of a value within two known values in a sequence of values. 

// From a selected test function y(x) = (5.0/(x))*sin(5*x)+(x-6), 21 numbers of (x,y) equal-spaced sequential points were generated. 
// Using the known 21 (x,y) points, 1001 numbers of (x,y) equal-spaced cubic spline interpolated points were calculated.
// Since the test function y(x) is known exactly, the exact y-value for any x-value can be calculated.
// For each point, the interpolation error is calculated as the difference between the exact (x,y) value and the interpolated (x,y) value. 
// This program writes outputs as tab delimited results to both screen and named text file

// COMPILATION: $ g++ -lgmp -lm -std=c++11 -o cbspline-test.xx cbspline-test.cpp
// EXECUTION:   $ ./cbspline-test.xx 

// GNUPLOT 1:   gnuplot> load "cbspline-versus-exact-function-plots.gpl"
// GNUPLOT 2:   gnuplot> load "cbspline-interpolated-point-errors.gpl"

#include <iostream>
#include <fstream>
#include <boost/math/interpolators/cubic_b_spline.hpp>
using namespace std;

// ========================================================
int main(int argc, char* argv[]) {
// ========================================================

    // Vector size 21 = 20 space intervals, size 1001 = 1000 space intervals  
    std::vector<double> x_knot(21), x_exact(1001), x_cbspline(1001);
    std::vector<double> y_knot(21), y_exact(1001), y_cbspline(1001); 
    std::vector<double> y_diff(1001);

    double x;   double x0 = 0.0;    double t0 = 0.0;    
    double xstep1 = 0.5;    double xstep2 = 0.01;       

    ofstream outfile;                                   // Output data file tab delimited values
    outfile.open("cbspline-errors-1000-points.dat");    // y_diff = (y_exact - y_cbspline)

    // Handling zero-point infinity (1/x) when x = 0
    x0 = 1e-18; 
    x_knot[0] = x0; 
    y_knot[0] = (5.0/(x0))*sin(5*x0)+(x0-6);            // Selected test function

    for (int i = 1; i <= 20; i++) { // Note: Loop i begins with 1 not 0, 20 points 
        x = (i*xstep1); 
        x_knot[i] = x;
        y_knot[i] = (5.0/(x))*sin(5*x)+(x-6); 
    }

    // Execution of the cubic spline interpolation from Boost library
    // NOTE: Using xstep1 = 0.5 based on knot points stepsize (for 21 known points) 
    boost::math::cubic_b_spline<double> spline(y_knot.begin(), y_knot.end(), t0, xstep1);

    // Again handling zero-point infinity (1/x) when x = 0
    x_cbspline[0] = x_knot[0];  
    y_cbspline[0] = y_knot[0];

    for (int i = 1; i <= 1000; ++i) {       // 1000 points.
        x = (i*xstep2);  
        x_cbspline[i] = x;
        // NOTE: y = spline(x) is valid for any value of x in xrange [0:10] 
        // meaning, any x within range [x_knot.begin() and x_knot.end()]
        y_cbspline[i] = spline(x);   
    }

    // Write headers for screen display and output file
    std::cout << "# x_exact[i] \t y_exact[i] \t y_cbspline[i] \t (y_diff[i])" << std::endl;  
    outfile   << "# x_exact[i] \t y_exact[i] \t y_cbspline[i] \t (y_diff[i])" << std::endl;  

    // Again handling zero-point infinity (1/x) when x = 0
    x_exact[0] = x_knot[0]; 
    y_exact[0] = y_knot[0];
     y_diff[0] = (y_exact[0] - y_cbspline[0]);
    std::cout << x_exact[0] << "\t" << y_exact[0] << "\t" << y_cbspline[0] << "\t" << y_diff[0] << std::endl;  // Write to screen
    outfile   << x_exact[0] << "\t" << y_exact[0] << "\t" << y_cbspline[0] << "\t" << y_diff[0] << std::endl;  // Write to text file

    for (int i = 1; i <= 1000; ++i) {       // 1000 points
        x = (i*xstep2);  
        x_exact[i] = x;
        y_exact[i] = (5.0/(x))*sin(5*x)+(x-6); 
         y_diff[i] = (y_exact[i] - y_cbspline[i]);
        std::cout << x_exact[i] << "\t" << y_exact[i] << "\t" << y_cbspline[i] << "\t" << y_diff[i] << std::endl;  // Write to screen
        outfile   << x_exact[i] << "\t" << y_exact[i] << "\t" << y_cbspline[i] << "\t" << y_diff[i] << std::endl;  // Write to text file
    }
    outfile.close();

return(0);
}
// ========================================================
/*
# GNUPLOT script 1
# File: cbspline-versus-exact-function-plots.gpl

set term qt persist size 700,500
set xrange [-1:10.0]
set yrange [-12.0:20.0]
# set autoscale         # set xtics automatically
set xtics 0.5
set ytics 2.0
# set xtic auto         # set xtics automatically
# set ytic auto         # set ytics automatically
set grid x
set grid y
set xlabel "x" 
set ylabel "y(x)"
set title "Function points y(x) = (5.0/(x))*sin(5*x)+(x-6)"
set yzeroaxis linetype 3 linewidth 1.5 linecolor 'black'
set xzeroaxis linetype 3 linewidth 1.5 linecolor 'black'
show xzeroaxis
show yzeroaxis
show grid

plot 'cbspline-errors-1000-points.dat' using 1:2 with lines linetype 3 linewidth 1.0 linecolor 'blue' title 'exact function curve', 'cbspline-errors-1000-points.dat' using 1:3 with lines linecolor 'red' linetype 3 linewidth 1.0 title 'cubic spline interpolated curve'

*/
// ========================================================
/*
# GNUPLOT script 2
# File: cbspline-interpolated-point-errors.gpl

set term qt persist size 700,500
set xrange [-1:10.0]
set yrange [-2.50:2.5]
# set autoscale         # set xtics automatically
set xtics 0.5
set ytics 0.5
# set xtic auto         # set xtics automatically
# set ytic auto         # set ytics automatically
set grid x
set grid y
set xlabel "x" 
set ylabel "y"
set title "Function points y = (5.0/(x))*sin(5*x)+(x-6)"
set yzeroaxis linetype 3 linewidth 1.5 linecolor 'black'
set xzeroaxis linetype 3 linewidth 1.5 linecolor 'black'
show xzeroaxis
show yzeroaxis
show grid

plot 'cbspline-errors-1000-points.dat' using 1:4 with lines linetype 3 linewidth 1.0 linecolor 'red' title 'cubic spline interpolated errors'

*/
// ========================================================