matlab/python を使用して二重閾値法のエンドポイント検出を実行する

終点検出とは、音声を含む信号から音声の開始点と終了点を判定することを指しますが、
音声信号に基づくディープラーニングでは、モデルの学習前に終点検出を行い、有効な励振信号をそれぞれ抽出するだけでなく、音声信号の数を増やすことができます。サンプルを収集し、ネットワーク トレーニング中の不要な計算を削減し、モデル トレーニングの精度を向上させます。

1. 二重閾値法の原理

二重閾値法はもともと短期平均エネルギーと短期平均ゼロクロス率に基づいて提案されたものであり、その原理は、中国語の語尾語には母音がありエネルギーが大きいため、短期語尾語から語尾を見つけることができるというものである。子音は周波数が高く、それに対応する短期平均ゼロクロス率を持っています。したがって、これら 2 つの特性を使用して頭子音と語尾を見つけることは、完全な中国語の音節を見つけることと同じです。二重閾値法は次のとおりです。図に示すように、2 レベルの決定を使用して実装されます。

aは音声の波形です

a は音声の波形、b は音声の短期平均エネルギー、c は音声の短期平均ゼロクロス レートです。


1. 第 1 段階の判定①では、
音声の短期エネルギー包絡線から選択されたより高い閾値 (閾値) T2 に基づいて大まかな判定を行い、
T2 閾値を超える音声について大まかに判定します。音声である必要があり (つまり、CD セグメント間の音声である必要があります)、音声の開始点と終了点は、しきい値と短期エネルギー エンベロープの交点に対応する時点の外側にある必要があります (つまり、 ② 平均
エネルギーの下限値(しきい値)T1 を決め、点 C から左へ、点 D から右へそれぞれ探索し、短期エネルギーが高くなる 2 点 B と E を見つけます。エンベロープが閾値 T1 と交差するため、BE セグメントは短期エネルギーエンベロープに従って二重閾値法を使用し、音声セグメントの開始点と終了点の位置は時間エネルギーによって決定されます。

2. 第 2 段階の判定
は短期平均ゼロクロス率で、左の B 点と右の E 点から探索し、短期平均ゼロクロス率が 0 となる 2 点 A と F を見つけます。一定の閾値 T3 より低い、これが音声区間の開始点と終了点です。

この 2 段階の判定に基づいて、音声の始点位置 A と終点位置 F が求められますが、音声発音時の単語間の無音部分が発音間の休止を表す最小限の長さとなることを考慮すると、この最小の長さは音声セグメントの終了を決定するために使用され、実際には音声の尾部の長さを延長することに相当します。音声の終点は音声波形上でそれぞれ A と F+ としてマークされます (図では終点が確認できます。位置は F ですが、実際の処理では F+ まで拡張されます)

終点検出の具体的な動作としては、まず音声をフレームに分割し、そのフレーム分割に基づいて短期平均エネルギーと短期平均ゼロクロス率を求め、比較判定する。しきい値に従ってフレームごとに作成されます。

2. デュアルパラメータデュアル閾値エンドポイント検出の例

エネルギー、自己相関関数、および二重閾値決定を使用してエンドポイントを抽出します。

エネルギーとゼロ交差率を使用してエンドポイントを検出する以前の二重しきい値方法は、実際には 2 つのパラメーターの二重しきい値検出方法であり、エネルギーとゼロ交差率の 2 つのパラメーターが関与し、3 つまたは 4 つのしきい値が与えられます。 T1 同時に T2、T3 (T4) このうち、一方のパラメータは dst1、もう一方のパラメータは dst2 です。

以下に、第 2 レベルの判定
1 の説明をさらに導入します。第 1 レベルの判定
①は、第 1 パラメータ dst1 で選択されたより高い閾値 T2 (または第 2 パラメータ dst2 で選択されたより高い閾値 T4) に基づいて 1 回実行されます。判定とは、T2(またはT4)の閾値を超える値が音声であることを意味します
② 第一パラメータdst1の下限閾値T1を決め、①の交点から両側に探索を拡張し、それぞれdst1と閾値T1を求めます。 2つの交点を音声素片の始点位置と終点位置と大まかに判定
2.第2段階の判定は
dst2に基づき、第1段階の判定で得られた始点位置と終点位置から検索を拡張する両端にdst2と一定の閾値T3を求め、
その2つの交点が音声素片の開始点と終了点となります。

この考え方に従って、対応する関数がコンパイルされます:
(1) vad_param2D
関数: 2 つのパラメーター dst1 および dst2 に従って音声エンドポイントの位置を抽出します。
呼び出し形式: [voiceseg, vsl, SF, NF] = vad_param2D (dst1 、dst2、T1、T2、T3、T4);

例: 2 つのパラメータを使用したエンドポイント検出

①入力パラメータの x は音声信号系列であり、フレームを分割するためにフレーム長を wlen、フレームシフトを inc に設定し、NIS は先頭の非音声区間のフレーム数とします。

音声処理において雑音を推定する場合、音声の先頭に先頭無音区間が存在することが多く、この先頭無音区間を利用して雑音の特徴を推定する。

実際には、先頭無音区間のフレーム数が分からない場合もありますが、音声信号の波形から先頭無音区間 IS の長さを推定することができます。 NIS は計算できます。NIS
= fix(IS * fs - wlen) / inc + 1)

