2021-07-19

本文详细介绍了语音识别中常用的特征提取方法,包括MFCC(梅尔频率倒谱系数)和FBank(滤波器组)。MFCC基于人耳听觉模型,通过预加重、分帧、加窗、FFT、功率谱、滤波器组、DCT等步骤,提取具有低相关性的特征。FBank则侧重于模拟人耳对声音频率的感知。PNCC是一种增强版的特征,适合噪声和混响环境。MFCC在多数情况下用于语音识别,而FBank在神经网络模型中表现出色。
摘要由CSDN通过智能技术生成

语音识别特征处理(MFCC,Fbank,PNCC)

语音参数提取特征

FBank特征提取流程图

在这里插入图片描述

MFCC特征提取流程图

在这里插入图片描述

MFCC特征提取

梅尔倒谱系数(Mel-scale FrequencyCepstral Coefficients,简称MFCC)。依据人的听觉实验结果来分析语音的频谱,

MFCC分析依据的听觉机理有两个

第一梅尔刻度(Mel scale):人耳感知的声音频率和声音的实际频率并不是线性的,有下面公式

  1. 从频率转换为梅尔刻度的公式为在这里插入图片描述

  2. 从梅尔回到频率:在这里插入图片描述

式中𝑓𝑚𝑒𝑙fmel是以梅尔(Mel)为单位的感知频域(简称梅尔频域),𝑓f是以𝐻𝑧Hz为单位的实际语音频率。𝑓𝑚𝑒𝑙fmel与𝑓f的关系曲线如下图所示,若能将语音信号的频域变换为感知频域中,能更好的模拟听觉过程的处理
在这里插入图片描述

第二临界带(Critical Band):把进入人耳的声音频率用临界带进行划分,将语音在频域上就被划分成一系列的频率群,组成了滤波器组,即Mel滤波器组。

研究表明,人耳对不同频率的声波有不同的听觉敏感度。从200Hz到5000Hz的语音信号对语音的清晰度影响较大。两个响度不等的声音作用于人耳时,则响度较高的频率成分的存在会影响到对响度较低的频率成分的感受,使其变得不易察觉,这种现象称为掩蔽效应

由于频率较低的声音(低音)在内耳蜗基底膜上行波传递距离大于频率较高的声音(高音),因此低音容易掩蔽高音。低音掩蔽的临界带宽较高频要小。所以,人们从低频到高频这一段频带内按临界带宽的大小由密到疏安排一组带通滤波器,对输入信号进行滤波。将每个带通滤波器输出的信号能量作为信号的基本特征,对此特征经过进一步处理后就可以作为语音的输入特征。由于这种特征不依赖于信号的性质,对输入信号不做任何的假设和限制,又利用了听觉模型的研究成果。因此,这种参数比基于声道模型的LPCC相比具有更好的鲁棒性,更符合人耳的听觉特性,而且当信噪比降低时仍然具有较好的识别性能。

求MFCC的步骤

  1. 将信号帧化为短帧
  2. 对于每一帧,计算功率谱的周期图估计
  3. 将mel滤波器组应用于功率谱,求滤波器组的能量,将每个滤波器中的能量相加
  4. 取所有滤波器组能量的对数
  5. 取对数滤波器组能量的离散余弦变换(DCT)。
  6. 保持DCT系数2-13,其余部分丢弃

通常还有其他事情要做,有时会将帧能量附加到每个特征向量上。通常还会附加Delta和Delta-Delta 特征。提升也通常应用于最终特征。

一、预处理

预处理包括预加重、分帧、加窗函数。假设我们的语音信号采样频率为8000Hz,语音数据在这里获取

import numpy
import scipy.io.wavfile
from scipy.fftpack import dct
 
sample_rate, signal = scipy.io.wavfile.read('OSR_us_000_0010_8k.wav') 
signal = signal[0:int(3.5 * sample_rate)]  # 我们只取前3.5s

在这里插入图片描述

​ 图1:时域 中的语音信号

添加随机噪声

有时候我们需要进行数据增强,会手动合成一些音频。某些人工合成(使用软件)的音频可能会造成一些数字错误,诸如underflow或者overflow。 这种情况下,通过添加随机噪声可以解决这一类问题。公式如下:

​ s(n)=s(n)+q∗rand()s(n)=s(n)+q∗rand()
q用于控制添加噪声的强度,rand() 产生[ -1.0, 1.0 )的随机数。

注意:Kaldi中是在分帧之后的下一步添加随机噪声

(1)、预加重(Pre-Emphasis)

