Scipy.signal.butter ile bant geçiren Butterworth filtresi nasıl uygulanır


84

GÜNCELLEME:

Bu soruya göre bir Scipy Tarifi buldum! Bu nedenle, ilgilenen herkes için doğrudan şu adrese gidin: İçindekiler »Sinyal işleme» Butterworth Bandpass


1-D numpy array (zaman serisi) için Butterworth bant geçiren filtre uygulamak gibi başlangıçta basit bir görev gibi görünen şeyi başarmakta zorlanıyorum.

Dahil etmem gereken parametreler, sample_rate, HERTZ İÇİNDEKİ kesme frekansları ve muhtemelen sıradır (zayıflama, doğal frekans vb. Gibi diğer parametreler benim için daha belirsizdir, bu nedenle herhangi bir "varsayılan" değer olur).

Şu anda sahip olduğum şey, yüksek geçiş filtresi olarak çalışıyor gibi görünüyor, ancak doğru yapıp yapmadığımdan emin değilim:

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

görüntü açıklamasını buraya girin

Dokümanlar ve örnekler kafa karıştırıcı ve anlaşılmaz, ancak "bant geçişi için" olarak işaretlenen açıklamada sunulan formu uygulamak istiyorum. Yorumlardaki soru işaretleri, neler olduğunu anlamadan bir örneği kopyalayıp yapıştırdığımı gösteriyor.

Elektrik mühendisliği veya bilim adamıyım, sadece EMG sinyalleri üzerinde oldukça basit bir bant geçiren filtreleme gerçekleştirmesi gereken bir tıbbi ekipman tasarımcısı değilim.


Dsp.stackexchange'de bir şey denedim, ancak kavramsal mühendislik konularına çok fazla (halledebileceğimden daha fazla) odaklanıyorlar ve scipy işlevlerini kullanmaya pek değil.
heltonbiker

Yanıtlar:


119

Buttord kullanımını atlayabilir ve bunun yerine filtre için bir sipariş seçebilir ve filtreleme kriterinizi karşılayıp karşılamadığına bakabilirsiniz. Bir bant geçiren filtre için filtre katsayılarını oluşturmak için, butter () filtre sırasını, kesme frekanslarını Wn=[low, high](örnekleme frekansının yarısı olan Nyquist frekansının fraksiyonu olarak ifade edilir) ve bant türünü verin btype="band".

İşte Butterworth bant geçiren filtreyle çalışmak için birkaç kullanışlılık işlevini tanımlayan bir komut dosyası. Bir betik olarak çalıştırıldığında, iki çizim yapar. Birincisi, aynı örnekleme hızı ve kesme frekansları için birkaç filtre sırasındaki frekans tepkisini gösterir. Diğer grafik, filtrenin (sıra = 6 ile) örnek bir zaman serisi üzerindeki etkisini gösterir.

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

İşte bu komut dosyası tarafından oluşturulan grafikler:

Birkaç filtre siparişi için frekans yanıtı

görüntü açıklamasını buraya girin


1
Filtrelenmiş çıktının neden her zaman sıfır değerinden başladığını biliyor musunuz? Gerçek giriş değeriyle eşleştirmek mümkün mü x[0]? Cheby1 düşük geçiş filtresi ile benzer şeyler denedim ve aynı sorunu yaşadım.
LWZ

2
@LWZ: işlevi scipy.signal.lfilter_zive ziargümanını kullanın lfilter. Ayrıntılar için docstring'e bakın lfilter_zi. TL; DR? Sadece değiştirmek y = lfilter(b, a, data)için zi = lfilter_zi(b, a); y, zo = lfilter(b, a, data, zi=zi*data[0]). (Ancak bu, bant geçişi veya yüksek geçiş filtresi ile bir fark yaratmayabilir.)
Warren Weckesser

1
scipy.signal.lfiter()Wrt'nin çıkışında orijinal sinyale ve çıkışa 180 derecelik faz kayması olduğunu fark ettim signal.filtfilt(), bu neden? filtfilt()Zamanlama benim için önemliyse bunun yerine kullanmalı mıyım ?
Jason

1
Bu, filtrenin o frekanstaki faz gecikmesidir. Butterworth filtresinden geçen bir sinüzoidin faz gecikmesi, doğrusal olmayan bir şekilde frekansa bağlıdır. Sıfır faz gecikmesi için evet kullanabilirsiniz filtfilt(). Buradaki cevabım filtfilt(), filtrenin neden olduğu bir gecikmeyi önlemek için kullanmanın bir örneğini içerir .
Warren Weckesser

1
Hey Jason, dsp.stackexchange.com adresinde sinyal işleme teorisi hakkında sorular sormanızı tavsiye ederim . Beklendiği gibi çalışmayan yazdığınız bir kod hakkında sorunuz varsa, burada stackoverflow'da yeni bir soru başlatabilirsiniz.
Warren Weckesser

41

Kabul edilen cevaptaki filtre tasarım yöntemi doğrudur, ancak bir kusuru vardır. B tasarlanmış scipy bant geçiş filtreleri, bir olan stabil ve sonuçlanabilir hatalı filtreler de daha yüksek bir filtre siparişleri .

