web-dev-qa-db-fra.com

Création d'un filtre passe-bas dans SciPy - Comprendre les méthodes et les unités

J'essaie de filtrer un signal de fréquence cardiaque bruyant avec Python. Parce que la fréquence cardiaque ne devrait jamais être environ 220 battements par minute, je souhaite éliminer tous les bruits supérieurs à 220 bpm. J'ai converti 220/minute en 3.66666666 Hertz puis converti ce Hertz en rad/s pour obtenir 23.0383461 rad/s.

La fréquence d'échantillonnage de la puce qui prend les données est de 30Hz, donc je l'ai convertie en rad/s pour obtenir 188,495559 rad/s.

Après avoir recherché des informations en ligne, j'ai trouvé des fonctions pour un filtre passe-bande que je voulais transformer en filtre passe-bas. Voici le lien du code de bande passante , donc je l'ai converti pour être ceci:

from scipy.signal import butter, lfilter
from scipy.signal import freqs

def butter_lowpass(cutOff, fs, order=5):
    nyq = 0.5 * fs
    normalCutoff = cutOff / nyq
    b, a = butter(order, normalCutoff, btype='low', analog = True)
    return b, a

def butter_lowpass_filter(data, cutOff, fs, order=4):
    b, a = butter_lowpass(cutOff, fs, order=order)
    y = lfilter(b, a, data)
    return y

cutOff = 23.1 #cutoff frequency in rad/s
fs = 188.495559 #sampling frequency in rad/s
order = 20 #order of filter

#print sticker_data.ps1_dxdt2

y = butter_lowpass_filter(data, cutOff, fs, order)
plt.plot(y)

Cela me laisse très perplexe, car je suis presque sûr que la fonction beurre prend en compte la fréquence de coupure et d'échantillonnage en rad/s, mais il semble que j'obtienne une sortie étrange. Est-ce réellement en Hz?

Deuxièmement, quel est le but de ces deux lignes:

    nyq = 0.5 * fs
    normalCutoff = cutOff / nyq

Je sais que c'est quelque chose qui concerne la normalisation, mais je pensais que le nyquist était deux fois plus rapide que l'échantillonnage, et non une moitié. Et pourquoi utilisez-vous le nyquist comme normalisateur?

Peut-on expliquer plus comment créer des filtres avec ces fonctions?

J'ai tracé le filtre en utilisant

w, h = signal.freqs(b, a)
plt.plot(w, 20 * np.log10(abs(h)))
plt.xscale('log')
plt.title('Butterworth filter frequency response')
plt.xlabel('Frequency [radians / second]')
plt.ylabel('Amplitude [dB]')
plt.margins(0, 0.1)
plt.grid(which='both', axis='both')
plt.axvline(100, color='green') # cutoff frequency
plt.show()

et obtenu ce qui clairement ne coupe pas à 23 rad/s:

result

61
user3123955

Quelques commentaires:

  • La fréquence de Nyquist correspond à la moitié du taux d'échantillonnage.
  • Vous travaillez avec des données échantillonnées régulièrement, vous voulez donc un filtre numérique, pas un filtre analogique. Cela signifie que vous ne devez pas utiliser analog=True dans l'appel à butter, et vous devez utiliser scipy.signal.freqz (et non freqs) pour générer la réponse en fréquence.
  • L'un des objectifs de ces fonctions utilitaires courtes est de vous permettre de laisser toutes vos fréquences exprimées en Hz. Vous ne devriez pas avoir à convertir en rad/sec. Tant que vous exprimez vos fréquences avec des unités cohérentes, la mise à l'échelle dans les fonctions utilitaires se charge de la normalisation.

Voici ma version modifiée de votre script, suivie de l'intrigue qu'il génère.

import numpy as np
from scipy.signal import butter, lfilter, freqz
import matplotlib.pyplot as plt


def butter_lowpass(cutoff, fs, order=5):
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = butter(order, normal_cutoff, btype='low', analog=False)
    return b, a

def butter_lowpass_filter(data, cutoff, fs, order=5):
    b, a = butter_lowpass(cutoff, fs, order=order)
    y = lfilter(b, a, data)
    return y


# Filter requirements.
order = 6
fs = 30.0       # sample rate, Hz
cutoff = 3.667  # desired cutoff frequency of the filter, Hz

# Get the filter coefficients so we can check its frequency response.
b, a = butter_lowpass(cutoff, fs, order)

# Plot the frequency response.
w, h = freqz(b, a, worN=8000)
plt.subplot(2, 1, 1)
plt.plot(0.5*fs*w/np.pi, np.abs(h), 'b')
plt.plot(cutoff, 0.5*np.sqrt(2), 'ko')
plt.axvline(cutoff, color='k')
plt.xlim(0, 0.5*fs)
plt.title("Lowpass Filter Frequency Response")
plt.xlabel('Frequency [Hz]')
plt.grid()


# Demonstrate the use of the filter.
# First make some data to be filtered.
T = 5.0         # seconds
n = int(T * fs) # total number of samples
t = np.linspace(0, T, n, endpoint=False)
# "Noisy" data.  We want to recover the 1.2 Hz signal from this.
data = np.sin(1.2*2*np.pi*t) + 1.5*np.cos(9*2*np.pi*t) + 0.5*np.sin(12.0*2*np.pi*t)

# Filter the data, and plot both the original and filtered signals.
y = butter_lowpass_filter(data, cutoff, fs, order)

plt.subplot(2, 1, 2)
plt.plot(t, data, 'b-', label='data')
plt.plot(t, y, 'g-', linewidth=2, label='filtered data')
plt.xlabel('Time [sec]')
plt.grid()
plt.legend()

plt.subplots_adjust(hspace=0.35)
plt.show()

lowpass example

125
Warren Weckesser