一步一步讲解和实现ASR中常用的语音特征——FBank和MFCC的提取,包括算法原理、代码和可视化等。
完整Jupyter Notebook链接:https://github.com/Magic-Bubble/SpeechProcessForMachineLearning/blob/master/speech_process.ipynb
文章目录
语音信号的产生
语音通常是指人说话的声音。从生物学的角度来看,是气流通过声带、咽喉、口腔、鼻腔等发出声音;从信号的角度来看,不同位置的震动频率不一样,最后的信号是由基频和一些谐波构成。

之后被设备接收后(比如麦克风),会通过A/D转换,将模拟信号转换为数字信号,一般会有采样、量化和编码三个步骤,采样率要遵循奈奎斯特采样定律: f s > = 2 f fs >= 2f fs>=2f,比如电话语音的频率一般在300Hz~3400Hz,所以采用8kHz的采样率足矣。
下面采用一个30s左右的16比特PCM编码后的语音wav为例。
准备工作
1. 导包
import numpy as np
from scipy.io import wavfile
from scipy.fftpack import dct
import warnings
warnings.filterwarnings('ignore')
import matplotlib.pyplot as plt
%matplotlib inline
2. 绘图工具
# 绘制时域图
def plot_time(signal, sample_rate):
time = np.arange(0, len(signal)) * (1.0 / sample_rate)
plt.figure(figsize=(20, 5))
plt.plot(time, signal)
plt.xlabel('Time(s)')
plt.ylabel('Amplitude')
plt.grid()
# 绘制频域图
def plot_freq(signal, sample_rate, fft_size=512):
xf = np.fft.rfft(signal, fft_size) / fft_size
freqs = np.linspace(0, sample_rate/2, fft_size/2 + 1)
xfp = 20 * np.log10(np.clip(np.abs(xf), 1e-20, 1e100))
plt.figure(figsize=(20, 5))
plt.plot(freqs, xfp)
plt.xlabel('Freq(hz)')
plt.ylabel('dB')
plt.grid()
# 绘制频谱图
def plot_spectrogram(spec, note):
fig = plt.figure(figsize=(20, 5))
heatmap = plt.pcolor(spec)
fig.colorbar(mappable=heatmap)
plt.xlabel('Time(s)')
plt.ylabel(note)
plt.tight_layout()
3. 数据准备
sample_rate, signal = wavfile.read('./resources/OSR_us_000_0010_8k.wav')
signal = signal[0: int(3.5 * sample_rate)] # Keep the first 3.5 seconds
print('sample rate:', sample_rate, ', frame length:', len(signal))
sample rate: 8000 , frame length: 28000
plot_time(signal, sample_rate)
plot_freq(signal, sample_rate)
预加重(Pre-Emphasis)
预加重一般是数字语音信号处理的第一步。语音信号往往会有频谱倾斜(Spectral Tilt)现象,即高频部分的幅度会比低频部分的小,预加重在这里就是起到一个平衡频谱的作用,增大高频部分的幅度。它使用如下的一阶滤波器来实现:
y ( t ) = x ( t ) − α x ( t − 1 ) , 0.95 < α < 0.99 y(t) = x(t) - \alpha x(t-1), \ \ \ \ 0.95 < \alpha < 0.99 y(t)=x(t)−αx(t−1), 0.95<α<0.99
笔者对这个公式的理解是:信号频率的高低主要是由信号电平变化的速度所决定,对信号做一阶差分时,高频部分(变化快的地方)差分值大,低频部分(变化慢的地方)差分值小,达到平衡频谱的作用。
pre_emphasis = 0.97
emphasized_signal = np.append(signal[0], signal[1:] - pre_emphasis * signal[:-1])
plot_time(emphasized_signal, sample_rate)
plot_freq(emphasized_signal, sample_rate)
从下面这个图来看,确实起到了平衡频谱的作用。
分帧(Framing)
在预加重之后,需要将信号分成短时帧。做这一步的原因是:信号中的频率会随时间变化(不稳定的),一些信号处理算法(比如傅里叶变换)通常希望信号是稳定,也就是说对整个信号进行处理是没有意义的,因为信号的频率轮廓会随着时间的推移而丢失。为了避免这种情况,需要对信号进行分帧处理,认为每一帧之内的信号是短时不变的。一般设置帧长取20ms~40ms,相邻帧之间50%(+/-10%)的覆盖。对于ASR而言,通常取帧长为25ms,覆盖为10ms。
frame_size, frame_stride = 0.025, 0.01
frame_length, frame_step = int(round(frame_size * sample_rate)