对信号应用预加重滤波器以放大高频,预加重滤波器在以下几个方面很有用:

  1. 平衡频谱,因为高频通常与较低频率相比具有较小的幅度,
  2. 避免在傅里叶变换操作操作过程中出现数值问题
  3. 改善信号 - 噪声比(SNR)
  4. 消除发声过程中声带和嘴唇的效应,来补偿语音信号受到发音系统所抑制的高频部分,也为了突出高频的共振峰。

预加重处理其实是将语音信号通过一个高通滤波器:
在这里插入图片描述

其中滤波器系数(𝛼α)的通常为0.95或0.97,这里取pre_emphasis =0.97:

emphasized_signal = numpy.append(signal[0], signal[1:] - pre_emphasis * signal[:-1])

在这里插入图片描述

预加重后的时域信号

题外话:预加重在现代系统中的影响不大,主要是因为除避免在现代FFT实现中不应成为问题的FFT数值问题,大多数预加重滤波器的动机都可以通过均值归一化来实现(在本文后面讨论)。 在现代FFT实现中。

(2)分帧(Framing)

在预加重之后,我们需要将信号分成短时帧。因此在大多数情况下,语音信号是非平稳的,对整个信号进行傅里叶变换是没有意义的,因为我们会随着时间的推移丢失信号的频率轮廓。语音信号是短时平稳信号。因此我们在短时帧上进行傅里叶变换,通过连接相邻帧来获得信号频率轮廓的良好近似。将信号帧化为20-40 ms帧。标准是25毫秒 frame_size = 0.025。这意味着8kHz信号的帧长度为0.025 * 8000 = 200个采样点。帧移通常为10毫秒 frame_stride = 0.01(80个采样点),为了避免相邻两帧的变化过大,因此会让两相邻帧之间有一段重叠区域,通常约为每帧语音的1/2或1/3或50%(+/-10%),我们设置为15毫秒 overlap=0.015,因此此重叠区域包含了0.015*8000=120个取样点。第一个语音帧0开始,下一个语音帧从80开始,直到到达语音文件的末尾。如果语音文件没有划分为偶数个帧,则用零填充它以使其完成。

frame_length, frame_step = frame_size * sample_rate, frame_stride * sample_rate  # 从秒转换为采样点
signal_length = len(emphasized_signal)
frame_length = int(round(frame_length))
frame_step = int(round(frame_step))
# 确保我们至少有1帧
num_frames = int(numpy.ceil(float(numpy.abs(signal_length - frame_length)) / frame_step))  
 
pad_signal_length = num_frames * frame_step + frame_length
z = numpy.zeros((pad_signal_length - signal_length))
# 填充信号,确保所有帧的采样数相等,而不从原始信号中截断任何采样
pad_signal = numpy.append(emphasized_signal, z) 
 
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 = pad_signal[indices.astype(numpy.int32, copy=False)]

(3)、加窗(Window)

将信号分割成帧后,我们再对每个帧乘以一个窗函数,如Hamming窗口。以增加帧左端和右端的连续性。抵消FFT假设(数据是无限的),并减少频谱泄漏。汉明窗的形式如下:

在这里插入图片描述

式0≤𝑛≤𝑁−10≤n≤N−1,𝑁是窗口长度,我们这里假设,N是窗口长度,我们这里假设a=0.46$
在这里插入图片描述

frames *= numpy.hamming(frame_length)
# frames *= 0.54 - 0.46 * numpy.cos((2 * numpy.pi * n) / (frame_length - 1))  # 内部实现

二、FFT(Fourier-Transform)

由于信号在时域上的变换通常很难看出信号的特性,通常对它做FFT变换转换为频域上的能量分布来观察,不同的能量分布,就能代表不同语音的特性。接下来我们对分帧加窗后的各帧信号进行做一个N点FFT来计算频谱,也称为短时傅立叶变换(STFT),其中N通常为256或512,NFFT=512;
在这里插入图片描述

mag_frames = numpy.absolute(numpy.fft.rfft(frames, NFFT))   # fft的幅度(magnitude)

三、功率谱(Power Spectrum)

然后我们使用以下公式计算功率谱(周期图periodogram),对语音信号的频谱取模平方(取对数或者去平方,因为频率不可能为负,负值要舍去)得到语音信号的谱线能量。
在这里插入图片描述

其中,𝑋𝑖是信号X的第𝑖帧,这可以用以下几行来实现:

pow_frames = ((1.0 / NFFT) * ((mag_frames) ** 2))  # 功率谱

