Python語音基礎操作--10.1基於動態時間規整(DTW)的孤立字語音識別試驗

《語音信號處理試驗教程》(梁瑞宇等)的代碼主要是Matlab實現的,現在Python比較熱門,所以把這個項目大部分內容寫成了Python實現,大部分是手動寫的。使用CSDN博客查看幫助文件:

Python語音基礎操作–2.1語音錄製,播放,讀取
Python語音基礎操作–2.2語音編輯
Python語音基礎操作–2.3聲強與響度
Python語音基礎操作–2.4語音信號生成
Python語音基礎操作–3.1語音分幀與加窗
Python語音基礎操作–3.2短時時域分析
Python語音基礎操作–3.3短時頻域分析
Python語音基礎操作–3.4倒譜分析與MFCC係數
Python語音基礎操作–4.1語音端點檢測
Python語音基礎操作–4.2基音週期檢測
Python語音基礎操作–4.3共振峯估計
Python語音基礎操作–5.1自適應濾波
Python語音基礎操作–5.2譜減法
Python語音基礎操作–5.4小波分解
Python語音基礎操作–6.1PCM編碼
Python語音基礎操作–6.2LPC編碼
Python語音基礎操作–6.3ADPCM編碼
Python語音基礎操作–7.1幀合併
Python語音基礎操作–7.2LPC的語音合成
Python語音基礎操作–10.1基於動態時間規整(DTW)的孤立字語音識別試驗
Python語音基礎操作–10.2隱馬爾科夫模型的孤立字識別
Python語音基礎操作–11.1矢量量化(VQ)的說話人情感識別
Python語音基礎操作–11.2基於GMM的說話人識別模型
Python語音基礎操作–12.1基於KNN的情感識別
Python語音基礎操作–12.2基於神經網絡的情感識別
Python語音基礎操作–12.3基於支持向量機SVM的語音情感識別
Python語音基礎操作–12.4基於LDA,PCA的語音情感識別

代碼可在Github上下載busyyang/python_sound_open

基於動態時間規整(DTW)的孤立字語音識別試驗

模板匹配法語音識別系統

用戶將詞彙表中每個詞依次說一遍,並且將其特徵矢量時序作爲模板存入模板庫,在識別階段,將輸入語音的特徵矢量時間序列依次與模板庫中每個模板進行相識度比較,將相識度最高者作爲識別的結果輸出。

特徵

使用MFCC係數以及一階和二階差分作爲特徵參數。MFCC是將人耳的聽覺特徵與語音參數相結合的一種特徵參數。MFCC的計算可以參考3.4節。

動態時間規整(DTW)

在識別階段的模式匹配中,不能簡單地將輸入模板與詞庫中模板相比較實現識別,因爲語音信號具有相當大的隨機性,這些差異不僅好酷哦音強的大小,頻譜的偏移,還有發音持續時間不可能是完全相同的,而詞庫中模板不可能睡着模板輸入持續時間的變換而進行伸縮,所以時間規整是不可少的。DTW是吧時間規整和距離測度計算結合起來的非線性規整技術,是模板匹配的方法。

假設某一參考模板的特徵矢量爲:a1,...am,...,aMa_1,...a_m,...,a_M,輸入語音的特徵矢量序列b1,..,bm,...,bMb_1,..,b_m,...,b_M,而且MNM\neq N,那麼動態時間規整就是要找到時間規整函數m=T(n)m=T(n),把輸入的時間軸n非線性映射到參考模板的時間軸mm上,並且滿足:
D=minT(n)n=1Nd[n,T(n)]D=\underset{T(n)}{\min}\sum_{n=1}^Nd[n,T(n)]

d[n,T(n)]d[n,T(n)]表示兩個矢量之間的距離,可以採用歐式距離計算d(x,y)=1ki=1k(xiyi)2d(x,y)=\frac{1}{k}\sqrt{\sum_{i=1}^k(x_i-y_i)^2},D是最佳時間路徑下的兩個補辦的距離測度。

這是一個典型的最優化問題,一般採用動態規劃算法(DP)實現,爲了方便計算,把一個N階段的決策過程轉化爲N個簡單段的決策過程,也就是爲N個子問題逐一做出決策,根據語音信號的性質,時間規整函數要滿足:

  • 邊界條件,T(1)=1,T(N)=MT(1)=1,T(N)=M
  • 單調上升:T(n+1)T(n)0T(n+1)-T(n)\geqslant 0
  • 連續性限制:有些特殊音素可能會對正確識別起很大作用,某個音素的差異可能就是區分的依據,爲了保證信息損失最小,規整函數一般規定不云溪跳過任何一點,即Φ(in+1)Φ(in)1\Phi(i_n+1)-\Phi(i_n)\leqslant 1

