6.15. Filtragem e espectrogramas¶
A filtragem, suavização e espectros de magnitude são as tarefas adjacentes a uma FFT bruta: suavizar ou aplicar um filtro passa-banda a um fluxo de amostras, calcular espectros de magnitude num ciclo contínuo sem alocações, e reinterpretar buffers brutos de periféricos como arrays de floats. As ferramentas disponíveis:
sosfilt()– filtro digital aplicado através de secções de segunda ordem em cascata.spectrogram()– magnitudeabs(fft(...))sem alocações intermédias.from_int16_buffer()e os outros auxiliaresulab.utilsfrom_*_buffer– extraem um array de floats de um buffer cujo dtype ofrombuffer()incorporado não suporta.
6.15.1. Filtragem com sosfilt¶
sosfilt() aplica um filtro digital de resposta impulsiva infinita (IIR) como uma cascata de secções de segunda ordem (SOS) – uma forma numericamente robusta. sos é uma sequência de secções de comprimento 6; x é a entrada a 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)
Cada linha de sos contém seis coeficientes [b0, b1, b2, a0, a1, a2] para uma secção biquad. O array sos é geralmente pré-calculado num PC com scipy.signal.iirfilter(..., output='sos') e copiado para o script da câmara como um literal Python.
A palavra-chave opcional zi= transporta o estado do filtro entre buffers. Passe um estado inicial de forma (n_sections, 2) e a função devolve (y, zf) – a saída filtrada e o estado final – para que o estado final de um buffer alimente o estado inicial do seguinte:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
Este é o padrão standard para um filtro contínuo sobre dados em buffer – entrada de microfone lida 1024 amostras de cada vez, amostras de ADC acumuladas em blocos conduzidos por DMA, leituras de IMU recolhidas ao longo de uma janela.
6.15.2. Espectrogramas¶
spectrogram() calcula a magnitude da transformada de Fourier. É conceptualmente equivalente a np.sqrt(real * real + imag * imag) após uma chamada a fft(), mas reúne o trabalho numa única chamada – sem manter o real * real intermédio, imag * imag, a soma, ou o array de magnitude explícito na RAM em qualquer momento. Isso torna-o a ferramenta certa em qualquer ciclo onde os espectros são calculados repetidamente:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
A forma do argumento espelha fft(): um array real, ou um par (real, imag) quando a entrada tem parte imaginária.
Três argumentos de palavra-chave ajudam na alocação:
scratchpad=None– um array de floats denso a 1-D de comprimento2 * len(signal)quespectrogram()usa como espaço de trabalho.out=None– um array de floats a 1-D para escrever o resultado.log=False– quandoTrue, calculalog()da magnitude antes de devolver, incorporado na mesma chamada.
O padrão de processamento contínuo é alocar tudo uma vez e nunca mais alocar:
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 ...
Compare com a versão óbvia mas ineficiente:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
Ambas produzem os mesmos números, mas a primeira versão não aloca nada dentro do ciclo – a câmara mantém a mesma memória em uso a cada iteração, e o ciclo corre mais rapidamente.
6.15.3. Buffers de periféricos com mais de 16 bits¶
frombuffer() apenas suporta os dtypes que o próprio numpy define (uint8 / int8, uint16 / int16, float). Quando um periférico produz amostras de inteiros de 32 bits – um ADC de 24 ou 32 bits, um microfone de alta resolução – o ulab.utils expõe auxiliares de conversão explícitos:
Cada um recebe um buffer semelhante a bytes e devolve um ndarray de floats:
from ulab import utils
buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])
As funções aceitam os mesmos controlos de poupança de alocação que spectrogram():
count=eoffset=para saltar um cabeçalho ou limitar a leitura.out=para escrever num array de floats pré-alocado.byteswap=Truequando o periférico não concorda com o MCU na ordem dos bytes.
O padrão combinado – uma chamada from_int32_buffer() diretamente para uma chamada spectrogram(), ambas com buffers out= de fora do ciclo – é o modelo certo para um analisador de espectro contínuo a correr num microfone de alta resolução.