1. 程式人生 > >雙門限法語音端點檢測(Python實現)

雙門限法語音端點檢測(Python實現)

寫在前面

花了幾天時間寫完了第一個視聽覺訊號處理的實驗,其實還挺簡單的,在這裡分享一下。

本文介紹一下利用雙門限法進行語音端點檢測的方法,該方法主要利用了語音的短時能量短時過零率,關於這兩個語音特徵如何求解,前兩篇文章已經介紹過了(短時能量短時過零率),這裡就不詳細介紹了。這篇文章的重點在雙門限法的演算法思想和實現過程。

先來解釋一下什麼叫端點檢測

端點檢測就是在一段包含語音的訊號中,準確地確定語音的起始點和終止點,將語音段和非語音段區分開。我們知道,一段語音中,有靜音部分和濁音部分,靜音部分包括清音、噪音和無聲(噪音可以歸結到無聲中),濁音部分和清音才是我們需要聽的語音,因此,可以說只有這兩部分才是對我們有用的語音。可以說,端點檢測就是將這兩部分區分出來。(注意,清音部分屬於聲音中的子音,能量小,過零率高,一般是在濁音部分的前面)

演算法介紹

演算法簡介

雙門限法有三個閾值,前兩個是語音能量的閾值,最後一個是語音過零率的閾值,至於為什麼三個閾值卻稱為“雙門限法”呢?我覺得這裡的雙門限不是指兩個閾值,而是指,能量和過零率這兩個時域特徵。

至於為什麼能用這兩個特徵來進行端點檢測呢?最主要的原因就是:濁音的能量高於清音,清音的過零率高於無聲部分。這樣的話,我們就可以先利用能量,將濁音部分區分出來,再利用過零率,將清音也提取出來,就完成了端點檢測。

演算法步驟:

雙門限法

  1. 第一步是取一個較高的短時能量作為閾值MH,利用這個閾值,我們就可以先分出語音中的濁音部分(如圖,A1到A2區間)。本次實驗的MH,我取的是所有幀的短時能量的平均數的一半(平均數我試過了,偏大,處理有問題)。
  2. 第二步是取一個較低的能量閾值ML,利用這個閾值,我們可以從A1,A2,向兩端進行搜尋,將較低能量段的語音部分也加入到語音段,進一步擴大語音段範圍(如圖所示,B1-B2之間還是語音段)。本次實驗中,我首先計算語音前一段的靜音部分的能量均值(前5幀),我將靜音部分的能量均值和MH的平均數的一半作為ML。
  3. 第三步是利用短時過零率,短時過零率的閾值為Zs。由於語音的兩端部分是子音(也就是清音部分),也是語音中的一部分,但是子音的能量與靜音部分的能量一樣低,但是過零率比靜音部分高出很多。為了區分開二者,將利用短時能量區分完的語音段繼續向兩端進行搜尋,短時過零率大於3倍Zs的部分,則認為是語音的清音部分。將該部分加入語言段,就是求得的語音段(如圖C1-C2部分)。

至於為什麼要這麼設定這三個閾值,我只能告訴你,這是經驗,或許你的比我設的更好

Python實現:

# 利用短時能量,短時過零率,使用雙門限法進行端點檢測
def endPointDetect(wave_data, energy, zeroCrossingRate) :
    sum = 0
    energyAverage = 0
    for en in energy :
        sum = sum + en
    energyAverage = sum / len(energy)

    sum = 0
    for en in energy[:5] :
        sum = sum + en
    ML = sum / 5                        
    MH = energyAverage / 4              #較高的能量閾值
    ML = (ML + MH) / 4    #較低的能量閾值
    sum = 0
    for zcr in zeroCrossingRate[:5] :
        sum = float(sum) + zcr             
    Zs = sum / 5                     #過零率閾值

    A = []
    B = []
    C = []

    # 首先利用較大能量閾值 MH 進行初步檢測
    flag = 0
    for i in range(len(energy)):
        if len(A) == 0 and flag == 0 and energy[i] > MH :
            A.append(i)
            flag = 1
        elif flag == 0 and energy[i] > MH and i - 21 > A[len(A) - 1]:
            A.append(i)
            flag = 1
        elif flag == 0 and energy[i] > MH and i - 21 <= A[len(A) - 1]:
            A = A[:len(A) - 1]
            flag = 1

        if flag == 1 and energy[i] < MH :
            A.append(i)
            flag = 0
    print("較高能量閾值,計算後的濁音A:" + str(A))

    # 利用較小能量閾值 ML 進行第二步能量檢測
    for j in range(len(A)) :
        i = A[j]
        if j % 2 == 1 :
            while i < len(energy) and energy[i] > ML :
                i = i + 1
            B.append(i)
        else :
            while i > 0 and energy[i] > ML :
                i = i - 1
            B.append(i)
    print("較低能量閾值,增加一段語言B:" + str(B))

    # 利用過零率進行最後一步檢測
    for j in range(len(B)) :
        i = B[j]
        if j % 2 == 1 :
            while i < len(zeroCrossingRate) and zeroCrossingRate[i] >= 3 * Zs :
                i = i + 1
            C.append(i)
        else :
            while i > 0 and zeroCrossingRate[i] >= 3 * Zs :
                i = i - 1
            C.append(i)
    print("過零率閾值,最終語音分段C:" + str(C))
    return C

寫在後面

具體的實現過程在這裡,包括前兩篇文章的能量和過零率的提取。