使用遞推的方式進行計算,也就是計算(0,0)到(N,M)點的最短距離,首先計算(N,M)前一個點到他的距離,然後在計算(0,0)到(N,M)前一個點的距離,得到最短距離。

import numpy as np
from chapter3_分析實驗.timefeature import *
from chapter2_基礎.soundBase import *
from chapter3_分析實驗.mel import *


def my_vad(x):
    """
    端點檢測
    :param X:輸入爲錄入語音
    :return:輸出爲有用信號
    """
    Ini = 0.1  # 初始靜默時間
    Ts = 0.01  # 窗的時長
    Tsh = 0.005  # 幀移時長
    Fs = 16000  # 採樣頻率
    counter1 = 0  # 以下四個參數用來尋找起始點和結束點
    counter2 = 0
    counter3 = 0
    counter4 = 0
    ZCRCountf = 0  # 用於存儲過零率檢測結果
    ZCRCountb = 0
    ZTh = 40  # 過零閾值
    w_sam = int(Ts * Fs)  # 窗口長度
    o_sam = int(Tsh * Fs)  # 幀移長度
    lengthX = len(x)
    segs = int((lengthX - w_sam) / o_sam) + 1
    sil = int((Ini - Ts) / Tsh) + 1
    win = np.hamming(w_sam)
    Limit = o_sam * (segs - 1) + 1
    FrmtIndex = [i for i in range(0, Limit, o_sam)]  # 每一幀的起始位置
    # 短時過零率
    ZCR_Vector = STZcr(x, w_sam, o_sam)
    # 能量
    Erg_Vector = STMn(x, w_sam, o_sam)
    IMN = np.mean(Erg_Vector[:sil])
    IMX = np.max(Erg_Vector)
    l1 = 0.03 * (IMX - IMN) + IMN
    l2 = 4 * IMN
    ITL = 100 * np.min((l1, l2))
    ITU = 10 * ITL
    IZC = np.mean(ZCR_Vector[:sil])
    stddev = np.std(ZCR_Vector[:sil])
    IZCT = np.min((ZTh, IZC + 2 * stddev))
    indexi = np.zeros(lengthX)
    indexj, indexk, indexl = indexi, indexi, indexi
    # 搜尋超過能量閾值上限的部分
    for i in range(len(Erg_Vector)):
        if Erg_Vector[i] > ITU:
            indexi[counter1] = i
            counter1 += 1
    ITUs = int(indexi[0])
    # 搜尋能量超過能量下限的部分
    for j in range(ITUs - 1, -1, -1):
        if Erg_Vector[j] < ITL:
            indexj[counter2] = j
            counter2 += 1
    start = int(indexj[0]) + 1
    Erg_Vectorf = np.flip(Erg_Vector, axis=0)
    # 重複上面過程相當於找結束幀
    for k in range(len(Erg_Vectorf)):
        if Erg_Vectorf[k] > ITU:
            indexi[counter3] = k
            counter3 += 1
    ITUs = int(indexk[0])
    for l in range(ITUs - 1, -1, -1):
        if Erg_Vectorf[l] < ITL:
            indexl[counter4] = l
            counter4 += 1
    finish = len(Erg_Vector) - int(indexl[0])  # 第一級判決結束幀
    # 從第一級判決起始幀開始進行第二判決(過零率)端點檢測
    BackSearch = np.min((start, 25))
    for m in range(start, start - BackSearch, -1):
        rate = ZCR_Vector[m]
        if rate > IZCT:
            ZCRCountb += 1
            realstart = m
    if ZCRCountb > 3:
        start = realstart

    FwdSearch = np.min((len(Erg_Vector) - finish, 25))
    for n in range(finish, finish + FwdSearch):
        rate = ZCR_Vector[n]
        if rate > IZCT:
            ZCRCountf += 1
            realfinish = n
    if ZCRCountf > 3:
        finish = realfinish
    x_start = FrmtIndex[start]
    x_finish = FrmtIndex[finish]
    trimmed_X = x[x_start:x_finish]
    return trimmed_X