② ここでの議論では、騒音は定常であると考えられているため、推定された騒音の短期平均エネルギーと自己相関関数は音声全体に適用できます。

③音声信号を x(n) とし、ウィンドウ処理とフレーミングを行うと xi(m) となります。ここで、添字 i は i 番目のフレームを表し、フレーム長は N、フレームの総数は fn、エネルギーは各フレームの は次のように計算されます:
AMP i = ∑ m = 1 N xi 2 ( m ) AMP_i\ = \displaystyle \sum^{N} _{m=1} x_i^{2}(m)AMP_ _私は =m = 1Nバツ2(メートル)

音声信号は x(n) で、ウィンドウ処理とフレーミングの後、xi(m) になります。ここで、添え字 i は i 番目のフレーム (i=1,2,3,...,M) を表し、Mはフレームの総数であり、
それぞれのフレーム データの短期自己相関関数は次のように定義されます:
R i ( k ) = ∑ m = 1 L − kxi ( m ) xi ( m + k ) R_i(k) = \displaystyle \sum^{Lk} _{m= 1}x_i(m)x_i(m+k)R私は( k )=m = 1L kバツ私は(メートル) ×私は( m+k )
L は音声フレームの長さ、k は遅延量であり、
隣接する 2 つのフレーム間の相関関数を計算すると相互相関関数となり、その式は
R i ( k ) = ∑ m = 1 L − kxi − 1 ( m ) xi ( m + k ) R_i(k) = \displaystyle \sum^{Lk} _{m=1} x_{i-1}(m)x_i(m+k)R私は( k )=m = 1L kバツi 1(メートル) ×私は( m+k )
音声信号は準定常信号でゆっくりと変化するため、騒音の状況に応じて 2 つの閾値 T3 と T4 を設定し、相関関数の最大値が T4 より大きい場合に音声と判定し、相関関数の最大値が T4 より大きい場合に音声と判定します。相関関数の最大値が T3 より大きいか小さい場合、音声信号の終点と判断されます。

④ 最初に、先頭の無音セグメントのノイズの短期平均エネルギーと自己相関関数を計算します:
etemp=mean(etemp(1:NIS));
thredth=max(Rum(2:NIS));

次に、これら 2 つの値に基づいて、エネルギーの 2 つのしきい値 (T1) と (T2)、および相互相関関数のしきい値 (T3) を設定します: T2 = 4 etemp; T1 = 2 etemp; T3=
1.1 * thredth
;

ここでの閾値は固定値ではなく、先頭の非音声区間の演算ノイズに応じて動的に変化し、一度閾値を設定すれば二閾値法による検出が可能となる。

⑤検出完了後、エンドポイントを x1 と x2 とし、さらに 2 つのシーケンス SF と NF を設定します (どちらも 1xfn の配列です)。SF=1 はフレームがトークフレームであることを意味し、SF=0 はトークフレームであることを意味します
。フレームは無音です 通話フレーム;
NF は SF の反対で、NF=1 はフレームが非通話フレームであることを意味し、NF=0 はフレームが通話フレームであることを意味します。

⑥同時に、音声の終点の情報を与える音声セグメントの構造データも設定されます。分析された音声グループにはいくつかのポーズが存在する可能性があるため、各グループには開始時刻と終了時刻が設定されています。これには、音声セグメントの各グループの開始時刻、終了時刻、長さがすべてフレーム単位で含まれています。

voiceeg の計算は、次の 2 つのステートメントによって完了します:
speechIndex=find(SF==1);
voiceeg=findSegment(speechIndex);

プログラムリストは以下の通りです。


clear all; clc; close all;
IS=0.25;                                % 设置前导无话段长度
wlen=200;                               % 设置帧长为25ms
inc=80;                                 % 求帧移
SNR=10;                            		 % 设置信噪比
fle = 'D:\audio\test1\51_674_740001.wav';

