web-dev-qa-db-fra.com

Comment implémenter un filtre Butterworth passe-bande avec Scipy.signal.butter

MISE À JOUR:

J'ai trouvé une recette Scipy basée sur cette question! Alors, si vous êtes intéressé, allez directement à: Contenu "Traitement du signal" Butterworth Bandpass


J'ai du mal à réaliser ce qui semblait initialement une tâche simple de mise en œuvre d'un filtre passe-bande de Butterworth pour un tableau numpy 1-D (série chronologique).

Les paramètres que je dois inclure sont le taux d'échantillonnage, les fréquences de coupure IN HERTZ et éventuellement leur ordre (d'autres paramètres, tels que l'atténuation, la fréquence propre, etc., sont plus obscurs pour moi, donc toute valeur "par défaut" ferait l'affaire).

Ce que j'ai maintenant, c'est ceci, qui semble fonctionner comme un filtre passe-haut, mais je ne suis pas sûr si je le fais correctement:

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

enter image description here

La documentation et les exemples sont confus et obscurs, mais j'aimerais implémenter le formulaire présenté dans la recommandation et portant la mention "for bandpass". Les points d'interrogation dans les commentaires montrent où je viens de copier-coller un exemple sans comprendre ce qui se passe.

Je ne suis ni un ingénieur électricien ni un scientifique, mais un concepteur d’équipements médicaux qui doit effectuer un filtrage passe-bande plutôt simple sur les signaux EMG.

68
heltonbiker

Vous pouvez ignorer l'utilisation de buttord et choisir un ordre pour le filtre et voir s'il répond à vos critères de filtrage. Pour générer les coefficients de filtre pour un filtre passe-bande, donnez à butter () l'ordre du filtre, les fréquences de coupure Wn=[low, high] (exprimé en fraction de la fréquence de Nyquist, qui correspond à la moitié de la fréquence d’échantillonnage) et du type de bande btype="band".

Voici un script qui définit deux fonctions pratiques pour l’utilisation d’un filtre passe-bande Butterworth. Lorsqu'il est exécuté en tant que script, il crée deux tracés. L'un montre la réponse en fréquence pour plusieurs ordres de filtrage pour le même taux d'échantillonnage et les mêmes fréquences de coupure. L'autre graphique montre l'effet du filtre (avec l'ordre = 6) sur une série chronologique.

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 __== "__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()

Voici les tracés générés par ce script:

Frequency response for several filter orders

enter image description here

96
Warren Weckesser

La méthode de conception du filtre dans réponse acceptée est correcte, mais elle présente un défaut. Les filtres passe-bande SciPy conçus avec b, a sont instables et peuvent entraîner filtres erronés à ordres de filtrage supérieurs.

Utilisez plutôt la sortie sos (sections de second ordre) de la conception du filtre.

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

En outre, vous pouvez tracer la réponse en fréquence en modifiant

b, a = butter_bandpass(lowcut, highcut, fs, order=order)
w, h = freqz(b, a, worN=2000)

à

sos = butter_bandpass(lowcut, highcut, fs, order=order)
w, h = sosfreqz(sos, worN=2000)
20
user13107

Pour un filtre passe-bande, ws est un tuple contenant les fréquences des coins inférieur et supérieur. Ils représentent la fréquence numérique lorsque la réponse du filtre est inférieure de 3 dB à la bande passante.

wp est un tuple contenant les fréquences numériques de la bande d'arrêt. Ils représentent l'emplacement où commence l'atténuation maximale.

gpass est l'atténuation maximale dans la bande passante en dB, tandis que gstop est l'atténuation dans les bandes d'arrêt.

Supposons, par exemple, que vous vouliez concevoir un filtre pour une fréquence d'échantillonnage de 8 000 échantillons/seconde avec des fréquences de coupure de 300 et 3 100 Hz. La fréquence de Nyquist est la fréquence d'échantillonnage divisée par deux, ou dans cet exemple, 4000 Hz. La fréquence numérique équivalente est 1.0. Les deux fréquences de coupure sont alors 300/4000 et 3100/4000.

Supposons maintenant que vous vouliez que les bandes d’arrêt soient abaissées de 30 dB +/- 100 Hz par rapport aux fréquences de coupure. Ainsi, vos bandes d'arrêt commenceraient à 200 et 3200 Hz, ce qui donnerait les fréquences numériques de 200/4000 et 3200/4000.

Pour créer votre filtre, appelez buttord en tant que

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)

La longueur du filtre résultant dépendra de la profondeur des bandes d'arrêt et de la raideur de la courbe de réponse qui est déterminée par la différence entre la fréquence de coupure et la fréquence de la bande d'arrêt.

4
sizzzzlerz