[NLP-ASR] 语音识别项目整理(一) 音频预处理

简介之前参与过114对话系统的项目,中间搁置很久,现在把之前做过的内容整理一下,一是为自己回顾,二是也希望分享自己看的内容,中间也遇到一些问题,如果您可以提一些建议将不胜感激.114查询主要分为4
强烈推介IDEA2021.1.3破解激活,IntelliJ IDEA 注册码,2021.1.3IDEA 激活码  

  

 

简介
之前参与过114对话系统的项目,中间搁置很久,现在把之前做过的内容整理一下,一是为自己回顾,二是也希望分享自己看的内容,中间也遇到一些问题,如果您可以提一些建议将不胜感激.
114查询主要分为4个任务,该对话系统希望通过构建神经网络学习模型,以实现将传统的需要接线员回复用户问题的方式,转换为可以实现机器自动回复用户问题的智能对话。由于拿到的是114电话录音数据,并没有标记好的文本,加上语音中有当地方言、特定字母数字在现有商用转录识别效果差等问题,所以需要自己实现语音识别的模块,以便后期可以针对我们数据集的特定进行训练优化。这篇blog将介绍语音预处理内容。
由于本人研究生阶段研究方向是NLP,未系统学习信号内容,所以专业知识有所欠缺,以下资料是在项目过程中查阅资料等整理的,如有不当,还望指教~

参考链接:

  1. 采样与量化,重构与内插:https://wenku.baidu.com/view/bd6b310b700abb68a882fb64.html
  2. 音频信息处理:基础知识:https://wenku.baidu.com/view/614101450722192e4536f6bc.html
  3. ADPCM编码和解码: https://www.cnblogs.com/hwl1023/p/5580813.html
  4. java实现V3格式音频文件向wav文件的转换: https://mox-sir.iteye.com/blog/2181641
  5. WAVE PCM soundfile format: http://soundfile.sapp.org/doc/WaveFormat/
  6. wav文件格式分析与详解: https://www.cnblogs.com/ranson7zop/p/7657874.html
  7. 傅里叶分析:https://zhuanlan.zhihu.com/p/19763358
  8. 音频重采样造成音质损失的原理: https://blog.csdn.net/longbei9029/article/details/81237335
  9. 窗函数图示:https://blog.csdn.net/juhou/article/details/81194566
  10. 音频预处理:https://haythamfayek.com/2016/04/21/speech-processing-for-machine-learning.html

 

1. 语音基础知识

1.1 模拟音频信号

模拟音频信号有两个重要参数:频率幅度.声音的频率体现音调的高低,声波幅度的大小体现声音的强弱。那么给出一段语音信号,如何度量频率和振幅呢?

  1. 频率
    一个声源每秒钟可产生成百上千个波,我们把每秒钟波峰所发生的数目称之为信号的频率,用单位赫兹(Hz)或千赫兹(kHz)表示。
  2. 幅度
    信号的幅度表示信号的基线到当前波峰的距离。幅度决定了信号音量的强弱程度。幅度越大,声音越强。对音频信号,声音的强度用分贝(dB)表示,分贝的幅度就是音量。

In [186]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/freq_amplitude.png", width = 400, height = 400)

只听到从山间传来架构君的声音:
我军青坂在东门,天寒饮马太白窟。有谁来对上联或下联?

Out[186]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

1.2 声音的A/D转换

A/D转换就是把模拟信号转换为数字信号的过程,模拟电信号变为了由"0","1"组成的bit信号。
A/D转换关键步骤是声音的采样量化编码

1.2.1 采样

为实现A/D转换,需要把模拟音频信号波形进行分割,这种方法称为采样(Sampling)采样的过程是每隔一个时间间隔在模拟声音的波形上取一个幅度值,把时间上的连续信号变成时间上的离散信号。 该时间间隔称为采样周期, 其倒数为采样频率,表示计算机每秒中采集多少声音样本。

