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)')

运行方式

  1. 安装依赖:pip install numpy scipy matplotlib
  2. 将上述代码保存为 stft_demo.py,直接运行 python stft_demo.py

验证方法与判定标准

运行后应弹出两幅语谱图:

  • 短窗(64点):脉冲在时间轴上表现为一条清晰的竖直亮线(时间定位准确),但1kHz正弦在频率轴上表现为较宽的亮带(频率模糊)。
  • 长窗(512点):1kHz正弦表现为细窄的水平亮线(频率精确),但脉冲的能量在时间轴上被拉宽,竖直亮线变模糊。

对比两图即可直观验证:窗长增加时频率分辨率提升而时间分辨率下降,两者不可兼得。若图中能清晰看到上述差异,则案例实现正确。

关键说明

本案例使用合成信号而非真实语音,目的是清晰展示分辨率权衡——真实语音频谱更复杂,不利于观察这一核心现象。若需处理真实语音,将wavfile.read()读取的数据替换x即可,其余代码无需改动。