4. 实现预加重滤波器
任务目标
对一段语音信号执行短时傅里叶变换(STFT),将结果以语谱图形式可视化,并通过改变窗长观察时间分辨率与频率分辨率之间的权衡关系。
核心原理与实现
STFT通过在时间轴上滑动窗函数截取信号片段,对每个片段做FFT,得到二维时频矩阵。窗长越短,时间分辨率越高(能区分相邻的瞬态事件),但频率分辨率越低(频域bin间隔变大);窗长越长则相反。本案例使用Python实现,核心依赖为numpy、scipy和matplotlib。
完整代码
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from scipy.signal import get_window
def stft(x, win_len, hop_len, n_fft, win_type='hann'):
"""短时傅里叶变换,返回复数谱矩阵 shape=(n_freq, n_frame)"""
win = get_window(win_type, win_len)
n_frames = 1 + (len(x) - win_len) // hop_len
# 预分配频谱矩阵
spec = np.zeros((n_fft // 2 + 1, n_frames), dtype=np.complex128)
for i in range(n_frames):
start = i * hop_len
segment = x[start:start + win_len] * win
# 零填充至n_fft长度
spectrum = np.fft.rfft(segment, n=n_fft)
spec[:, i] = spectrum
return spec
def plot_spectrogram(spec, sr, hop_len, title):
"""绘制语谱图(dB刻度)"""
magnitude = np.abs(spec)
# 转dB并限制动态范围
db = 20 * np.log10(magnitude + 1e-10)
db = np.clip(db, db.max() - 80, db.max())
plt.figure(figsize=(10, 5))
plt.imshow(db, aspect='auto', origin='lower', cmap='magma',
extent=[0, spec.shape[1] * hop_len / sr, 0, sr / 2])
plt.xlabel('时间 (s)')
plt.ylabel('频率 (Hz)')
plt.title(title)
plt.colorbar(label='dB')
plt.tight_layout()
plt.show()
# 生成测试信号:1kHz正弦 + 0.2s处短促脉冲
sr = 8000
t = np.linspace(0, 1, sr, endpoint=False)
x = 0.5 * np.sin(2 * np.pi * 1000 * t)
x[1600:1620] += 2.0 # 在0.2s处加入脉冲
# 短窗:高时间分辨率,低频率分辨率
win_len_short = 64
hop = win_len_short // 2
spec_short = stft(x, win_len_short, hop, n_fft=256)
plot_spectrogram(spec_short, sr, hop, f'STFT 窗长={win_len_short}点 ({win_len_short/sr*1000:.1f}ms)')
# 长窗:高频率分辨率,低时间分辨率
win_len_long = 512
hop = win_len_long // 2
spec_long = stft(x, win_len_long, hop, n_fft=1024)
plot_spectrogram(spec_long, sr, hop, f'STFT 窗长={win_len_long}点 ({win_len_long/sr*1000:.1f}ms)')
运行方式
- 安装依赖:
pip install numpy scipy matplotlib - 将上述代码保存为
stft_demo.py,直接运行python stft_demo.py
验证方法与判定标准
运行后应弹出两幅语谱图:
- 短窗(64点):脉冲在时间轴上表现为一条清晰的竖直亮线(时间定位准确),但1kHz正弦在频率轴上表现为较宽的亮带(频率模糊)。
- 长窗(512点):1kHz正弦表现为细窄的水平亮线(频率精确),但脉冲的能量在时间轴上被拉宽,竖直亮线变模糊。
对比两图即可直观验证:窗长增加时频率分辨率提升而时间分辨率下降,两者不可兼得。若图中能清晰看到上述差异,则案例实现正确。
关键说明
本案例使用合成信号而非真实语音,目的是清晰展示分辨率权衡——真实语音频谱更复杂,不利于观察这一核心现象。若需处理真实语音,将wavfile.read()读取的数据替换x即可,其余代码无需改动。