梅尔频率倒谱系数(MFCC)是语音识别和音频处理中广泛使用的特征提取技术,它模拟了人耳对声音的非线性感知特性。其Python实现通常遵循一套标准化的信号处理流程,下面将详细解构其原理、步骤并提供完整的代码示例。
### 一、MFCC提取核心步骤与原理
MFCC的提取是一个多阶段的信号变换过程,旨在将原始的时域音频信号转换为一组能够有效表征其声学特性的倒谱系数。其主要步骤可概括如下表所示:
| 步骤 | 目的 | 关键操作/公式 |
| :--- | :--- | :--- |
| **1. 预加重** | 提升高频分量,平衡频谱 | `y[t] = x[t] - α * x[t-1]` (通常α=0.97) [ref_1][ref_2][ref_3] |
| **2. 分帧** | 将非平稳信号近似为短时平稳处理 | 帧长通常为20-40ms,帧移为10ms(重叠50%)[ref_1][ref_2] |
| **3. 加窗** | 减少分帧带来的频谱泄漏 | 应用汉明窗:`w[n] = 0.54 - 0.46 * cos(2πn/(N-1))` [ref_1][ref_3] |
| **4. 快速傅里叶变换** | 将时域信号转换为频域 | 计算每帧信号的功率谱 `P = \|FFT(x_windowed)\|^2 / N` [ref_2][ref_5] |
| **5. 梅尔滤波器组** | 将线性频率映射到模拟人耳感知的梅尔尺度 | 一组重叠的三角滤波器,在梅尔尺度上均匀分布 [ref_1][ref_2][ref_5] |
| **6. 取对数** | 将乘性噪声(如信道影响)转换为加性 | 计算每个滤波器组输出的对数能量 `log(E)` [ref_2][ref_4] |
| **7. 离散余弦变换** | 解相关,压缩信息,得到倒谱系数 | `MFCC[i] = Σ_{j=1}^{M} log(E[j]) * cos(i*(j-0.5)*π/M)` [ref_1][ref_4][ref_5] |
| **8. 动态特征提取** | 表征特征的时序变化 | 计算一阶差分(Delta)和二阶差分(Delta-Delta)[ref_4] |
其中,**梅尔频率**与**线性频率**的转换关系为:`mel(f) = 2595 * log10(1 + f / 700)` [ref_2][ref_5]。
### 二、Python代码实现详解
以下是一个从零开始实现的MFCC提取函数,并附有详细注释。
```python
import numpy as np
import scipy.fftpack as fft
from scipy.signal import get_window
def compute_mfcc(signal, sample_rate=16000, frame_len=0.025, frame_shift=0.01,
num_filters=26, nfft=512, num_ceps=13, pre_emphasis_coeff=0.97):
"""
计算音频信号的MFCC特征。
参数:
signal: 输入的一维音频信号数组。
sample_rate: 采样率 (Hz)。
frame_len: 帧长 (秒)。
frame_shift: 帧移 (秒)。
num_filters: 梅尔滤波器数量。
nfft: FFT点数。
num_ceps: 需要保留的MFCC系数个数(通常2-13维)。
pre_emphasis_coeff: 预加重系数。
返回:
mfcc_feat: 形状为 (帧数, num_ceps) 的MFCC特征矩阵。
"""
# ---------- 1. 预加重 ----------
emphasized_signal = np.append(signal[0], signal[1:] - pre_emphasis_coeff * signal[:-1]) [ref_1][ref_3]
# ---------- 2. 分帧 ----------
frame_length = int(round(frame_len * sample_rate))
frame_step = int(round(frame_shift * sample_rate))
signal_length = len(emphasized_signal)
num_frames = int(np.ceil((signal_length - frame_length) / frame_step)) + 1
# 填充信号以确保能分出整数帧
pad_signal_length = (num_frames - 1) * frame_step + frame_length
pad_signal = np.pad(emphasized_signal, (0, pad_signal_length - signal_length), 'constant')
# 使用切片操作创建帧矩阵 (高效方式)
indices = np.tile(np.arange(0, frame_length), (num_frames, 1)) + \
np.tile(np.arange(0, num_frames * frame_step, frame_step), (frame_length, 1)).T
frames = pad_signal[indices.astype(np.int32, copy=False)]
# ---------- 3. 加窗 (汉明窗) ----------
frames *= get_window('hamming', frame_length) [ref_1][ref_3]
# ---------- 4. 快速傅里叶变换与功率谱 ----------
mag_frames = np.absolute(np.fft.rfft(frames, nfft)) # 使用rfft计算实数FFT,更高效
pow_frames = ((1.0 / nfft) * (mag_frames ** 2)) [ref_2][ref_5]
# ---------- 5. 梅尔滤波器组 ----------
# 将频率(Hz)转换为梅尔频率
def hz_to_mel(f):
return 2595 * np.log10(1 + f / 700.0) [ref_2][ref_5]
def mel_to_hz(m):
return 700 * (10 ** (m / 2595.0) - 1)
low_freq_mel = hz_to_mel(0)
high_freq_mel = hz_to_mel(sample_rate / 2)
mel_points = np.linspace(low_freq_mel, high_freq_mel, num_filters + 2) # 创建滤波器中心点
hz_points = mel_to_hz(mel_points)
# 计算FFT bins对应的频率点
fft_bins = np.floor((nfft + 1) * hz_points / sample_rate).astype(int)
# 构建三角形滤波器组
filter_bank = np.zeros((num_filters, int(nfft / 2 + 1)))
for m in range(1, num_filters + 1):
f_m_minus, f_m, f_m_plus = fft_bins[m-1], fft_bins[m], fft_bins[m+1]
for k in range(f_m_minus, f_m):
filter_bank[m-1, k] = (k - fft_bins[m-1]) / (fft_bins[m] - fft_bins[m-1])
for k in range(f_m, f_m_plus):
filter_bank[m-1, k] = (fft_bins[m+1] - k) / (fft_bins[m+1] - fft_bins[m])
# ---------- 6. 滤波器组应用与取对数 ----------
filter_bank_energies = np.dot(pow_frames, filter_bank.T)
# 将0值替换为一个非常小的正数,防止取log(0)
filter_bank_energies = np.where(filter_bank_energies == 0, np.finfo(float).eps, filter_bank_energies)
log_filter_bank_energies = np.log(filter_bank_energies) [ref_2][ref_4]
# ---------- 7. 离散余弦变换 (DCT) 得到MFCC ----------
mfcc = fft.dct(log_filter_bank_energies, type=2, axis=1, norm='ortho') [ref_1][ref_4]
mfcc_feat = mfcc[:, 1:num_ceps+1] # 保留第2到第num_ceps+1个系数,通常忽略第0个系数(能量)[ref_1]
# (可选) 谱加权 (Liftering),提升高频MFCC系数
# cep_lifter = 22
# (nframes, ncoeff) = mfcc_feat.shape
# n = np.arange(ncoeff)
# lift = 1 + (cep_lifter / 2) * np.sin(np.pi * n / cep_lifter)
# mfcc_feat *= lift
return mfcc_feat
# ========== 使用示例 ==========
if __name__ == "__main__":
# 1. 生成一个示例音频信号(1秒,16000Hz采样率,440Hz正弦波)
duration = 1.0
sample_rate = 16000
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
example_signal = 0.5 * np.sin(2 * np.pi * 440.0 * t) # 440Hz 正弦波
# 2. 计算MFCC
mfccs = compute_mfcc(example_signal, sample_rate=sample_rate)
print(f"MFCC特征矩阵形状: {mfccs.shape}") # 例如: (99, 13)
print(f"第一帧的MFCC系数: {mfccs[0]}")
```
### 三、使用现有库快速提取
对于实际项目,使用成熟的库(如`python_speech_features`或`librosa`)更为高效可靠。
#### 1. 使用 `python_speech_features` 库
```python
from python_speech_features import mfcc, logfbank
import scipy.io.wavfile as wav
# 读取音频文件
sample_rate, signal = wav.read('audio.wav')
# 提取MFCC特征 (默认参数已针对语音识别优化)
mfcc_features = mfcc(signal, samplerate=sample_rate, numcep=13, nfilt=26, nfft=512)
# 提取对数滤波器组能量 (FBank)
fbank_features = logfbank(signal, samplerate=sample_rate, nfilt=26, nfft=512)
print(f"python_speech_features MFCC shape: {mfcc_features.shape}") [ref_4]
```
#### 2. 使用 `librosa` 库
```python
import librosa
# 加载音频
signal, sample_rate = librosa.load('audio.wav', sr=16000)
# 提取MFCC特征
mfcc_librosa = librosa.feature.mfcc(y=signal, sr=sample_rate, n_mfcc=13, n_fft=512, hop_length=160, n_mels=26)
# librosa的输出是 (n_mfcc, 时间帧数),通常需要转置以匹配其他库的格式
mfcc_librosa = mfcc_librosa.T
print(f"Librosa MFCC shape: {mfcc_librosa.shape}")
```
### 四、关键参数选择与应用场景
| 参数 | 典型值 | 说明 |
| :--- | :--- | :--- |
| **采样率 (`sample_rate`)** | 16000 Hz | 电话语音带宽通常为8kHz,16kHz可保留足够信息。 |
| **帧长 (`frame_len`)** | 0.025 s (25ms) | 平衡时间分辨率和平稳性假设。 |
| **帧移 (`frame_shift`)** | 0.01 s (10ms) | 提供足够的时间重叠,确保信息连续性。 |
| **FFT点数 (`nfft`)** | 512 | 对于16kHz采样率,频率分辨率约为31.25Hz,通常足够。 |
| **梅尔滤波器数 (`num_filters`)** | 26-40 | 数量越多,频带划分越细,但维度也越高。 |
| **MFCC系数 (`num_ceps`)** | 13 | 通常保留2-13维,第0维(能量)和第1维有时被剔除或单独处理。 |
**应用场景示例**:在语音识别(ASR)系统中,13维MFCC系数常与它们的一阶、二阶差分(共39维)拼接使用,作为声学模型的输入特征 [ref_4]。在声纹识别中,MFCC特征也用于表征说话人独特的声道特性 [ref_4]。
### 五、算法变体与增强
1. **差分系数(Delta & Delta-Delta)**:为了捕捉特征的动态变化,会对MFCC计算一阶和二阶差分,公式为:
`d_t = Σ_{n=1}^{N} n (c_{t+n} - c_{t-n}) / (2Σ_{n=1}^{N} n^2)`,其中`c_t`是第t帧的MFCC向量 [ref_4]。
2. **均值归一化(CMVN)**:对每个MFCC系数维度跨帧进行均值和方差归一化,以消除信道噪声和录音条件的影响 [ref_1]。
3. **FBank特征**:在某些深度学习模型中(如CNN、端到端ASR),直接使用经过梅尔滤波器组和取对数后的**对数梅尔滤波器组能量(Log-Mel Filterbank Energies)** 作为特征,跳过DCT步骤,以保留更多频谱细节 [ref_2]。
通过上述步骤和代码,可以完整地实现从音频信号到MFCC特征的转换。实际应用中,建议先使用`librosa`或`python_speech_features`等库进行快速原型开发,若对性能或特定处理有极致要求,再考虑参考其源码进行自定义实现 [ref_6]。