def myDCW(F, R):
    """
    動態時間規劃
    :param F:爲模板MFCC參數矩陣
    :param R:爲當前語音MFCC參數矩陣
    :return:cost爲最佳匹配距離
    """
    r1, c1 = F.shape
    r2, c2 = R.shape
    distence = np.zeros((r1, r2))
    for n in range(r1):
        for m in range(r2):
            FR = np.power(F[n, :] - R[m, :], 2)
            distence[n, m] = np.sqrt(np.sum(FR)) / c1

    D = np.zeros((r1 + 1, r2 + 1))
    D[0, :] = np.inf
    D[:, 0] = np.inf
    D[0, 0] = 0
    D[1:, 1:] = distence

    # 尋找整個過程的最短匹配距離
    for i in range(r1):
        for j in range(r2):
            dmin = min(D[i, j], D[i, j + 1], D[i + 1, j])
            D[i + 1, j + 1] += dmin

    cost = D[r1, r2]
    return cost


def deltacoeff(x):
    """
    計算MFCC差分系數
    :param x:
    :return:
    """
    nr, nc = x.shape
    N = 2
    diff = np.zeros((nr, nc))
    for t in range(2, nr - 2):
        for n in range(N):
            diff[t, :] += n * (x[t + n, :] - x[t - n, :])
        diff[t, :] /= 10
    return diff


def mfccf(num, s, Fs):
    """
    計算並返回信號s的mfcc參數及其一階和二階差分參數
    :param num:
    :param s:
    :param Fs:
    :return:
    """
    N = 512  # FFT數
    Tf = 0.02  # 窗口的時長
    n = int(Fs * Tf)  # 每個窗口的長度
    M = 24  # M爲濾波器組數
    l = len(s)
    Ts = 0.01  # 幀移時長
    FrameStep = int(Fs * Ts)  # 幀移
    lifter = np.array([i for i in range(num)])
    lifter = 1 + int(num / 2) * np.sin(lifter * np.pi / num)

    if np.mean(np.abs(s)) > 0.01:
        s = s / np.max(s)
    # 計算MFCC
    mfcc = Nmfcc(s, Fs, M, n, FrameStep)

    mfcc_l = np.multiply(mfcc, lifter)
    d1 = deltacoeff(mfcc_l)
    d2 = deltacoeff(d1)
    return np.hstack((mfcc_l, d1, d2))


def CMN(r):
    """
    歸一化
    :param r:
    :return:
    """
    return r - np.mean(r, axis=1, keepdims=True)


def DTWScores(r, features, N):
    """
    DTW尋找最小失真值
    :param r:爲當前讀入語音的MFCC參數矩陣
    :param features:模型參數
    :param N:爲每個模板數量詞彙數
    :return:
    """
    # 初始化判別矩陣
    scores1 = np.zeros(N)
    scores2 = np.zeros(N)
    scores3 = np.zeros(N)

    for i in range(N):
        scores1[i] = myDCW(CMN(features['p1_{}'.format(i)]), r)
        scores2[i] = myDCW(CMN(features['p2_{}'.format(i)]), r)
        scores3[i] = myDCW(CMN(features['p2_{}'.format(i)]), r)
    return scores1, scores2, scores3


if __name__ == '__main__':
    # 製作模板集
    features = {}
    for i in range(10):
        data, fs, bits = soundBase('p1/{}.wav'.format(i)).audioread()
        speechIn1 = my_vad(data)
        fm = mfccf(12, speechIn1, fs)
        features['p1_{}'.format(i)] = fm
    for i in range(10):
        data, fs, bits = soundBase('p2/{}.wav'.format(i)).audioread()
        speechIn1 = my_vad(data)
        fm = mfccf(12, speechIn1, fs)
        features['p2_{}'.format(i)] = fm
    for i in range(10):
        data, fs, bits = soundBase('p3/{}.wav'.format(i)).audioread()
        speechIn1 = my_vad(data)
        fm = mfccf(12, speechIn1, fs)
        features['p3_{}'.format(i)] = fm

    soundBase('mysound.wav').audiorecorder(rate=16000, channels=1)
    data, fs, bits = soundBase('mysound.wav').audioread()
    mysound = my_vad(data)
    fm_my = mfccf(12, mysound, fs)
    fm_my = CMN(fm_my)

    scores = DTWScores(fm_my, features, 10)
    s = {}
    for i in range(len(scores)):
        tmp = np.argsort(scores[i])[:2]
        for j in tmp:
            if j in s.keys():
                s[j] += 1
            else:
                s[j] = 1
    print(s)
    s = sorted(s.items(), key=lambda x: x[1], reverse=True)[0]

    if np.max(data) < 0.01:
        print('Microphone get no signal......')
    elif s[1] < 2:
        print('The word you have said could not be properly recognised.')
    else:
        print('You just saied: {}'.format(s[0]))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章