滑动窗口算法实现

在数字滤波器中,滑动窗口滤波法和平均值滤波法是最为常见和最易实现的两种算法,其中平均值比较简单,实现起来比较容易,而滑动窗口理解起来很容易,但实现起来相对困难。本文的主要内容一是从原理上对比一下两种算法的差别,另外就是通过C代码实现滑动窗口滤波算法,并且提供对应的测试程序。本文所写的滑动窗口算法可用于数字电源中。

平均值滤波与滑动窗口滤波对比

在这里插入图片描述
请看上图中的平均值滤波和滑动窗口法,假设两种方法的采样周期都是Ts,平均值法以n为单位求取平均值,而滑动窗口的窗口长度为n,我们可以发现,在平均值法中,计算最小周期为n倍Ts,因为如图所示,假设我们在t1~tn这段时间内计算出一个平均值Avg1,则该时间段内的所有数据都是被用过了,是要丢弃的,而要得到下一个平均值Avg2,则需要等待n倍Ts的时间。
而滑动窗口法如右边所示,窗口长度是固定的,每滑动一次的时候,都会新加入一个新数据,舍弃最初始的数据,然后求取窗口内数据的均值,计算周期最小为Ts,也就是产生一个新数据都会被几十看到。
所以,我们可以从这两种方法中得出:
1、滑动窗口法的计算周期比均值法的计算周期短,每次产生的新数据变化都会对整体产生影响,而平均值法是要等变化达到一定的量时,才更新出新的数据,这就好像两个态度不同两个人,一个比较积极,一有变化,就积极响应,一个比较懒散,要有一段时间的变化后,才去响应。
2、在那种变化速率不太快的情况下,两种方法得到的结果其实是差不多的,但是在那种对变化速率很敏感的地方,滑动窗口还是比均值法优一些。

运用在实际中的一般写法

在数字电路的滤波中,实现的方法如下:
均值法:设定均值计算长度n,每次定时器或者AD刷新,都录入一个数据,计数器+1,把新数据加入累加和中,然后判断是否等于计算长度,如果等于计算长度,就求取均值,累加和清0,计数器清0,如果不等于计算长度,则将新的数据加入累加和中,示例代码如下:

//AD刷新或者定时器刷新函数中
{
//接收新数据
temp = sample;
//计数器+1
i=i+1;
//求和
sum = sum+temp;
if(i==n)
{
average = sum/n;
sum=0;
i=0;
}
}

而滑动窗口法也是类似,大体的实现思路如下:
1、接收新数据
2、丢弃最初始的数据
3、累加和-丢弃的最初始的数据+新的数据
4、均值=累加和/窗口长度

滑动窗口滤波实现

通过对滑动窗口的分析,我们可以发现,滑动窗口其实就是一个队列,先进先出,就像火车过隧道一样,隧道长度就是窗口,火车进出隧道遵循的就是火车头先进,火车头先出,火车尾部后进,火车尾部就后出,而隧道的长度始终保持不变。本文就是基于队列思想实现的,可直接在采样中断或者定时器中断函数中直接调用,另外,本文中的代码还对一种特殊情况做了处理,那就是窗口长度大于数据量时,这种情况在运用中是存在的,比如在数据采样的时候,刚开始的时候是没有数据,数据量得从0开始增加,这种情况下窗口长度是大于数据量的,针对这种情况,本文也完美地实现了即使数据量小于窗口长度,也能准确计算出窗口内有效数据的均值。详细代码见下文。

头文件实现

#ifndef ALG_LINKED_LIST_H
#define ALG_LINKED_LIST_H



#define LINKED_LEN 100    //循环链表长度

typedef struct{
 float Sample[LINKED_LEN];
    unsigned int Head; //表头
 unsigned int Tail; //表尾
 unsigned int Win_full_flag; //窗口是否满了标志
 float Sample_value;    //采样倿
 float Average;         //采样均忿
 float Sample_discarded;  //采样丢弃倿
 float Sum;             //窗口内数据濻和
 void (*init)();
 void (*update_data)();  //更新数据 
 void (*data_average)();
}LINKED_LIST;

typedef LINKED_LIST *List_Handle;
#define LIST_DEFAULT {\
 {0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0, \
 0,0,0,0,0,0,0,0,0,0,\
    }, \
 0,0,0,0.0,0.0,0.0,0.0, \
 (void (*)(unsigned long))Init, \
 (void (*)(unsigned long))Update_data, \
 (void (*)(unsigned long))Data_average, \
}

void Init(List_Handle s);
void Update_data(List_Handle s);
void Data_average(List_Handle s);

extern LINKED_LIST list;

具体函数实现

#include ".\..\include\Alg_linked_list.h"



void Init(List_Handle s)
{
       s->Tail = LINKED_LEN;
}

void Update_data(List_Handle s)
{
    
 
    
 if(s->Head < s->Tail)
 {
        s->Sample_discarded = s->Sample[s->Head]; //记录丢弃倿
        s->Sample[s->Head] = s->Sample_value;     //新加入的数据
        s->Head = s->Head +1;
  
 }
 else if(s->Head == s->Tail)
 {
        s->Head = 0;
        s->Sample_discarded = s->Sample[s->Head]; //记录丢弃倿
        s->Sample[s->Head] = s->Sample_value;     //新加入的数据
        s->Head = s->Head +1;
        s->Win_full_flag = 1;
        
 }
 
}
void Data_average(List_Handle s)
{
 if(s->Win_full_flag == 0)
 {
        s->Sum = s->Sum + s->Sample[s->Head - 1];
        s->Average = s->Sum/(s->Head);
 }
 else
 {
        s->Sum = s->Sum + s->Sample[(s->Head + s->Tail -1)%LINKED_LEN] - s->Sample_discarded;
        s->Average = s->Sum/LINKED_LEN;
 }
    
}

测试程序

#include "Alg_linked_list.c"
#include "stdlib.h"
#include "stdio.h"
LINKED_LIST list = LIST_DEFAULT;
void main()
{ 
    list.init(&list);
    float temp;
    for(int i=0;i<201;i++)
    {
        temp = 20.0 + rand()/32767.0;
        //printf("%7.6f\n",temp);
        list.Sample_value = temp;
        list.update_data(&list);
        list.data_average(&list);
    }
}

代码中的文件命名及变量命名有些不规范,读者可根据自己的喜好更改,队列英文单词应该是queue。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