6.15. Фільтрація та спектрограми¶
Фільтрація, згладжування та амплітудні спектри є суміжними задачами до сирого FFT: згладжування або смугова фільтрація потоку зразків, обчислення амплітудних спектрів у потоковому циклі без виділення пам’яті, та інтерпретація сирих буферів периферійних пристроїв як масивів з рухомою крапкою. Наявні інструменти:
sosfilt()– цифровий фільтр, що застосовується через каскадні секції другого порядку.spectrogram()– амплітудаabs(fft(...))без проміжних виділень пам’яті.from_int16_buffer()та інші допоміжні функціїulab.utilsfrom_*_buffer– отримання масиву з рухомою крапкою з буфера, тип даних якого не підтримує вбудована функціяfrombuffer().
6.15.1. Фільтрація за допомогою sosfilt¶
sosfilt() застосовує цифровий фільтр нескінченної імпульсної характеристики (IIR) у вигляді каскаду секцій другого порядку (SOS) — чисельно стійка форма. sos — це послідовність секцій довжиною 6; x — одновимірний вхід:
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)
Кожен рядок sos містить шість коефіцієнтів [b0, b1, b2, a0, a1, a2] для однієї секції бікваду. Масив sos зазвичай попередньо обчислюється на ПК за допомогою scipy.signal.iirfilter(..., output='sos') і копіюється до скрипта камери як літерал Python.
Необов’язкове ключове слово zi= передає стан фільтра між буферами. Передайте початковий стан форми (n_sections, 2), і функція повертає (y, zf) – відфільтрований вихід і кінцевий стан – щоб кінцевий стан одного буфера слугував початковим станом наступного:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Це стандартний шаблон для потокового фільтра на буферизованих даних – мікрофонний вхід, що зчитується по 1024 зразки, ADC-зразки, накопичені в блоках під керуванням DMA, показання IMU, зібрані у вікні.
6.15.2. Спектрограми¶
spectrogram() обчислює амплітуду перетворення Фур’є. Концептуально це еквівалентно np.sqrt(real * real + imag * imag) після виклику fft(), але об’єднує роботу в один виклик – без утримання в RAM проміжних real * real, imag * imag, суми або явного масиву амплітуд у будь-який момент. Це правильний інструмент у будь-якому циклі, де спектри обчислюються повторно:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
Форма аргументу відповідає fft(): один дійсний масив або пара (real, imag), коли вхід має уявну частину.
Три ключові аргументи допомагають з виділенням пам’яті:
scratchpad=None– одновимірний щільний масив з рухомою крапкою довжиною2 * len(signal), якийspectrogram()використовує як робочий простір.out=None– одновимірний масив з рухомою крапкою для запису результату.log=False– якщоTrue, застосовуєlog()до амплітуди перед поверненням, об’єднуючи це в той самий виклик.
Шаблон потокової обробки – виділити все один раз і більше не виділяти:
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 ...
Порівняйте з очевидним, але неефективним варіантом:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Обидва дають однакові числа, але перший варіант нічого не виділяє всередині циклу – камера тримає одну й ту саму пам’ять на кожній ітерації, і цикл виконується швидше.
6.15.3. Буфери периферійних пристроїв ширше 16 біт¶
frombuffer() обробляє лише типи даних, визначені самим numpy (uint8 / int8, uint16 / int16, float). Коли периферійний пристрій виробляє 32-бітні цілочисельні зразки – 24- або 32-бітний ADC, мікрофон з високою роздільною здатністю – ulab.utils надає явні допоміжні функції перетворення:
Кожна приймає байтоподібний буфер і повертає ndarray з рухомою крапкою:
from ulab import utils
buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])
Функції приймають ті самі параметри збереження пам’яті, що й spectrogram():
count=таoffset=для пропуску заголовка або обмеження читання.out=для запису в попередньо виділений масив з рухомою крапкою.byteswap=True, якщо периферійний пристрій і MCU використовують різний порядок байтів.
Комбінований шаблон – один виклик from_int32_buffer() безпосередньо до одного виклику spectrogram(), обидва з буферами out= поза циклом – є правильним шаблоном для потокового аналізатора спектра на мікрофоні з високою роздільною здатністю.