如何使用Scipy.signal.butter实现带通Butterworth滤波器


83

更新:

我发现了基于此问题的Scipy食谱!因此,对于感兴趣的任何人,请直接转到:目录»信号处理»Butterworth Bandpass


我很难实现最初看起来像是为一维numpy数组(时间序列)实现Butterworth带通滤波器的简单任务。

我必须包括的参数是sample_rate,HERTZ的截止频率以及可能的阶数(其他参数(例如衰减,固有频率等)对我来说比较晦涩,因此任何“默认”值都可以)。

我现在所拥有的就是这个,它似乎可以用作高通滤波​​器,但是我不确定自己是否做对了:

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

在此处输入图片说明

这些文档和示例令人困惑且晦涩难懂,但我想实现表述为“带通”的推荐形式。注释中的问号显示了我只是复制粘贴了一些示例而不了解所发生的情况。

我既不是电气工程也不是科学家,只是医疗设备设计人员需要对EMG信号执行一些相当简单的带通滤波。


我已经在dsp.stackexchange上尝试过一些东西,但是它们在工程学的概念问题上过于关注(超出我的能力),而不是在使用scipy函数。
heltonbiker 2012年

Answers:


117

您可以跳过使用buttord,而只是为过滤器选择一个订单,看看它是否符合您的过滤条件。要生成带通滤波器的滤波器系数,请给butter()设置滤波器阶数,截止频率Wn=[low, high](表示为奈奎斯特频率的分数,是采样频率的一半)和频带类型btype="band"

这是一个脚本,该脚本定义了用于使用Butterworth带通滤波器的几个便捷功能。当作为脚本运行时,它会绘制两个图。一个显示了对于相同采样率和截止频率在几个滤波器阶上的频率响应。另一幅图展示了滤波器(阶数为6)对采样时间序列的影响。

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

这是此脚本生成的图:

几个滤波器阶的频率响应

在此处输入图片说明


1
您知道为什么滤波后的输出总是从零开始吗?是否可以将其与实际输入值匹配x[0]?我用Cheby1低通滤波器尝试了类似的东西,但遇到了同样的问题。
LWZ

2
@LWZ:使用函数scipy.signal.lfilter_zizi参数lfilter。有关详细信息,请参见的文档字符串lfilter_zi。TL; DR?只需更改y = lfilter(b, a, data)为即可zi = lfilter_zi(b, a); y, zo = lfilter(b, a, data, zi=zi*data[0])。(但是,这可能与带通或高通滤波器没有关系。)
Warren Weckesser 2013年

1
我注意到scipy.signal.lfiter()wrt的输出与原始信号和signal.filtfilt()输出存在180度相移,这是为什么呢?filtfilt()如果时间对我很重要,我应该改用吗?
杰森

1
那就是该频率下滤波器的相位延迟。通过巴特沃思滤波器的正弦波相位延迟与频率非线性相关。对于零相位延迟,可以,您可以使用filtfilt()。我在这里的答案包括一个示例,该示例filtfilt()用于避免滤波器引起的滞后。
沃伦·韦克瑟

1
嘿杰森,我建议在dsp.stackexchange.com上询问有关信号处理理论的问题。如果您对编写的某些代码有疑问,无法正常工作,可以在stackoverflow上开始一个新问题。
沃伦·韦克瑟

37

公认答案中的滤波器设计方法是正确的,但是有缺陷。用b,a设计的SciPy带通滤波器是不稳定的,并且可能在较高的滤波器阶数下导致错误的滤波器

相反,请使用滤波器设计的sos(二阶部分)输出。

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

另外,您可以通过更改频率响应来绘制

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)

+1,因为在许多情况下,这是更好的选择。就像在对已接受答案的评论中一样,也可以使用前向后向滤波来消除相位延迟。只需替换sosfiltsosfiltfilt
迈克(Mike)

@Mike和user13107相同的错误还会影响高通和低通Butterworth滤波器吗?解决方案是否相同?
dewarrn1

3
@ dewarrn1称其为“ bug”实际上是不正确的。该算法已正确实现,但固有地不稳定,因此这是算法的错误选择。但是,是的,它会影响任何高阶滤波器-不仅会影响高通或低通,不仅会影响Butterworth滤波器,还会影响Chebyshev等滤波器。总之,总的来说,最好总是选择sos输出,因为这样可以始终避免不稳定。并且除非需要实时处理,否则应始终使用sosfiltfilt
迈克,

抱歉,很久以前我没有注意到这个答案!@ user13107,是的,当滤波器的阶数较大时,线性滤波器的传递函数(或“ ba”)表示形式存在一些严重的数值问题。当所需带宽与采样频率相比较小时,即使是相对低阶的滤波器也会出现问题。我的原始答案是在SOS表示形式添加到SciPy以及自fs变量添加到中的许多功能之前编写的scipy.signal。答案早就该更新了。
沃伦·韦克瑟


4

对于带通滤波器,ws是一个包含较低和较高转折频率的元组。这些代表滤波器响应比通带小3 dB的数字频率。

wp是一个包含阻带数字频率的元组。它们代表最大衰减开始的位置。

gpass是通带中的最大衰减,以dB为单位,而gstop是阻带中的衰减。

举例来说,您想设计一个滤波器,采样率为8000个样本/秒,转折频率为300和3100 Hz。奈奎斯特频率是采样率除以二,在本例中为4000 Hz。等效数字频率为1.0。则两个转折频率分别为300/4000和3100/4000。

现在假设您希望阻带比拐角频率下降30 dB +/- 100 Hz。因此,您的阻带将从200和3200 Hz开始,导致数字频率为200/4000和3200/4000。

要创建过滤器,您可以将buttord称为

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)

所得到的滤波器的长度将取决于阻带的深度和响应曲线的陡度,该陡度由转折频率和阻带频率之间的差确定。


我尝试实现它,但是仍然缺少一些东西。一件事是gpass=0.0提高了除以零的误差,因此我将其更改为0.1并停止了错误。除此之外,文档butter还说:Passband and stopband edge frequencies, normalized from 0 to 1 (1 corresponds to pi radians / sample).我不确定您的回答是否正确计算了,所以我仍在努力,并将很快提供一些反馈。
heltonbiker

(同样,尽管mywswpbtype
i

1
根据docs.scipy.org/doc/scipy/reference/generation/…上的文档,buttord设计了低通,高通和带通滤波器。至于gpass,我猜buttord在通带中不允许0 dB衰减。然后将其设置为一些非零值。
sizzzzlerz 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.