在mel时频图的基础上,介绍MFCCs转换的代码和可视化
继上一节,这里将MFCCs变换分为以下6步
1.预处理滤波
这里是放大信号高频分量:
-
用于提高音频信噪比,增强主要信号成分的幅度,相对于噪声,主信号能量比例更高,从而提高信号的质量和清晰度,使信号更容易被检测和分析。
-
若高频分量的幅度较低,会导致信号在频谱上看起来不均衡。通过对信号进行滤波或调整,可以增强高频分量的幅度,使其与低频分量更平衡,提高信号的频谱均衡性。
公式如下:
$y(t) =x(t)−\alpha⋅x(t−1), \alpha = 0.97$
波形图对比(有剪枝的效果):
y, sr = librosa.load(librosa.example('trumpet'))
y = y[0:int(3.5 * sr)] # 取前3.5秒(之后没声音)
pre_emphasis = 0.97
y_preemphasized = np.append(y[0], y[1:] - pre_emphasis * y[:-1])
注:均值归一化可实现大部分预加重效果
2. 音频切片
信号中的频率会随着时间而变化,因此,对整个信号进行傅立叶变换是没有意义的,会随着时间推移而丢失信号的频率。假设信号中的频率在很短的时间内是稳定的,对短时间的信号片段(帧)进行傅立叶变换,再通过连接相邻帧来获得信号的频率轮廓,可以得到良好的信号频率近似值。
在语音处理中,典型的帧大小通常在20毫秒(ms)到40ms之间,相邻帧之间有50%的重叠(+/-10%)。比较常见的设置是帧大小为25ms(frame_size = 0.025 sec = 25ms),帧间隔为10ms(15ms,frame_stride = 0.01 sec)。
-
trumpet的一个frame(这里除去重叠,约15ms)如图:
-
显示完整wave和frame如图:
代码如下:
frame_size = 0.025
frame_stride = 0.01 # 10ms stide 帧移时间,非重叠部分。意味着frame之间间隔(重叠)有 25- 10 = 15ms
frame_length = frame_size * sample_rate # seconds to samples
frame_step = frame_stride * sample_rate
signal_length = len(y_preemphasized) # 117601 个采样点
frame_length = int(round(frame_length)) # 每个frame 551个采样点,每个0.025秒
frame_step = int(round(frame_step)) # 帧移采样点部分,非重合部分为220个采样点,即10ms
num_frames = int(numpy.ceil(float(numpy.abs(signal_length - frame_length)) / frame_step)) # 533,Make sure that we have at least 1 frame
pad_signal_length = num_frames * frame_step + frame_length # 117260(没有覆盖全部杨本点) + 511 = 117811
z = numpy.zeros((pad_signal_length - signal_length)) # 117811 - 117601 = 210 覆盖全部样本点需要额外210个点
pad_signal = numpy.append(y_preemphasized, z) # 填充210个点,保证覆盖全部样本点
indices = numpy.tile(numpy.arange(0, frame_length), (num_frames, 1)) + numpy.tile(numpy.arange(0, num_frames * frame_step, frame_step), (frame_length, 1)).T
# frames的索引 [0, 550], [220, 770], [440, 990], [660,1210]…, [117040, 117590]
第一个frame前220( frame_stride * sample_rate)个点是独立的,即10ms不是重叠的;
另外330即15ms是重叠的, 最后一个frame的后 pad_signal(210个点)是0填充的;
剩下的frame都是前后重叠的。
3. 窗口函数
对每一帧应用一个窗口函数,这里应用hamming窗,类似正态分布的加权函数。
图中展示了200个样本(x轴),对应加权系数(y轴)的hamming函数:
代码如下:
# 生成200个样本的Hamming窗口
hamming_window = np.hamming(200) # frame_length = 200
# frames *= 0.54 - 0.46 * numpy.cos((2 * numpy.pi * n) / (frame_length - 1)) # Explicit Implementation **
# 归一化使得振幅为1
hamming_window /= np.max(hamming_window)
4. 傅里叶变换
对每一帧进行快速傅里叶变换(Fast Fourier Transform,FFT), 更具体来说是离散傅里叶变换(DFT)的一种方法: 短时傅里叶变换(Short-Time Fourier-Transform, STFT),
这里的FFT函数为:
F F T ( x k ) = ∑ n = 0 N − 1 x k e − i 2 π N k ∗ n FFT(x_k) = \sum_{n=0}^{N-1} x_k e^{-i \frac{2\pi}{N} k*n} FFT(xk)=∑n=0N−1xke−iN2πk∗n
这里的k是第k个frame,N是n_fft, 即一个frame所含的样本点(通常为256 or 512), 代码如下:
mag_frames = numpy.absolute(numpy.fft.rfft(frames, N)) # Magnitude of the FFT
之后计算功率谱:
pow_frames = ((1.0 / N) * ((mag_frames) ** 2)) # Power Spectrum
5. 滤波器组(Filter Bank)
选择滤波器组的数量(通常是40个),并计算各个滤波器的频率。
滤波器组是一组三角形波,用于拟合声波
- 为了迎合人的感知,滤波器在低频区域较密,在高频区域则较散
- 每个滤波器是一个三角型频率波,线性下降至相邻滤波器的中心频率处时,响应为 0
- 每个滤波器对一定频率范围内的频率成分有最大的灵敏度,而对超出该范围的频率成分灵敏度逐渐减小
如图展示 10个三角滤波器(num_mel = 10),为适应人类感知,低频密,且振幅高:
下图是另一种展示方式:
- 滤波器组输出函数:
1. librosa.filters.mel
librosa.filters.mel 是 Librosa 库中的一个函数,用于生成梅尔滤波器(Mel filter bank)。
梅尔滤波器是一组滤波器,用于将频谱转换为梅尔频谱,这是语音和音频信号处理中常用的一种表示方法。
2. 输入参数:
sr (number [scalar]): 采样率。默认值为 22050。
n_fft (int > 0 [scalar]): FFT 的大小。默认值为 2048。
n_mels (int > 0 [scalar]): 梅尔滤波器的数量。默认值为 128。
fmin (float >= 0 [scalar]): 最低频率(赫兹)。默认值为 0.0。
fmax (float > 0 [scalar] or None): 最高频率(赫兹)。如果为 None,则使用 sr / 2.0。
htk (bool): 如果为 True,使用 HTK 公式来计算梅尔频率。默认值为 False。
norm (None or 'slaney'): 如果为 None,不进行归一化。如果为 'slaney',则归一化滤波器总和以满足 Slaney 的标准。默认值为 'slaney'。
dtype (np.dtype): 输出数组的数据类型。默认值为 np.float32。
3. 返回值
mel_basis : shape=(n_mels, 1 + n_fft/2), 每一行代表一个梅尔滤波器。
6. Mel频谱
有了特定数量的梅尔组频率,转为对应的梅尔谱步骤如下
- 转换频率轴:首先将频率轴从赫兹转换为 Mel 频率轴。
high_freq_mel = (2595 * numpy.log10(1 + (sample_rate / 2) / 700)) # Convert Hz to Mel
- 定义滤波器中心频率:在 Mel 频率轴上等间隔划分40个点作为滤波器的中心频率。
mel_points = numpy.linspace(low_freq_mel, high_freq_mel, num_mel + 2)
- 构建滤波器:将这些中心频率转换回赫兹,然后在赫兹频率轴上构建三角滤波器。
fbank,通过for循环完成三角滤波器设置
- 应用滤波器:将三角滤波器应用于功率谱,以提取各个滤波器的频带能量。
filter_banks = numpy.dot(pow_frames, fbank.T)
整体代码如下:
num_mel = 40
low_freq_mel = 0
N = 512 # NFFT
frames = pad_signal[indices.astype(numpy.int32, copy=False)] # (533, 551)
frames *= numpy.hamming(frame_length)
mag_frames = numpy.absolute(numpy.fft.rfft(frames, N)) # Magnitude of the FFT (533, 257)
pow_frames = ((1.0 / N) * ((mag_frames) ** 2)) # Power Spectrum (533, 257)
high_freq_mel = (2595 * numpy.log10(1 + (sample_rate / 2) / 700)) # Convert Hz to Mel
mel_points = numpy.linspace(low_freq_mel, high_freq_mel, num_mel + 2) # Equally spaced in Mel scale
hz_points = (700 * (10**(mel_points / 2595) - 1)) # Convert Mel to Hz
bin = numpy.floor((N + 1) * hz_points / sample_rate)
fbank = numpy.zeros((num_mel, int(numpy.floor(N / 2 + 1)))) # (num_mel, N /2 + 1)
for m in range(1, num_mel + 1): # 依次制作不同频率的三角滤波器
f_m_minus = int(bin[m - 1]) # left
f_m = int(bin[m]) # center
f_m_plus = int(bin[m + 1]) # right
for k in range(f_m_minus, f_m):
fbank[m - 1, k] = (k - bin[m - 1]) / (bin[m] - bin[m - 1])
for k in range(f_m, f_m_plus):
fbank[m - 1, k] = (bin[m + 1] - k) / (bin[m + 1] - bin[m])
filter_banks = numpy.dot(pow_frames, fbank.T) # (533,257) x (257, num_mel) = (533, 257)
filter_banks = numpy.where(filter_banks == 0, numpy.finfo(float).eps, filter_banks) # 替换0值,方便转db
filter_banks = 20 * numpy.log10(filter_banks) # 通过log将梅尔组各个三角频率的能量值转换为分贝(dB)
-
filter_banks可视化:
-
均值归一化 (Normalize)
filter_banks -= (numpy.mean(filter_banks, axis=0) + 1e-8)
代码就一行,用于提升信噪比效果,区别在y轴底部区域:
7. 离散余弦变换(DCT)
mel谱的滤波组内,各滤波器相关性强,即存在冗余。
离散余弦变换(DCT)是将滤波器组的信号转换为不同频率的余弦函数,均匀各频率的信息分布,降低相关性。
DCT变换后能量更紧凑,通常集中在前面几个离散系数中,即mel频率倒谱系数(MFCCs)。
- MFCCs的倒谱系数
对于语音识别,通常保留第2-13个倒谱系数。
第0个系数代表整体信息,无法区分音素。
第1个系数代表低频信息,通常是噪声。
第2-13个倒谱系数:包含音素的重要特征。
第14个以上:捕捉细节和滤波器的快速变化,受噪音和瞬变影响较大,贡献小。
0-12的MFCCs系数可视化:
代码:
from scipy.fftpack import dct # Apply DCT to the filter banks
num_ceps = 12
mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1:(num_ceps + 1)]
- 正弦提升
以弱化更高的 MFCC,提高噪声信号中的语音识别能力。
效果如图:
代码如下,这里cep_lifter用于控制提升程度
cep_lifter = 22 # 22-27
(nframes, ncoeff) = mfcc.shape
n = numpy.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * numpy.sin(numpy.pi * n / cep_lifter)
mfcc *= lift
Reference
- https://haythamfayek.com/2016/04/21/speech-processing-for-machine-learning.html
- https://github.com/disanda/d_code/tree/master/3.Audio/5_mel_num