6.15. Filtrare și spectrograme#
Filtrarea, netezirea și spectrele de magnitudine sunt sarcinile adiacente unei FFT brute: netezirea sau filtrarea trece-bandă a unui flux de eșantioane, calcularea spectrelor de magnitudine într-o buclă de tip flux fără alocare și reinterpretarea tampoanelor (buffer) brute ale perifericelor ca tablouri de numere în virgulă mobilă. Instrumentele disponibile:
sosfilt()– filtru digital aplicat prin secțiuni de ordinul doi în cascadă.spectrogram()– magnitudineabs(fft(...))fără alocări intermediare.from_int16_buffer()și celelalte funcții ajutătoarefrom_*_bufferdinulab.utils– extrag un tablou de numere în virgulă mobilă dintr-un tampon (buffer) al cărui dtype nu este acoperit de funcția încorporatăfrombuffer().
6.15.1. Filtrare cu sosfilt#
sosfilt() aplică un filtru digital cu răspuns infinit la impuls (IIR) sub forma unei cascade de secțiuni de ordinul doi (SOS) – o formă robustă numeric. sos este o secvență de secțiuni de lungime 6; x este intrarea 1-D:
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)
Fiecare rând din sos conține șase coeficienți [b0, b1, b2, a0, a1, a2] pentru o secțiune biquad. Tabloul sos este de obicei precalculat pe un PC cu scipy.signal.iirfilter(..., output='sos') și copiat în scriptul camerei ca un literal Python.
Cuvântul-cheie opțional zi= transportă starea filtrului între tampoane (buffer). Transmite o stare inițială de forma (n_sections, 2) și funcția returnează (y, zf) – ieșirea filtrată și starea finală – astfel încât starea finală a unui tampon (buffer) alimentează starea inițială a celui următor:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Acesta este modelul standard pentru un filtru de tip flux aplicat datelor tamponate – intrare de microfon citită câte 1024 de eșantioane odată, eșantioane ADC acumulate în fragmente acționate prin DMA, citiri IMU colectate pe o fereastră.
6.15.2. Spectrograme#
spectrogram() calculează magnitudinea transformatei Fourier. Este conceptual echivalentă cu np.sqrt(real * real + imag * imag) după un apel la fft(), dar comprimă munca într-un singur apel – fără a păstra în RAM, în niciun moment, valorile intermediare real * real, imag * imag, suma sau tabloul de magnitudine explicit. Acest lucru o face instrumentul potrivit în orice buclă în care spectrele sunt calculate în mod repetat:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
Forma argumentelor reflectă fft(): un singur tablou real sau o pereche (real, imag) atunci când intrarea are o parte imaginară.
Trei argumente de tip cuvânt-cheie ajută la alocare:
scratchpad=None– un tablou dens 1-D de numere în virgulă mobilă, de lungime2 * len(signal), pe carespectrogram()îl folosește drept spațiu de lucru.out=None– un tablou 1-D de numere în virgulă mobilă în care se scrie rezultatul.log=False– când esteTrue, se aplicălog()magnitudinii înainte de returnare, comprimat în același apel.
Modelul de tip flux constă în a aloca totul o singură dată și a nu mai aloca niciodată:
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 ...
Comparați cu versiunea evidentă, dar risipitoare:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Ambele produc aceleași numere, dar prima versiune nu alocă nimic în interiorul buclei – camera păstrează aceeași memorie în uz la fiecare iterație, iar bucla rulează mai repede.
6.15.3. Tampoane de periferice mai late de 16 biți#
frombuffer() gestionează doar dtype-urile pe care numpy însuși le definește (uint8 / int8, uint16 / int16, float). Când un periferic produce eșantioane de numere întregi pe 32 de biți – un ADC pe 24 sau 32 de biți, un microfon de înaltă rezoluție – ulab.utils expune funcții ajutătoare explicite de conversie:
Fiecare primește un tampon (buffer) de tip bytes și returnează un ndarray de numere în virgulă mobilă:
from ulab import utils
buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])
Funcțiile acceptă aceleași opțiuni de economisire a alocării ca spectrogram():
count=șioffset=pentru a sări un antet sau a limita citirea.out=pentru a scrie într-un tablou de numere în virgulă mobilă prealocat.byteswap=Truecând perifericul nu este de acord cu MCU asupra ordinii octeților.
Modelul combinat – un apel from_int32_buffer() direct într-un apel spectrogram(), ambele cu tampoane (buffer) out= din afara buclei – este șablonul potrivit pentru un analizor de spectru de tip flux care rulează pe un microfon de înaltă rezoluție.