You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Python Matplotlib实现音频数据属性长时段绘图

解决方案:长时段传感器数据下采样与树莓派实时绘图优化

针对你在树莓派上用Matplotlib处理声卡采集数据时遇到的长时段数据下采样和实时绘图延迟问题,我整理了具体的实现方案和优化建议:


一、实现长时段数据的下采样

要绘制长时段的RMS等统计属性,直接保留每一个Chunk的计算结果会导致数据量爆炸,下采样的核心是按固定窗口聚合数据,既保留趋势又减少绘图压力。

具体实现思路:

  • 维护临时缓存,存储最近N个Chunk的RMS值和时间戳
  • 当缓存达到设定的窗口大小(或时间间隔)时,计算窗口内的统计值(均值/最大值/中位数)作为下采样后的有效数据点
  • 只保留下采样后的历史数据,避免数据量无限增长

集成到现有代码的示例:

# 新增下采样配置:每10个Chunk聚合一次(对应约0.5秒,因CHUNK=44100/20=2205,每Chunk耗时~0.05秒)
DOWN_SAMPLE_WINDOW = 10
rms_cache = []
ts_cache = []
downsampled_ts = []
downsampled_rms = []

while True:
    data = stream.read(CHUNK)
    data = np.frombuffer(data, np.int16)
    d = data.astype(np.float)
    rms2 = np.sqrt(np.mean(d**2))
    
    # 填充临时缓存
    ts_cache.append(dt.datetime.now())
    rms_cache.append(rms2)
    
    # 达到窗口大小,执行下采样
    if len(rms_cache) >= DOWN_SAMPLE_WINDOW:
        # 取窗口内RMS的均值作为下采样点(可根据需求替换为最大值/中位数)
        window_rms = np.mean(rms_cache)
        # 取窗口中间的时间戳作为下采样点的时间标记
        window_ts = ts_cache[len(ts_cache)//2]
        # 存入下采样历史数据
        downsampled_ts.append(window_ts)
        downsampled_rms.append(window_rms)
        # 清空缓存准备下一轮
        rms_cache = []
        ts_cache = []
    
    # 绘制下采样后的RMS数据(替代原来的全量数据绘图)
    ax2.clear()
    ax2.plot(downsampled_ts, downsampled_rms, color='black')
    # ... 其余绘图格式代码保持不变

二、优化树莓派上的实时绘图性能

你当前用plt.pause()+ax2.clear()的方式,在树莓派这类资源有限的设备上效率极低,这里有两个更优的方案:

1. 用FuncAnimation结合Blitting优化

无Blitting的FuncAnimation效率确实差,但开启Blitting后只会重绘变化的部分,能大幅提升性能。如果需要动态更新坐标轴,可以只在必要时调整范围,避免每次重绘全轴:

from matplotlib.animation import FuncAnimation

# 初始化时固定坐标轴初始范围,避免每次自动缩放
ax2.set_xlim(dt.datetime.now() - dt.timedelta(minutes=5), dt.datetime.now())
ax2.set_ylim(0, 2000)  # 根据你的RMS实际范围调整

def update(frame):
    global downsampled_ts, downsampled_rms, rms_cache, ts_cache
    # 读取音频数据并计算RMS
    data = stream.read(CHUNK)
    data = np.frombuffer(data, np.int16)
    d = data.astype(np.float)
    rms2 = np.sqrt(np.mean(d**2))
    
    # 填充缓存并执行下采样
    ts_cache.append(dt.datetime.now())
    rms_cache.append(rms2)
    if len(rms_cache) >= DOWN_SAMPLE_WINDOW:
        window_rms = np.mean(rms_cache)
        window_ts = ts_cache[len(ts_cache)//2]
        downsampled_ts.append(window_ts)
        downsampled_rms.append(window_rms)
        rms_cache = []
        ts_cache = []
    
    # 更新振幅曲线
    line1.set_ydata(data)
    # 更新RMS曲线(仅更新数据,不重绘整个轴)
    line2.set_data(downsampled_ts, downsampled_rms)
    
    # 当时间超出x轴范围时,平移x轴(替代clear重绘)
    if downsampled_ts and downsampled_ts[-1] > ax2.get_xlim()[1]:
        new_right = downsampled_ts[-1]
        new_left = new_right - dt.timedelta(minutes=5)
        ax2.set_xlim(new_left, new_right)
        return line1, line2, ax2
    
    return line1, line2

# 初始化RMS曲线
line2, = ax2.plot([], [], color='black')

# 创建动画并开启Blitting
ani = FuncAnimation(fig, update, interval=50, blit=True)
plt.show()

2. 改用PyQtGraph替代Matplotlib

如果Matplotlib无论怎么优化都达不到要求,PyQtGraph是更适合实时绘图的选择——它基于PyQt,底层用OpenGL渲染,在树莓派上的性能远高于Matplotlib。

简化示例框架:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PyQt5.QtCore import QTimer
import pyqtgraph as pg
import pyaudio
import numpy as np
import datetime as dt

class AudioPlotWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Sensor Audio Plot")
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        
        # 振幅绘图区
        self.amplitude_plot = pg.PlotWidget(title="Amplitude")
        self.amplitude_plot.setYRange(-10e3, 10e3)
        self.amplitude_curve = self.amplitude_plot.plot()
        layout.addWidget(self.amplitude_plot)
        
        # RMS绘图区
        self.rms_plot = pg.PlotWidget(title="RMS")
        self.rms_plot.setYRange(0, 2000)
        self.rms_curve = self.rms_plot.plot()
        layout.addWidget(self.rms_plot)
        
        # 音频初始化
        self.mic = pyaudio.PyAudio()
        self.FORMAT = pyaudio.paInt16
        self.CHANNELS = 1
        self.RATE = 44100
        self.CHUNK = int(self.RATE/20)
        self.stream = self.mic.open(format=self.FORMAT, channels=self.CHANNELS, 
                                    rate=self.RATE, input=True, frames_per_buffer=self.CHUNK)
        
        # 下采样配置
        self.DOWN_SAMPLE_WINDOW = 10
        self.rms_cache = []
        self.ts_cache = []
        self.downsampled_ts = []
        self.downsampled_rms = []
        
        # 定时更新绘图
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_plots)
        self.timer.start(50)  # 每50ms更新一次
    
    def update_plots(self):
        # 读取音频数据并更新振幅曲线
        data = self.stream.read(self.CHUNK)
        data = np.frombuffer(data, np.int16)
        self.amplitude_curve.setData(data)
        
        # 计算RMS并执行下采样
        d = data.astype(np.float)
        rms2 = np.sqrt(np.mean(d**2))
        self.ts_cache.append(dt.datetime.now())
        self.rms_cache.append(rms2)
        
        if len(self.rms_cache) >= self.DOWN_SAMPLE_WINDOW:
            window_rms = np.mean(self.rms_cache)
            window_ts = self.ts_cache[len(self.ts_cache)//2]
            self.downsampled_ts.append(window_ts.timestamp())  # PyQtGraph用时间戳更高效
            self.downsampled_rms.append(window_rms)
            self.rms_cache = []
            self.ts_cache = []
        
        # 更新RMS曲线并自动平移x轴
        self.rms_curve.setData(self.downsampled_ts, self.downsampled_rms)
        if self.downsampled_ts:
            self.rms_plot.setXRange(self.downsampled_ts[-1] - 300, self.downsampled_ts[-1])  # 显示最近5分钟数据

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AudioPlotWindow()
    window.show()
    sys.exit(app.exec_())

树莓派额外优化建议

  • 关闭不必要的后台图形进程,释放CPU资源
  • 可尝试降低采样率(如从44100降到22050),减少每个Chunk的数据量
  • 使用Python 3.9+版本,配合numpy的向量化操作进一步提升计算效率

内容的提问来源于stack exchange,提问作者Sander_k

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:02:21