Filter: gleitender Mittelwert

Häufig müssen in technischen Systemen (wie z.B. Steuergeräte) externe Werte von Sensoren eingelesen werden. Im Normalfall müssen diese Daten aber noch aufbereitet werden, da von Natur aus etliche Störgrößen (Messrauschen oder Messwertausreißer) auf das Signal Einfluss nehmen.

Damit man die Daten nutzen kann, müssen diese erst bearbeitet / gefiltert werden.

Eine relativ einfache und gebräuchliche Art der Filterung für verrauschte Werte ist der gleitende Mittelwert oder gleitende Durchschnitt (eng. Moving-average filter).

Bei diesem Filter wird ein Fenster mit fester Breite über die Daten verschoben und aus den innenliegenden Werten ein neuer Datenpunkt errechnet.

    \[ y (n) =\frac{1}{Fensterbreite}  \cdot  (x(n) + x(n-1) + \dots +x(n-(Fensterbreite-1))\]

Hier werden die Eingangswerte gleichgewichtet und somit stellt diese Filterart die einfachste Form eine FIR-Filters dar.

Im unteren Fenster sieht man das Rohsignal in Rot. Das blaue Signal stellt den gefilterten Wert mit einer Fensterbreite von 5 dar.

Gleitender Durchschnitt

Man kann gut erkennen, dass der Verlauf etwas „weicher“ als beim Originalverlauf ist, allerdings sieht man auch eine Verschiebung des Signals.

Der gleitende Mittelwert findet aber nicht nur in der Technik eine breite Anwendung, sondern kommt auch in der Finanzwelt zum Einsatz. Unten in der Grafik seht ihr mit Grün und Rot zwei unterschiedliche Durchschnitte mit Zeiträumen von 38 Tage und 200 Tage

Aktie Gleitender Durchschnitt

 

Implementierug in C

Um z.B. Daten in Steuergeräten zu filtern, kann man die oben beschriebene Funktion relativ leicht in der Programmiersprache C umsetzen. Um das Ganze etwas objektorientierter zu gestalten, wird im Header eine Struktur mit den benötigten Attributen deklariert. Wir benötigen die Fensterbreite, einen Ringbuffer, einen Zeiger auf den aktuellen Wert und die Summe der Daten im Buffer.


Headerdatei:

typedef struct
{
int FilterSize;
float* Buffer;
float* PositionPointer;
float Sum;
} MA_FILTER;
int InitMovingAverage(MA_FILTER* me, int FilterSize); //Initialize Filter
float DoMaFilter(MA_FILTER* me, float NewValue);      //Update Filter

Mit der Init-Funktion übergibt man dem Filter die gewünschte Fensterbreite und die Funktion DoMaFilter muss zyklisch mit dem aktuellen Messwert aufgerufen werden.


c-Datei:

#include <stdlib.h>
#include "moving_average.h"
/******************************************************************************/
/**
*  @par Name:
*                InitMovingAverage
*
*  @par Description:
*                Initializes a Moving Average Filter
*
*  @param[in] me           Pointer to Filter Object
*  @param[in] FilterSize   Filter Size (Size of Array)
*
*  @return No Error if Initialization was ok
*
*******************************************************************************/
int InitMovingAverage(MA_FILTER* me, int FilterSize)
{
int Index;
me->FilterSize = FilterSize;
//Allocate Buffer
me->Buffer = malloc(FilterSize * sizeof(float));
if(NULL == me->Buffer)
{
return (-1);
}
else
{
me->PositionPointer = me->Buffer;
//Clear Buffer
for(Index = 0; Index < me->FilterSize; Index++)
{
me->Buffer[Index] = 0;
}
return 0;
}
}

In der Ini-Funktion wird ein Array mit der angegebenen Fensterbreite dynamisch allokiert, der Zeiger auf die erste Adresse des Buffers gesetzt und anschließend der Buffer mit Nullen beschrieben. Falls das Reservieren des Speicherplatzes nicht erfolgreich war, wird als Fehlermeldung eine -1 zurückgegeben.

/******************************************************************************/
/**
*  @par Name:
*                DoMaFilter
*
*  @par Description:
*                Update a Moving Average Filter
*
*  @param[in] me           Pointer to Filter Object
*  @param[in] NewValue     New Input Value
*
*  @return Filtered Value
*
*******************************************************************************/
float DoMaFilter(MA_FILTER* me, float NewValue)
{
// Remove oldest value
me->Sum -= *me->PositionPointer;
// Update buffer
*me->PositionPointer = NewValue;
// Update the sum with new value
me->Sum += NewValue;
// Set Pointer to next value in buffer
if((++me->PositionPointer) >= (me->Buffer + me->FilterSize)
{
me->PositionPointer = me->Buffer;
}
return me->Sum / (float) me->FilterSize;
}

In der zyklisch aufgerufenen Funktion wird erst der läteste Wert von der Summe abgezogen und dann der neue Abtastwert in im Speicher abgelegt und die Summe neu berechnet. In der if-Abfrage wird der Positionszeiger um einen Platz verschoben und geprüft, ob man am Ende des Speichers ist. Falls ja wird an die Anfangsadresse gesprungen.

Danach wird die Summe durch die Fensterbreite geteilt und zurückgegeben.

Weiterführende Literatur (Amazon-Links):