6.15. Filteren en spectrogrammen¶
Filteren, gladstrijken en magnitudespectra zijn de aangrenzende taken bij een ruwe FFT: het gladstrijken of bandpas-filteren van een stroom samples, het berekenen van magnitudespectra in een streaming-lus zonder allocatie, en het herinterpreteren van ruwe randapparaatbuffers als float-arrays. De beschikbare hulpmiddelen:
sosfilt()– digitaal filter dat wordt toegepast via gecascadeerde tweede-orde-secties.spectrogram()– magnitudeabs(fft(...))zonder tussenliggende allocaties.from_int16_buffer()en de overigeulab.utilsfrom_*_buffer-hulpfuncties – haal een float-array uit een buffer waarvan het dtype niet door de ingebouwdefrombuffer()wordt gedekt.
6.15.1. Filteren met sosfilt¶
sosfilt() past een digitaal infinite impulse response (IIR) filter toe als een cascade van second-order sections (SOS) – een numeriek robuuste vorm. sos is een reeks secties met lengte 6; x is de eendimensionale invoer:
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)
Elke rij van sos bevat zes coëfficiënten [b0, b1, b2, a0, a1, a2] voor één biquad-sectie. De sos-array wordt doorgaans vooraf op een pc berekend met scipy.signal.iirfilter(..., output='sos') en als Python-literal in het camerascript gekopieerd.
Het optionele zi=-trefwoord draagt filterstatus over tussen buffers. Geef een begintoestand met vorm (n_sections, 2) mee, en de functie geeft (y, zf) terug – de gefilterde uitvoer en de eindtoestand – zodat de eindtoestand van de ene buffer de begintoestand van de volgende voedt:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Dit is het standaardpatroon voor een streaming-filter op gebufferde data – microfooninvoer die per 1024 samples wordt ingelezen, ADC-samples die in DMA-aangestuurde brokken worden verzameld, IMU-metingen die over een venster worden verzameld.
6.15.2. Spectrogrammen¶
spectrogram() berekent de magnitude van de Fouriertransformatie. Het is conceptueel equivalent aan np.sqrt(real * real + imag * imag) na een aanroep van fft(), maar bundelt het werk in één aanroep – zonder op enig moment de tussenliggende real * real, imag * imag, de som of de expliciete magnitude-array in het RAM aan te houden. Daarmee is het het juiste hulpmiddel in elke lus waarin spectra herhaaldelijk worden berekend:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
De argumentvorm weerspiegelt fft(): één reële array, of een (real, imag)-paar wanneer de invoer een imaginair deel heeft.
Drie trefwoordargumenten helpen bij allocatie:
scratchpad=None– een eendimensionale dichte float-array met lengte2 * len(signal)diespectrogram()als werkruimte gebruikt.out=None– een eendimensionale float-array om het resultaat in te schrijven.log=False– wanneerTrue, wordt vóór het teruggeven delog()van de magnitude genomen, gebundeld in dezelfde aanroep.
Het streaming-patroon is om alles één keer te alloceren en daarna nooit meer:
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 ...
Vergelijk met de voor de hand liggende maar verspillende versie:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Beide leveren dezelfde getallen op, maar de eerste versie alloceert niets binnen de lus – de camera houdt elke iteratie hetzelfde geheugen in gebruik, en de lus draait sneller.
6.15.3. Randapparaatbuffers breder dan 16 bit¶
frombuffer() verwerkt alleen de dtypes die numpy zelf definieert (uint8 / int8, uint16 / int16, float). Wanneer een randapparaat 32-bits integer-samples produceert – een 24- of 32-bits ADC, een hogeresolutiemicrofoon – biedt ulab.utils expliciete conversiehulpfuncties:
Elk neemt een bytes-achtige buffer en geeft een float ndarray terug:
from ulab import utils
buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])
De functies accepteren dezelfde allocatiebesparende knoppen als spectrogram():
count=enoffset=om een header over te slaan of het inlezen te beperken.out=om in een vooraf gealloceerde float-array te schrijven.byteswap=Truewanneer het randapparaat het oneens is met de MCU over de byte-volgorde.
Het gecombineerde patroon – één from_int32_buffer()-aanroep rechtstreeks in één spectrogram()-aanroep, beide met out=-buffers van buiten de lus – is het juiste sjabloon voor een streaming-spectrumanalysator die op een hogeresolutiemicrofoon draait.