6.15. Suodatus ja spektrogrammit

Suodatus, tasoitus ja magnitudispektrit ovat raakaa FFT:tä lähellä olevia tehtäviä: näytevirran tasoitus tai kaistanpäästösuodatus, magnitudispektrien laskeminen suoratoistosilmukassa ilman varaamista ja raakojen oheislaitepuskureiden uudelleentulkinta liukulukutaulukoina. Käytettävissä olevat työkalut:

  • sosfilt() – digitaalinen suodatin, joka toteutetaan kaskadoiduilla toisen asteen osioilla.

  • spectrogram() – magnitudi abs(fft(...)) ilman välivaiheen varauksia.

  • from_int16_buffer() ja muut ulab.utils-moduulin from_*_buffer-apufunktiot – vetävät liukulukutaulukon puskurista, jonka dtype-tyyppiä sisäänrakennettu frombuffer() ei kata.

6.15.1. Suodatus sosfilt-funktiolla

sosfilt() soveltaa digitaalisen äärettömän impulssivasteen (IIR) suodattimen toisen asteen osioiden (SOS) kaskadina – numeerisesti robusti muoto. sos on jono pituudeltaan 6:n osioita; x on yksiulotteinen syöte:

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)

Jokainen sos-rivi sisältää kuusi kerrointa [b0, b1, b2, a0, a1, a2] yhdelle biquad-osiolle. sos-taulukko esilasketaan yleensä PC:llä komennolla scipy.signal.iirfilter(..., output='sos') ja kopioidaan kameran skriptiin Python-literaalina.

Valinnainen avainsana zi= kuljettaa suodattimen tilan puskurien välillä. Anna alkutila muodoltaan (n_sections, 2), niin funktio palauttaa (y, zf) – suodatetun tulosteen ja lopullisen tilan – jolloin yhden puskurin lopullinen tila syöttää seuraavan alkutilan:

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

Tämä on vakiokaava puskuroidun datan suoratoistosuodattimelle – mikrofonisyöte luettuna 1024 näytettä kerrallaan, ADC-näytteet kerättyinä DMA-vetoisina lohkoina, IMU-lukemat kerättyinä ikkunan yli.

6.15.2. Spektrogrammit

spectrogram() laskee Fourier-muunnoksen magnitudin. Se vastaa käsitteellisesti lauseketta np.sqrt(real * real + imag * imag) fft()-kutsun jälkeen, mutta kokoaa työn yhteen kutsuun – pitämättä välivaiheen real * real-, imag * imag-arvoja, summaa tai eksplisiittistä magnitudituaulukkoa missään vaiheessa RAM-muistissa. Se tekee siitä oikean työkalun missä tahansa silmukassa, jossa spektrejä lasketaan toistuvasti:

from ulab import numpy as np
from ulab import utils

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

Argumenttimuoto noudattaa fft()-funktiota: yksi reaalitaulukko tai (real, imag)-pari, kun syötteellä on imaginaariosa.

Kolme avainsana-argumenttia auttaa varaamisessa:

  • scratchpad=None – yksiulotteinen tiheä liukulukutaulukko pituudeltaan 2 * len(signal), jota spectrogram() käyttää työtilana.

  • out=None – yksiulotteinen liukulukutaulukko, johon tulos kirjoitetaan.

  • log=False – kun True, ottaa magnitudin log()-arvon ennen palauttamista, samaan kutsuun koottuna.

Suoratoistokaava on varata kaikki kerran eikä koskaan varata uudelleen:

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

Vertaa ilmeiseen mutta tuhlailevaan versioon:

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

Molemmat tuottavat samat luvut, mutta ensimmäinen versio ei varaa mitään silmukan sisällä – kamera pitää saman muistin käytössä joka iteraatiolla, ja silmukka suoriutuu nopeammin.

6.15.3. 16-bittiä leveämmät oheislaitepuskurit

frombuffer() käsittelee vain ne dtype-tyypit, jotka numpy itse määrittelee (uint8 / int8, uint16 / int16, float). Kun oheislaite tuottaa 32-bittisiä kokonaislukunäytteitä – 24- tai 32-bittinen ADC, korkearesoluutioinen mikrofoni – ulab.utils tarjoaa eksplisiittiset muunnosapufunktiot:

Kukin ottaa tavumaisen puskurin ja palauttaa liukuluku-ndarray-taulukon:

from ulab import utils

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

Funktiot hyväksyvät samat varaamista säästävät säätimet kuin spectrogram():

  • count= ja offset= ohittavat otsikon tai rajaavat lukemisen.

  • out= kirjoittaa ennalta varattuun liukulukutaulukkoon.

  • byteswap=True kun oheislaite on eri mieltä MCU:n kanssa tavujärjestyksestä.

Yhdistetty kaava – yksi from_int32_buffer()-kutsu suoraan yhteen spectrogram()-kutsuun, molemmat silmukan ulkopuolelta tulevilla out=-puskureilla – on oikea malli korkearesoluutioisella mikrofonilla pyörivälle suoratoistospektrianalysaattorille.