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是吧时间规整和距离测度计算结合起来的非线性规整技术,是模板匹配的方法。

假设某一参考模板的特征矢量为: a 1 , . . . a m , . . . , a M a_1,...a_m,...,a_M a1,...am,...,aM,输入语音的特征矢量序列 b 1 , . . , b m , . . . , b M b_1,..,b_m,...,b_M b1,..,bm,...,bM,而且 M ≠ N M\neq N M=N,那么动态时间规整就是要找到时间规整函数 m = T ( n ) m=T(n) m=T(n),把输入的时间轴n非线性映射到参考模板的时间轴 m m m上,并且满足:
D = min ⁡ T ( n ) ∑ n = 1 N d [ n , T ( n ) ] D=\underset{T(n)}{\min}\sum_{n=1}^Nd[n,T(n)] D=T(n)minn=1Nd[n,T(n)]

d [ n , T ( n ) ] d[n,T(n)] d[n,T(n)]表示两个矢量之间的距离,可以采用欧式距离计算 d ( x , y ) = 1 k ∑ i = 1 k ( x i − y i ) 2 d(x,y)=\frac{1}{k}\sqrt{\sum_{i=1}^k(x_i-y_i)^2} d(x,y)=k1i=1k(xiyi)2 ,D是最佳时间路径下的两个补办的距离测度。

这是一个典型的最优化问题,一般采用动态规划算法(DP)实现,为了方便计算,把一个N阶段的决策过程转化为N个简单段的决策过程,也就是为N个子问题逐一做出决策,根据语音信号的性质,时间规整函数要满足:

  • 边界条件, T ( 1 ) = 1 , T ( N ) = M T(1)=1,T(N)=M T(1)=1,T(N)=M
  • 单调上升: T ( n + 1 ) − T ( n ) ⩾ 0 T(n+1)-T(n)\geqslant 0 T(n+1)T(n)0
  • 连续性限制:有些特殊音素可能会对正确识别起很大作用,某个音素的差异可能就是区分的依据,为了保证信息损失最小,规整函数一般规定不云溪跳过任何一点,即 Φ ( i n + 1 ) − Φ ( i n ) ⩽ 1 \Phi(i_n+1)-\Phi(i_n)\leqslant 1 Φ(in+1)Φ(in)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]))

你可能感兴趣的:(语音信号,Python)