3. 语音特征提取

任务目标

本案例实现语音特征提取中的MFCC(Mel频率倒谱系数)计算流程。输入为一帧时域语音信号(采样率16kHz,帧长25ms,即400个采样点),输出为该帧对应的13维MFCC特征向量(不含第0维能量)。代码使用Python和NumPy实现,不依赖第三方音频处理库,便于理解算法内部机制。

核心实现

import numpy as np

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

def framing(signal, sample_rate=16000, frame_len=0.025, frame_shift=0.010):
    """分帧:返回 (num_frames, frame_len_samples) 数组"""
    frame_len_samples = int(sample_rate * frame_len)
    frame_shift_samples = int(sample_rate * frame_shift)
    num_frames = 1 + (len(signal) - frame_len_samples) // frame_shift_samples
    frames = np.stack([signal[i * frame_shift_samples: i * frame_shift_samples + frame_len_samples]
                       for i in range(num_frames)])
    return frames

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_filterbank(sample_rate=16000, n_fft=512, n_mels=26):
    """构造Mel滤波器组,返回 (n_mels, n_fft//2+1) 矩阵"""
    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)

    filterbank = np.zeros((n_mels, n_fft // 2 + 1))
    for m in range(1, n_mels + 1):
        left, center, right = bin_points[m - 1], bin_points[m], bin_points[m + 1]
        for k in range(left, center):
            filterbank[m - 1, k] = (k - left) / (center - left)
        for k in range(center, right):
            filterbank[m - 1, k] = (right - k) / (right - center)
    return filterbank

def mfcc(frame, sample_rate=16000, n_mels=26, n_mfcc=13):
    """
    计算单帧MFCC特征
    输入: frame - 一帧时域信号 (400,)
    输出: (13,) 维MFCC向量
    """
    # 1. 预加重
    emphasized = pre_emphasis(frame)

    # 2. 加窗(汉明窗)
    windowed = emphasized * np.hamming(len(emphasized))

    # 3. FFT(512点,不足补零)
    n_fft = 512
    spectrum = np.fft.rfft(windowed, n=n_fft)
    power = np.abs(spectrum) ** 2

    # 4. Mel滤波
    filterbank = mel_filterbank(sample_rate, n_fft, n_mels)
    mel_energy = np.dot(filterbank, power)

    # 5. 取对数
    log_mel = np.log(mel_energy + 1e-10)

    # 6. DCT-II(取前n_mfcc个系数)
    n_frames = len(log_mel)
    dct_coeffs = np.zeros(n_mfcc)
    for k in range(n_mfcc):
        dct_coeffs[k] = np.sum(log_mel * np.cos(np.pi * k * (2 * np.arange(n_frames) + 1) / (2 * n_frames)))
    dct_coeffs *= np.sqrt(2 / n_frames)

    return dct_coeffs

# ===== 最小验证闭环 =====
if __name__ == "__main__":
    # 构造测试信号:400点、16kHz的1kHz正弦波(模拟一帧语音)
    sample_rate = 16000
    t = np.arange(400) / sample_rate
    test_frame = 0.5 * np.sin(2 * np.pi * 1000 * t)

    # 计算MFCC
    mfcc_vec = mfcc(test_frame)

    # 验证输出维度
    assert mfcc_vec.shape == (13,), f"输出维度错误: {mfcc_vec.shape}"

    # 验证数值有限性
    assert np.all(np.isfinite(mfcc_vec)), "MFCC包含非有限值"

    # 验证DCT系数能量集中性(前几阶系数应显著大于高阶系数)
    energy_ratio = np.sum(np.abs(mfcc_vec[:3])) / (np.sum(np.abs(mfcc_vec)) + 1e-12)
    assert energy_ratio > 0.5, f"前3阶系数能量占比异常: {energy_ratio:.3f}"

    print("MFCC维度: 13")
    print("前5个系数:", np.round(mfcc_vec[:5], 4))
    print("验证通过: 输出维度正确,数值有限,能量集中性合理")

运行与验证

环境依赖:仅需Python 3.6+和NumPy(pip install numpy)。

运行方式:将上述代码保存为 mfcc_extract.py,在终端执行 python mfcc_extract.py。

验证判定标准:

  • 程序无异常退出,输出MFCC维度: 13;
  • 输出前5个系数为有限数值;
  • 显示验证通过,说明三个断言全部满足:维度正确、数值有限、前3阶系数能量占比大于50%(符合语音信号DCT能量集中的特性)。

关键说明

本实现覆盖MFCC完整计算链:预加重→分帧(单帧输入已隐含)→加窗→FFT→Mel滤波→对数→DCT。代码中framing函数已提供但未在mfcc()中调用,因为本案例输入为单帧;若处理连续语音,需先调用framing对整段信号分帧,再逐帧调用mfcc()。Mel滤波器组采用26个滤波器,输出取前13维DCT系数(不含第0维能量项),这是语音唤醒任务中常用的配置。