平滑算法

时间:2012-10-19 11:25:51

标签: c++ algorithm filter signal-processing

我写了这段代码来平滑曲线。 它在一个点旁边需要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;
        }

    }

}

但是我为每个点获得了非常高的值,而不是与前一点相似的值。形状最大化了很多,这个算法出了什么问题?

6 个答案:

答案 0 :(得分:10)

你在这里看到的是实现boxcar window function的有限脉冲响应(FIR)滤波器的低音实现。考虑到DSP方面的问题,您需要使用vector相等的FIR系数过滤传入的NO_OF_NEIGHBOURS,每个系数的值均为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);

代码非常灵活,您甚至可以根据需要更改窗口形状。通过修改构造函数中定义的系数来完成此操作。

注意:这将为您的实现提供略有不同的输出,因为这是一个因果过滤器(仅取决于当前样本和以前的样本)。您的实现不是因果关系,因为它在未来的样本中及时展望以获得平均值,这就是为什么您需要条件语句来处理接近矢量末尾的情况。如果你想要输出就像你试图用你的过滤器一样使用这个算法,那么通过这个算法反向运行你的向量(只要窗口函数是对称的,这就可以正常工作)。这样你就可以得到类似的输出而没有讨厌的条件部分算法。

答案 1 :(得分:3)

在以下块中:

            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
            }

答案 2 :(得分:3)

过滤有助于'记忆'平滑。这是learnvst's答案的反向传递,以防止phase distortion

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/filtfilt.html

答案 3 :(得分:1)

该点的当前值使用两次:一次是因为您使用+=而一次是y==0。所以你要构建例如6个点的总和,但只能除以5.这个问题出现在IF和ELSE的情况下。另外:你应该检查向量是否足够长,否则你的ELSE案例将以负指数读取。

以下本身不是问题,只是一个想法:您是否考虑使用仅触及每个点两次的算法?:您可以存储临时xy值(初始化为与第一个点相同),然后当您访问每个点时,您只需添加新点,如果它超过NEIGHBOURS,则减去最旧的点。保持每个点的“运行总和”更新并将此值除以NEIGHBOURS - 数字存储到新点。

答案 4 :(得分:1)

当您需要获取邻居点时,您可以使用点本身进行添加 - 只需将索引偏移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
 }

答案 5 :(得分:0)

这对我来说很好:

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);
}