四、滤波器组(Filter Banks)

计算Mel滤波器组,将功率谱通过一组Mel刻度(通常取40个滤波器,nfilt=40)的三角滤波器(triangular filters)来提取频带(frequency bands)。

这个Mel滤波器组就像人类的听觉感知系统(耳朵),人耳只关注某些特定的频率分量(人的听觉对频率是有选择性的)。它对不同频率信号的灵敏度是不同的,换言之,它只让某些频率的信号通过,而压根就直接无视它不想感知的某些频率信号。但是这些滤波器在频率坐标轴上却不是统一分布的,在低频区域有很多的滤波器,他们分布比较密集,但在高频区域,滤波器的数目就变得比较少,分布很稀疏。因此Mel刻度的目的是模拟人耳对声音的非线性感知,在较低的频率下更具辨别力,在较高的频率下则不具辨别力。我们可以使用以下公式在赫兹(f)和梅尔(m)之间进行转换:

我们可以用下面的公式,在语音频率和Mel频率间转换:

  1. 从频率转换为梅尔刻度的公式为:在这里插入图片描述

  2. 从梅尔回到频率:在这里插入图片描述

定义一个有M个三角滤波器的滤波器组(滤波器的个数和临界带的个数相近),M通常取22-40,26是标准,本文取nfilt = 40。滤波器组中的每个滤波器都是三角形的,中心频率为f(m) ,中心频率处的响应为1,并向0线性减小,直到达到两个相邻滤波器的中心频率,其中响应为0,各f(m)之间的间隔随着m值的增大而增宽,如图所示:
在这里插入图片描述

这可以通过以下等式建模,三角滤波器的频率响应定义为:
在这里插入图片描述

nfilt = 40
low_freq_mel = 0
high_freq_mel = (2595 * np.log10(1 + (sample_rate / 2) / 700))  # 将Hz转换为Mel
# 我们要做40个滤波器组,为此需要42个点,这意味着在们需要low_freq_mel和high_freq_mel之间线性间隔40个点
mel_points = np.linspace(low_freq_mel, high_freq_mel, nfilt + 2)  # 使得Mel scale间距相等
hz_points = (700 * (10 ** (mel_points / 2595) - 1))  # 将Mel转换回-Hz
# bin = sample_rate/NFFT    # frequency bin的计算公式
# bins = hz_points/bin=hz_points*NFFT/ sample_rate    # 得出每个hz_point中有多少frequency bin
bins = np.floor((NFFT + 1) * hz_points / sample_rate)
 
fbank = np.zeros((nfilt, int(np.floor(NFFT / 2 + 1))))
for m in range(1, nfilt + 1):
    f_m_minus = int(bins[m - 1])  # 左
    f_m = int(bins[m])  # 中
    f_m_plus = int(bins[m + 1])  # 右
 
    for k in range(f_m_minus, f_m):
        fbank[m - 1, k] = (k - bins[m - 1]) / (bins[m] - bins[m - 1])
    for k in range(f_m, f_m_plus):
        fbank[m - 1, k] = (bins[m + 1] - k) / (bins[m + 1] - bins[m])
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

信号的功率谱经过 Filter Bank 后,得到的谱图为:
在这里插入图片描述

如果经过Mel scale滤波器组是所需的特征,那么我们可以跳过下一步。

五、梅尔频率倒谱系数(MFCCs)

一步骤中计算的滤波器组系数是高度相关的,这在某些机器学习算法中可能是有问题的。因此,我们可以应用离散余弦变换(DCT)对滤波器组系数去相关处理,并产生滤波器组的压缩表示。通常,对于自动语音识别(ASR),保留所得到的个倒频谱系数2-13,其余部分被丢弃; 我们这里取 num_ceps = 12丢弃其他系数原因是它们代表了滤波器组系数的快速变化,并且这些精细的细节对自动语音识别(ASR)没有贡献。
在这里插入图片描述

L阶指MFCC系数阶数,通常取2-13。这里M是三角滤波器个数。

mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1 : (num_ceps + 1)] # 保持在2-13

可以将正弦提升器(Liftering在倒谱域中进行过滤。 注意在谱图和倒谱图中分别使用filtering和liftering)应用于MFCC以去强调更高的MFCC,其已被证明可以改善噪声信号中的语音识别。

(nframes, ncoeff) = mfcc.shape
n = numpy.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * numpy.sin(numpy.pi * n / cep_lifter)
mfcc *= lift

生成的MFCC:

在这里插入图片描述

六、均值归一化(Mean Normalization)

