如何用Python实现类Audacity音频频谱峰值检测?结果不符问题排查
问题
我是数字信号处理(DSP)新手,想要找到音频文件的频谱峰值。我通常用Audacity打开音频文件并查看频谱,手动找到120Hz的峰值,但手动操作太麻烦。我误以为Audacity展示的是spectrogram(频谱图),于是用Python写了如下代码:
import matplotlib.pyplot as plt from scipy import signal from scipy.io import wavfile import numpy as np sample_rate, samples = wavfile.read('audio1.wav') frequencies, times, spectrogram = signal.spectrogram(samples, sample_rate) # 获取最大值 x,y=np.where(spectrogram == spectrogram.max()) print("最大值对应的频率索引") print(x) print("频率值") print(frequencies[x])
但运行后得到的峰值频率是74.21875Hz,和Audacity里找到的120Hz差很多。请问我哪里错了?能不能用Python实现这个任务?是不是不该从spectrogram里找峰值?
解答
核心问题:混淆了两种频谱概念
Audacity默认显示的频谱是整个音频信号的傅里叶变换(FFT)幅度谱——它把整个音频的能量在频域做了全局统计,是静态的频率分布结果。而你用的signal.spectrogram生成的是时变频谱图:它把音频切成小段分别做FFT,展示的是不同时间点的频率分布,你取的是某个时间切片里的最大频率,自然和全局峰值不一致。
用Python实现Audacity式的全局频谱峰值
直接对整个音频做FFT就能得到和Audacity一致的结果,步骤如下:
- 处理音频:如果是双声道,先转成单声道(Audacity默认显示混合后的单声道频谱)
- 计算FFT并生成幅度谱
- 生成频率轴,找到幅度最大的频率值
代码示例
import numpy as np from scipy.io import wavfile # 读取音频 sample_rate, samples = wavfile.read('audio1.wav') # 处理双声道:转单声道(取平均值) if len(samples.shape) > 1: samples = np.mean(samples, axis=1) # 计算FFT n = len(samples) fft_result = np.fft.fft(samples) # 取幅度谱(只保留正频率部分) amplitude = np.abs(fft_result[:n//2]) # 生成频率轴 frequencies = np.fft.fftfreq(n, 1/sample_rate)[:n//2] # 找到峰值对应的频率 peak_idx = np.argmax(amplitude) peak_freq = frequencies[peak_idx] print(f"全局频谱峰值频率:{peak_freq:.2f}Hz")
额外优化:过滤频谱毛刺
如果音频有高频噪声,直接取最大值可能得到干扰峰值,可以用平滑处理或峰值检测函数筛选真正的峰值:
from scipy.signal import find_peaks # 平滑幅度谱(可选) smoothed_amp = np.convolve(amplitude, np.ones(5)/5, mode='same') # 检测峰值,设置最小高度和距离过滤干扰 peaks, _ = find_peaks(smoothed_amp, height=np.max(smoothed_amp)*0.5, distance=10) # 找到最大的有效峰值 peak_idx = peaks[np.argmax(smoothed_amp[peaks])] peak_freq = frequencies[peak_idx] print(f"筛选后的峰值频率:{peak_freq:.2f}Hz")
什么时候用spectrogram?
如果你需要看某个时间段内的频率峰值而非全局峰值,spectrogram是合适的,但需要调整逻辑:比如计算每个频率在所有时间点的能量总和,再找总和最大的频率;或者定位到你在Audacity里看到120Hz的时间段,单独提取该段的频谱。
内容的提问来源于stack exchange,提问作者mad
相关产品推荐
相关产品推荐