[xx,fs]=wavread(fle);                   % 读入数据
xx=xx-mean(xx);                         % 消除直流分量
x=xx/max(abs(xx));                      % 幅值归一化
N=length(x);                            % 取信号长度
time=(0:N-1)/fs;                        % 设置时间
signal=Gnoisegen(x,SNR);                % 叠加噪声
wnd=hamming(wlen);                      % 设置窗函数
overlap=wlen-inc;                       % 求重叠区长度
NIS=fix((IS*fs-wlen)/inc +1);           % 求前导无话段帧数
y=enframe(signal,wnd,inc)';             % 分帧
fn=size(y,2);                           % 求帧数
frameTime=frame2time(fn, wlen, inc, fs);% 计算各帧对应的时间

for k=2 : fn                            % 计算互相关函数
    u1=y(:,k-1);
    u2=y(:,k);
    ru=xcorr(u1,u2);
    Ru(k)=max(ru);
end
Rum=multimidfilter(Ru,1);              % 平滑处理
Rum=Rum/max(Rum);                       % 归一化
%thredth=max(Rum(2:NIS));                % 计算阈值
%T3=1.1*thredth;
%T4=1.3*thredth;
T3 =  0.065;

etemp=sum(y.^2);                        % 求取短时平均能量
etemp=etemp/max(etemp);                 % 能量幅值归一化
fn=size(y,2);                           % 帧数
% etemp = mean(etemp(1:NIS));           %计算初始无话段区间的能量平均值
% T2 = 4*etemp; T1 = 2*etemp;					%设置能量的阈值
T1=0.2;                            	 	  % 设置阈值
T2=0.15;

[voiceseg,vsl,SF,NF]=vad_param2D(etemp,Rum,T1,T2,T3);   % 双参数双门限端点检测

% 作图
subplot 311; plot(time,x,'k');
title('纯语音波形');
ylabel('幅值'); axis([0 max(time) -1 1]);

subplot 312; plot(frameTime,etemp,'k')
title('语音短时能量图');
ylabel('幅值'); axis([0 max(time) 0 1]);
xlabel('时间/s');
line([0 max(time)],[T1 T1],'color','b','LineStyle','--');
line([0 max(time)],[T2 T2],'color','r','LineStyle','--');

subplot 313; plot(frameTime,Rum,'k');
title('短时自相关函数'); axis([0 max(time) 0 1.2]);
xlabel('时间/s'); ylabel('幅值'); 
line([0,frameTime(fn)], [T3 T3], 'color','b','LineStyle','--');

for k=1 : vsl                           % 标出语音端点
    nx1=voiceseg(k).begin; nx2=voiceseg(k).end;
    fprintf('%4d   %4d   %4d    %4d\n',k,nx1,nx2,nx2-nx1);
    subplot 311; 
    line([frameTime(nx1) frameTime(nx1)],[-1 1],'color','b','LineStyle','--');
    line([frameTime(nx2) frameTime(nx2)],[-1 1],'color','r','LineStyle','--');
end

エンドポイント検出結果 図
ここに画像の説明を挿入します
3.vad_param2D 関数の説明とその他の指示

名前:vad_param2D
機能:短期平均エネルギーと自己相関関数の最大値を用いて音声エンドポイント位置を抽出 呼び出し
形式:function [voiceseg, vsl, SF, NF] = vad_param2D (dst1, dst2, T1, T2) 、T3、T4)
説明:
① dst1 と dst2 は、前述のエネルギーとゼロクロス レート、または自己相関関数などの他のパラメーターです。 ② dst1 の 2
つのしきい値 T1 と T2、および 1 つまたは 2 つのしきい値 T3 ( dst2 の T4) と T4) はすべて呼び出されます この関数は以前に計算されています これらのしきい値は dst1 と dst2 の元のデータの値に依存することも、平滑化された値にすることもできます ③ しきい値は 1 つだけ持ち込むことができ
ますdst2 (関数呼び出し時に T3 のみを書き込み、T4 は書き込まない) で、しきい値を 2 つ持ってくることもできます。関数内では、T4 が含まれるかどうかが自動的に判定されます。T4 が入った後のみ、T4 が判定に使用されます。

⑦ vad_param2D 関数に設定するパラメータとして、maxsilence は発話末尾の無音部分の最小長、minlin は発話区間の最小長を示し、いずれも固定です。

T1、T2、T3などのパラメータは動的に変化しますが、その比例係数も固定です。