奈奎斯特(Nyquist)理论
采样频率与声音频率之间有着一定的关系,根据奈奎斯特理论,只有采样频率高于声音信号最高频率的两倍时,才能把数字信号表示的声音还原为原来的声音。$$f_s >= 2f$$

采样的方式有很多,这里不再赘述,可以参考链接1学习了解。

 

1.2.2 量化

采样只解决了音频波形信号在时间坐标上(横轴) 把一个波形切成若干个等分的数字化问题,但是还需要用某种数字化的方法来反映某一瞬间声波幅度的电压值大小,该值的大小影响音量的高低。 我们把对声波波形幅度的数字化表示称之为"量化"。
量化的过程是将采样后的信号按照整个声波的幅度划分为有限个区段的集合,把落入某个区段内的样值归为一类,并赋予相同的量化值。

如何分割采样信号的幅度呢?
采取二进制的方式,以8bit或16bit的方式划分纵轴。也就是说在一个以8位为记录模式的音效中,其纵轴将会被划分为$2^8 = 256$个量化等级,用以记录其幅值大小。也即为若每个量化级用长度为b比特的二进制表示,那么量化级n的个数为 $n = 2^b$
如下图所示,b = 3时的量化方式,其前三个采样值可以用二进制序列”100 110 111“表示。

所以可以简单理解:采样是横轴对时间分段,量化是纵轴对振幅分段。

In [195]:
此代码由Java架构师必看网-架构君整理
from IPython.display import Image Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/quantizing_enc.png", width = 300, height = 300)

Out[195]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

1.2.3 编码

模拟信号量经过采样和量化后,形成一系列的离散信号——脉冲数字信号。这些脉冲数字信号可以以一定的方式进行编码,形成计算机内部运行的数据。
编码就是按照一定的格式把经过采样和量化得到的离散数据记录下来,并在有用的数据中加入一些用于纠错、同步和控制的数据。 在数据回放时,可以根据所记录的纠错数据判别读出的声音数据是否有错,如在一定范围内有错,可以加以纠正。

编码的形式有很多,常用的编码方式是脉冲编码调制(PCM).在wav中也有采用ADPCM的编码方式,这里主要对这两种进行介绍。

 

1.2.3.1 PCM

如果我们对一个声音信号进行采样,采用16位量化,比如采集53个点.如果我们直接存储每一个点的16位的采样值,这样就需要53X16=848位,大约是106字节。如下图:

In [189]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/pcm.png", width = 300, height = 300)

Out[189]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

1.2.3.2 DPCM

但我们换个思路,我们不存储采样值,而存储采样点两两之间的差值(采样值可能会很大,需要更多的位数来表达,比如16个位,但是两点之间一般来说是比较连续的,差值不会太大,所以这个差值只需要很少的几个位即可表达,比如4个位)。这样,我们只需要知道前一个点的值,又知道它与下一个点的差值,就可以计算得到下一个点了。这个差值就是所谓的“差分”。DPCM即为差分脉冲编码调制。 这样对于1.2.3.1中用16位表示53个点,DPCM只需要4位,这样存储大小减为原来的 1/4。

 

1.2.3.3 ADPCM

ADPCM为自适应差分脉冲编码调制。自适应体现在哪里呢?
考虑DPCM存储的是两点之差,但对于有的差值大,有的差值小;如果差值大过有限位数可表示的范围,那么数据就会丢失,造成失真。如何更好的保存原始音频的信息呢?
如果有一种方法,可以把两点之间的差值变换到固定的几个位即可表达的范围内,那就好了。而且这种变换是实时的,并且具有自适应性和预测能力的。这就是ADPCM的基本思想。它定义了一些因子,这种算法如果发现两点之间差值变大之后,就会用差值去和相应的因子作除法,从而减小了差值,让它可以减少到几个位可表达的数值范围内。而选择哪一个因子来除它,这就是ADPCM编码要作的事情了。

