Scipy.signal.butterを使用してバンドパスバターワースフィルターを実装する方法


83

更新:

この質問に基づいたScipyレシピを見つけました!したがって、興味のある方は、次のサイトに直接アクセスしてください。目次»信号処理»バターワースバンドパス


1次元のnumpy配列(時系列)にバターワースバンドパスフィルターを実装するという、最初は単純なタスクのように見えたものを実現するのに苦労しています。

私が含めなければならないパラメーターは、sample_rate、ヘルツ単位のカットオフ周波数、そして場合によっては次数です(減衰、固有振動数などの他のパラメーターは私にはわかりにくいので、「デフォルト」値ならどれでもかまいません)。

私が今持っているのはこれです。これはハイパスフィルターとして機能しているようですが、正しく実行しているかどうかはわかりません。

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

回答:


117

buttordの使用をスキップして、代わりにフィルターの順序を選択し、それがフィルター基準を満たしているかどうかを確認することができます。バンドパスフィルターのフィルター係数を生成するには、butter()にフィルター次数、カットオフ周波数Wn=[low, high](サンプリング周波数の半分であるナイキスト周波数の割合として表される)、およびバンドタイプを指定しbtype="band"ます。

これは、バターワースバンドパスフィルターを操作するためのいくつかの便利な関数を定義するスクリプトです。スクリプトとして実行すると、2つのプロットが作成されます。1つは、同じサンプリングレートとカットオフ周波数に対する複数のフィルター次数での周波数応答を示しています。もう1つのプロットは、サンプル時系列に対するフィルター(次数= 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 2013

2
@LWZ:関数scipy.signal.lfilter_ziとのzi引数を使用しますlfilter。詳細については、のdocstringを参照してください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の出力から元の信号と出力に180度の位相シフトがあることに気づきましたが、それはsignal.filtfilt()なぜですか?filtfilt()タイミングが重要な場合は、代わりに使用する必要がありますか?
ジェイソン

1
これが、その周波数でのフィルターの位相遅延です。バターワースフィルターを通過する正弦波の位相遅延は、周波数に非線形に依存します。ゼロ位相遅延の場合、はい、を使用できますfiltfilt()ここでの私の答えfiltfilt()、フィルターによって引き起こされるラグを回避するために使用する例が含まれています。
ウォーレンウェッケサー2017

1
ちょっとジェイソン、私はdsp.stackexchange.comで信号処理理論について質問することをお勧めします。作成したコードで期待どおりに機能しない質問がある場合は、stackoverflowで新しい質問を開始できます。
ウォーレンウェッケサー2017

37

受け入れられた回答のフィルター設計方法は正しいですが、欠陥があります。b、aで設計されたSciPyバンドパスフィルターは不安定であり、より高いフィルター次数誤ったフィルターが発生する可能性があります。

代わりに、フィルター設計のsos(2次セクション)出力を使用します。

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とuser13107同じバグがハイパスとローパスのバターワースフィルターにも影響しますか?そして、解決策は同じですか?
dewarrn1

3
@ dewarrn1これを「バグ」と呼ぶのは実際には正しくありません。アルゴリズムは正しく実装されていますが、本質的に不安定であるため、アルゴリズムの選択としては不適切です。しかし、はい、それは高次のすべてのフィルターに影響します—ハイパスまたはローパスだけでなく、バ​​ターワースフィルターだけでなく、チェビシェフなどの他のフィルターにも影響します。とにかく、一般的には、常にsos出力を選択するのが最善です。これにより、常に不安定さが回避されるためです。また、リアルタイム処理が必要な場合を除いて、常にを使用する必要がありますsosfiltfilt
マイク

申し訳ありませんが、私はずっと前にこの答えに気づいていませんでした!@ user13107、はい、線形フィルターの伝達関数(または 'ba')表現には、フィルターの次数が大きい場合にいくつかの重大な数値問題があります。比較的低次のフィルターでも、必要な帯域幅がサンプリング周波数と比較して小さい場合、問題が発生する可能性があります。私の元の答えは、SOS表現がSciPyに追加される前、およびfs引数がの多くの関数に追加される前に書かれましたscipy.signal。答えは更新のために長い間遅れています。
ウォーレンウェッケサー

これについて何か助けはありますか?stackoverflow.com/q/60866193/5025009
seralouk

4

バンドパスフィルターの場合、wsは下限と上限のコーナー周波数を含むタプルです。これらは、フィルター応答が通過帯域より3dB小さいデジタル周波数を表します。

wpは、阻止帯域のデジタル周波数を含むタプルです。これらは、最大減衰が始まる場所を表します。

gpassは通過帯域の最大減衰(dB)であり、gstopは阻止帯域の減衰です。

たとえば、コーナー周波数が300Hzと3100Hzの8000サンプル/秒のサンプリングレートのフィルターを設計するとします。ナイキスト周波数は、サンプルレートを2で割った値、この例では4000Hzです。同等のデジタル周波数は1.0です。その場合、2つのコーナー周波数は300/4000と3100/4000になります。

ここで、阻止帯域をコーナー周波数から30 dB +/- 100Hz下げたいとしましょう。したがって、阻止帯域は200Hzと3200Hzで始まり、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)

結果として得られるフィルターの長さは、阻止帯域の深さと、コーナー周波数と阻止帯域周波数の差によって決定される応答曲線の急峻さに依存します。


実装しようとしましたが、まだ何かが足りません。1つは、gpass=0.0ゼロ除算エラーが発生するため、0.1に変更して、エラーを停止しました。それに加えて、次のようなドキュメントがありbutterます。Passband and stopband edge frequencies, normalized from 0 to 1 (1 corresponds to pi radians / sample).あなたの答えが正しく計算されたかどうかは疑わしいので、私はまだそれに取り組んでおり、すぐにフィードバックを提供します。
heltonbiker 2012

(また、mywsとにwpはそれぞれ2つの要素がありますが、フィルターは(btype引数を介して)ローパスまたはハイパスのみを実行し、バンドパスは実行しません)
heltonbiker 2012

1
docs.scipy.org/doc/scipy/reference/generated/…のドキュメントによると、バタードは低、高、およびバンドパスフィルターを設計します。gpassに関しては、buttordは通過帯域で0dBの減衰を許可しないと思います。次に、ゼロ以外の値に設定します。
sizzzzlerz 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.