用于平滑的算法

Algorithm for smoothing

本文关键字:算法 平滑 用于      更新时间:2023-10-16

我写了此代码以平滑曲线。它在一个点旁边需要5分,并添加它们并平均。

/* Smoothing */
void smoothing(vector<Point2D> &a)
{
    //How many neighbours to smooth
    int NO_OF_NEIGHBOURS=10;
    vector<Point2D> tmp=a;
    for(int i=0;i<a.size();i++)
    {
        if(i+NO_OF_NEIGHBOURS+1<a.size())
        {
            for(int j=1;j<NO_OF_NEIGHBOURS;j++)
            {
                a.at(i).x+=a.at(i+j).x;
                a.at(i).y+=a.at(i+j).y;
            }
            a.at(i).x/=NO_OF_NEIGHBOURS;
            a.at(i).y/=NO_OF_NEIGHBOURS;
        }
        else
        {
            for(int j=1;j<NO_OF_NEIGHBOURS;j++)
            {
                a.at(i).x+=tmp.at(i-j).x;
                a.at(i).y+=tmp.at(i-j).y;
            }
            a.at(i).x/=NO_OF_NEIGHBOURS;
            a.at(i).y/=NO_OF_NEIGHBOURS;
        }
    }
}

,但我得到的每个点的值很高,而不是与上点相似的值。形状最大化很多,此算法出了什么问题?

您的外观是在这里实现有限的脉冲响应(FIR)滤波器的低音量,该滤镜实现了实现Boxcar窗口功能的滤波器。考虑到DSP的问题,您需要使用NO_OF_NEIGHBOURS相等的FIR系数过滤传入的vector,每个FIR系数都具有1/NO_OF_NEIGHBOURS的值。通常最好使用已建立的算法而不是重新发明车轮。

这是一个相当大的实现,我迅速敲出了滤器,使过滤了两倍。您可以轻松地修改它以过滤您的数据类型。该演示显示了仅出于演示目的的锯函数的几个周期(0,.25,.5,1)的过滤。它编译了,所以您可以玩它。

#include <iostream>
#include <vector>
using namespace std;
class boxFIR
{
    int numCoeffs; //MUST be > 0
    vector<double> b; //Filter coefficients
    vector<double> m; //Filter memories
public:
    boxFIR(int _numCoeffs) :
    numCoeffs(_numCoeffs)
    {
        if (numCoeffs<1)
            numCoeffs = 1; //Must be > 0 or bad stuff happens
        double val = 1./numCoeffs;
        for (int ii=0; ii<numCoeffs; ++ii) {
            b.push_back(val);
            m.push_back(0.);
        }
    }    
    void filter(vector<double> &a)
    {
        double output;
        for (int nn=0; nn<a.size(); ++nn)
        {
            //Apply smoothing filter to signal
            output = 0;
            m[0] = a[nn];
            for (int ii=0; ii<numCoeffs; ++ii) {
                output+=b[ii]*m[ii];
            }
            //Reshuffle memories
            for (int ii = numCoeffs-1; ii!=0; --ii) {
                m[ii] = m[ii-1];
            }                        
            a[nn] = output;
        }
    }

};
int main(int argc, const char * argv[])
{
    boxFIR box(1); //If this is 1, then no filtering happens, use bigger ints for more smoothing
    //Make a rising saw function for demo
    vector<double> a;
    a.push_back(0.); a.push_back(0.25); a.push_back(0.5); a.push_back(0.75); a.push_back(1.);
    a.push_back(0.); a.push_back(0.25); a.push_back(0.5); a.push_back(0.75); a.push_back(1.);
    a.push_back(0.); a.push_back(0.25); a.push_back(0.5); a.push_back(0.75); a.push_back(1.);
    a.push_back(0.); a.push_back(0.25); a.push_back(0.5); a.push_back(0.75); a.push_back(1.);
    box.filter(a);
    for (int nn=0; nn<a.size(); ++nn)
    {
        cout << a[nn] << endl;
    }
}

使用此线路增加过滤器系数的数量,以查看逐渐平滑的输出。只有1个滤镜系数,就没有平滑。

boxFIR box(1);

该代码足够灵活,如果愿意,您甚至可以更改窗口形状。通过修改构造函数中定义的系数来执行此操作。

注意:这将使您的实现略有不同,因为这是一个因果过滤器(仅取决于当前样本和以前的样本)。您的实现不是因果关系,因为它会在未来的样本中及时提前保持平均水平,这就是为什么您需要在矢量末尾的情况下需要条件陈述的原因。如果您希望像尝试使用此算法尝试使用过滤器执行的输出,请在反向的情况下通过此算法运行矢量(只要窗口函数是对称的,就可以正常运行该算法。这样,您可以在没有算法的讨厌的条件部分的情况下获得类似的输出。

在以下块中:

            for(int j=0;j<NO_OF_NEIGHBOURS;j++)
            {
                a.at(i).x=a.at(i).x+a.at(i+j).x;
                a.at(i).y=a.at(i).y+a.at(i+j).y;
            }

对于每个邻居,您分别添加a.at(i)的x和y到邻居值。

我正确理解,应该是这样的。

            for(int j=0;j<NO_OF_NEIGHBOURS;j++)
            {
                a.at(i).x += a.at(i+j+1).x
                a.at(i).y += a.at(i+j+1).y
            }

过滤非常适合'内存'平滑。这是LearnVST答案的相反通过,以防止阶段失真:

for (int i = a.size(); i > 0; --i)
{
    // Apply smoothing filter to signal
    output = 0;
    m[m.size() - 1] = a[i - 1];
    for (int j = numCoeffs; j > 0; --j) 
        output += b[j - 1] * m[j - 1];
    // Reshuffle memories
    for (int j = 0; j != numCoeffs; ++j) 
        m[j] = m[j + 1];
    a[i - 1] = output;
}

MATLAB中的零相变形FIR滤波器的更多信息:http://www.mathworks.com/help/signal/ref/ref/filtfilt.html

点的当前值使用两次:一次是因为您使用+=,并且如果y==0,则使用一次。因此,您正在构建例如6点的总和,但仅除以5。此问题在IF和其他情况下。另外:您应该检查向量是否足够长,否则您的其他内容将以负索引阅读。

以下本身不是问题,而只是一个想法:您是否考虑使用仅触摸每个点两次的算法?当您访问每个点时,只需添加新点,然后减去最古老的点,如果它比您的NEIGHBOURS较远。您将此"运行总和"保留为每个点,并将此值存储为新点的NEIGHBOURS -NUMBER。

当您需要邻居点时,您会增加点本身 - 只需将索引偏移1:

for(int j=0;j<NO_OF_NEIGHBOURS;j++)
 {
    a.at(i).x += a.at(i+j+1).x
    a.at(i).y += a.at(i+j+1).y
 }

这对我来说很好:

for (i = 0; i < lenInput; i++)
{
    float x = 0;
    for (int j = -neighbours; j <= neighbours; j++)
    {
        x += input[(i + j <= 0) || (i + j >= lenInput) ? i : i + j];
    }
    output[i] = x / (neighbours * 2 + 1);
}