ADPCM算法巧妙的利用了音频信号的特点,也就是音频信号上的点与它前面的若干个点是有一定的相关性的,从而可以对下一个点进行预测,从而预先估计这个差值,从而选取相应的除数因子,去把差值归化到数值范围内.

In [190]:
此代码由Java架构师必看网-架构君整理
from IPython.display import Image Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/adpcm.png", width = 300, height = 300)

Out[190]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

2. V3 转 wav

114的电话录音数据是.V3格式的,一般播放器是不支持的,并且也没有见到直接用该数据格式进行音频处理的。音频处理常用是转换为.wav格式。在转格式时,注意几个点,编码格式、采样率、编码位数、通道数、文件头
本实验中采用的是https://mox-sir.iteye.com/blog/2181641 中的代码。本实验中采用的ADPCM编码,单通道,对于采样率和编码位数则需要看V3文件中是如何采样的。本实验中试了几种方式,但只有6khz采样率,uint8编码合适。转换时也尝试采用了8k采样率和16bit编码,但效果很差,基本听不清楚。采用8k采样率,8-bit编码的效果是:说话人声音很尖,语速被加快;采用8k采样率,16-bit编码的效果是:说话人声音很低,很粗,完全不像女声了;并且语速非常慢,以至于转换后只有前部分的语音被转换来,后部分的都丢失了。
在文件格式转换过程中,文件头信息设置很重要。(当然上面链接的代码中都写好了,但还是可以了解一下)。
Digital Audio - Creating a WAV (RIFF) file http://www.topherlee.com/software/pcm-tut-wavformat.html 中有对wav的文件头信息的详尽介绍。 这里涉及到一个点就是RIFF.
RIFF
在windows环境下,大部分的多媒体文件都依循着一些通用的结构来存放,这些结构称为“资源互换文件格式”(Resources Interchange File Format),简称RIFF。RIFF可以看作一种树状结构,其基本构成单位是块(chunk)。每个块由“辨别码”、“数据大小”及“数据”等构成。 RIFF文件的前4字节为其辨别码“RIFF"的ASCII字符码,紧跟其后的双字节数据则标示整个文件大小(单位为字节Byte)。由于表示文件长度或块长度的”数据大小“信息占用4Byte,所以,事实上一个WAVE文件或文件中块的长度为数据大小加8。
WAVE
wav文件又称波形文件,来源于对声音模拟波形的采样,并以不同的量化位数把这些采样点的值轮换成二进制数,然后存入磁盘,这就产生了波形文件。wav声音文件是使用RIFF的格式描述的,它由文件头和波形音频文件数据块组成。文件头包括标识符、语音特征值、声道特征以及PCM格式类型标志等、WAV数据块是由数据字块标记、数据子块长度和波形音频数据3个数据字块组成。
在WAVE文件中,所采用的编码方式有PCM(Pulse Code Modulation-脉冲编码调制)和ADPCM(Adaptive Differential Pulse Code Modulation-自适应差分脉冲编码调制)两种。
WAVE文件是非常简单的一种RIFF文件,它的格式类型为"WAVE"。RIFF块包含两个子块,这两个子块的ID分别是"fmt"和"data",其中"fmt"子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。

参考链接:https://blog.csdn.net/cwfjimogudan/article/details/71112171
https://wenku.baidu.com/view/614101450722192e4536f6bc.html

In [192]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/canonical_wave_file_format.png", width = 500, height = 500)

Out[192]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

以上图示显示了一个wave file format的格式,简洁形象。
那么根据这个图示,再看V3转wav的代码就很明了了。其中

int wFormatTag = 1; // format tag (01 = Windows PCM)
tmpArr = new byte[2];
toShortBinary(wFormatTag, tmpArr, 0);
filewriter.write(tmpArr);// format tag (01 = Windows PCM)

表示了转换后的wav文件是采用的PCM编码

为什么要强调看这个信息呢?因为下面需要对采样位数、采样率进行转换,不同的编码格式显然需要不同的转换方式。

 

3. 特征提取

