6.15. Szűrés és spektrogramok

A szűrés, a simítás és a magnitúdóspektrumok a nyers FFT szomszédos feladatai: mintafolyam simítása vagy sáváteresztő szűrése, magnitúdóspektrumok kiszámítása egy folyamatos ciklusban lefoglalás nélkül, valamint nyers perifériapufferek újraértelmezése float tömbökként. Az elérhető eszközök:

  • sosfilt() – kaszkádba kapcsolt másodrendű szekciókon keresztül alkalmazott digitális szűrő.

  • spectrogram() – magnitúdó (abs(fft(...))) közbenső lefoglalások nélkül.

  • A from_int16_buffer() és a többi ulab.utils from_*_buffer segédfüggvény – float tömböt nyer ki egy olyan pufferből, amelynek dtype-ját a beépített frombuffer() nem fedi le.

6.15.1. Szűrés sosfilt-tel

A sosfilt() egy digitális végtelen impulzusválaszú (IIR) szűrőt alkalmaz másodrendű szekciók (SOS) kaszkádjaként – ez egy numerikusan robusztus forma. A sos 6 hosszúságú szekciók sorozata; az x az 1-D bemenet:

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)

A sos minden sora egy biquad szekció hat együtthatóját tartalmazza: [b0, b1, b2, a0, a1, a2]. A sos tömböt általában egy PC-n előre kiszámítják a scipy.signal.iirfilter(..., output='sos') hívással, majd Python literálként bemásolják a kamera szkriptjébe.

Az opcionális zi= kulcsszó átviszi a szűrő állapotát a pufferek között. Adj át egy (n_sections, 2) alakú kezdeti állapotot, és a függvény visszaadja az (y, zf)-t – a szűrt kimenetet és a végső állapotot –, így az egyik puffer végső állapota a következő kezdeti állapotába kerül:

y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...

Ez a szabványos minta egy pufferelt adatokon futó folyamatos szűrőhöz – mikrofonbemenet egyszerre 1024 mintával beolvasva, ADC-minták DMA-vezérelt darabokban felhalmozva, IMU-leolvasások egy ablakon át gyűjtve.

6.15.2. Spektrogramok

A spectrogram() kiszámítja a Fourier-transzformáció magnitúdóját. Fogalmilag egyenértékű a np.sqrt(real * real + imag * imag) kifejezéssel egy fft() hívás után, de a munkát egyetlen hívásba sűríti – anélkül, hogy a közbenső real * real, imag * imag, az összeg vagy az explicit magnitúdótömb bármikor is a RAM-ban lenne. Ez teszi a megfelelő eszközzé minden olyan ciklusban, ahol a spektrumokat ismételten számítják ki:

from ulab import numpy as np
from ulab import utils

x        = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)

Az argumentumforma a fft() formáját tükrözi: egy valós tömb, vagy egy (real, imag) pár, ha a bemenetnek van képzetes része.

Három kulcsszavas argumentum segít a lefoglalásban:

  • scratchpad=None – egy 2 * len(signal) hosszúságú 1-D sűrű float tömb, amelyet a spectrogram() munkaterületként használ.

  • out=None – egy 1-D float tömb, amelybe az eredményt írja.

  • log=False – amikor True, a visszaadás előtt veszi a magnitúdó log()-ját, ugyanabba a hívásba sűrítve.

A folyamatos minta lényege, hogy mindent egyszer foglalsz le, és soha többé nem foglalsz:

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 ...

Vesd össze a nyilvánvaló, de pazarló változattal:

while True:
    signal   = read_samples(N)
    spectrum = np.log(utils.spectrogram(signal))   # two allocations

Mindkettő ugyanazokat a számokat adja, de az első változat semmit sem foglal le a cikluson belül – a kamera minden iterációban ugyanazt a memóriát használja, és a ciklus gyorsabban fut.

6.15.3. 16 bitnél szélesebb perifériapufferek

A frombuffer() csak azokat a dtype-okat kezeli, amelyeket maga a numpy definiál (uint8 / int8, uint16 / int16, float). Amikor egy periféria 32 bites egész mintákat állít elő – egy 24 vagy 32 bites ADC, egy nagy felbontású mikrofon –, a ulab.utils explicit konverziós segédfüggvényeket tesz elérhetővé:

Mindegyik egy bytes-szerű puffert vesz és egy float ndarray-t ad vissza:

from ulab import utils

buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])

A függvények ugyanazokat a lefoglalást megtakarító beállításokat fogadják el, mint a spectrogram():

  • count= és offset= egy fejléc kihagyásához vagy a beolvasás korlátozásához.

  • out= egy előre lefoglalt float tömbbe való íráshoz.

  • byteswap=True amikor a periféria nem egyezik az MCU-val a bájtsorrendben.

A kombinált minta – egyetlen from_int32_buffer() hívás közvetlenül egyetlen spectrogram() hívásba, mindkettő a cikluson kívülről származó out= pufferekkel – a megfelelő sablon egy nagy felbontású mikrofonon futó folyamatos spektrumanalizátorhoz.