Cách triển khai bộ lọc Butterworth băng thông với Scipy.signal.butter


83

CẬP NHẬT:

Tôi đã tìm thấy Công thức Scipy dựa trên câu hỏi này! Vì vậy, đối với bất kỳ ai quan tâm, hãy truy cập ngay: Nội dung »Xử lý tín hiệu» Butterworth Bandpass


Tôi đang gặp khó khăn để đạt được điều mà ban đầu có vẻ là một nhiệm vụ đơn giản là triển khai bộ lọc thông dải Butterworth cho mảng không có 1-D (chuỗi thời gian).

Các thông số tôi phải bao gồm là sample_rate, tần số cắt TRONG HERTZ và có thể là thứ tự (các thông số khác, như suy hao, tần số tự nhiên, v.v. đối với tôi khó hiểu hơn, vì vậy bất kỳ giá trị "mặc định" nào cũng vậy).

Những gì tôi có bây giờ là cái này, có vẻ như hoạt động như một bộ lọc thông cao nhưng tôi không có cách nào chắc chắn liệu mình có làm đúng hay không:

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

nhập mô tả hình ảnh ở đây

Các tài liệu và ví dụ khó hiểu và tối nghĩa, nhưng tôi muốn triển khai biểu mẫu được trình bày trong phần khen thưởng được đánh dấu là "cho dải thông". Các dấu chấm hỏi trong phần nhận xét cho thấy nơi tôi chỉ sao chép và dán một số ví dụ mà không hiểu chuyện gì đang xảy ra.

Tôi không phải là kỹ sư điện hay nhà khoa học, chỉ là một nhà thiết kế thiết bị y tế cần thực hiện một số lọc thông dải khá đơn giản trên các tín hiệu EMG.


Tôi đã thử một cái gì đó tại dsp.stackexchange, nhưng họ tập trung quá nhiều (nhiều hơn những gì tôi có thể xử lý) vào các vấn đề khái niệm của kỹ thuật và không quá nhiều trong việc sử dụng các hàm scipy.
heltonbiker

Câu trả lời:


117

Bạn có thể bỏ qua việc sử dụng mông và thay vào đó chỉ cần chọn thứ tự cho bộ lọc và xem liệu nó có đáp ứng tiêu chí lọc của bạn hay không. Để tạo hệ số bộ lọc cho bộ lọc thông dải, hãy cung cấp cho butter () thứ tự bộ lọc, các tần số cắt Wn=[low, high](được biểu thị bằng phần nhỏ của tần số Nyquist, bằng một nửa tần số lấy mẫu) và loại dải btype="band".

Đây là một tập lệnh xác định một vài hàm thuận tiện để làm việc với bộ lọc thông dải Butterworth. Khi chạy như một tập lệnh, nó tạo ra hai âm mưu. Một cho thấy đáp ứng tần số ở một số thứ tự bộ lọc cho cùng tốc độ lấy mẫu và tần số cắt. Biểu đồ khác thể hiện tác động của bộ lọc (với thứ tự = 6) trên một chuỗi thời gian mẫu.

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

Dưới đây là các âm mưu được tạo bởi tập lệnh này:

Phản hồi tần suất cho một số đơn đặt hàng bộ lọc

nhập mô tả hình ảnh ở đây


1
Bạn có biết tại sao đầu ra được lọc luôn bắt đầu ở giá trị 0 không? Có thể khớp nó với giá trị đầu vào thực tế x[0]không? Tôi đã thử những thứ tương tự với bộ lọc thông thấp Cheby1 và tôi gặp vấn đề tương tự.
LWZ

2
@LWZ: Sử dụng hàm scipy.signal.lfilter_ziziđối số lfilter. Để biết chi tiết, hãy xem chuỗi tài liệu cho lfilter_zi. TL; DR? Chỉ cần thay đổi y = lfilter(b, a, data)thành zi = lfilter_zi(b, a); y, zo = lfilter(b, a, data, zi=zi*data[0]). (Nhưng điều này có thể không tạo sự khác biệt với một bandpass hoặc vượt qua bộ lọc cao.)
Warren Weckesser

1
Tôi nhận thấy rằng có sự dịch chuyển pha 180 độ trong đầu ra của scipy.signal.lfiter()wrt thành tín hiệu ban đầu và signal.filtfilt()đầu ra, tại sao vậy? Tôi có nên sử dụng filtfilt()thay thế nếu thời gian là quan trọng đối với tôi?
Jason

1
Đó là độ trễ pha của bộ lọc ở tần số đó. Độ trễ pha hình sin qua bộ lọc Butterworth phụ thuộc phi tuyến tính vào tần số. Đối với độ trễ pha bằng 0, có, bạn có thể sử dụng filtfilt(). Câu trả lời của tôi ở đây bao gồm một ví dụ về cách sử dụng filtfilt()để tránh độ trễ do bộ lọc gây ra.
Warren Weckesser

1
Xin chào Jason, tôi khuyên bạn nên đặt câu hỏi về lý thuyết xử lý tín hiệu tại dsp.stackexchange.com . Nếu bạn có câu hỏi về một số mã bạn đã viết không hoạt động như mong đợi, bạn có thể bắt đầu một câu hỏi mới tại đây trên stackoverflow.
Warren Weckesser

37