3.1 读取音频文件

可以使用scipy.io.wavfile.read(somefile)来读取.wav音频文件。它会返回一个元组,第一项为音频的采样率,第二项为音频数据的numpy数组。 一般常用的是
16-bit PCM [-32768, +32767] int16
8-bit PCM [0, 255] uint8
训练集 清华的thchs30的音频数据格式:16k的采样率, int16编码。
我的音频是6khz采样率, 8bit.
所以涉及到格式转换的问题。uint8 -> int16; 6khz -> 16khz

In [171]:
import scipy.io.wavfile as wav 
import matplotlib.pyplot as plt 
import os 
import numpy as np 

 

通过plot显示wavsignal

In [172]:
# load the china-unicom test data
filepath = "/home/lsy/project/dialogue/ASR_test/preprocess/test_6kuint8/0747033-6k-uint8.wav"
fs, wavsignal = wav.read(filepath) 
plt.plot(wavsignal)
plt.show()

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

3.2 PCM格式WAV文件uint8转换为int16类型

为了与训练数据集中数据的格式统一,需要将uint8转换为int16.除了使用常用的sox等开源工具,想能否自己代码实现转换。转换前首先要考虑wav文件的编码格式,在2中介绍了,本实验中采用的PCM编码方式,也就是非压缩格式,那么存储的直接是8位采样值。那么这就简单一些了。

思路是将uint8数据范围scale到int16范围内,如以下convert_scale函数所示. 由于uint8是无符号类型的,int16有符号,所以应先减去中值再扩展。

(但是如果采用的其他编码方式,比如ADPCM编码,则不可以用这种转换方式,因为ADPCM存储的信息并不是原本的采样量化后的值,而是差分值。如果直接将uint8 scale到int16空间,相当于将原音频的采样点之间的差值人为放大,这会造成失真。)

当然用c++的话,可以直接用移位实现,https://stackoverflow.com/questions/24449957/converting-a-8-bit-pcm-to-16-bit-pcm
INT16 sample16 = (INT16)(sample8 - 0x80) << 8;

In [193]:
def convert_scale(x):
    return (int)(x - 128) * 256
w = [convert(x) for x in wavsignal]

In [174]:
plt.plot(w)
plt.show()

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

3.3 重采样 (6kHz -> 16kHz)-16kHz)">¶

尝试了sox转换,scipy.signal.resample转换等方式,但都有高频部分丢失的问题。(虽然转换后人耳听是正常的,但是看文末的语谱图便发现高频部分信息都丢失了)目前这个问题并没有很好的解决,如果您有很好的resample的方法欢迎指导交流~

 

首先查看自己的音频的采样率

In [175]:
print(fs)# sample rate

 
6000

 

由于训练集中采用的是16kHz的采样率,为了与其保持一致,这里将自己的语音数据由6kHz重采样到16kHz.
很多开源工具提供该功能,比如使用sox, 或ffmpeg等,这里直接使用的scipy.signal.resample函数。https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.resample.html
scipy.signal.resample(x, num, t=None, axis=0, window=None)
Resample x to num samples using Fourier method along the given axis. 当然采取这种重采样方式可能是造成高频信息丢失的原因,具体分析见文末语谱图的分析部分。

In [176]:
from scipy import signal
wavsignal = signal.resample(w, (int)(len(w) / 6 * 16))
wavsignal = np.clip(wavsignal, a_min = -32768, a_max = 32767)
plt.plot(wavsignal)
plt.show()
print(len(wavsignal))

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
317920

 

3.4 观察数据细节

从上图看出,其噪声是非常明显的,以至于人声的特征并不清晰。所以我们需要将其中的细节plot出来进行分析,以方便之后的处理。

In [177]:
plt.plot(wavsignal[12000:14000])
plt.show()

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

以下几幅plots将不同阶段的wavsignal表现出来。最后三张图的波形分布与之前有明显的不同,可以认为是噪声。

