使用SciPy查找FFT峰值并绘图时出现异常问题求助
问题分析与修复方案
核心错误点
直接对复数FFT结果找峰值
scipy.signal.find_peaks仅能处理实数值,而scipy.fft.fft返回的是复数数组,直接传入会错误识别实部/虚部的波动,而非信号的幅度峰值。频率轴计算逻辑混乱
代码中xf = np.linspace(0.0, 1.0/(10.0*T), n//10)存在两个问题:- 音频信号的有效频率范围是0到奈奎斯特频率(
rate/2,即1/(2*T)),超过该范围的FFT结果是镜像冗余数据。 - 仅取前
n//10个点绘制,会丢失大部分有效频率信息,且峰值可能落在未绘制区间,导致显示错位。
- 音频信号的有效频率范围是0到奈奎斯特频率(
峰值绘制未取幅度绝对值
plt.plot(z, 10.0/n * yf[z], "x")中yf[z]是复数,直接绘制会出现数值异常,必须取绝对值才能对应幅度轴刻度。峰值位置超出绘制范围
若峰值出现在n//10之后的位置,绘制时会在图上显示完全偏离的错误点。
修正后的代码
import struct import numpy as np import scipy from matplotlib import pyplot as plt def fft_plot(audio, rate): n = len(audio) T = 1/rate # 计算FFT并提取幅度值 yf = scipy.fft.fft(audio) yf_amp = np.abs(yf) # 基于幅度谱查找峰值,可根据信号调整prominence参数 z, _ = scipy.signal.find_peaks(yf_amp, prominence=100) # 生成正确的频率轴:覆盖0到奈奎斯特频率,点数匹配FFT前半部分 xf = np.linspace(0.0, rate/2, n//2) fig, ax = plt.subplots() # 绘制归一化后的幅度谱(仅前半部分有效数据) ax.plot(xf, (2.0/n) * yf_amp[:n//2]) # 过滤掉超出绘制范围的峰值,仅显示有效区间内的点 valid_peaks = z[z < n//2] ax.plot(xf[valid_peaks], (2.0/n) * yf_amp[valid_peaks], "x", color="red") plt.grid() plt.xlabel("Frequency -->") plt.ylabel("Magnitude") plt.show() def get_span(fi): b = fi.read(768*4) if len(b) < 768*4: return None r = struct.unpack('768i', b) return [float(x/(2**31-1)) for x in r] with open('/tmp/samples.raw', 'rb') as fi: while span := get_span(fi): fft_plot(span, 48000)
额外说明
- 对于440Hz方波,其FFT峰值应出现在440Hz、1320Hz、2200Hz等奇次谐波位置,修正后的代码可正确识别这些峰值。
- 可根据实际信号的幅度大小调整
prominence参数,避免误识别噪声带来的伪峰值。 - 用
2.0/n作为归一化系数,更符合实信号FFT幅度谱的物理意义。
内容的提问来源于stack exchange,提问作者ZioByte
相关产品推荐
相关产品推荐

