語音信號的端點檢測方法有不少種,簡單的方法能夠直接經過計算出聲音的音量大小,找到音量大於某個閾值的部分,認爲該部分爲須要的語音信號,該部分與閾值的交點即爲端點,其他部分認爲非語音幀。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 :