In [178]:
start = 12000
step = 1000
for i in range(5):
    fig = plt.figure(figsize=(20,3))
    plt.plot(wavsignal[start + i * step: start + (i+1)*step])
    plt.show()

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

这个wav是通过v3直接转换过来的,为了使语音更加干净,还需要预处理。

 

3.5 预加重 (Pre-emphasis)

3.5.1 定义:

预加重是一种在发送端对输入信号高频分量进行补偿的信号处理方式。随着信号速率的增加,信号在传输过程中受损很大,为了在接收终端能得到比较好的信号波形,就需要对受损的信号进行补偿,预加重技术的思想就是在传输线的始端增强信号的高频成分,以补偿高频分量在传输过程中的过大衰减。而预加重对噪声并没有影响,因此有效地提高了输出信噪比。

3.5.2 对语音进行预加重处理的原因:

参考:
1) https://blog.csdn.net/xiaoyaoren3134/article/details/48678553
2) https://blog.csdn.net/cwfjimogudan/article/details/71112171
3) https://haythamfayek.com/2016/04/21/speech-processing-for-machine-learning.html
(1) 平衡频谱,因为高频相较于低频通常具有较小的幅度。声道的终端为口和唇。从声道输出的是速度波,而语音信号是声压波,二者之比的倒数称为辐射阻抗。它表征口和唇和辐射效应,也包括圆形头部的绕射效应等。语音信号s(n)的平均功率谱受声门激励和口鼻辐射的影响,高频端大约在800Hz以上按6dB/oct (倍频程)衰减,频率越高相应的成分越小,为此要在对语音信号s(n)进行分析之前对其高频部分加以提升。
(2) 避免在傅里叶变换操作期间的数值问题;
(3) 改善信噪比
备注:在现代的ASR系统中,预加重并没有非常大的影响,因为其大部分作用可以通过下文所讲的mean normalization实现。虽然预加重可以避免FFT中的数值问题,但是在现代的FFT实现中,这并不是大问题。

3.5.3 预加重的方法

一般采用一阶FIR高通数字滤波器来实现预加重,公式为
$H(z) = 1 - uz^{-1}$
设n时刻的语音采样值为$s(n)$,经过预加重处理后的结果为
$y(t) = x(t) - \alpha x(t - 1)$
其中a为预加重系数,$0.9 < a < 1.0$,一般取值0.95或者0.97.

In [179]:
# origin plot
plt.plot(wavsignal)
plt.show()
# pre-emphasised plot
pre_emphasis = 0.97
wavsignal = np.append([wavsignal[0]], [(wavsignal[i + 1] - pre_emphasis * wavsignal[i]) for i in range(len(wavsignal) - 1)])
# clip to int16
wavsignal = np.clip(wavsignal, a_min = -32768, a_max = 32767)
plt.plot(wavsignal)
plt.show()

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

3.6 分帧 (Framing)

3.6.1为什么要分帧呢

首先需要了解当前的算法是如何处理语音信号的。一般首先采用的是傅里叶变化,将语音信号由时域转换到频域空间。傅里叶变化是针对周期函数的,这是它的限制所在。语音信号,其频率是不断变化的,也就是“非周期性”的。如果直接将这样长的语音信号进行傅里叶变换,是很难获得其信号频率的良好近似的。那么如何采用傅里叶变化对语音信号进行处理呢?
这就需要考虑到语音的特性了。语音在较短的时间内变化是平稳的,即具有”短时平稳性“(10-30ms内可以认为语音信号近似不变),因此可以将长语音截断为短的片段,进行“短时分析”。我们可以认为这段短时语音是具有周期性的,可以对其进行周期延拓。这样就得到了一个周期函数,可以进行傅里叶变换了。 上面中”短的片段“,每一个短的片段称为”一帧“。将不定长的音频切分成固定长度的小段,这一步称为分帧

3.6.2 帧长和帧移

帧长就是每一帧的时间长度,一般取20mx-40ms.
帧移就是每次处理完一帧后向后移动的步长,一般设置与帧长有 50%(+/-10%)的重叠。