Phương pháp thiết kế bộ lọc trong câu trả lời được chấp nhận là đúng, nhưng nó có một lỗ hổng. Các bộ lọc thông dải SciPy được thiết kế với b, a không ổn định và có thể dẫn đến các bộ lọc saicác thứ tự bộ lọc cao hơn .

Thay vào đó, hãy sử dụng đầu ra sos (phần bậc hai) của thiết kế bộ lọc.

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

Ngoài ra, bạn có thể vẽ biểu đồ tần số đáp ứng bằng cách thay đổi

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

đến

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

+1 vì đây là cách tốt hơn để thực hiện trong nhiều trường hợp. Như trong các nhận xét về câu trả lời được chấp nhận, cũng có thể loại bỏ độ trễ pha bằng cách sử dụng lọc chuyển tiếp-lùi. Chỉ cần thay thế sosfiltbằng sosfiltfilt.
Mike

@Mike và user13107 Có phải cùng một lỗi cũng ảnh hưởng đến bộ lọc Butterworth thông cao và thông thấp không? Và giải pháp có giống nhau không?
dewarrn1

3
@ dewarrn1 Thực ra gọi nó là "lỗi" là không đúng; thuật toán được triển khai chính xác, nhưng nó vốn không ổn định nên nó chỉ là một lựa chọn sai của thuật toán. Nhưng có, nó ảnh hưởng đến bất kỳ bộ lọc nào ở thứ tự cao hơn - không chỉ bộ lọc cao hoặc thấp và không chỉ bộ lọc Butterworth, mà còn những bộ lọc khác như Chebyshev , v.v. Dù sao, nói chung, tốt nhất là luôn luôn chọn sosđầu ra, vì điều đó sẽ luôn tránh được sự bất ổn. Và trừ khi bạn cần xử lý thời gian thực, bạn chỉ nên sử dụng luôn sosfiltfilt.
Mike

Xin lỗi, tôi đã không nhận thấy câu trả lời này từ lâu! @ user13107, vâng, biểu diễn hàm truyền (hoặc 'ba') của bộ lọc tuyến tính có một số vấn đề số nghiêm trọng khi thứ tự của bộ lọc lớn. Ngay cả các bộ lọc bậc tương đối thấp cũng có thể gặp vấn đề khi băng thông mong muốn nhỏ so với tần số lấy mẫu. Câu trả lời ban đầu của tôi được viết trước khi biểu diễn SOS được thêm vào SciPy và trước khi fsđối số được thêm vào nhiều hàm trong scipy.signal. Câu trả lời đã quá hạn cho một bản cập nhật.
Warren Weckesser

bất kỳ giúp đỡ cho điều này? stackoverflow.com/q/60866193/5025009
seralouk

4

Đối với bộ lọc thông dải, ws là một bộ chứa tần số góc dưới và góc trên. Chúng đại diện cho tần số kỹ thuật số nơi đáp ứng của bộ lọc nhỏ hơn 3 dB so với băng thông.

wp là một bộ tuple chứa các tần số kỹ thuật số dải dừng. Chúng đại diện cho vị trí bắt đầu suy giảm cực đại.

gpass là độ suy giảm tối đa trong băng thông tính bằng dB trong khi gstop là suy hao trong băng dừng.

Ví dụ: giả sử bạn muốn thiết kế một bộ lọc cho tốc độ lấy mẫu là 8000 mẫu / giây có tần số góc là 300 và 3100 Hz. Tần số Nyquist là tốc độ mẫu chia cho hai, hoặc trong ví dụ này là 4000 Hz. Tần số kỹ thuật số tương đương là 1,0. Hai tần số góc khi đó là 300/4000 và 3100/4000.

Bây giờ, giả sử bạn muốn các dải dừng giảm 30 dB +/- 100 Hz so với tần số góc. Do đó, các dải dừng của bạn sẽ bắt đầu ở 200 và 3200 Hz, dẫn đến các tần số kỹ thuật số là 200/4000 và 3200/4000.

Để tạo bộ lọc của bạn, bạn sẽ gọi mông là

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)

Chiều dài của bộ lọc kết quả sẽ phụ thuộc vào độ sâu của các dải dừng và độ dốc của đường cong đáp ứng được xác định bởi sự khác biệt giữa tần số góc và tần số dải dừng.


Tôi đã cố gắng thực hiện nó, nhưng một cái gì đó vẫn còn thiếu. Một điều là gpass=0.0làm tăng lỗi chia cho 0, vì vậy tôi đã thay đổi nó thành 0,1 và lỗi đã dừng lại. Bên cạnh đó, các tài liệu cho butterrằng: Passband and stopband edge frequencies, normalized from 0 to 1 (1 corresponds to pi radians / sample).Tôi nghi ngờ liệu câu trả lời của bạn có đúng các phép tính hay không, vì vậy tôi vẫn đang nghiên cứu và sẽ sớm đưa ra một số phản hồi.
heltonbiker

(còn, mặc dù tôi wswpcó hai yếu tố, mỗi bộ lọc chỉ thực hiện thấp hoặc cao vượt qua (thông qua btypetham số), nhưng không phải band-pass)
heltonbiker

1
Theo tài liệu tại docs.scipy.org/doc/scipy/reference/generated/… , assord thiết kế các bộ lọc low, high và band pass. Theo như gpass, tôi đoán mông không cho phép suy giảm 0 dB trong băng thông. Đặt nó thành một số giá trị khác 0 sau đó.
sizzzzlerz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.