実際、エンドポイント検出での正しい検出は多くの要因の影響を受けます: ノイズ環境が主要な要因です。さまざまなノイズやさまざまな S/N 比が検出の精度に影響するため、これらのパラメーターを変更したり、調整したりすることができます。実際の状況に応じて。

機能フローチャートは以下の通りです
ここに画像の説明を挿入します

名前: findSegment
関数: SF の値が 1 のアドレスに基づいて、音声セグメントの各グループの開始時刻、終了時刻、長さを結合します 呼び出し形式: function soundSegment=findSegment(express) プログラム リストは次のとおり
です

function soundSegment=findSegment(express)
if express(1)==0
    voicedIndex=find(express);                     % 寻找express中为1的位置
else
    voicedIndex=express;
end

soundSegment = [];
k = 1;
soundSegment(k).begin = voicedIndex(1);            % 设置第一组有话段的起始位置
for i=1:length(voicedIndex)-1,
	if voicedIndex(i+1)-voicedIndex(i)>1,          % 本组有话段结束
		soundSegment(k).end = voicedIndex(i);      % 设置本组有话段的结束位置
		soundSegment(k+1).begin = voicedIndex(i+1);% 设置下一组有话段的起始位置  
		k = k+1;
	end
end
soundSegment(k).end = voicedIndex(end);            % 最后一组有话段的结束位置
% 计算每组有话段的长度
for i=1 :k
    soundSegment(i).duration=soundSegment(i).end-soundSegment(i).begin+1;
end

注: 実際、単一パラメータを使用した二重閾値法 (短期間の平均エネルギーのみを使用) は、オーディオ データ セットに対してエンドポイント検出を実行でき、その効果は良好です。

3.Python は二重閾値メソッドのエンドポイント検出を実装します

"""
    本程序是进行端点检测
    双门限法进行端点检测
"""
import numpy as np
import wave
from scipy.io import wavfile
import soundfile as sf
import numpy as np
import matplotlib.pyplot as plt
import os
import re
from pydub import AudioSegment
plt.rcParams['font.family'] = ['sans-serif']
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def STAc(x):
    """
    计算短时相关函数
    :param x:
    :return:
    """
    para = np.zeros(x.shape)
    fn = x.shape[1]
    for i in range(fn):
        R = np.correlate(x[:, i], x[:, i], 'valid')
        para[:, i] = R
    return para


def STEn(x, win, inc):
    """
    计算短时能量函数
    :param x:
    :param win:
    :param inc:
    :return:
    """
    X = enframe(x, win, inc)
    s = np.multiply(X, X)
    return np.sum(s, axis=1)


def STZcr(x, win, inc, delta=0):
    """
    计算短时过零率
    :param x:
    :param win:
    :param inc:
    :return:
    """
    absx = np.abs(x)
    x = np.where(absx < delta, 0, x)
    X = enframe(x, win, inc)
    X1 = X[:, :-1]
    X2 = X[:, 1:]
    s = np.multiply(X1, X2)
    sgn = np.where(s < 0, 1, 0)
    return np.sum(sgn, axis=1)


def FrameTimeC(frameNum, frameLen, inc, fs):
    ll = np.array([i for i in range(frameNum)])
    return ((ll - 1) * inc + frameLen / 2) / fs


def enframe(x, win, inc=None):
    nx = len(x)
    if isinstance(win, list) or isinstance(win, np.ndarray):
        nwin = len(win)
        nlen = nwin  # 帧长=窗长
    elif isinstance(win, int):
        nwin = 1
        nlen = win  # 设置为帧长
    if inc is None:
        inc = nlen
    nf = (nx - nlen + inc) // inc
    frameout = np.zeros((nf, nlen))
    indf = np.multiply(inc, np.array([i for i in range(nf)]))
    for i in range(nf):
        frameout[i, :] = x[indf[i]:indf[i] + nlen]
    if isinstance(win, list) or isinstance(win, np.ndarray):
        frameout = np.multiply(frameout, np.array(win))
    return frameout

def findSegment(express):
    """
    分割成語音段
    :param express:
    :return:
    """
    if express[0] == 0:
        voiceIndex = np.where(express)
    else:
        voiceIndex = express
    d_voice = np.where(np.diff(voiceIndex) > 1)[0]
    voiceseg = {
    
    }
    if len(d_voice) > 0:
        for i in range(len(d_voice) + 1):
            seg = {
    
    }
            if i == 0:
                st = voiceIndex[0]
                en = voiceIndex[d_voice[i]]
            elif i == len(d_voice):
                st = voiceIndex[d_voice[i - 1]+1]
                en = voiceIndex[-1]
            else:
                st = voiceIndex[d_voice[i - 1]+1]
                en = voiceIndex[d_voice[i]]
            seg['start'] = st
            seg['end'] = en
            seg['duration'] = en - st + 1
            voiceseg[i] = seg
    return voiceseg