3.6.3 为什么要设置帧移

  1. 考虑不设置帧移的情况。对于一个100ms长的音频,取frame size =25ms,那么可以得到4帧,[0,25), [25,50), [50,75),[75,100),每一帧经过处理后得到一个值,这个值可以看做对应帧的特征表征,那么最终也就得到4个值。
  2. 考虑设置帧移的情况。同样取frame size = 25ms, frame stride = 10ms, 那么可以得到9帧,[0, 25), [9, 34), [19, 44)...[89, 100),每一帧经过处理后得到一个值,那么最终可以得到9个值。相比较,2中可以提取到更为细致而丰富的语音信息,因为显然其处理的粒度比1小很多,也可以更好地捕获到1中相邻两帧的边缘信息。(举个例子,对于1中25ms附近的语音信号特征,只在第二帧[25,50)中进行了提取;在2中,[0,25),[9,34),[19,44)这三帧都对其进行了特征提取,所以更为平滑准确。)

 

3.7 构造海明窗

在上面进行分帧后,需要对帧加窗函数。

3.7.1 为什么需要加窗函数

(1) 截取一小段音频以便进行周期性延拓与傅里叶变换
(2) 不同的窗函数的频谱泄漏不同,可以根据数据与任务要求进行选择
仍然承接以上分帧的思路,运用计算机实现工程测试信号处理时,不可能对无限长的信号进行测量和运算,而是取其有限的时间片段进行分析。做法是从信号中截取一个时间片段,然后用截取的信号时间片段进行周期延拓处理,得到虚拟的无限长的信号,然后就可以对信号进行傅里叶变换、相关分析等数学处理。那么窗函数就需要是在某一区间内有非零值,而在其余区间值几乎为0,那么用该函数f, 乘以任何一个其他函数g, f * g只有一部分有非零值。这里的f即为窗函数,g即为我们的音频,这样就可以截取出一小段音频了。

无限长的信号被截断以后,其频谱发生了畸变,原来集中在f(0)处的能量被分散到两个较宽的频带中去了(这种现象称之为频谱能量泄漏)。
也可以想象成,如果将原波形在窗口两端直接截断,使其取值为0,这就为在进行傅里叶变换,也就是进行时域到频域的变换时,为了要拟合这个突然降为0的波形,需要引入高频成分,那么这样进行傅里叶变换的结果并不能很好的反应原音频的真实情况了。
为了减少频谱能量泄漏,所以需要用这种在两端平滑变为0的窗函数,以避免谱泄露。

3.7.2 窗函数的选择

不同的窗函数对信号频谱的影响是不一样的,这主要是因为不同的窗函数,产生泄漏的大小不一样,频率分辨能力也不一样。信号的截断产生了能量泄漏,而用FFT算法计算频谱又产生了栅栏效应,从原理上讲这两种误差都是不能消除的,但是我们可以通过选择不同的窗函数对它们的影响进行抑制。(矩形窗主瓣窄,旁瓣大,频率识别精度最高,幅值识别精度最低;布莱克曼窗主瓣宽,旁瓣小,频率识别精度最低,但幅值识别精度最高)。
https://baike.baidu.com/item/%E7%AA%97%E5%87%BD%E6%95%B0/3497822?fr=aladdin 中对矩形窗、三角窗、汉宁窗(Hanning)、海明窗(Hamming)、高斯窗进行了比较。
https://blog.csdn.net/juhou/article/details/81194566 中则显示了窗函数及其主旁瓣衰减的图示。

(1) 矩形窗

这种窗的优点是主瓣比较集中,缺点是旁瓣较高,并有负旁瓣,导致变换中带进了高频干扰和泄漏,甚至出现负谱现象。

In [180]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/rect.png", width = 400, height = 400)

Out[180]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

(2) 汉宁窗 (Hanning)

