6.15. Фильтрация и спектрограммы¶
Фильтрация, сглаживание и амплитудные спектры – это задачи, смежные с сырым FFT: сглаживание или полосовая фильтрация потока отсчётов, вычисление амплитудных спектров в потоковом цикле без выделения памяти и переинтерпретация сырых буферов периферии как массивов чисел с плавающей точкой. Доступные инструменты:
sosfilt()– цифровой фильтр, применяемый через каскад секций второго порядка.spectrogram()– амплитудаabs(fft(...))без промежуточных выделений памяти.from_int16_buffer()и другие вспомогательные функцииfrom_*_bufferизulab.utils– извлекают массив чисел с плавающей точкой из буфера, тип данных которого не охватывает встроенная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=, заданными вне цикла, – это правильный шаблон для потокового спектроанализатора, работающего на микрофоне высокого разрешения.