6.15. การกรองและสเปกโตรแกรม

การกรอง การปรับให้เรียบ และสเปกตรัมขนาด เป็นงานที่อยู่ติดกับ FFT ดิบ ได้แก่ การปรับให้เรียบหรือกรองแบบ band-pass สตรีมของตัวอย่าง การคำนวณสเปกตรัมขนาดในลูปการสตรีมโดยไม่ต้องจัดสรรหน่วยความจำ และการตีความบัฟเฟอร์อุปกรณ์ต่อพ่วงดิบใหม่เป็นอาร์เรย์ float เครื่องมือที่มีอยู่:

  • sosfilt() -- ตัวกรองดิจิทัลที่ใช้ผ่านส่วนอันดับสองแบบเรียงต่อกัน

  • spectrogram() -- ขนาด abs(fft(...)) โดยไม่มีการจัดสรรหน่วยความจำกลาง

  • from_int16_buffer() และตัวช่วยเหลือ from_*_buffer อื่น ๆ ของ ulab.utils -- ดึงอาร์เรย์ float ออกจากบัฟเฟอร์ที่มี dtype ซึ่ง frombuffer() ในตัวไม่รองรับ

6.15.1. การกรองด้วย sosfilt

sosfilt() ใช้ตัวกรองดิจิทัล infinite impulse response (IIR) เป็นแบบเรียงต่อกันของ second-order sections (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] สำหรับส่วน biquad หนึ่งส่วน อาร์เรย์ sos มักถูกคำนวณล่วงหน้าบน PC ด้วย scipy.signal.iirfilter(..., output='sos') แล้วคัดลอกลงในสคริปต์กล้องเป็น Python literal

คีย์เวิร์ด 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 ตัวอย่างต่อครั้ง ตัวอย่าง ADC ที่สะสมเป็นชิ้น ๆ ที่ขับเคลื่อนด้วย DMA หรือการอ่าน IMU ที่รวบรวมไว้ในหน้าต่าง

6.15.2. สเปกโตรแกรม

spectrogram() คำนวณขนาดของการแปลงฟูเรียร์ มีความเท่ากันทางแนวคิดกับ np.sqrt(real * real + imag * imag) หลังการเรียก fft() แต่รวมงานทั้งหมดไว้ในการเรียกครั้งเดียว โดยไม่เก็บ 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 -- อาร์เรย์ float แบบหนาแน่น 1 มิติที่มีความยาว 2 * len(signal) ซึ่ง spectrogram() ใช้เป็นพื้นที่ทำงาน

  • out=None -- อาร์เรย์ float 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() รองรับเฉพาะ dtype ที่ numpy กำหนดเอง (uint8 / int8, uint16 / int16, float) เมื่ออุปกรณ์ต่อพ่วงผลิตตัวอย่างจำนวนเต็ม 32 บิต เช่น ADC 24 หรือ 32 บิต หรือไมโครโฟนความละเอียดสูง ulab.utils จะเปิดเผยฟังก์ชันช่วยเหลือการแปลงที่ชัดเจน:

แต่ละตัวรับบัฟเฟอร์ที่คล้ายไบต์และคืนค่า ndarray แบบ float:

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= เพื่อเขียนลงในอาร์เรย์ float ที่จัดสรรล่วงหน้า

  • byteswap=True เมื่ออุปกรณ์ต่อพ่วงไม่ตรงกับ MCU ในเรื่องลำดับไบต์

รูปแบบรวม การเรียก from_int32_buffer() หนึ่งครั้งตรงไปยังการเรียก spectrogram() หนึ่งครั้ง ทั้งคู่ใช้บัฟเฟอร์ out= จากภายนอกลูป เป็นเทมเพลตที่ถูกต้องสำหรับเครื่องวิเคราะห์สเปกตรัมแบบสตรีมที่ทำงานบนไมโครโฟนความละเอียดสูง