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.