6.15. Filtraggio e spettrogrammi¶
Il filtraggio, lo smoothing e gli spettri di magnitudine sono i compiti adiacenti a una FFT grezza: smoothing o filtraggio passa-banda di un flusso di campioni, calcolo di spettri di magnitudine in un loop in streaming senza allocare, e re-interpretazione di buffer grezzi delle periferiche come array di float. Gli strumenti disponibili:
sosfilt()– filtro digitale applicato tramite sezioni del secondo ordine in cascata.spectrogram()– magnitudineabs(fft(...))senza allocazioni intermedie.from_int16_buffer()e le altre funzioni di supportofrom_*_bufferdiulab.utils– estraggono un array di float da un buffer il cui dtype non è coperto dallafrombuffer()integrata.
6.15.1. Filtraggio con sosfilt¶
sosfilt() applica un filtro digitale a risposta impulsiva infinita (IIR) come una cascata di sezioni del secondo ordine (SOS) – una forma numericamente robusta. sos è una sequenza di sezioni di lunghezza 6; x è l’input monodimensionale:
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)
Ogni riga di sos contiene sei coefficienti [b0, b1, b2, a0, a1, a2] per una sezione biquad. L’array sos viene di solito pre-calcolato su un PC con scipy.signal.iirfilter(..., output='sos') e copiato nello script della camera come letterale Python.
La parola chiave opzionale zi= trasporta lo stato del filtro tra i buffer. Passa uno stato iniziale di forma (n_sections, 2) e la funzione restituisce (y, zf) – l’output filtrato e lo stato finale – così lo stato finale di un buffer alimenta lo stato iniziale del successivo:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Questo è lo schema standard per un filtro in streaming su dati bufferizzati – input da microfono letto 1024 campioni alla volta, campioni ADC accumulati in blocchi pilotati da DMA, letture IMU raccolte su una finestra.
6.15.2. Spettrogrammi¶
spectrogram() calcola la magnitudine della trasformata di Fourier. È concettualmente equivalente a np.sqrt(real * real + imag * imag) dopo una chiamata a fft(), ma riunisce il lavoro in un’unica chiamata – senza mai mantenere in RAM in alcun momento l’intermedio real * real, imag * imag, la somma, o l’array di magnitudine esplicito. Questo lo rende lo strumento giusto in qualsiasi loop in cui gli spettri vengono calcolati ripetutamente:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
La forma degli argomenti rispecchia fft(): un array reale, oppure una coppia (real, imag) quando l’input ha una parte immaginaria.
Tre argomenti per parola chiave aiutano con l’allocazione:
scratchpad=None– un array di float denso monodimensionale di lunghezza2 * len(signal)chespectrogram()usa come spazio di lavoro.out=None– un array di float monodimensionale in cui scrivere il risultato.log=False– quando èTrue, calcolalog()della magnitudine prima di restituirla, riunito nella stessa chiamata.
Lo schema in streaming consiste nell’allocare tutto una volta e non allocare mai più:
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 ...
Confronta con la versione ovvia ma dispendiosa:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Entrambe producono gli stessi numeri, ma la prima versione non alloca nulla all’interno del loop – la camera mantiene in uso la stessa memoria a ogni iterazione, e il loop gira più velocemente.
6.15.3. Buffer di periferiche più larghi di 16 bit¶
frombuffer() gestisce solo i dtype che numpy stesso definisce (uint8 / int8, uint16 / int16, float). Quando una periferica produce campioni interi a 32 bit – un ADC a 24 o 32 bit, un microfono ad alta risoluzione – ulab.utils espone funzioni di conversione esplicite:
Ciascuna accetta un buffer di tipo bytes e restituisce un ndarray di 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])
Le funzioni accettano le stesse manopole per il risparmio di allocazione di spectrogram():
count=eoffset=per saltare un’intestazione o limitare la lettura.out=per scrivere in un array di float pre-allocato.byteswap=Truequando la periferica è in disaccordo con l’MCU sull’ordine dei byte.
Lo schema combinato – una chiamata a from_int32_buffer() direttamente in una chiamata a spectrogram(), entrambe con buffer out= provenienti dall’esterno del loop – è il modello giusto per un analizzatore di spettro in streaming in esecuzione su un microfono ad alta risoluzione.