Wie schreibe ich Tiefpassfilter für abgetastetes Signal in Python?

16

Ich habe ein Signal, das alle 1 ns (1e-9 sec) abgetastet wurde und zum Beispiel 1e4 Punkte hat. Ich muss hohe Frequenzen aus diesem Signal herausfiltern. Angenommen, ich muss Frequenzen über 10 MHz filtern. Ich möchte, dass für Frequenzen unterhalb der Grenzfrequenz das Signal unverändert weitergegeben wird. Dies bedeutet, dass die Verstärkung des Filters für Frequenzen unter der Grenzfrequenz 1 beträgt. Ich möchte die Filterreihenfolge festlegen können. Ich meine, Filter erster Ordnung haben eine Steigung von 20 dB / Dekade (Power Roll Off) nach der Grenzfrequenz, Filter zweiter Ordnung haben eine Steigung von 40 dB / Dekade nach der Grenzfrequenz und so weiter. Hohe Leistung von Code ist wichtig.

Alex
quelle

Antworten:

19

Der Frequenzgang für den mit der Butterfunktion ausgelegten Filter beträgt:

Antwort von Butterworth-Filter

Es gibt jedoch keinen Grund, den Filter auf einen konstanten monotonen Filteraufbau zu beschränken. Wenn Sie eine höhere Dämpfung im Sperrbereich und einen steileren Übergang wünschen, gibt es andere Möglichkeiten. Weitere Informationen zum Festlegen eines Filters mithilfe von iirdesing finden Sie hier . Wie aus den Frequenzgangdiagrammen für das Butterdesign hervorgeht , ist die Grenzfrequenz (-3 dB Punkt) weit vom Ziel entfernt. Dies kann durch Heruntertasten vor dem Filtern verringert werden (die Entwurfsfunktionen werden mit einem derart engen Filter, 2% der Bandbreite, eine schwierige Zeit haben). Betrachten wir das Filtern der ursprünglichen Samplerate mit dem angegebenen Cutoff.

import numpy as np
from scipy import signal
from matplotlib import pyplot as plt

from scipy.signal import fir_filter_design as ffd
from scipy.signal import filter_design as ifd

# setup some of the required parameters
Fs = 1e9           # sample-rate defined in the question, down-sampled

# remez (fir) design arguements
Fpass = 10e6       # passband edge
Fstop = 11.1e6     # stopband edge, transition band 100kHz
Wp = Fpass/(Fs)    # pass normalized frequency
Ws = Fstop/(Fs)    # stop normalized frequency

# iirdesign agruements
Wip = (Fpass)/(Fs/2)
Wis = (Fstop+1e6)/(Fs/2)
Rp = 1             # passband ripple
As = 42            # stopband attenuation

# Create a FIR filter, the remez function takes a list of 
# "bands" and the amplitude for each band.
taps = 4096
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

# The iirdesign takes passband, stopband, passband ripple, 
# and stop attenuation.
bc, ac = ifd.iirdesign(Wip, Wis, Rp, As, ftype='ellip')  
bb, ab = ifd.iirdesign(Wip, Wis, Rp, As, ftype='cheby2') 

Original-Abtastratenfilter

Da wir versuchen, einen so kleinen Prozentsatz der Bandbreite zu filtern, hat der Filter, wie erwähnt, keine scharfe Grenze. In diesem Fall, Tiefpassfilter, können wir die Bandbreite reduzieren, um einen besser aussehenden Filter zu erhalten. Mit der Resample-Funktion python / scipy.signal kann die Bandbreite reduziert werden.

Beachten Sie, dass die Resample-Funktion eine Filterung durchführt, um Aliasing zu verhindern. Das Vorfiltern kann auch durchgeführt werden (um das Aliasing zu reduzieren). In diesem Fall können Sie das Resampling einfach um 100 wiederholen und fertig , aber die Frage zum Erstellen von Filtern wird gestellt. In diesem Beispiel werden wir den Wert um 25 verringern und einen neuen Filter erstellen

R = 25;            # how much to down sample by
Fsr = Fs/25.       # down-sampled sample rate
xs = signal.resample(x, len(x)/25.)

Wenn wir die Entwurfsparameter für den FIR-Filter aktualisieren, lautet die neue Antwort.

# Down sampled version, create new filter and plot spectrum
R = 25.             # how much to down sample by
Fsr = Fs/R          # down-sampled sample rate
Fstop = 11.1e6      # modified stopband
Wp = Fpass/(Fsr)    # pass normalized frequency
Ws = Fstop/(Fsr)    # stop normalized frequency
taps = 256
br = ffd.remez(taps, [0, Wp, Ws, .5], [1,0], maxiter=10000) 

Downsampled Filter Response

Der Filter, der mit den heruntergerechneten Daten arbeitet, reagiert besser. Ein weiterer Vorteil der Verwendung eines FIR-Filters besteht darin, dass Sie eine lineare Phasenantwort haben.

