# Median-Filter für CoDeSys



## Ossi (15 August 2013)

Hallo,

für eine Messwertaufbereitung benötige ich einen Median-Filter, damit Ausreißer nicht so ins Gewicht gehen.
Die Messwerte werden über eine For-Schleife eingelesen und dass Array dann sortiert. Bei den Messwerten handelt es sich um Real-Werte (wären vielleicht skalierte INt-Werte besser --> weniger Speicher?). Der Median wird dann an den Ausgang übergeben.
Es scheint mit 21 Werten ganz gut zu funktionieren, aber das Fenster müsste vergrößert werden. Wenn z.B. mit 201 Werten gearbeitet werden soll, macht die WAGO 750-872 PLC nichts mehr. Gibt es vielleicht irgendwo einen Median-Filter in einer freiverfügbaren Bibliothek, der getestet wurde?
Hier mal der Code:

PROGRAM Filter


VAR
    MessSignal:REAL; (*Eingang*)
    Count: INT;
    ValueArray: ARRAY [0..20] OF REAL;
    myCount: INT;
    k: INT;
    l: INT;
    Value: REAL;
    FilterSignal:REAL; (*Ausgang*)
END_VAR

IF Count>20 THEN Count:=1; END_IF

FOR Count:=1 TO 21 DO
    ValueArray[Count-1]:=MessSignal;
    myCount:=myCount+1;
END_FOR;

FOR k:=1 TO 20 DO
    FOR l:=1 TO 21 DO
        IF ValueArray[k-1]>ValueArray[l-1]
            THEN
                Value:=ValueArray[k-1];
                ValueArray[k-1]:=ValueArray[l-1];
                ValueArray[l-1]:=Value;
        END_IF
    END_FOR
END_FOR
IF k>20 THEN

    (*YValue:=;*)
    FilterSignal:=ValueArray[10];
    k:=1;
    l:=1;
END_IF


Vielleicht hat jemand ja schon mal so einen Filter benutzt und kann mir helfen? Vielen Dank,

Ossi


----------



## MasterOhh (15 August 2013)

Bis du sicher das dein Filter so funktioniert?

Du füllst dein ValueArray 

```
FOR Count:=1 TO 21 DO
  ValueArray[Count-1]:=MessSignal;
  myCount:=myCount+1;
END_FOR;
```
komplett mit dem gleichen Messwert. D.h. alle Elemente haben den selben Wert, was willst du da noch Filtern ?


Das die Wago bei einer Fenstergröße von 200 in die Knie geht sollte dich nicht wundern. Immerhin muss deine verschachtelte Schleifenanordnung dann 40000 mal je Zyklus abgearbeitet werden. 

Brauchst du überhaupt so ein großes Fenster? 

Um Messwerte zu filtern verwende ich i.d.R einen gleitenen Mittelwert. Der haut schon bei einer Breite von 50 Werten die groben Ausreißer zuverlässig platt und ist auch wesentlich resourcenschonender. 


Noch ein paar kleine Anmerkungen zur Syntax:
Zählervariablen für FOR-Schleifen brauchst du nicht zurücksetzen. Das macht die Schleife selber ( FOR *i:=1* TO).
Wenn dein Array von 0..20 geht, dann kannst du die FOR-Schleife auch bei 0 beginnen lassen ( FOR *i:=0* TO), das spart dir dieses ständige -1 im Index.
Alternativ kannst du das Array aber auch von 1..21 deklarieren.

Der letzte Abschnitt 

```
IF k> 20 THEN
```
zeigt, dass du die zyklische Abarbeitung eines SPS Programms noch nicht so ganz verinnerlicht hast. Die beiden Schleifen werden im Zyklus *immer komplett* durchlaufen (wenn sie nicht durch den EXIT Befehl unterbrochen werden). D.H. k ist auch immer >20 wenn das Programm an der IF Abfrage ankommt. Also kannst du dir das IF sparen.


----------



## Thomas_v2.1 (15 August 2013)

Das zeitraubendste ist der Sortieralgorithmus Bubblesort den du dort verwendest.
Das meiste bringt es wenn du auf ein effizientes Sortierverfahren wechselst.
Zum Vergleich in Zeiteinheiten:
Bubblesort n². bei 200 Elementen = 40000
Quicksort: im Mittel n log n, bei 200 Elementen = 460 (wobei es schlimmstenfalls vorkommen kann dass es auch 40000 benötigt)

Und da gibt es noch effizientere Verfahren (Radixsort à la 00 alex)


----------



## Ossi (15 August 2013)

Hallo,
Deine Hinweise waren sehr hilfreich! Vielen Dank!
Ich habe mich jetzt für einen gleitenden Mittelwert entschieden...

IF Count<20 AND Init=FALSE THEN
    ValueArray[Count]:=MessSignal;
    Count:=Count+1;
    IF count=20 THEN Init:=TRUE; END_IF
