在SciPy中创建低通滤波器 - 理解方法和单位
我正在用Python处理一个嘈杂的心率信号。因为心率一般不会超过每分钟220次,所以我想把超过220次的噪声过滤掉。我把220次每分钟转换成了3.66666666赫兹,然后又把赫兹转换成了弧度每秒,得到了23.0383461弧度/秒。
这个采集数据的芯片的采样频率是30赫兹,所以我把它转换成弧度每秒,得到了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弧度/秒处截止:
1 个回答
200
几点说明:
- 奈奎斯特频率是采样率的一半。
- 你正在处理的是定期采样的数据,所以你需要使用数字滤波器,而不是模拟滤波器。这意味着在调用
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()