AKTUALISIEREN:
Ich habe ein Scipy-Rezept gefunden, das auf dieser Frage basiert! Wenn Sie interessiert sind, gehen Sie direkt zu: Inhalt »Signalverarbeitung» Butterworth Bandpass
Es fällt mir schwer, eine anfangs einfache Aufgabe der Implementierung eines Butterworth-Bandpassfilters für ein 1-D-Numpy-Array (Zeitreihen) zu erreichen.
Die Parameter, die ich einschließen muss, sind die Abtastrate, die Grenzfrequenzen IN HERTZ und möglicherweise die Reihenfolge (andere Parameter wie Dämpfung, Eigenfrequenz usw. sind für mich dunkler, daher würde jeder "Standard" -Wert ausreichen).
Was ich jetzt habe, ist das, was als Hochpassfilter zu funktionieren scheint, aber ich bin mir nicht sicher, ob ich es richtig mache:
def butter_highpass(interval, sampling_rate, cutoff, order=5):
nyq = sampling_rate * 0.5
stopfreq = float(cutoff)
cornerfreq = 0.4 * stopfreq # (?)
ws = cornerfreq/nyq
wp = stopfreq/nyq
# for bandpass:
# wp = [0.2, 0.5], ws = [0.1, 0.6]
N, wn = scipy.signal.buttord(wp, ws, 3, 16) # (?)
# for hardcoded order:
# N = order
b, a = scipy.signal.butter(N, wn, btype='high') # should 'high' be here for bandpass?
sf = scipy.signal.lfilter(b, a, interval)
return sf
Die Dokumente und Beispiele sind verwirrend und undurchsichtig, aber ich möchte das Formular implementieren, das in der Empfehlung "für Bandpass" angegeben ist. Die Fragezeichen in den Kommentaren zeigen, wo ich gerade ein Beispiel kopiert habe, ohne zu verstehen, was passiert.
Ich bin kein Elektrotechniker oder Wissenschaftler, sondern nur ein Entwickler medizinischer Geräte, der eine recht einfache Bandpassfilterung für EMG-Signale durchführen muss.
quelle
Antworten:
Sie können die Verwendung von Buttord überspringen und stattdessen einfach eine Reihenfolge für den Filter auswählen und prüfen, ob er Ihrem Filterkriterium entspricht. Um die Filterkoeffizienten für ein Bandpassfilter zu generieren, geben Sie butter () die Filterreihenfolge, die Grenzfrequenzen
Wn=[low, high]
(ausgedrückt als Bruchteil der Nyquist-Frequenz, die die Hälfte der Abtastfrequenz beträgt) und den Bandtyp anbtype="band"
.Hier ist ein Skript, das einige praktische Funktionen für die Arbeit mit einem Butterworth-Bandpassfilter definiert. Wenn es als Skript ausgeführt wird, werden zwei Diagramme erstellt. Einer zeigt den Frequenzgang bei mehreren Filterreihenfolgen bei gleicher Abtastrate und Grenzfrequenz. Das andere Diagramm zeigt die Wirkung des Filters (mit der Reihenfolge = 6) auf eine Stichprobenzeitreihe.
from scipy.signal import butter, lfilter def butter_bandpass(lowcut, highcut, fs, order=5): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq b, a = butter(order, [low, high], btype='band') return b, a def butter_bandpass_filter(data, lowcut, highcut, fs, order=5): b, a = butter_bandpass(lowcut, highcut, fs, order=order) y = lfilter(b, a, data) return y if __name__ == "__main__": import numpy as np import matplotlib.pyplot as plt from scipy.signal import freqz # Sample rate and desired cutoff frequencies (in Hz). fs = 5000.0 lowcut = 500.0 highcut = 1250.0 # Plot the frequency response for a few different orders. plt.figure(1) plt.clf() for order in [3, 6, 9]: b, a = butter_bandpass(lowcut, highcut, fs, order=order) w, h = freqz(b, a, worN=2000) plt.plot((fs * 0.5 / np.pi) * w, abs(h), label="order = %d" % order) plt.plot([0, 0.5 * fs], [np.sqrt(0.5), np.sqrt(0.5)], '--', label='sqrt(0.5)') plt.xlabel('Frequency (Hz)') plt.ylabel('Gain') plt.grid(True) plt.legend(loc='best') # Filter a noisy signal. T = 0.05 nsamples = T * fs t = np.linspace(0, T, nsamples, endpoint=False) a = 0.02 f0 = 600.0 x = 0.1 * np.sin(2 * np.pi * 1.2 * np.sqrt(t)) x += 0.01 * np.cos(2 * np.pi * 312 * t + 0.1) x += a * np.cos(2 * np.pi * f0 * t + .11) x += 0.03 * np.cos(2 * np.pi * 2000 * t) plt.figure(2) plt.clf() plt.plot(t, x, label='Noisy signal') y = butter_bandpass_filter(x, lowcut, highcut, fs, order=6) plt.plot(t, y, label='Filtered signal (%g Hz)' % f0) plt.xlabel('time (seconds)') plt.hlines([-a, a], 0, T, linestyles='--') plt.grid(True) plt.axis('tight') plt.legend(loc='upper left') plt.show()
Hier sind die Diagramme, die von diesem Skript generiert werden:
quelle
x[0]
? Ich habe ähnliche Dinge mit dem Cheby1-Tiefpassfilter ausprobiert und das gleiche Problem festgestellt.scipy.signal.lfilter_zi
und daszi
Argument zulfilter
. Einzelheiten finden Sie in der Dokumentzeichenfolge fürlfilter_zi
. TL; DR? Wechseln Sie einfachy = lfilter(b, a, data)
zuzi = lfilter_zi(b, a); y, zo = lfilter(b, a, data, zi=zi*data[0])
. (Aber dies könnte mit einem Bandpass oder Hochpassfilter keinen Unterschied machen.)scipy.signal.lfiter()
wrt zum ursprünglichen Signal und zumsignal.filtfilt()
Ausgang um 180 Grad phasenverschoben ist. Warum ist das so? Sollte ichfiltfilt()
stattdessen verwenden, wenn mir das Timing wichtig ist?filtfilt()
. Meine Antwort hier enthält ein Beispiel für die Verwendungfiltfilt()
, um eine durch den Filter verursachte Verzögerung zu vermeiden.Die Filterentwurfsmethode in der akzeptierten Antwort ist korrekt, weist jedoch einen Fehler auf. Mit b, a entworfene SciPy-Bandpassfilter sind instabil und können bei höheren Filterordnungen zu fehlerhaften Filtern führen .
Verwenden Sie stattdessen die sos-Ausgabe (Abschnitte zweiter Ordnung) des Filterdesigns.
from scipy.signal import butter, sosfilt, sosfreqz def butter_bandpass(lowcut, highcut, fs, order=5): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq sos = butter(order, [low, high], analog=False, btype='band', output='sos') return sos def butter_bandpass_filter(data, lowcut, highcut, fs, order=5): sos = butter_bandpass(lowcut, highcut, fs, order=order) y = sosfilt(sos, data) return y
Sie können den Frequenzgang auch durch Ändern darstellen
b, a = butter_bandpass(lowcut, highcut, fs, order=order) w, h = freqz(b, a, worN=2000)
zu
sos = butter_bandpass(lowcut, highcut, fs, order=order) w, h = sosfreqz(sos, worN=2000)
quelle
sosfilt
mitsosfiltfilt
.sos
Ausgabe auszuwählen , da dies immer die Instabilität vermeidet. Und wenn Sie keine Echtzeitverarbeitung benötigen, sollten Sie diese immer verwendensosfiltfilt
.fs
Argument zu vielen Funktionen in hinzugefügt wurdescipy.signal
. Die Antwort für ein Update ist längst überfällig.Für ein Bandpassfilter ist ws ein Tupel, das die Frequenzen der unteren und oberen Ecke enthält. Diese stellen die digitale Frequenz dar, bei der die Filterantwort 3 dB unter dem Durchlassbereich liegt.
wp ist ein Tupel, das die digitalen Frequenzen des Stoppbands enthält. Sie stellen den Ort dar, an dem die maximale Dämpfung beginnt.
gpass ist die maximale Dämpfung im Durchlassbereich in dB, während gstop die Aufmerksamkeit in den Stoppbändern ist.
Angenommen, Sie möchten einen Filter für eine Abtastrate von 8000 Abtastungen / s mit Eckfrequenzen von 300 und 3100 Hz entwerfen. Die Nyquist-Frequenz ist die Abtastrate geteilt durch zwei oder in diesem Beispiel 4000 Hz. Die äquivalente digitale Frequenz beträgt 1,0. Die beiden Eckfrequenzen sind dann 300/4000 und 3100/4000.
Nehmen wir nun an, Sie wollten, dass die Stoppbänder 30 dB +/- 100 Hz unter den Eckfrequenzen liegen. Somit würden Ihre Stoppbänder bei 200 und 3200 Hz beginnen, was zu den digitalen Frequenzen von 200/4000 und 3200/4000 führen würde.
Um Ihren Filter zu erstellen, würden Sie buttord als bezeichnen
fs = 8000.0 fso2 = fs/2 N,wn = scipy.signal.buttord(ws=[300/fso2,3100/fso2], wp=[200/fs02,3200/fs02], gpass=0.0, gstop=30.0)
Die Länge des resultierenden Filters hängt von der Tiefe der Stoppbänder und der Steilheit der Antwortkurve ab, die durch die Differenz zwischen der Eckfrequenz und der Stoppbandfrequenz bestimmt wird.
quelle
gpass=0.0
eine Division durch einen Fehler von Null ausgelöst wird, also habe ich sie auf 0,1 geändert und der Fehler wurde gestoppt. Abgesehen davonbutter
sagen die Dokumente zum Beispiel :Passband and stopband edge frequencies, normalized from 0 to 1 (1 corresponds to pi radians / sample).
Ich bin mir nicht sicher, ob Ihre Antwort die Berechnungen richtig gemacht hat, also arbeite ich noch daran und werde bald ein Feedback geben.ws
undwp
jeweils zwei Elemente haben, führt der Filter nur Tief- oder Hochpass (überbtype
Argument), aber keinen Bandpass durch)