Як реалізувати смуговий фільтр Баттерворта за допомогою Scipy.signal.butter


84

ОНОВЛЕННЯ:

Я знайшов рецепт Scipy, заснований на цьому питанні! Отже, для всіх, хто цікавиться, перейдіть прямо до: Зміст »Обробка сигналів» Butterworth Bandpass


Мені важко домогтися того, що спочатку здавалося простим завданням реалізації смугового фільтра Баттерворта для одновимірного масиву numpy (часовий ряд).

Параметри, які я повинен включити, - це 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

введіть тут опис зображення

Документи та приклади заплутані та незрозумілі, але я хотів би застосувати форму, представлену в рекомендації, позначену як "для пропуску". Знак питань у коментарях показує, де я просто скопіював якийсь приклад, не розуміючи, що відбувається.

Я не електричний інженер і не вчений, я просто дизайнер медичного обладнання, якому потрібно виконати досить просту смугову фільтрацію сигналів ЕМГ.


Я пробував щось на dsp.stackexchange, але вони занадто сильно (більше, ніж я можу впоратися) зосереджуються на концептуальних питаннях інженерії, і не так багато на використанні функцій scipy.
heltonbiker

Відповіді:


119

Ви можете пропустити використання buttord, а замість цього просто вибрати замовлення для фільтра та перевірити, чи відповідає він вашим критеріям фільтрації. Щоб сформувати коефіцієнти фільтра для смугового фільтра, вкажіть масло () порядок фільтра, частоти відсікання Wn=[low, high](виражені як частка частоти Найквіста, яка дорівнює половині частоти дискретизації) і тип смуги btype="band".

Ось сценарій, який визначає пару зручних функцій для роботи з смуговим фільтром Баттерворта. Коли він запускається як сценарій, він робить дві схеми. Один показує частотну характеристику при декількох замовленнях фільтрів для однакової частоти дискретизації та частот відсічення. Інший графік демонструє вплив фільтра (із замовленням = 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_ziта ziаргумент для 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]). (Але це не може мати значення для смугового або високочастотного фільтра.)
Уоррен Векессер,

1
Я помітив, що є вихід фази на 180 градусів на виході scipy.signal.lfiter()wrt до вихідного сигналу та на signal.filtfilt()виході, чому це? Чи слід використовувати filtfilt()замість цього, якщо для мене важливий час?
Джейсон,

1
Це фазова затримка фільтра на цій частоті. Затримка фази синусоїди через фільтр Баттерворта нелінійно залежить від частоти. Для нульової фазової затримки, так, ви можете використовувати filtfilt(). Моя відповідь тут включає приклад використання, filtfilt()щоб уникнути затримки, викликаної фільтром.
Warren Weckesser

1
Гей, Джейсоне , я рекомендую задавати питання щодо теорії обробки сигналів на dsp.stackexchange.com . Якщо у вас є запитання щодо написаного вами коду, який не працює належним чином, ви можете почати нове запитання тут, на stackoverflow.
Warren Weckesser

41

Метод розробки фільтра у прийнятій відповіді правильний, але він має недолік. Смугові фільтри SciPy, розроблені з b, a, нестабільні і можуть спричинити помилкові фільтри при вищому порядку замовлення .

Натомість використовуйте 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, тому що зараз у багатьох випадках це кращий спосіб. Як і в коментарях до прийнятої відповіді, також можна усунути затримку фази за допомогою фільтрації вперед-назад. Просто замініть sosfiltна sosfiltfilt.
Mike

@Mike та user13107 Чи впливає одна і та ж помилка на високочастотні та низькочастотні фільтри Баттерворта? І рішення однакове?
dewarrn1

3
@ dewarrn1 Насправді невірно називати це "помилкою"; алгоритм правильно реалізований, але за своєю суттю нестабільний, тому це просто поганий вибір алгоритму. Але так, це впливає на будь-який фільтр вищого порядку - не лише на високочастотні та низькочастотні та не лише на фільтри Баттерворта, а й на інші, такі як Чебишев тощо. У будь-якому випадку, загалом, найкраще просто завжди вибирати sosвихід, оскільки це завжди дозволить уникнути нестабільності. І якщо вам не потрібна обробка в режимі реального часу, ви повинні просто завжди користуватися sosfiltfilt.
Mike

Вибачте, я давно не помітив цієї відповіді! @ user13107, так, представлення функції передачі (або 'ba') лінійного фільтра має серйозні числові проблеми, коли порядок фільтра великий. Навіть фільтри відносно низького порядку можуть мати проблеми, коли бажана пропускна здатність мала в порівнянні з частотою дискретизації. Моя оригінальна відповідь була написана до того, як представлення SOS було додано до SciPy, і до того, як fsаргумент був доданий до багатьох функцій у scipy.signal. Відповідь давно назріла для оновлення.
Warren Weckesser

будь-яка допомога для цього? stackoverflow.com/q/60866193/5025009
seralouk

4

Для смугового фільтра ws - це кортеж, що містить нижню та верхню кутові частоти. Вони представляють цифрову частоту, де відгук фільтра на 3 дБ менше, ніж смуга пропускання.

wp - це кортеж, що містить цифрові частоти смуги зупинки. Вони представляють місце, де починається максимальне загасання.

gpass - це максимальне затухання в смузі пропускання в дБ, тоді як gstop - зменшення напруги в смугах зупинки.

Скажімо, наприклад, ви хотіли розробити фільтр для частоти дискретизації 8000 семплів / сек з кутовими частотами 300 та 3100 Гц. Частота Найквіста - це частота дискретизації, поділена на два, або в цьому прикладі 4000 Гц. Еквівалентна цифрова частота - 1,0. Тоді дві кутові частоти становлять 300/4000 та 3100/4000.

Тепер, скажімо, ви хотіли, щоб смуги зупинок падали на 30 дБ +/- 100 Гц від кутових частот. Таким чином, ваші зупинки починаються з 200 та 3200 Гц, в результаті чого цифрові частоти становлять 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

(також, хоча мій wsі wpмає два елементи кожен, фільтр виконує лише низький або високий прохід (за допомогою btypeаргументу), але не смуговий прохід)
heltonbiker

1
Згідно з документацією на docs.scipy.org/doc/scipy/reference/generated/… , buttord розробляє низькі, високі та смугові фільтри. Що стосується gpass, я думаю, buttord не дозволяє ослаблення 0 дБ в смузі пропускання. Тоді встановіть для нього ненульове значення.
sizzzzlerz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.