4. 语音特征提取

任务目标

本案例实现语音特征提取中的滤波器组(Filter Bank)与梅尔频率倒谱系数(MFCC)核心算法。输入为一段原始语音波形(以NumPy数组表示),输出为对应的滤波器组特征与MFCC特征矩阵,用于后续关键词识别模型的输入。

环境依赖

需要 numpy 和 scipy。若需读取真实音频文件,额外使用 soundfile 或 librosa(本案例为聚焦算法,直接构造波形数据)。

核心实现

import numpy as np
from scipy.fft import dct

def pre_emphasis(signal, alpha=0.97):
    """预加重:提升高频分量"""
    return np.append(signal[0], signal[1:] - alpha * signal[:-1])

def framing(signal, sample_rate, frame_len=0.025, frame_step=0.010):
    """分帧:每帧25ms,帧移10ms"""
    frame_length = int(sample_rate * frame_len)
    frame_step_len = int(sample_rate * frame_step)
    signal_len = len(signal)
    num_frames = 1 + (signal_len - frame_length) // frame_step_len
    indices = np.tile(np.arange(0, frame_length), (num_frames, 1)) + \
              np.tile(np.arange(0, num_frames * frame_step_len, frame_step_len), (frame_length, 1)).T
    return signal[indices]

def hz_to_mel(hz):
    return 2595 * np.log10(1 + hz / 700)

def mel_to_hz(mel):
    return 700 * (10 ** (mel / 2595) - 1)

def mel_filter_bank(sample_rate, n_fft, n_mels=40):
    """构造梅尔滤波器组(三角形滤波器)"""
    low_mel = hz_to_mel(0)
    high_mel = hz_to_mel(sample_rate / 2)
    mel_points = np.linspace(low_mel, high_mel, n_mels + 2)
    hz_points = mel_to_hz(mel_points)
    bin_points = np.floor((n_fft + 1) * hz_points / sample_rate).astype(int)
    filter_bank = np.zeros((n_mels, n_fft // 2 + 1))
    for i in range(1, n_mels + 1):
        left, center, right = bin_points[i - 1], bin_points[i], bin_points[i + 1]
        if center > left:
            filter_bank[i - 1, left:center] = np.linspace(0, 1, center - left)
        if right > center:
            filter_bank[i - 1, center:right] = np.linspace(1, 0, right - center)
    return filter_bank

def extract_fbank(signal, sample_rate, n_mels=40, n_fft=512):
    """提取滤波器组特征(对数能量)"""
    emphasized = pre_emphasis(signal)
    frames = framing(emphasized, sample_rate)
    # 加汉明窗
    window = np.hamming(int(sample_rate * 0.025))
    frames *= window
    # 功率谱
    mag_spec = np.abs(np.fft.rfft(frames, n_fft))
    power_spec = mag_spec ** 2
    # 梅尔滤波
    fbank = mel_filter_bank(sample_rate, n_fft, n_mels)
    mel_energy = np.dot(power_spec, fbank.T)
    mel_energy = np.where(mel_energy == 0, np.finfo(float).eps, mel_energy)
    return np.log(mel_energy)

def extract_mfcc(fbank, n_mfcc=13):
    """从滤波器组特征计算MFCC(DCT-II)"""
    return dct(fbank, type=2, axis=1, norm='ortho')[:, :n_mfcc]

# ===== 最小验证入口 =====
if __name__ == "__main__":
    sample_rate = 16000
    duration = 0.1  # 100ms
    t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
    # 合成包含两个频率分量的测试信号
    test_signal = 0.5 * np.sin(2 * np.pi * 300 * t) + 0.3 * np.sin(2 * np.pi * 1800 * t)

    fbank_feat = extract_fbank(test_signal, sample_rate)
    mfcc_feat = extract_mfcc(fbank_feat)

    print(f"滤波器组特征形状: {fbank_feat.shape}")  # 预期: (帧数, 40)
    print(f"MFCC特征形状: {mfcc_feat.shape}")        # 预期: (帧数, 13)
    print(f"滤波器组特征值范围: [{fbank_feat.min():.2f}, {fbank_feat.max():.2f}]")
    print(f"MFCC第0列(能量)均值: {mfcc_feat[:, 0].mean():.4f}")

运行与验证

直接运行脚本:

python feature_extraction.py

通过条件:

  • 程序无异常退出,输出特征形状与注释一致(帧数取决于输入长度,100ms@16kHz经分帧后约8帧);
  • 滤波器组特征值均为负数(对数能量),MFCC第0列数值显著大于其他列(代表帧能量);
  • 修改输入信号频率(如将1800Hz改为200Hz),MFCC高阶系数应发生明显变化,说明特征对频谱内容敏感。

关键说明

  • 本实现覆盖从波形到MFCC的完整链路:预加重→分帧→加窗→FFT→梅尔滤波→对数→DCT;
  • 滤波器组特征保留40维(含较多频谱细节),MFCC取前13维(含大部分能量信息),两者均可直接作为关键词识别模型的输入;
  • 实际工程中建议使用 librosa.feature.mfcc 验证本实现正确性,但本案例不依赖该库,算法完全自洽。
语音特征提取流程(滤波器组 → MFCC) 原始语音 16kHz采样 预加重 α=0.97 分帧+加窗 25ms/10ms FFT功率谱 512点 梅尔滤波 40通道 对数能量 滤波器组特征 DCT 取13维 log DCT-II MFCC