6.15. 필터링과 스펙트로그램¶
필터링, 평활화, 크기 스펙트럼은 원시 FFT에 인접한 작업입니다. 샘플 스트림을 평활화하거나 대역 통과 필터링하기, 할당 없이 스트리밍 루프에서 크기 스펙트럼 계산하기, 원시 주변장치 버퍼를 부동소수점 배열로 재해석하기 등이 있습니다. 사용 가능한 도구는 다음과 같습니다:
sosfilt()– 직렬 연결된 2차 섹션을 통해 적용되는 디지털 필터.spectrogram()– 중간 할당 없는 크기abs(fft(...)).from_int16_buffer()및 다른ulab.utilsfrom_*_buffer헬퍼들 – 내장frombuffer()가 다루지 않는 dtype의 버퍼에서 부동소수점 배열을 추출합니다.
6.15.1. sosfilt로 필터링하기¶
sosfilt() 는 디지털 무한 임펄스 응답(IIR) 필터를 2차 섹션(SOS)의 캐스케이드로 적용하며, 이는 수치적으로 안정적인 형식입니다. sos 는 길이가 6인 섹션들의 시퀀스이고, x 는 1차원 입력입니다:
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)
sos의 각 행은 하나의 바이쿼드 섹션에 대한 여섯 개의 계수 [b0, b1, b2, a0, a1, a2]를 담고 있습니다. sos 배열은 보통 PC에서 scipy.signal.iirfilter(..., output='sos')로 미리 계산하여 카메라 스크립트에 Python 리터럴로 복사합니다.
선택적 zi= 키워드는 버퍼 간에 필터 상태를 전달합니다. 형상이 (n_sections, 2)인 초기 상태를 전달하면 함수는 (y, zf) – 필터링된 출력과 최종 상태 – 를 반환하므로, 한 버퍼의 최종 상태가 다음 버퍼의 초기 상태로 공급됩니다:
y0, zf0 = sp.signal.sosfilt(sos, buffer0, zi=zi)
y1, zf1 = sp.signal.sosfilt(sos, buffer1, zi=zf0)
# ...
이는 버퍼링된 데이터에 대한 스트리밍 필터의 표준 패턴입니다. 한 번에 1024개 샘플씩 읽는 마이크 입력, DMA 기반 청크로 누적되는 ADC 샘플, 한 윈도우 동안 수집되는 IMU 측정값 등이 그 예입니다.
6.15.2. 스펙트로그램¶
spectrogram()은 푸리에 변환의 크기를 계산합니다. 개념적으로는 fft() 호출 이후의 np.sqrt(real * real + imag * imag)와 동등하지만, 작업을 하나의 호출로 합치며 – 중간의 real * real, imag * imag, 그 합 또는 명시적 크기 배열을 RAM에 어느 시점에도 보관하지 않습니다. 그래서 스펙트럼을 반복적으로 계산하는 어떤 루프에서든 올바른 도구가 됩니다:
from ulab import numpy as np
from ulab import utils
x = np.linspace(0, 10, num=1024)
spectrum = utils.spectrogram(x)
인자 형식은 fft()를 그대로 따릅니다. 하나의 실수 배열, 또는 입력에 허수부가 있을 때는 (real, imag) 쌍입니다.
세 개의 키워드 인자가 할당에 도움이 됩니다:
scratchpad=None–spectrogram()이 작업 공간으로 사용하는, 길이2 * len(signal)의 1차원 밀집 부동소수점 배열입니다.out=None– 결과를 기록할 1차원 부동소수점 배열입니다.log=False–True일 때, 반환하기 전에 크기에log()를 취하며, 동일한 호출에 합쳐집니다.
스트리밍 패턴은 모든 것을 한 번 할당하고 다시는 할당하지 않는 것입니다:
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 ...
명백하지만 낭비적인 버전과 비교해 보세요:
while True:
signal = read_samples(N)
spectrum = np.log(utils.spectrogram(signal)) # two allocations
둘 다 같은 숫자를 만들지만, 첫 번째 버전은 루프 내부에서 아무것도 할당하지 않습니다 – 카메라는 매 반복마다 동일한 메모리를 사용하고, 루프는 더 빠르게 실행됩니다.
6.15.3. 16비트보다 넓은 주변장치 버퍼¶
frombuffer()는 numpy 자체가 정의하는 dtype(uint8 / int8, uint16 / int16, float)만 다룹니다. 주변장치가 32비트 정수 샘플을 생성할 때 – 24비트 또는 32비트 ADC, 고해상도 마이크 – ulab.utils는 명시적 변환 헬퍼를 제공합니다:
각각은 bytes 유사 버퍼를 받아 부동소수점 ndarray를 반환합니다:
from ulab import utils
buf = bytearray([1, 1, 0, 0, 0, 0, 0, 255])
utils.from_uint32_buffer(buf)
# array([257.0, 4278190080.0])
이 함수들은 spectrogram()과 동일한 할당 절약 옵션을 받습니다:
헤더를 건너뛰거나 읽기를 제한하는
count=와offset=.미리 할당된 부동소수점 배열에 기록하는
out=.주변장치가 MCU와 바이트 순서에 대해 일치하지 않을 때의
byteswap=True.
결합된 패턴 – 하나의 from_int32_buffer() 호출을 곧바로 하나의 spectrogram() 호출로 연결하고, 둘 다 루프 외부에서 가져온 out= 버퍼를 사용하는 것 – 은 고해상도 마이크에서 동작하는 스트리밍 스펙트럼 분석기를 위한 올바른 템플릿입니다.