6.15. Lọc tín hiệu và phổ tần số

Lọc, làm mượt và phổ biên độ là các công việc liền kề với FFT thuần túy: làm mượt hoặc lọc thông dải một luồng mẫu, tính phổ biên độ trong một vòng lặp truyền phát mà không cấp phát thêm bộ nhớ, và tái diễn giải các bộ đệm ngoại vi thô dưới dạng mảng float. Các công cụ sẵn có:

  • sosfilt() -- bộ lọc số áp dụng qua các phần bậc hai nối tiếp.

  • spectrogram() -- biên độ abs(fft(...)) không cấp phát trung gian.

  • from_int16_buffer() và các hàm trợ giúp from_*_buffer khác của ulab.utils -- lấy mảng float từ bộ đệm có dtype mà hàm frombuffer() tích hợp không hỗ trợ.

6.15.1. Lọc với sosfilt

sosfilt() áp dụng bộ lọc đáp ứng xung vô hạn (IIR) kỹ thuật số như một chuỗi các phần bậc hai (SOS) -- một dạng bền vững về mặt số học. sos là một chuỗi các phần có độ dài 6; x là đầu vào 1 chiều:

from ulab import numpy as np
from ulab import scipy as sp

x   = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
sos = [[1, 2, 3, 1, 5, 6],
       [1, 2, 3, 1, 5, 6]]
y   = sp.signal.sosfilt(sos, x)

Mỗi hàng của sos chứa sáu hệ số [b0, b1, b2, a0, a1, a2] cho một phần biquad. Mảng sos thường được tính trước trên PC với scipy.signal.iirfilter(..., output='sos') và sao chép vào tập lệnh camera dưới dạng hằng số Python.

Từ khóa tùy chọn zi= mang trạng thái bộ lọc qua các bộ đệm. Truyền trạng thái ban đầu có hình dạng (n_sections, 2) và hàm trả về (y, zf) -- đầu ra đã lọc và trạng thái cuối cùng -- để trạng thái cuối của một bộ đệm trở thành trạng thái đầu của bộ đệm tiếp theo:

y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...

Đây là mẫu chuẩn cho bộ lọc truyền phát trên dữ liệu có bộ đệm -- đầu vào microphone đọc 1024 mẫu mỗi lần, các mẫu ADC tích lũy theo từng khối DMA, các số đọc IMU thu thập trong một cửa sổ.

6.15.2. Phổ tần số

spectrogram() tính biên độ của biến đổi Fourier. Về mặt khái niệm, nó tương đương với np.sqrt(real * real + imag * imag) sau lệnh gọi fft(), nhưng gộp công việc vào một lệnh gọi -- mà không giữ mảng trung gian real * real, imag * imag, tổng, hoặc mảng biên độ rõ ràng trong RAM tại bất kỳ thời điểm nào. Điều đó làm cho nó là công cụ phù hợp trong bất kỳ vòng lặp nào mà phổ tần số được tính lặp đi lặp lại:

from ulab import numpy as np
from ulab import utils

x        = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)

Dạng đối số phản ánh fft(): một mảng thực, hoặc một cặp (real, imag) khi đầu vào có phần ảo.

Ba đối số từ khóa hỗ trợ phân bổ bộ nhớ:

  • scratchpad=None -- mảng float dày đặc 1 chiều có độ dài 2 * len(signal)spectrogram() sử dụng làm không gian làm việc.

  • out=None -- mảng float 1 chiều để ghi kết quả vào.

  • log=False -- khi True, lấy log() của biên độ trước khi trả về, được gộp vào cùng một lệnh gọi.

Mẫu truyền phát là cấp phát tất cả một lần và không bao giờ cấp phát thêm:

from ulab import numpy as np
from ulab import utils

N = 1024
scratch = np.zeros(2 * N)
out     = np.zeros(N)

while True:
    signal = read_samples(N)
    utils.spectrogram(signal, out=out, scratchpad=scratch,
                      log=True)
    # out now holds the log-magnitude spectrum for this window ...

So sánh với phiên bản rõ ràng nhưng lãng phí:

while True:
    signal   = read_samples(N)
    spectrum = np.log(utils.spectrogram(signal))   # two allocations

Cả hai đều tạo ra cùng một số, nhưng phiên bản đầu tiên không cấp phát gì bên trong vòng lặp -- camera giữ cùng bộ nhớ được sử dụng trong mỗi lần lặp, và vòng lặp chạy nhanh hơn.

6.15.3. Bộ đệm ngoại vi rộng hơn 16 bit

frombuffer() chỉ xử lý các dtype mà numpy tự định nghĩa (uint8 / int8, uint16 / int16, float). Khi một ngoại vi tạo ra các mẫu số nguyên 32 bit -- ADC 24 hoặc 32 bit, microphone độ phân giải cao -- ulab.utils cung cấp các hàm chuyển đổi rõ ràng:

Mỗi hàm nhận một bộ đệm dạng bytes và trả về một ndarray dạng float:

from ulab import utils

buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])

Các hàm chấp nhận cùng các tùy chọn tiết kiệm bộ nhớ như spectrogram():

  • count=offset= để bỏ qua tiêu đề hoặc giới hạn lượng đọc.

  • out= để ghi vào mảng float đã được cấp phát trước.

  • byteswap=True khi ngoại vi không đồng ý với MCU về thứ tự byte.

Mẫu kết hợp -- một lệnh gọi from_int32_buffer() trực tiếp vào một lệnh gọi spectrogram(), cả hai với các bộ đệm out= từ bên ngoài vòng lặp -- là mẫu đúng đắn cho bộ phân tích phổ truyền phát chạy trên microphone độ phân giải cao.