平滑曲线同时保持其下面的区域不变的算法

时间:2014-12-04 04:56:35

标签: algorithm math curve-fitting

考虑由点(x1,y1), (x2,y2), (x3,y3), ... ,(xn,yn)

定义的离散曲线

定义常量SUM = y1+y2+y3+...+yn。假设我们改变某些k个y点(增加或减少)的值,使得这些变化点的总和小于或等于常数SUM

在给定以下两个条件的情况下,调整其他y点的最佳方式是什么:

  1. y点的总和(y1' + y2' + ... + yn')应保持不变,即SUM
  2. 曲线应保留尽可能多的原始形状。
  3. 一个简单的解决方案是定义一些delta如下:

    delta = (ym1' + ym2' + ym3' + ... + ymk') - (ym1 + ym2 + ym3 + ... + ymk')
    

    并将此delta平均分配给其他点。这里ym1'是修改后的修改点的值,ym1是修改前修改后的点的值,将delta作为修改的总差异。

    然而,这并不能确保完全平滑的曲线,因为变化点附近的区域会显得粗糙。这个问题是否存在更好的解决方案/算法?

3 个答案:

答案 0 :(得分:4)

我使用了以下方法,虽然它有点OTT。

考虑将d [i]添加到y [i],得到s [i],平滑值。 我们力求最小化

S = Sum{ 1<=i<N-1 | sqr( s[i+1]-2*s[i]+s[i-1] } + f*Sum{ 0<=i<N | sqr( d[i])}

第一项是曲线的(近似)二阶导数的平方和,第二项是从原始位置移开的惩罚。 f是(正)常数。一个小代数将此重述为

S = sqr( ||A*d - b||)

其中矩阵A具有良好的结构,实际上A'* A是五对角线,这意味着正态方程(即d = Inv(A'* A)* A'* b)可以有效地求解。请注意,d是直接计算的,不需要初始化它。

考虑到这个问题的解决方案,我们可以计算解决方案d ^到同一个问题,但是约束一个'* d = 0(其中一个是所有的向量),像这样

d^ = d - (One'*d/Q) * e
e = Inv(A'*A)*One
Q = One'*e

f使用什么值?一个简单的方法是在各种fs的样本曲线上尝试这个过程,并选择一个看起来不错的值。另一种方法是选择平滑度的估计值,例如二阶导数的均方根,然后是应该达到的值,然后搜索给出该值的f。作为一般规则,较大的f是平滑曲线的平滑度较小。

这一切的一些动机。目的是找到一条“平滑”曲线“接近”给定的曲线。为此,我们需要一个平滑度量(S中的第一项)和一个接近度(第二项。为什么这些度量?嗯,每个都很容易计算,每个都是变量的二次方(d []) ;这将意味着问题成为线性最小二乘的一个实例,有效的算法可用。此外,每个和中的每个项都取决于变量的附近值,这反过来意味着“逆协方差”(A') * A)将具有带状结构,因此可以有效地解决最小二乘问题。为什么要引入f?好吧,如果我们没有f(或将其设置为0),我们可以通过设置d [i] =来最小化S -y [i],获得一条完美平滑的曲线s [] = 0,这与y曲线无关。另一方面,如果f是巨大的,那么为了最小化s,我们应该专注于第二项,并设置d [i] = 0,我们的“平滑”曲线就是原始曲线。所以我们可以合理地假设,当我们改变f时,相应的解决方案会有所不同在非常平滑但远离y(小f)和接近y但有点粗糙(大f)之间。

人们常说,我在这里提倡使用的正规方程式是解决最小二乘问题的一种不好的方法,这通常是正确的。然而,对于“漂亮”的带状系统 - 就像这里的那样 - 通过使用正规方程而失去稳定性并不是那么大,而速度的增益是如此之大。我已经使用这种方法在合理的时间内平滑了数千个点的曲线。

要了解A是什么,请考虑我们有4分的情况。那么我们对S的表达归结为:

sqr( s[2] - 2*s[1] + s[0]) + sqr( s[3] - 2*s[2] + s[1]) + f*(d[0]*d[0] + .. + d[3]*d[3]).

如果我们替换s [i] = y [i] + d [i],我们就会得到,例如,

s[2] - 2*s[1] + s[0] = d[2]-2*d[1]+d[0] + y[2]-2*y[1]+y[0]

所以我们看到这个是sqr(|| A * d-b ||)我们应该

A = ( 1 -2  1  0)
    ( 0  1 -2  1)
    ( f  0  0  0)
    ( 0  f  0  0)
    ( 0  0  f  0)
    ( 0  0  0  f)
and
b = ( -(y[2]-2*y[1]+y[0]))
    ( -(y[3]-2*y[2]+y[1]))
    ( 0 )
    ( 0 )
    ( 0 )
    ( 0 )

在实现中,您可能不希望形成A和b,因为它们仅用于形成正规方程项A'* A和A'* b。直接累积它们会更简单。

答案 1 :(得分:2)

这是一个受限制的优化问题。要最小化的功能是原始曲线和修改曲线的积分差异。约束是曲线下面积和修改点的新位置。自己编写这样的代码并不容易。最好使用一些开源优化代码,例如:ool

答案 2 :(得分:1)

如何保持相同的动态范围?

  1. 计算原始min0,max0 y - 值
  2. 平滑y值
  3. 计算新的min1,max1 y - 值
  4. 线性插值所有值以匹配原始最小值y

    y=min1+(y-min1)*(max0-min0)/(max1-min1)
    
  5. 就是这样

    对该区域不确定,但这应该使形状更接近原始形状。我现在在阅读你的问题时得到了这个想法,现在我遇到了类似的问题,所以我尝试编码并立即尝试+1以获得这个想法:)

    您可以调整此项并与区域结合

    所以在此之前计算区域并应用#1 ..#4 然后计算新区域。然后将所有值乘以old_area/new_area比率。如果您还有负值而不计算绝对面积,那么您必须分别处理正面和负面区域,并找到乘法比率,以便最大限度地适应展位的原始区域。

    [edit1]恒定动态范围的一些结果

    constant dynamic range

    正如您所见,形状略微向左移动。每个图像在应用几百个平滑操作之后。我正在考虑细分到局部最小最大间隔以改善这个......

    [edit2]为自己的目的完成了过滤器

    void advanced_smooth(double *p,int n)
        {
        int i,j,i0,i1;
        double a0,a1,b0,b1,dp,w0,w1;
        double *p0,*p1,*w; int *q;
        if (n<3) return;
        p0=new double[n<<2]; if (p0==NULL) return;
        p1=p0+n;
        w =p1+n;
        q =(int*)((double*)(w+n));
        // compute original min,max
        for (a0=p[0],i=0;i<n;i++) if (a0>p[i]) a0=p[i];
        for (a1=p[0],i=0;i<n;i++) if (a1<p[i]) a1=p[i];
        for (i=0;i<n;i++) p0[i]=p[i];                   // store original values for range restoration
        // compute local min max positions to p1[]
        dp=0.01*(a1-a0);                                // min delta treshold
        // compute first derivation
        p1[0]=0.0; for (i=1;i<n;i++) p1[i]=p[i]-p[i-1];
        for (i=1;i<n-1;i++)                             // eliminate glitches
         if (p1[i]*p1[i-1]<0.0)
          if (p1[i]*p1[i+1]<0.0)
           if (fabs(p1[i])<=dp)
            p1[i]=0.5*(p1[i-1]+p1[i+1]);
        for (i0=1;i0;)                                  // remove zeros from derivation
         for (i0=0,i=0;i<n;i++)
          if (fabs(p1[i])<dp)
            {
                 if ((i>  0)&&(fabs(p1[i-1])>=dp)) { i0=1; p1[i]=p1[i-1]; }
            else if ((i<n-1)&&(fabs(p1[i+1])>=dp)) { i0=1; p1[i]=p1[i+1]; }
            }
        // find local min,max to q[]
        q[n-2]=0; q[n-1]=0; for (i=1;i<n-1;i++) if (p1[i]*p1[i-1]<0.0) q[i-1]=1; else q[i-1]=0;
        for (i=0;i<n;i++)                               // set sign as +max,-min
         if ((q[i])&&(p1[i]<-dp)) q[i]=-q[i];           // this shifts smooth curve to the left !!!
        // compute weights
        for (i0=0,i1=1;i1<n;i0=i1,i1++)                 // loop through all local min,max intervals
            {
            for (;(!q[i1])&&(i1<n-1);i1++);             // <i0,i1>
            b0=0.5*(p[i0]+p[i1]);
            b1=fabs(p[i1]-p[i0]);
            if (b1>=1e-6)
            for (b1=0.35/b1,i=i0;i<=i1;i++)             // compute weights bigger near local min max
             w[i]=0.8+(fabs(p[i]-b0)*b1);
            }
        // smooth few times
        for (j=0;j<5;j++)
            {
            for (i=0;i<n  ;i++) p1[i]=p[i];             // store data to avoid shifting by using half filtered data
            for (i=1;i<n-1;i++)                         // FIR smooth filter
                {
                w0=w[i];
                w1=(1.0-w0)*0.5;
                p[i]=(w1*p1[i-1])+(w0*p1[i])+(w1*p1[i+1]);
                }
            for (i=1;i<n-1;i++)                         // avoid local min,max shifting too much
                {
                if (q[i]>0)                             // local max
                    {
                    if (p[i]<p[i-1]) p[i]=p[i-1];       // can not be lower then neigbours
                    if (p[i]<p[i+1]) p[i]=p[i+1];
                    }
                if (q[i]<0)                             // local min
                    {
                    if (p[i]>p[i-1]) p[i]=p[i-1];       // can not be higher then neigbours
                    if (p[i]>p[i+1]) p[i]=p[i+1];
                    }
                }
            }
        for (i0=0,i1=1;i1<n;i0=i1,i1++)                 // loop through all local min,max intervals
            {
            for (;(!q[i1])&&(i1<n-1);i1++);             // <i0,i1>
            // restore original local min,max
            a0=p0[i0]; b0=p[i0];
            a1=p0[i1]; b1=p[i1];
            if (a0>a1)
                {
                dp=a0; a0=a1; a1=dp;
                dp=b0; b0=b1; b1=dp;
                }
            b1-=b0;
            if (b1>=1e-6)
             for (dp=(a1-a0)/b1,i=i0;i<=i1;i++)
              p[i]=a0+((p[i]-b0)*dp);
            }
        delete[] p0;
        }
    

    所以p[n]是输入/输出数据。很少有东西可以调整如下:

    • 权重计算(常数0.8和0.35表示权重为<0.8,0.8+0.35/2>
    • 平滑传球次数(for循环中现为5次)
    • 重量越大,过滤1.0意味着没有变化
    • 越少

    背后的主要理念是:

    1. 找到当地极端
    2. 计算平滑权重

      如此接近当地极端几乎没有输出的变化

    3. <强>光滑

    4. 修复所有本地极端之间每个时间间隔的动态范围
    5. <强> [注释]

      我也尝试恢复该区域,但这与我的任务不相容,因为它会扭曲形状很多。因此,如果你真的需要这个区域,那么关注那个而不是形状。平滑导致信号大部分收缩,因此在区域恢复后,形状上升。

      实际过滤器状态没有明显的侧移形状(这是我的主要目标)。一些图像的信号更颠簸(原始滤波器极其糟糕):

      constant dynamic range of local extremes

      您可以看到没有可见的信号形状变化。在非常沉重的平滑之后,局部极端趋势会产生尖锐的尖峰,但这是预期的

      希望它有所帮助...