在SciPy中创建低通滤波器 - 理解方法和单位

107

我正在尝试使用Python过滤嘈杂的心率信号。因为心率不应超过每分钟220次,所以我想过滤掉所有高于220 bpm的噪音。我将每分钟220次转换为3.66666666赫兹,然后将该赫兹转换为弧度/秒,得到23.0383461弧度/秒。

采集数据的芯片的采样频率为30Hz,因此我将其转换为弧度/秒,得到188.495559弧度/秒。

在网上查找了一些资料后,我找到了一些用于带通滤波器的函数,我想将其制作成低通滤波器。这是带通代码的链接,所以我将其转换为:

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)

我对此感到非常困惑,因为我相当确定butter函数接受截止频率和采样频率(以弧度/秒为单位),但我似乎得到了奇怪的输出。它实际上是以赫兹为单位吗?

其次,这两行代码的目的是什么:

    nyq = 0.5 * fs
    normalCutoff = cutOff / nyq

我知道这与归一化有关,但我以为奈奎斯特频率是采样频率的两倍,而不是一半。为什么要使用奈奎斯特频率作为归一化器呢?

有人能解释一下如何使用这些函数创建滤波器吗?

我使用以下方法绘制了滤波器:

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()

得到了这个,显然没有在23 rad/s处截断:

result

1个回答

198

几点建议:

  • 奈奎斯特频率是采样率的一半。
  • 你正在处理定期采样的数据,因此需要数字滤波器而不是模拟滤波器。这意味着在调用butter函数时,不应使用analog=True参数,并且应使用scipy.signal.freqz生成频率响应(而不是freqs函数)。
  • 这些实用程序函数的一个目标是让你可以将所有频率都以Hz表示。你不需要转换为rad/sec。只要使用一致的单位表示频率,SciPy函数的fs参数就会自动进行缩放。

以下是我修改后的脚本以及它生成的图形。

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


def butter_lowpass(cutoff, fs, order=5):
    return butter(order, cutoff, fs=fs, btype='low', analog=False)

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, fs=fs, worN=8000)
plt.subplot(2, 1, 1)
plt.plot(w, 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


5
确定,考虑“你需要以两倍的带宽采样”的措辞;这意味着采样率必须是信号带宽的两倍。 - Warren Weckesser
4
换句话说,你正在以30赫兹进行采样。这意味着你的信号带宽不应超过15赫兹;15赫兹是奈奎斯特频率。 - Warren Weckesser
2
复活这个话题:我在另一个线程中看到建议使用filtfilt而不是lfilter进行前向/后向滤波。你对此有什么看法? - Bar
1
@Bar:这些评论不是解决你的问题的正确场所。你可以在StackOverflow上创建一个新的问题,但是管理员可能会关闭它,因为它不是一个编程问题。最好的地方可能是http://dsp.stackexchange.com/。 - Warren Weckesser
1
@samjewell 这是一个我似乎总是与 freqz 一起使用的随机数。我喜欢 平滑 的图形,具有足够的多余分辨率,以便我可以稍微放大一点而不必重新生成图形,8000 在大多数情况下都足够实现这一点。 - Warren Weckesser
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接