END_IF

IF PufferFil=TRUE THEN
    PufferArray[20]:=MessSignal;
    ValueArray:=PufferArray;
    PufferFil:=FALSE;
END_IF

FOR i:=1 TO 20 DO
    Value:=Value+ValueArray_;
    IF i<20 THEN
    PufferArray:=ValueArray[i+1];
    PufferFil:=TRUE;
    END_IF
END_FOR

(*YValue:=;*)
FilterSignal:=Value/20;
Value:=0;

Gruß,

Ossi_


----------



## KingHelmer (15 August 2013)

> (Radixsort à la 00 alex)



ich sehe es kommen, in 20 Jahren bekommt er den Nobel-Preis


----------



## MasterOhh (15 August 2013)

Welchen Filter aus der OSCAT Lib hast du denn genommen? Ich habe auf die schnelle nur den Filter_MAV_W bzw Filter_MAV_DW gefunden. Beide können keine REAL-Werte verarbeiten und auch nur über 32 Werte Filtern.

Im Grunde ist ein Gleitender Mittelwert ganz einfach zu programmieren.


```
VAR_INPUT
  Messwert_neu :REAL;
END_VAR
VAR_OUTPUT
  FilterWert :REAL;
END_VAR
VAR
  Puffer :ARRAY[1..100] OF REAL; 
  Summe :REAL;
  i,pos :INT;
END_VAR

IF pos > 100 THEN
  pos := 1;
END_IF

Puffer[pos] := Messwert_neu;
Summe := 0;

FOR i := 1 TO 100 DO
  Summe := Summe + Puffer[i];
END_FOR

FilterWert := Summe / 100.0;
pos := pos +1;
```
wie immer ohne Gewähr

Der aktuelle Messwert wird fortlaufend in den Puffer geschrieben, sodass der Puffer immer den neuen Wert und die 99 letzten Werte enthält.
Diese 100 Werte werden dann aufsummiert und durch 100 dividiert.  => Mittelwert
Im nächsten Zyklus kommt wieder eine neuer Messwert hinzu und es gibt einen neuen Mittelwert.
Bei den ersten 99 Messwerten kommt hier natürlich nur Murks raus, weil der Puffer ja noch mit 0 gefüllt ist. Das musst du berücksichtigen!
Da gibt es aber Lösungen für, z.B. im ersten Aufruf den Puffer mit dem ersten Messwert vollschreiben.

Über wieviele Werte du filterst, solltest du je nach benötigter Reaktionszeit und Rauschunterdrückung abwägen.

Wenn du starke Spitzen im Signal hast, wirst du die mit einem MAV Filter nie ganz weg bekommen. Starke Ausreißer werden nur "breit gezogen" aber nicht entfernt. Für sowas ist dann z.B. ein Median wieder besser geeignet.


----------



## Thomas_v2.1 (15 August 2013)

Median gibt es in der Oscat Bibliothek, nennt sich _ARRAY_MEDIAN.
Beim Aufruf muss man aber die Größe des Arrays mit übergeben.

Beispiel:

```
VAR
	werte : ARRAY[1..10] OF REAL;
	median : REAL;
END_VAR
```

Wenn du den Median über das gesamte Array (also alle 10 Elemente) bilden willst, muss der Aufruf so lauten:

```
median := _ARRAY_MEDIAN(pt := ADR(werte), size := SIZEOF(werte));
```

Soll der Median über z.B. die ersten 5 Elemente des Arrays gebildet werden, dann so:

```
median := _ARRAY_MEDIAN(pt := ADR(werte), size := 5 * SIZEOF(werte[1]));
```
da der Parameter "size" die Größe des Arrays im Speicher, und nicht die Anzahl der Elemente angibt.


----------



## MasterOhh (15 August 2013)

Müsste man mal reinschauen wie dort die Werte Sortiert werden.


----------



## Thomas_v2.1 (15 August 2013)

Das sieht bei Oscat nach Quicksort aus. Ist bei einer sehr größen Anzahl an Elementen vielleicht nicht die beste Lösung, da es im ungünstigen Fall dann doch sehr lange dauern kann.
Wobei es im hier gezeigten Beispielcode wohl daran liegt, dass es hier mit 20 Elementen nicht funktioniert weil überhaupt kein korrekter Median gebildet wurde. Vielleicht erstmal mit einem korrekten Median über 20 Werte probieren.

Des weiteren ist zu beachten dass das Array in der Oscat-Sortier-Funktion verändert wird. Wenn man z.B. ein Median fortlaufend über die letzten n Werte erstellen will, muss man sich die alten Werte vorher wegsichern, bzw. die Oscat Median Funktion auf einer Kopie des Arrays arbeiten lassen.


----------