如前所述,为了平衡频谱并改善信噪比(SNR),我们可以简单地从所有帧中减去每个系数的平均值。

filter_banks -= (numpy.mean(filter_banks, axis=0) + 1e-8)

在这里插入图片描述
归一化滤波器数组

同样对于MFCC:

mfcc -= (numpy.mean(mfcc, axis=0) + 1e-8)

均值归一化MFCC:
在这里插入图片描述

均值方差归一化(CMVN)

实际情况下,受不同麦克风及音频通道的影响,会导致相同音素的特征差别比较大,通过CMVN可以得到均值为0,方差为1的标准特征。均值方差可以以一段语音为单位计算,但更好的是在一个较大的数据集上进行计算,这样识别效果会更加稳健。Kaldi中计算均值和方差的代码compute-cmvn-stats.cc, 归一化apply-cmvn.cc

fbank与mfcc的比较

fbank特征更多是希望符合声音信号的本质,拟合人耳的接收特性。

Filter Banks和MFCC对比:

  • 计算量:MFCC是在FBank的基础上进行的,所以MFCC的计算量更大
  • 特征区分度:FBank特征相关性较高(相邻滤波器组有重叠),MFCC具有更好的判别度,这也是在大多数语音识别论文中用的是MFCC,而不是FBank的原因
  • 信息量:FBank特征的提取更多的是希望符合声音信号的本质,拟合人耳接收的特性。MFCC做了DCT去相关处理,因此Filter Banks包含比MFCC更多的信息
  • 使用对角协方差矩阵的GMM由于忽略了不同特征维度的相关性,MFCC更适合用来做特征。
  • DNN/CNN可以更好的利用Filter Banks特征的相关性,降低损失。

从目前的趋势来看,因为神经网络的逐步发展,FBank特征越来越流行。

质疑傅里叶变换是否是必要的操作是明智的。鉴于傅立叶变换本身也是线性运算,忽略它并尝试直接从时域中的信号中学习可能是有益的。实际上,最近的一些工作已经尝试过,并且报告了积极的结果。然而,傅立叶变换操作是很难学习的操作,可能会增加实现相同性能所需的数据量和模型复杂性。此外,在进行短时傅里叶变换(stft)时,我们假设信号在这一短时间内是平稳的,因此傅里叶变换的线性不会构成一个关键问题。

附黄学东的spoken language processing书中关于MFCC选择的图表:
在这里插入图片描述

PNCC

power-normalized cepstral coefficients相比于MFCC特征:

  • 在噪声和混响场景下提升识别效果,尤其在训练语料是clean语音的时候
  • 相比于MFCC,计算量提升34.6%

使用pncc相比mfcc,噪声和口音测试集可以得到10-15%的相对提升

细节图
在这里插入图片描述

import scipy
from scipy.io import wavfile
from scipy import fftpack
from spafe.utils import vis
from spafe.features.pncc import pncc
# init input vars
num_ceps = 13
low_freq = 0
high_freq = 2000
nfilts = 24
nfft = 512
dct_type = 2,
use_energy = False,
lifter = 5
normalize = True
#import torchaudio
# read wav
fs, sig = wavfile.read("/media/xiex/2da68739-a3d3-449d-a47f-af4dc6373c6e/datasets/aishell/data_aishell/wav/dev/S0724/BAC009S0724W0122.wav")
#sig,fs = torchaudio.load("/media/xie/2da68739-a3d3-449d-a47f-af4dc6373c6e/datasets/aishell/data_aishell/wav/dev/S0724/BAC009S0724W0121.wav")
# compute features
print(fs,sig)
print(len(sig))
pnccs = pncc(sig=sig,
             fs=fs,
             num_ceps=num_ceps,
             nfilts=nfilts,
             nfft=nfft,
             low_freq=low_freq,
             high_freq=high_freq,
             dct_type=dct_type,
             use_energy=use_energy,
             lifter=lifter,
             normalize=normalize)
# visualize spectogram
vis.spectogram(sig, fs)
# visualize features
vis.visualize_features(pnccs, 'PNCC Index', 'Frame Index')

在这里插入图片描述
在这里插入图片描述

import numpy
from scipy.fftpack import dct
#feat=dct(pnccs,type=2,axis=1,norm='ortho')[:,:13] 
#feat2=dct(feat,type=2,axis=1,norm='ortho')[:,:13]
from python_speech_features import delta 
feat_mfcc = delta(pnccs,2)
feat_mfcc2 = delta(feat_mfcc,2)

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值