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验证本实现正确性,但本案例不依赖该库,算法完全自洽。