6.15. Filtrování a spektrogramy¶
Filtrování, vyhlazování a spektra velikostí jsou úlohy příbuzné surové FFT: vyhlazování nebo pásmová propust proudu vzorků, výpočet spekter velikostí ve streamovací smyčce bez alokace a nová interpretace surových bufferů periferií jako polí typu float. Dostupné nástroje:
sosfilt()– digitální filtr aplikovaný prostřednictvím kaskádovaných sekcí druhého řádu.spectrogram()– velikostabs(fft(...))bez mezilehlých alokací.from_int16_buffer()a ostatní pomocné funkceulab.utilstypufrom_*_buffer– získají pole typu float z bufferu, jehož dtype vestavěná funkcefrombuffer()nepokrývá.
6.15.1. Filtrování pomocí sosfilt¶
sosfilt() aplikuje digitální filtr s nekonečnou impulzní odezvou (IIR) jako kaskádu sekcí druhého řádu (SOS) – numericky robustní formu. sos je sekvence sekcí o délce 6; x je jednorozměrný vstup:
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)
Každý řádek pole sos obsahuje šest koeficientů [b0, b1, b2, a0, a1, a2] pro jednu biquad sekci. Pole sos se obvykle předem vypočítá na PC pomocí scipy.signal.iirfilter(..., output='sos') a zkopíruje do skriptu kamery jako Python literál.
Volitelný klíčový argument zi= přenáší stav filtru mezi buffery. Předejte počáteční stav tvaru (n_sections, 2) a funkce vrátí (y, zf) – filtrovaný výstup a koncový stav – takže koncový stav jednoho bufferu poslouží jako počáteční stav dalšího:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Toto je standardní vzor pro streamovací filtr nad bufferovanými daty – vstup z mikrofonu čtený po 1024 vzorcích, vzorky z ADC akumulované v blocích řízených DMA, hodnoty z IMU sbírané přes okno.
6.15.2. Spektrogramy¶
spectrogram() vypočítá velikost (magnitudu) Fourierovy transformace. Koncepčně je ekvivalentní výrazu np.sqrt(real * real + imag * imag) po volání fft(), ale skládá tuto práci do jediného volání – aniž by v kterémkoli okamžiku držel v RAM mezivýsledky real * real, imag * imag, součet nebo explicitní pole velikostí. To z ní činí správný nástroj v každé smyčce, kde se spektra počítají opakovaně:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
Forma argumentů odpovídá fft(): jedno reálné pole, nebo dvojice (real, imag), když má vstup imaginární část.
Tři klíčové argumenty pomáhají s alokací:
scratchpad=None– jednorozměrné husté pole typu float o délce2 * len(signal), kteréspectrogram()používá jako pracovní prostor.out=None– jednorozměrné pole typu float, do kterého se zapíše výsledek.log=False– když jeTrue, před vrácením se vezmelog()velikosti, vložená do téhož volání.
Streamovací vzor spočívá v tom, že se vše alokuje jednou a už nikdy znovu:
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 ...
Porovnejte s evidentní, ale plýtvavou verzí:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Obě produkují stejná čísla, ale první verze uvnitř smyčky nealokuje nic – kamera používá v každé iteraci stejnou paměť a smyčka běží rychleji.
6.15.3. Buffery periferií širší než 16 bitů¶
frombuffer() zvládá pouze dtypy, které definuje samotný numpy (uint8 / int8, uint16 / int16, float). Když periferie produkuje 32bitové celočíselné vzorky – 24- nebo 32bitový ADC, mikrofon s vysokým rozlišením – ulab.utils poskytuje explicitní pomocné funkce pro převod:
Každá přijímá buffer podobný bytes a vrací ndarray typu 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])
Tyto funkce přijímají stejné parametry pro úsporu alokací jako spectrogram():
count=aoffset=pro přeskočení hlavičky nebo omezení čtení.out=pro zápis do předem alokovaného pole typu float.byteswap=True, když se periferie s MCU neshodují v pořadí bytů.
Kombinovaný vzor – jedno volání from_int32_buffer() přímo do jednoho volání spectrogram(), obě s buffery out= vytvořenými mimo smyčku – je správnou šablonou pro streamovací spektrální analyzátor běžící na mikrofonu s vysokým rozlišením.