Python实现梅尔频谱:STFT窗口大小对幅值影响的疑问
关于STFT窗口大小导致幅值和频率范围变化的解答
1. 频率范围“变化”的真实原因
你当前绘图时,x轴用的是FFT的bin索引,而非实际频率值,这才是视觉上频率范围变化的核心原因。每个bin对应的实际频率计算公式为:
freq = bin_index * 采样率(sr) / 窗口大小(windowSize)
举个例子:
- 若采样率sr=16000,25ms窗口对应的大小是400,此时FFT的bin数为
400/2+1=201,x轴索引0-200对应实际频率0-8000Hz(奈奎斯特频率)。 - 当窗口大小设为2048时,bin数为
2048/2+1=1025,x轴索引0-1024对应的实际频率范围依然是0-8000Hz。
修正绘图代码,将x轴转换为实际频率即可统一对比:
window_size_samples = int(window_size * sr) freq_bins = np.arange(int(window_size_samples/2)+1) * sr / window_size_samples plt.plot(freq_bins, stft_matrix[0, :]) plt.xlabel('Frequency (Hz)') plt.ylabel('Magnitude') plt.show()
2. 幅值变化的原因
幅值变化主要来自三个因素:
- 窗口能量差异:Hanning窗的总能量随窗口大小增大而升高,加窗后信号的能量也会随之增加,最终反映在FFT幅值上。
- FFT的幅值特性:FFT的结果是窗口内所有样本的加权求和,窗口越大包含的样本数越多,幅值绝对值自然越大。
- 未做幅值归一化:标准STFT通常会对结果做归一化,补偿窗函数带来的能量损失。针对Hanning窗,常用的归一化方式是用
2 / np.sum(window)来修正:
修改你的STFT函数:def stft(signal, windowSize, windowStep): n_frames = 1 + int((len(signal)-windowSize)/windowStep) stft_matrix = np.zeros((n_frames, int(windowSize/2)+1),dtype=np.float32) # 预计算窗函数和归一化系数 window = np.hanning(windowSize) norm_factor = 2 / np.sum(window) for i in range(n_frames): start = i * windowStep end = start + windowSize frame = signal[start:end] * window frame_fft = np.fft.fft(frame)[:int(windowSize/2)+1] stft_matrix[i, :] = np.abs(frame_fft) * norm_factor return stft_matrix
3. 额外建议
- 计算帧数量时,
int()截断可能导致丢帧,建议用np.floor()或直接使用librosa的工具函数librosa.util.frame处理帧分割,逻辑更严谨。 - 可以用librosa官方的STFT实现验证你的结果:
librosa_stft = np.abs(librosa.stft(x, n_fft=int(window_size*sr), hop_length=int(window_step*sr), window='hann'))
你的STFT核心逻辑是正确的,只是缺少频率轴转换和幅值归一化步骤,导致出现视觉上的“异常”,修正后即可得到一致的对比结果。
内容的提问来源于stack exchange,提问作者Naufal Rifqi Habibie
相关产品推荐
相关产品推荐