Bunun yerine, filtre tasarımının sos (ikinci dereceden bölümler) çıktısını kullanın.

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

Ayrıca, değiştirerek frekans yanıtını çizebilirsiniz.

b, a = butter_bandpass(lowcut, highcut, fs, order=order)
w, h = freqz(b, a, worN=2000)

-e

sos = butter_bandpass(lowcut, highcut, fs, order=order)
w, h = sosfreqz(sos, worN=2000)

+1 çünkü artık birçok durumda gitmenin daha iyi yolu bu. Kabul edilen cevap hakkındaki yorumlarda olduğu gibi, ileri-geri filtreleme kullanarak faz gecikmesini ortadan kaldırmak da mümkündür. Sadece yerini sosfiltile sosfiltfilt.
Mike

@Mike ve user13107 Aynı hata yüksek geçişli ve düşük geçişli Butterworth filtrelerini de etkiliyor mu? Çözüm aynı mı?
dewarrn1

3
@ dewarrn1 Aslında "bug" olarak adlandırmak doğru değil; algoritma doğru bir şekilde uygulandı, ancak doğası gereği kararsız olduğundan, sadece kötü bir algoritma seçimi. Ancak evet, herhangi bir filtreyi daha yüksek sırada etkiler - sadece yüksek veya düşük geçişi değil, sadece Butterworth filtrelerini değil, aynı zamanda Chebyshev gibi diğerlerini de etkiler . Her neyse, genel olarak, her zaman sosçıktıyı seçmek en iyisidir , çünkü bu her zaman istikrarsızlığı önleyecektir. Ve gerçek zamanlı işlemeye ihtiyacınız olmadıkça, her zaman kullanmalısınız sosfiltfilt.
Mike

Üzgünüm, bu cevabı uzun zaman önce fark etmemiştim! @ user13107, evet, doğrusal bir filtrenin transfer fonksiyonu (veya 'ba') gösterimi, filtre sırası büyük olduğunda bazı ciddi sayısal sorunlar içerir. Nispeten düşük sıralı filtreler bile, istenen bant genişliği örnekleme frekansına kıyasla küçük olduğunda sorun yaşayabilir. Orijinal cevabım, SOS gösterimi SciPy'e eklenmeden önce ve fsargüman scipy.signal. Cevap bir güncelleme için çok gecikti.
Warren Weckesser

bunun için herhangi bir yardım? stackoverflow.com/q/60866193/5025009
seralouk

4

Bir bant geçiren filtre için ws, alt ve üst köşe frekanslarını içeren bir demettir. Bunlar, filtre yanıtının geçiş bandından 3 dB daha az olduğu dijital frekansı temsil eder.

wp, durdurma bandı dijital frekanslarını içeren bir demettir. Maksimum zayıflamanın başladığı yeri temsil ederler.

gpass, dB cinsinden geçiş bandındaki maksimum zayıflatmadır, gstop ise durdurma bantlarındaki zayıflamadır.

Örneğin, köşe frekansları 300 ve 3100 Hz olan 8000 örnek / saniyelik bir örnekleme hızı için bir filtre tasarlamak istediniz. Nyquist frekansı, ikiye bölünen örnekleme hızıdır veya bu örnekte 4000 Hz'dir. Eşdeğer dijital frekans 1.0'dır. İki köşe frekansı 300/4000 ve 3100 / 4000'dir.

Şimdi, durdurma bantlarının köşe frekanslarından 30 dB +/- 100 Hz aşağı olmasını istediğinizi varsayalım. Böylece, durdurma bantlarınız 200 ve 3200 Hz'de başlayacak ve 200/4000 ve 3200/4000 dijital frekansları ile sonuçlanacaktır.

Filtrenizi oluşturmak için şu şekilde buttord çağırırsınız

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)

Ortaya çıkan filtrenin uzunluğu, durdurma bantlarının derinliğine ve köşe frekansı ile durdurma bandı frekansı arasındaki fark tarafından belirlenen yanıt eğrisinin dikliğine bağlı olacaktır.


Uygulamaya çalıştım ama bir şeyler hala eksik. Bir şey, gpass=0.0sıfır hata ile bölme yükseltir, bu yüzden onu 0.1 olarak değiştirdim ve hata durdu. Bunun yanı sıra, dokümanlar butterşöyle der: Passband and stopband edge frequencies, normalized from 0 to 1 (1 corresponds to pi radians / sample).Cevabınızın hesaplamaları doğru yaptığından şüpheliyim, bu yüzden hala bunun üzerinde çalışıyorum ve yakında bazı geri bildirimler vereceğim.
heltonbiker

(ayrıca, my wsve wpher biri iki öğeye sahip olmasına rağmen , filtre yalnızca düşük veya yüksek geçiş ( btypebağımsız değişken aracılığıyla ) gerçekleştirir, ancak bant geçişi gerçekleştirmez)
heltonbiker

1
Docs.scipy.org/doc/scipy/reference/generated/… adresindeki belgelere göre , buttord düşük, yüksek ve bant geçiş filtreleri tasarlar. Gpass'a kadar, buttord geçiş bandında 0 dB zayıflamaya izin vermiyor sanırım. O zaman sıfır olmayan bir değere ayarlayın.
sizzzzlerz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.