语音信号的端点检测方法有不少种,简单的方法能够直接经过计算出声音的音量大小,找到音量大于某个阈值的部分,认为该部分为须要的语音信号,该部分与阈值的交点即为端点,其他部分认为非语音帧。python
计算音量的方法有两种,一种是以帧为单位(每一帧包含多个采样点),将该帧内的全部采样点的幅值的绝对值以后相加,做为该帧的音量值:git
Vi = sum(|Wi|)github
以采样率为 11025 Hz ,时长为 1s 的波形为例:该波形含有 11025 个采样点,若取帧长为 framesize = 256
,帧间重叠大小为 overlap = 128
,则计算出来的音量数组包含 frameNum = 11025 / (256 - 128) = 86.13,取整为 frameNum = 87
。计算前 86 帧的音量代码(代码为 volume.py
的 calcNormalVolume
):数组
for i in range(frameNum - 1): # 获取第 i 帧数据 curFrame = wave[i*step: i*step + framesize] curFrame = curFrame - np.mean(curFrame) # 公式: v = sum(|w|) volume[i] = np.sum(np.abs(curFrame))
不论采样点的数量是否可以被帧大小整除,最后一帧都须要单独判断,取波形长度和下一帧的波形长度较小的一个,并计算最后一帧的音量:dom
curFrame = wave[(frameNum - 1)*step: min((frameNum - 1)*step + framesize, wlen)] curFrame = curFrame - np.mean(curFrame) volume[(frameNum - 1)] = np.sum(np.abs(curFrame))
另外一种方法是计算分贝音量,与上面的代码差异在于计算 volume
时,使用的公式不一样:函数
Vi = 10 * log10( sum(|Wi| ^ 2) )3d
所以计算 volume[i]
的代码须要修改一下:code
v = np.sum(np.power(curFrame, 2)) volume[i] = 10 * np.log10(v) if v > 0 else 0
通常不多出现平方和 v 的值为 0 的状况,不过为了不这种状况,计算时当 v = 0 时不须要通过 log
运算,直接给音量赋 0 值。orm
理论上讲,当上面代码中计算出 v 为 0 的状况,通过对数运算后获得的值应该为
负无穷
而不是 0。blog
计算出音量后,就获得了一组离散的点,将其绘制在窗口上能够获得一个曲线, 阈值就是平行于横轴的一条直线,这条直线与曲线的交点认为是端点。判断曲线是否与阈值相交的方法很简单,(ys[i] - threshold) * (ys[i+1] - threshold) < 0
。这个方法的缺点在于,当 ys[i] 或者 ys[i+1] 刚好等于 threshold 时,可能会遗漏端点。
def simpleEndPointDetection(vol, wave: vp.Wave, thresholds): """一种简单的端点检测方法,首先计算出声波信号的音量(能量),分别以 音量最大值的10%和音量最小值的10倍为阈值,最后之前两种阈值的一半做为阈值。 分别找到三个阈值与波形的交点并绘制图形,在查找交点时,各个阈值之间没有相互联系 figure 1:绘制出声波信号的波形,并分别用 red green blue 三种颜色的竖直线段 画出检测到的语音信号的端点 figure 2: 绘制声波音量的波形。并分别用 red green blue 三种颜色的音量阈值横线 表示出三种不一样的阈值。 """ # 给出三个固定的阈值 threshold1 = thresholds[0] threshold2 = thresholds[1] threshold3 = thresholds[2] deltatime = wave.deltatime frame = np.arange(0, len(vol)) * deltatime # 分别找出三个不一样的阈值 index1 = vp.findIndex(vol, threshold1) * deltatime index2 = vp.findIndex(vol, threshold2) * deltatime index3 = vp.findIndex(vol, threshold3) * deltatime end = len(wave.ws) * (1.0 / wave.framerate) plt.subplot(211) plt.plot(wave.ts,wave.ws,color="black") if len(index1) > 0: plt.plot([index1,index1],[-1,1],'-r') if len(index2) > 0: plt.plot([index2,index2],[-1,1],'-g') if len(index3) > 0: plt.plot([index3,index3],[-1,1],'-b') plt.ylabel('Amplitude') plt.subplot(212) plt.plot(frame, vol, color="black") if len(index1) > 0: plt.plot([0,end],[threshold1,threshold1],'-r', label="threshold 1") if len(index2) > 0: plt.plot([0,end],[threshold2,threshold2],'-g', label="threshold 2") if len(index3) > 0: plt.plot([0,end],[threshold3,threshold3],'-b', label="threshold 3") plt.legend() plt.ylabel('Volume(absSum)') plt.xlabel('time(seconds)') plt.show()
上述代码经过 findIndex 找出端点的序号 index一、index二、index3,而后计算出这几个端点在时间轴上的数值(乘以 deltatime)。最后使用 plt 进行绘制。
另外一种比较复杂的是在给定一个阈值的基础上,再经过一个新的 threshold 计算出更大的语音部分,详见 volume.py
中的 findIndexWithPreIndex
。这种方法于上面的相似,可是只用到了两组端点索引。
calcNormalVolume 和 calcDbVolume 计算获得的曲线不一样,最终获得的端点也不同。以 one.wav 为语音样本,经过 DbVolume 计算获得的端点效果不如 normalVolume 获得的端点。
计算过零率也是以帧为单位,判断每两个相邻的采样值是否异号,代码和 findIndex
相似,这样能够获得每一帧中越过 0 的采样点的个数:
zcr[i] = sum(curFrame[:-1]*curFrame[1:] < 0) / framesize
与音量曲线相似,给定阈值以后就能够找到端点。绘制出过零率后能够看到,语音部分的过零率比非语音部分的过零率要低不少。
不管是经过音量仍是过零率,实际上都是经过固定的阈值找到端点,当信噪比较大状况下,很容易找到端点(one.wav 中的信噪比较大),但当信噪比较小时,固定的阈值就不必定可以找到端点了。而且固定的阈值(本文中用到的是占音量区间必定比例做为阈值)并不适应不一样的语音信号,难以找出端点。
其余常见的还有 MFCC 系数、自相关函数等等方法能够找到语音信号的端点。
github :