(Automatic Speech Recognition,ASR),其目标是将人类的语音转换为文字。
指包含语音的音频文件已经存在,需使用语音识别应用对音频的内容进行整体识别。典型应用有音视频会议记录转写、音频内容分析及审核、视频字幕生成等。
指包含语音的实时音频流,被连续不断地送入语音识别引擎,过程中获得的识别结果即时返回给调用方。典型应用有手机语音输入法、交互类语音产品(如智能音箱、车载助手)、会场同声字幕生成和翻译、网络直播平台实时监控、电话客服实时质量检测等。
预处理(格式转换、压缩编解码、音频数据抽取、声道选择(通常识别引擎只接收单声道数据)、采样率/重采样(常见的识别引擎和模型采样率一般为8kHz、16kHz),FBank特征提取。
语音信号被设备接收后 (比如麦克风),会通过 A / D A / D A/D 转换,将模拟信号转换为数字信号,一般会有采样、量化和编码三个步骤,采样率要遵循奈奎斯特采样定律: f s > = 2 f f s>=2 f fs>=2f ,比如电话语音的频率一般在 300 H z ∼ 3400 H z 300 \mathrm{~Hz} \sim 3400 \mathrm{~Hz} 300 Hz∼3400 Hz ,所以采用 8 k H z 8 \mathrm{kHz} 8kHz 的采样率足矣。
对于离线语音识别应用,断句模块的作用是快速过滤并切分出音频文件中的人声片段,且尽最大可能保证每个片段都为完整的一句话;对于实时在线语音识别应用,断句模块则需要能够从一个持续的语音流中,第一时间检测出用户什么时候开始说话(也称为起点检测),以及什么时候说完(也称为尾点检测)。
除断句外,由于一些应用本身的复杂性,导致原生的音频在被送入识别引擎之前,还需要进一步进行分析和过滤,我们把这类统称为音频场景分析。一般情况语种识别也会放在这里。
经典的语音识别概率模型 ,分为声学模型和语言模型两部分,现将语音转换为音素,再将音素转换为单词。
对于声学模型来说,单词是一个比较大的建模单元,因此声学模型p(Y|w)中的单词序列w会被进一步拆分成一个音素序列。假设Q是单词序列w对应的发音单元序列,这里简化为音素序列,那么声学模型p(Y|w)可以被进一步转写为。一般会用隐马尔可夫模型来进行建模。音素表,由声学专家定义。
语言模型,使用n-gram模型。
传统语音识别 缺点,精度差;优点,速度快可部署在嵌入式设备。
2014年左右,谷歌的研究人员发现,在大量数据的支撑下,直接用神经网络可以从输入的音频或音频对应的特征直接预测出与之对应的单词,而不需要像我们上面描述的那样,拆分成声学模型和语言模型。简单来说构建一个模型,input :语音,output:文本即可。
Paraformer 是阿里INTERSPEECH 2022 提出的一个模型,并已经开源。一种具有高识别率与计算效率的单轮非自回归模型 Paraformer。
该模型开箱可用,该开源模型的实际识别效果和TEG 的模型效果差不多。是目前唯一开箱即用的开源预训练模型, 我认为主要原因是该模型的训练集中包含了工业级别的数据。
工程部署和异常类型处理
import numpy as np
from scipy.io import wavfile
from scipy.fftpack import dct
import warnings
warnings.filterwarnings('ignore')
import matplotlib.pyplot as plt
# 绘制时域图
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()
plt.show()
# 绘制频域图
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, int(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()
plt.show()
# 绘制频谱图
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()
plt.show()
if __name__ == '__main__':
# signal 是音频信号,sample_rate 是采样率。fft_size 是用于离散傅里叶变换(FFT)的窗口大小。 spec 是频谱数据
sample_rate, signal = wavfile.read('./zh.wav')
# 打印采样率和信号长度的信息。
signal = signal[0: int(3.5 * sample_rate)] # Keep the first 3.5 seconds
print('sample rate:', sample_rate, ', frame length:', len(signal))
# plot_time函数绘制时域图,显示音频信号的波形。
# plot_freq 函数绘制频域图,显示音频信号的频谱信息。
plot_time(signal, sample_rate)
plot_freq(signal, sample_rate)
在音频信号中突出重要的高频信息,减小低频噪音的影响,从而改善音频信号的特性,提高信噪比,以便更好地进行后续信号处理和特征提取
语音信号往往会有频谱倾斜(Spectral Tilt)现象,即高频部分的幅度会比低频部分的小,预加重在这里就是起到一个平衡频谱的作用,增大高频部分的幅度。它使用如下的一阶滤波器来实现:
y ( t ) = x ( t ) − α x ( t − 1 ) , 0.95 < α < 0.99 y(t)=x(t)-\alpha x(t-1), \quad 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)
前后两张时域图对比:
在预加重之后,需要将信号分成短时帧。分为短时帧原因如下:
如何分帧:
# 定义帧的时长和帧之间的时间间隔(以秒为单位)
frame_size, frame_stride = 0.025, 0.01
# 计算帧的长度和帧之间的时间间隔(以样本点数表示)
frame_length, frame_step = int(round(frame_size * sample_rate)), int(round(frame_stride * sample_rate))
# 获取输入音频信号的长度
signal_length = len(emphasized_signal)
# 计算需要的帧数量
num_frames = int(np.ceil(np.abs(signal_length - frame_length) / frame_step)) + 1
# 计算填充后的信号长度,以确保能容纳所有帧
pad_signal_length = (num_frames - 1) * frame_step + frame_length
# 创建一个零数组,用于填充信号,以满足长度要求
z = np.zeros((pad_signal_length - signal_length))
pad_signal = np.append(emphasized_signal, z)
# 创建帧的索引矩阵
indices = np.arange(0, frame_length).reshape(1, -1) + np.arange(0, num_frames * frame_step, frame_step).reshape(-1, 1)
# 提取每个帧并存储在 frames 数组中
frames = pad_signal[indices]
# 打印帧数组的形状
print(frames.shape)
在分帧之后,通常需要对每帧的信号进行加窗处理。目的是让帧两端平滑地衰减,这样可以降低后续傅里叶变换后旁瓣的强度,取得更高质量的频谱。常用的窗有:矩形窗、汉明 (Hamming) 窗、汉宁窗 (Hanning),以汉明窗为例,其窗函数为:
w ( n ) = 0.54 − 0.46 cos ( 2 π n N − 1 ) w(n)=0.54-0.46 \cos \left(\frac{2 \pi n}{N-1}\right) w(n)=0.54−0.46cos(N−12πn)
这里的 0 < = n < = N − 1 , N 0<=n<=N-1 , N 0<=n<=N−1,N 是窗的宽度。
frames *= hamming
#%%
plot_time(frames[1], sample_rate)
对于每一帧的加窗信号,进行N点FFT变换,也称短时傅里叶变换(STFT),N通常取256或512,然后用如下的公式计算能量谱:
P = ∣ F F T ( x i ) ∣ 2 N P=\frac{\left|F F T\left(x_i\right)\right|^2}{N} P=N∣FFT(xi)∣2
在信号处理中,对加窗后的信号进行快速傅里叶变换(FFT)的主要目的是将信号从时域转换为频域,以便进行频谱分析和频域特征提取。这个过程具有以下几个重要的原因和优势:
NFFT = 512
mag_frames = np.absolute(np.fft.rfft(frames, NFFT))
pow_frames = ((1.0 / NFFT) * (mag_frames ** 2))
print(pow_frames.shape)
plt.figure(figsize=(20, 5))
plt.plot(pow_frames[1])
plt.grid()
过上面的步骤之后,在能量谱上应用Mel滤波器组,就能提取到FBank特征。Mel刻度,这是一个能模拟人耳接收声音规律的刻度,人耳在接收声音时呈现非线性状态,对高频的更不敏感,因此Mel刻度在低频区分辨度较高,在高频区分辨度较低,与频率之间的换算关系为:
m = 2595 log 10 ( 1 + f 700 ) f = 700 ( 1 0 m / 2595 − 1 ) \begin{gathered} m=2595 \log _{10}\left(1+\frac{f}{700}\right) \\ f=700\left(10^{m / 2595}-1\right) \end{gathered} m=2595log10(1+700f)f=700(10m/2595−1)
Mel滤波器组就是一系列的三角形滤波器,通常有 40 个或 80 个,在中心频率点响应值为 1 ,在两边的滤波器中心点衰减到0,如下图:
具体公式可以写为:
H m ( k ) = { 0 k < f ( m − 1 ) k − f ( m − 1 ) f ( m ) − f ( m − 1 ) f ( m − 1 ) ≤ k < f ( m ) 1 k = f ( m ) f ( m + 1 ) − k f ( m + 1 ) − f ( m ) f ( m ) < k ≤ f ( m + 1 ) 0 k > f ( m + 1 ) H_m(k)=\left\{\begin{array}{cl} 0 & k
最后在能量谱上应用Mel滤波器组,其公式为:
Y t ( m ) = ∑ k = 1 N H m ( k ) ∣ X t ( k ) ∣ 2 Y_t(m)=\sum_{k=1}^N H_m(k)\left|X_t(k)\right|^2 Yt(m)=k=1∑NHm(k)∣Xt(k)∣2
其中, k k k 表示FFT变换后的编号, m \mathrm{m} m 表示meli虑波器的编号。
low_freq_mel = 0
high_freq_mel = 2595 * np.log10(1 + (sample_rate / 2) / 700)
print(low_freq_mel, high_freq_mel)
nfilt = 40
mel_points = np.linspace(low_freq_mel, high_freq_mel, nfilt + 2) # 所有的mel中心点,为了方便后面计算mel滤波器组,左右两边各补一个中心点
hz_points = 700 * (10 ** (mel_points / 2595) - 1)
fbank = np.zeros((nfilt, int(NFFT / 2 + 1))) # 各个mel滤波器在能量谱对应点的取值
bin = (hz_points / (sample_rate / 2)) * (NFFT / 2) # 各个mel滤波器中心点对应FFT的区域编码,找到有值的位置
for i in range(1, nfilt + 1):
left = int(bin[i-1])
center = int(bin[i])
right = int(bin[i+1])
for j in range(left, center):
fbank[i-1, j+1] = (j + 1 - bin[i-1]) / (bin[i] - bin[i-1])
for j in range(center, right):
fbank[i-1, j+1] = (bin[i+1] - (j + 1)) / (bin[i+1] - bin[i])
print(fbank)
filter_banks = np.dot(pow_frames, fbank.T)
filter_banks = np.where(filter_banks == 0, np.finfo(float).eps, filter_banks)
filter_banks = 20 * np.log10(filter_banks) # dB
print(filter_banks.shape)
plot_spectrogram(filter_banks.T, 'Filter Banks')
Mel频率倒谱系数(MFCC)是一种常用于音频信号处理和语音识别的特征提取方法,它可以帮助捕捉声音的频谱信息。
对每个Mel滤波器通道的幅度谱取对数,以增加特征的动态范围,通常使用自然对数或对数变换。取对数后的结果表示为 l o g ( E ) log(E) log(E),其中 E E E 是能量。然后进行离散余弦变换(Discrete Cosine Transform,DCT): 对取对数后的Mel滤波器通道进行 D C T DCT DCT,以获得最终的 M F C C MFCC MFCC系数。 D C T DCT DCT的公式为:
C ( k ) = ∑ n = 0 N − 1 log ( E ( n ) ) ⋅ cos ( π N ⋅ k ⋅ ( n + 1 2 ) ) C(k) = \sum_{n=0}^{N-1} \log(E(n)) \cdot \cos\left(\frac{\pi}{N} \cdot k \cdot (n + \frac{1}{2})\right) C(k)=n=0∑N−1log(E(n))⋅cos(Nπ⋅k⋅(n+21))
其中, C ( k ) C(k) C(k) 是第 k k k 个 M F C C MFCC MFCC系数, N N N 是Mel滤波器通道的数量。
最终,得到的 M F C C MFCC MFCC系数 C ( k ) C(k) C(k) 可以用于进行音频特征的表示和分析,通常作为机器学习模型的输入用于语音识别等任务。这些系数捕捉了声音的频谱特征,具有很好的辨识性。
# MFCC绘制
num_ceps = 12
mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1:(num_ceps+1)]
print(mfcc.shape)
plot_spectrogram(mfcc.T, 'MFCC Coefficients')
MFCC(Mel频率倒谱系数)计算通常包括一个正弦提升(cepstral mean normalization 或 cepstral mean subtraction)步骤,用于进一步改善MFCC特征的性能。正弦提升的目的是减小不同说话人和环境条件下的语音信号之间的差异,以提高语音识别系统的鲁棒性。
正弦提升的主要思想是在MFCC系数上应用一个谱凸显(spectral flattening)函数,以平滑MFCC系数之间的差异,并降低高频分量的权重,从而减小高频分量对语音识别的影响。正弦提升通常包括以下几个步骤:
计算MFCC系数:首先,计算原始MFCC系数,按照上述步骤计算出MFCC系数。
计算MFCC的均值:对于每个MFCC系数 (C(k)),计算在训练数据集中的所有样本中该系数的均值。这将得到一个均值向量,其中每个元素对应一个MFCC系数。
应用正弦提升:对于每个MFCC系数 (C(k)),将其减去相应的均值 (M(k)) 并乘以一个正弦提升系数(通常是一个谱凸显函数的系数),得到提升后的MFCC系数 (C’(k))。
C i ′ ( k ) = w C i ( k ) C'_i(k) = wC_i(k) Ci′(k)=wCi(k)
w i = D 2 sin ( π ∗ i D ) w_i=\frac{D}{2} \sin \left(\frac{\pi * i}{D}\right) wi=2Dsin(Dπ∗i)
使用提升后的MFCC系数:可以使用提升后的MFCC系数 (C’(k)) 作为特征向量,用于语音识别或其他音频处理任务。
正弦提升有助于消除说话人和环境条件的变化,使MFCC特征更具判别性和鲁棒性。提升的程度通常由正弦提升系数的选择来控制,这个系数通常需要在训练数据上进行调整和优化。正弦提升是MFCC特征处理中的一个重要步骤,通常在计算MFCC后应用。
cep_lifter = 23
(nframes, ncoeff) = mfcc.shape
n = np.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * np.sin(np.pi * n / cep_lifter)
mfcc *= lift
plot_spectrogram(mfcc.T, 'MFCC Coefficients')
通俗的讲解语音识别技术 - 知乎 (zhihu.com)
ASR中常用的语音特征之FBank和MFCC(原理 + Python实现)_完成fbank、mfcc两种声学特征提取的代码实现-CSDN博客
np.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * np.sin(np.pi * n / cep_lifter)
mfcc *= lift
plot_spectrogram(mfcc.T, ‘MFCC Coefficients’)
[外链图片转存中...(img-y9xc8Gnf-1701701515375)]
## 参考笔记
[通俗的讲解语音识别技术 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/635372844)
[ASR中常用的语音特征之FBank和MFCC(原理 + Python实现)_完成fbank、mfcc两种声学特征提取的代码实现-CSDN博客](https://blog.csdn.net/Magical_Bubble/article/details/90295814)