语音处理加窗分帧
一、分帧
语音数据和视频数据不同,本没有帧的概念,但是为了传输与存储,我们采集的音频数据都是一段一段
的。为了程序能够进行批量处理,会根据指定的长度(时间段或者采样数)进行分段,结构化为我们编程
的数据结构,这就是分帧。
二、帧移
由于我们常用的信号处理方法都要求信号是连续的,也就说必须是信号开始到结束,中间不能有断开。然
而我们进行采样或者分帧后数据都断开了,所以要在帧与帧之间保留重叠部分数据,以满足连续的要求,
这部分重叠数据就是帧移。
三、加窗
介绍帧移的时候我们说了,我们处理信号的方法都要求信号是连续条件,但是分帧处理的时候中间断开
了,为了满足条件我们就将分好的帧数据乘一段同长度的数据,这段数据就是窗函数整个周期内的数据,
从最小变化到最大,然后最小。
四、滤波
我们知道,我们处理的语音其实是一种声波,声波是一种物质波。滤波的字面意思理解为过滤一些不同频
率的波。根据傅里叶变换,我们知道任意波可以分解为几种正弦波和余弦波的叠加,从概率论的角度,滤
波即加权。 滤波的作用就是给不同的信号分量不同的权重。最简单的loss pass filter, 就是直接把低
频的信号给0权重,而给高频部分1权重。对于更复杂的滤波,比如维纳滤波, 则要根据信号的统计知识来
设计权重。
当允许信号中较高频率的成分通过滤波器时,这种滤波器叫做高通滤波器。
当允许信号中较低频率的成分通过滤波器时,这种滤波器叫做低通滤波器。
当只允许信号中某个频率范围内的成分通过滤波器时,这种滤波器叫做带通滤波器。
当不允许信号中某个频率范围内的成分通过滤波器时,这种滤波器叫做带阻滤波器。
五、降噪
从统计信号处理的角度,降噪可以看成滤波的一种。降噪的目的在于突出信号本身而抑制噪声影响。从这
个角度,降噪就是给信号一个高的权重而给噪声一个低的权重。维纳滤波就是一个典型的降噪滤波器。
六、合成
在语音处理过程,先分帧,再在频域分成各个子带处理,处理后转成时域,合成语声。从描述上看,
语音合成就是和分帧相反的过程,保证信号数据经过我们变换处理后能够回到原来的状态。把每帧各个子
带转换成时间序列后相互叠加合成为一帧数据。
七、具体理解
1、为什么要进行分帧加窗操作?
语音信号为非平稳信号,其统计属性是随着时间变化的,以汉语为例,一句话中包含很多生母和韵母,不同的拼音,发音的特点很明显是不一样的;但是,语音又具有平稳的属性,比如汉语里的一个声母或者韵母,往往只会持续几十到几百毫秒,这一个发音单元里,语音信号表现出明显的稳定性、规律性,在进行语音识别时,对于一句话识别的过程也是以较小的发音单元(音素、字、字节)为单位进行识别的,因此可以用滑动窗来提取短时片段,也即进行分帧加窗操作。
2、如何进行分帧加窗操作?
2.1 相关术语
帧长:一帧语音信号的长度,长度可以用多种方式表示,如果用时间表示,一帧信号通常取在15ms-30ms之间,经验值为25ms(论文上大多数人用)。帧长为25ms的一帧信号指的是时长有25毫秒的语音信号。也可以用信号的采样点数来表示,如果一个信号的采样率为16kHz,则一帧信号由 16kHz * 25ms = 400个采样点组成。
帧移:指的是每次分帧时移动的距离,以第一帧信号的起始点开始移动一个帧移,开始下一帧。同样也可以用两种方式表示,用时间表示,常设为10ms,用采样点表示,16kHz采样率的信号帧移一般为160个采样点。
加窗:分帧后每一帧的开始和结束都会出现间断,因此分割的帧越多,与原始信号的误差就越大,加窗就是为了解决这个问题,使成帧后的信号变得连续,并且每一帧都会表现出周期函数的特性。常见的窗函数有:矩形窗、汉明窗、汉宁窗等,在语音信号处理中,通常使用汉明窗,其公式如下:
2.2 分帧加窗的具体操作
首先要根据信号长度、帧移、帧长计算出该信号一共可以分的帧数,帧数的计算公式如下:
帧数 = (信号长度-帧长)➗帧移 +1
具体的分帧操作如下图所示:
加窗操作比较简单,仅需将分帧的每一帧信号一次与窗函数进行相乘即可,其中窗函数可以从numpy里直接调用。
在分帧操作时,会遇到最后剩下的信号长度不够一帧的情况,此时需要将对这一段信号进行补零操作,使之达到一帧的长度,或者可以直接将之抛弃,因为最后一帧处于句子最末尾部分,大部分为静音片段。
3 分帧加窗的代码实现
以下是实现分帧加窗的具体代码:
def enframe(signal, frame_len=frame_len, frame_shift=frame_shift, win=np.hamming(frame_len)):
"""
calculate the number of frames:
frames = (num_samples -frame_len) / frame_shift +1
"""
num_samples = signal.size
num_frames = np.floor((num_samples - frame_len) / frame_shift)+1
# calculate the numbers of frames
frames = np.zeros((int(num_frames),frame_len)) # (num_frames,frame_len)
# Initialize an array for putting the frame signals into it
for i in range(int(num_frames)):
frames[i,:] = signal[i*frame_shift:i*frame_shift + frame_len]
frames[i,:] = frames[i,:] * win
return frames
其中需要注意以下几点:
- ①signal代表经过预加重后的信号,frame_len为帧长,frame_shift为帧移。
- ②np.hamming(frame_len)实现了汉明窗函数。
- ③上面的代码中,如果计算出信号长为5.2帧,则取为5帧,因为最后一帧一般都是静音信号,可以省略。初始化一个存放帧信号的数组frames,然后依次将- signal信号里的数据按照分帧操作赋值给frames。
- ④如果输入信号的采样率为16kHz,帧长为400个采样点,帧移为160个采样点,则经过分帧加窗后得到的数组的形状为(帧数行,帧长列)。