Christopher Felton
quelle
1
Vielen Dank. Wie erstellt man eine Grafik des Signalspektrums?
Alex
Vielen Dank für eine hervorragende Antwort! Ich frage mich, ob Sie möglicherweise erklären könnten, wie ein FIR-Filter auf der Grundlage der mit Remez berechneten Koeffizienten angewendet wird. Ich habe Probleme zu verstehen, was filtfiltfür den aParameter will .
ali_m
Sobald Sie die Koeffizienten eines Filterentwurfs haben ( b für FIR b und a für IIR), können Sie verschiedene Funktionen verwenden, um die Filterung durchzuführen: lfilter , convolve , filtfilt . Normalerweise funktionieren alle diese Funktionen ähnlich: y = Filter (b, a, x) Wenn Sie ein FIR-Filter haben, setzen Sie einfach a = 1 , x ist das Eingangssignal, b sind die FIR-Koeffizienten. Dieser Beitrag könnte auch helfen.
Christopher Felton
5

Funktioniert das?

from __future__ import division
from scipy.signal import butter, lfilter

fs = 1E9 # 1 ns -> 1 GHz
cutoff = 10E6 # 10 MHz
B, A = butter(1, cutoff / (fs / 2), btype='low') # 1st order Butterworth low-pass
filtered_signal = lfilter(B, A, signal, axis=0)

Sie haben Recht, die Dokumentation ist jedoch nicht sehr vollständig. Es sieht aus wie butterein Wrapper für iirfilter, der besser dokumentiert ist :

N: int Die Reihenfolge des Filters. Wn: array_like Eine skalare oder Länge-2-Sequenz, die die kritischen Frequenzen angibt.

Das meiste davon ist jedoch aus Matlab geklont, sodass Sie sich auch die Dokumentation ansehen können :

Die normalisierte Grenzfrequenz Wn muss eine Zahl zwischen 0 und 1 sein, wobei 1 der Nyquist-Frequenz entspricht (π Radiant pro Abtastung).

Aktualisieren:

Ich habe Dokumentation für diese Funktionen hinzugefügt . :) Github macht es einfach.

Endolith
quelle
1

Sie sind sich nicht sicher, was Ihre Anwendung ist, aber Sie können Gnuradio ausprobieren: http://gnuradio.org/doc/doxygen/classgr__firdes.html

Die Signalverarbeitungsblöcke sind in C ++ geschrieben (obwohl die Gnuradio-Flussdiagramme in Python sind), aber Sie sagten, hohe Leistung ist wichtig.

Wrapperapps
quelle
1

Ich habe gute Ergebnisse mit diesem FIR-Filter. Beachten Sie, dass der Filter zweimal vorwärts und rückwärts angewendet wird, um den Signalversatz auszugleichen ( filtfiltFunktion hat nicht funktioniert, weiß nicht warum):

def firfilt(interval, freq, sampling_rate):
    nfreq = freq/(0.5*sampling_rate)
    taps =  sampling_rate + 1
    a = 1
    b = scipy.signal.firwin(taps, cutoff=nfreq)
    firstpass = scipy.signal.lfilter(b, a, interval)
    secondpass = scipy.signal.lfilter(b, a, firstpass[::-1])[::-1]
    return secondpass

Eine großartige Ressource zum Filtern von Design und Verwendung, von wo ich diesen Code genommen habe und von wo aus Beispiele für Bandpass- und Hochpassfilter entnommen werden können, ist DIES .

Heltonbiker
quelle
Ich glaube nicht, dass es einen großen Vorteil gibt, einen FIR-Filter vorwärts und rückwärts zu filtern. Ein IIR-Filter kann von Vorwärts- / Rückwärtsfilterung (Filtfilt) profitieren, da Sie durch Rückwärtsfilterung die lineare Phase eines nichtlinearen Phasenfilters erhalten können.
Christopher Felton
2
@ChristopherFelton Ich kehre gerade um, um ein elektromyographisches RAW-Signal mit der geglätteten Version von sich selbst zu synchronisieren. Ich weiß, ich könnte das Signal einfach verschieben, aber zweimaliges Filtern macht weniger Probleme. Es ist bemerkenswert, dass der zweite Durchgang den bereits gefilterten ersten Durchgang fast nicht ändert ... Vielen Dank für die Notiz!
Heltonbiker
Ach ja So entfernen Sie die Verzögerung (Gruppenverzögerung), guter Punkt.
Christopher Felton
1

Ich habe keine kommentarrechte ...

@endolith: Ich verwende dasselbe wie Sie, außer dass ich scipy.signal.filtfilt (B, A, x) verwende, wobei x der zu filternde Eingabevektor ist - z. B. numpy.random.normal (size = (N)) . filtfilt leitet das Signal vorwärts und rückwärts weiter. Der Vollständigkeit halber (die meisten sind mit @endolith identisch):

import numpy as np
import scipy.signal as sps

input = np.random.normal(size=(N)) # Random signal as example
bz, az = sps.butter(FiltOrder, Bandwidth/(SamplingFreq/2)) # Gives you lowpass Butterworth as default
output = sps.filtfilt(bz, az, input) # Makes forward/reverse filtering (linear phase filter)

filtfilt, wie es auch von @heltonbiker vorgeschlagen wurde, erfordert, glaube ich, Anordnungen von Koeffizienten. Für den Fall, dass Sie eine Bandpassfilterung im komplexen Basisband durchführen müssen, ist eine aufwendigere Konfiguration erforderlich, die hier jedoch kein Problem darstellt.

Lars1
quelle