def vad_TwoThr1(x, wlen, inc, NIS):
    """
    使用门限法检测语音段
    :param x: 语音信号
    :param wlen: 分帧长度
    :param inc: 帧移
    :param NIS:
    :return:
    """
    maxsilence = 15
    minlen = 5
    status = 0
    y = enframe(x, wlen, inc)
    fn = y.shape[0]
    amp = STEn(x, wlen, inc)
    ampth = np.mean(amp[:NIS])
    amp2 = 7.5 * ampth
    amp1 = 10 * ampth
    xn = 0
    count = np.zeros(fn)
    silence = np.zeros(fn)
    x1 = np.zeros(fn)
    x2 = np.zeros(fn)
    for n in range(fn):
        if status == 0 or status == 1:
            if amp[n] > amp1:
                x1[xn] = max(1, n - count[xn] - 1)
                status = 2
                silence[xn] = 0
                count[xn] += 1
            elif amp[n] > amp2 :
                status = 1
                count[xn] += 1
            else:
                status = 0
                count[xn] = 0
                x1[xn] = 0
                x2[xn] = 0

        elif status == 2:
            if amp[n] > amp2 :
                count[xn] += 1
            else:
                silence[xn] += 1
                if silence[xn] < maxsilence:
                    count[xn] += 1
                elif count[xn] < minlen:
                    status = 0
                    silence[xn] = 0
                    count[xn] = 0
                else:
                    status = 3
                    x2[xn] = x1[xn] + count[xn]
        elif status == 3:
            status = 0
            xn += 1
            count[xn] = 0
            silence[xn] = 0
            x1[xn] = 0
            x2[xn] = 0
    el = len(x1[:xn])
    if x1[el - 1] == 0:
        el -= 1
    if x2[el - 1] == 0:
        print('Error: Not find endding point!\n')
        x2[el] = fn
    SF = np.zeros(fn)
    NF = np.ones(fn)
    for i in range(el):
        SF[int(x1[i]):int(x2[i])] = 1
        NF[int(x1[i]):int(x2[i])] = 0
    voiceseg = findSegment(np.where(SF == 1)[0])
    vsl = len(voiceseg.keys())
    return voiceseg, vsl, SF, NF, amp



if __name__ == '__main__':

    wavepath = './vadtest/60_805.wav'
    f = wave.open(wavepath, 'rb')
    params = f.getparams()
    nchannels, sampwidth, fs, nframes = params[:4]  # 声道数、量化位数、采样频率、采样点数
    str_data = f.readframes(nframes)  # 读取音频,字符串格式
    f.close()
    wavedata = np.fromstring(str_data, dtype=np.short)  # 将字符串转化为浮点型数据
    data = wavedata * 1.0 / (max(abs(wavedata)))  # wave幅值归一化

    data /= np.max(data)
    N = len(data)
    wlen = 200
    inc = 80
    IS = 1
    overlap = wlen - inc
    NIS = int((IS * fs - wlen) // inc + 1)
    fn = (N - wlen) // inc + 1

    frameTime = FrameTimeC(fn, wlen, inc, fs)  # 计算出对应的时间
    time = [i / fs for i in range(N)]

    #voiceseg, vsl, SF, NF, amp, zcr = vad_TwoThr(data, wlen, inc, NIS)
    voiceseg, vsl, SF, NF, amp = vad_TwoThr1(data, wlen, inc, NIS)

    plt.subplot(2, 1, 1)
    plt.ylabel('幅值')
    plt.ylim((-1, 1))  # 设置y轴刻度范围为0~11
    plt.title('纯音频波形图')  # 设置绘图标题
    plt.plot(time, data)

    plt.subplot(2, 1, 2)
    plt.plot(frameTime, amp)
    plt.ylabel('幅值')
    plt.xlabel('时间(s)')  # 设置x、y轴标签
    plt.title('音频短时能量图')  # 设置绘图标题

    for i in range(vsl):
        plt.subplot(2, 1, 1)
        plt.vlines(frameTime[voiceseg[i]['start']],-1,1,colors='r',linestyles='dashed')
        plt.vlines(frameTime[voiceseg[i]['end']], -1, 1, colors='g', linestyles='dashed')
    plt.show()

レンダリングここに画像の説明を挿入します

関連する知識は次から参照されます—>音声信号分析および合成における matlab の応用

おすすめ

転載: blog.csdn.net/tenju/article/details/129533060