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

  

 

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

参考连接:php

  1. 采样与量化,重构与内插:https://wenku.baidu.com/view/bd6b310b700abb68a882fb64.html
  2. 音频信息处理:基础知识:https://wenku.baidu.com/view/614101450722192e4536f6bc.html
  3. ADPCM编码和解码: http://www.javashuo.com/article/p-nwlosdis-bc.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文件格式分析与详解: http://www.javashuo.com/article/p-ufocgtoe-bh.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 模拟音频信号

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

  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]:
 

1.2 声音的A/D转换

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

1.2.1 采样

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

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

采样的方式有不少,这里再也不赘述,能够参考连接1学习了解。python

 

1.2.2 量化

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

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

因此能够简单理解:采样是横轴对时间分段,量化是纵轴对振幅分段。android

In [195]:
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]:
 

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]:
 

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]:
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]:
 

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]:
 

以上图示显示了一个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()
 
 

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()
 
 

3.3 重采样 (6kHz -> 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))
 
 
317920
 

3.4 观察数据细节

从上图看出,其噪声是很是明显的,以致于人声的特征并不清晰。因此咱们须要将其中的细节plot出来进行分析,以方便以后的处理。

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

如下几幅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()
 
 
 
 
 
 

这个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()
 
 
 

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]:
 

(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]:
 

(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]:
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()
 
 

3.8.2 分析

上图是音频通过傅里叶变换以后获得的语谱图。能够看到在纵轴75以上的部分信息是丢失很是严重的。该信息的丢失是在由6k->16k采样率变化时丢掉的。(对于6k采样率,每一个窗口中的数据为 6 * 25 = 150, 因为对称保留一半,因此留下 150/2 = 75,也就是上图所示的0-75的低频部分。) 我试过用sox修改采样率,可是也会产生一样的问题。为何会有这样的问题呢?

对于整数倍的上采样或降采样其效果较好,可是对于分数倍数,则会在插值时有一些问题.
https://blog.csdn.net/longbei9029/article/details/81237335 中用图示给出了形象的解释。(以44.1khz->48khz为例)
从图中能够看出,原始波形分4段5个采样点(包括首尾),若是整数倍转换,采用了8个分段9个采样点(采样率翻倍),波形是没有改变的。
可是,若是新的采样率是原来的1.5倍,采用6个分段7个采样点,新的波形就会和原波形相差很远,形成很大的偏差,换言之,这是一个有损转换过程。一样,44.1KHz和48KHz之间的转换也是属于非整数倍转换,会带来可观的音质损失。

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

另外,在http://forum.doom9.org/archive/index.php/t-131642.html 中”Terranigma“提出相似的问题:
”Does resampling from lets say, 44100 to 48000khz = loss of quality, or perhaps it may seem that way since 48000khz can hold higher frequencies than 44100khz?“
一位名为”3dsnar“的用户给出的解释是:从resample时采用的FIR做为一个低通滤波器的工做原理出发给出的解释. ( 至于为何须要低通滤波器:“因为抽取可能产生混叠,内插可能产生镜像,所以须要在抽取前进行抗混叠滤波,在内插后进行抗镜像滤波。抗混叠滤波和抗镜像滤波都是使用低通滤波器实现。”)

”To clarify. resampling applies an anti-aliasing (lowpass) FIR filter to the input signal during the resampling process. This causes some errors, including Gibbs effect in the time domain, and some frequency domain distortions (since the frequency characteristics of these filters is inperfect)

Resampling 44,1 -> 48 is possible, because you upsample 160 times and than downsample 147 time. This normally would be very ineficient, however something called polyphase filterbank and polyphase filtering routine is used, which applies both in one shot.

Anyway, this is still pretty computationally complex (44,1 -> 48), hence older resamplers used to use shorter FIR impulse responces for constructing the polyphse matrix, and therefore this particular convertion had alwas (than e.g. 44,1 -> 88,2) been viewed as more problematic and results of lower quality.

Today, a good resampler can make this conversion nearly perfectly (i.e. errors are inaudible, although present).“

目前我还在咨询并寻找更好的resample的方法,若是您有相关的建议欢指导交流!

In [197]:
from IPython.display import Image
Image(filename = "/home/lsy/Downloads/zhifubao.png", width = 200, height = 200)
Out[197]:
 

[支付宝] 若是您愿意送我一个小礼物~ O(∩_∩)O

 

The scientist does not study nature because it is useful to do so. He studies it because he takes pleasure in it, and he takes pleasure in it because it is beautiful. If nature were not beautiful it would not be worth knowing, and life would not be worth living. ~ Henri Poincare

相关文章
相关标签/搜索