汉宁窗主瓣加宽并降低,旁瓣则显著减小,从减小泄漏观点出发,汉宁窗优于矩形窗.但汉宁窗主瓣加宽,相当于分析带宽加宽,频率分辨力下降。

In [181]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/hanning.png", width = 400, height = 400)

Out[181]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

 

(3) 海明窗 (Hamming)

海明窗与汉宁窗都是余弦窗,只是加权系数不同。海明窗加权的系数能使旁瓣达到更小。分析表明,海明窗的第一旁瓣衰减为一42dB.海明窗旁瓣衰减速度为20dB/(10oct),这比汉宁窗衰减速度慢。海明窗与汉宁窗都是很有用的窗函数。
在Scipy.signal.hamming中对Hamming window有简要的介绍: $$ w(n) = 0.54 + 0.46cos(\frac{2\pi n}{M - 1}), 0 <= n <= M- 1$$ 其中,M是输出窗口内点的个数;w是窗,最大值被归一化为1. 本实验中选择的是海明窗。

In [182]:
from IPython.display import Image
Image(filename = "/home/lsy/project/dialogue/ASR_test/preprocess/window_func_img/hamming.png", width = 400, height = 400)

Out[182]:
[NLP-ASR] 语音识别项目整理(一) 音频预处理

In [183]:
x=np.linspace(0, 400 - 1, 400, dtype = np.int64)
w = 0.54 - 0.46 * np.cos(2 * np.pi * (x) / (400 - 1) ) # hamming window 
fs= 16000
# wav波形 加时间窗以及时移10ms
time_window = 25 # 单位ms
window_length = fs / 1000 * time_window # 计算窗长度的公式,目前全部为400固定值
print('window_length = ', window_length)
wav_arr = np.array(wavsignal)
wav_length = len(wavsignal)
print(wav_length)

 
window_length =  400.0
317920

 

3.8 傅里叶变换并生成语谱图

这里直接调用的scipy.fftpack进行傅里叶变换,并将变换后的结果用语谱图表示。

3.8.1 语谱图

语谱图就是语音频谱图。 https://wenku.baidu.com/view/30c1ae2da517866fb84ae45c3b3567ec112ddc14.html
语音的时域分析和频域分析是语音分析的两种重要方法,但各有局限性:
(1) 时域分析对语音信号的频率特性没有直观的了解;
(2) 频域特性中又没有语音信号随时间的变化关系。
因此人们研究语音的时频分析特性,把和时序相关的傅里叶分析的显示图形称为语谱图。
语谱图用三维的方式表示语音频谱特性,纵轴表示频率,横轴表示时间,颜色的深浅表示特定频带的能量大小,颜色深,表示该点的语音能量越强。 它综合了频谱图和时域波形的特点,明显的显示处语音频谱随时间的变化情况,或者可以说是一种动态的频谱。

In [184]:
from scipy.fftpack import fft
# 获取信号的时频图
# 计算循环终止的位置,也就是最终生成的窗数 = (总的ms - 最后一个窗口ms) / 窗口移动步长
# 10为帧移
range0_end = (int)((len(wavsignal)/fs*1000 - time_window) // 10) 
data_input = np.zeros((range0_end, 200), dtype = np.float) # 用于存放最终的频率特征数据
data_line = np.zeros((1, 400), dtype = np.float)
for i in range(0, range0_end):
    p_start = i * 160 # 16khz/s --> 16hz/ms --> 10ms * 16hz/ms = 160hz
    p_end = p_start + 400  # 16hz/ms * 25ms = 400
    data_line = wav_arr[p_start:p_end]
    data_line = data_line * w # 加窗
    data_line = np.abs(fft(data_line)) # 傅里叶变换
    data_input[i]=data_line[0:200] # 设置为400除以2的值(即200)是取一半数据,因为是对称的
data_input = np.log(data_input + 1)
#data_input = data_input[::]

fig = plt.figure(figsize=(20,10))
plt.imshow(data_input.T, origin = 'lower')
plt.show()