语音端点检测(2):单参数双门限法

引言


接着上一篇双参数双门限法的语音端点检测的文章写。

上一篇用了两个参数,短时能量和短时过零率。

这一篇只用一个参数,短时能量。当然,还可以用其他特征。总之,特征个数(种类)仅限一个。


基本思想


设置2个参数。从静音到大于大门限T2时,认为第一次大于T2时是语音起点。再看什么时候低于小门限T1,决定终点。大概是这样。

注意事项


这种度量,可以是越大越相似,可以是越小越相似。这要看实际采用什么作为“距离”度量了。

为了检测出语音之间的停顿,在检测的过程中,需要设定当前的状态(语音、过渡、静音等状态)。

一边检测,一边更新当前的状态。有时候,检测出当前好像是静音状态,但其实可能仅是语音之间的停顿而已。

因此,需要“延音”,即再往前走几帧。看是否真的是停顿。比如,设置一个停顿的计数器silence_counter。

停顿一帧,silence_counter++。判断是否大于预设的,最大允许停顿的帧数。

如果是,则看是否是突发噪声等。如果还没有,那如果本来是语音段来的话,就继续保持语音段,往前再延几帧。

总之,很是麻烦。很繁琐。容易重复,也容易漏。

为了避免这种情况的发生,最好,列出状态转移表,把当前每一种状态的判别情况写清楚。

最后,画出流程图。直接写代码很头疼。

主函数

%% 单参数的双门限VAD
% 作者:qcy
% 版本:v1.1
% 版本说明:1. 封装 单参数双门限检测 函数 my_vad_param1d();
%           2. 本文件是利用 短时能量特征 进行端点检测。

% 版本:v1.0
% 版本说明:使用对数谱距离作为标准
% 时间:2016年11月3日9:47:28

%%
clear;
close all;
clc

%% 导入音频
filedir=[];                            % 设置文件路径
% filename='bluesky1.wav';                % 设置文件名称
filename='seaboat.wav';                % 设置文件名称
% filename='静默检测.m4a';                % 设置文件名称
fle=[filedir filename];                % 构成文件路径和名称
SNR = 5;
[xx,fs] = audioread(fle);
xx=xx-mean(xx);                         % 消除直流分量
x=xx/max(abs(xx));                      % 幅值归一化
N=length(x);                            % 取信号长度
time=(0:N-1)/fs;                        % 设置时间
signal=Gnoisegen(x,SNR);                % 叠加噪声

figure(1);
subplot(311);
plot(time,signal);

%% 短时能量提取

% 设置帧长、步长
wlen_time = 0.02; % [s]
step_time = 0.01; % [s]
wlen = round(wlen_time*fs);
nstep = round(step_time*fs);
nframes = fix((length(x)-wlen)/nstep)+1; % 帧数
frame_time = frame2time(nframes, wlen, nstep, fs); % 计算每帧对应的时间

Er = get_st_energy( x,fs,wlen_time,step_time,'hamming','dB' );
% 因为有噪声,可以考虑对Er进行滤波,使其平滑
Er = multimidfilter(Er,3);

figure(1);
subplot(312)
plot(frame_time,Er);
% plot(Er);
title('短时能量');

%% 2. 获取噪声特性,定义静默检测中所需要的一些参数
% (1) 测量环境噪声,获取一些噪声的参数
noise_frame_idx=floor((0.1-wlen_time)/step_time)+1; % % 假定前100ms是环境噪声
eavg=mean(Er(1:noise_frame_idx)); % 计算环境噪声的 平均短时功率
esig=std(Er(1:noise_frame_idx)); % 计算环境噪声的 短时功率的标准差

% (2) 根据背景噪声设置阈值
ITU = -15; % constant in the range [-10 -20] dB
ITR_HIGH = max([ITU-10 eavg+3*esig]); % 均值往上加3倍标准差,作为短时功率的阈值
ITR_LOW = ITR_HIGH - 3; % 大于ITR_LOW,小于ITR_LOW,认为可能是语音和静音之间

%% 3. 单参数双门限检测
T1 = ITR_LOW;
T2 = ITR_HIGH;

SILENCE = 0;
TRANSITION = 1;
SPEECH = 2;

MAX_TRANSITION_FRAME_LENGTH = 10;
MIN_SPEECH_FRAME_LENGTH = 4;

dist = Er;
[B,E] = my_vad_param1d( dist,T1,T2,MAX_TRANSITION_FRAME_LENGTH,MIN_SPEECH_FRAME_LENGTH );

%% 画图
figure(1);
subplot(313);
plot(time,signal);
for k=1 : length(E)                           % 画出起止点位置
    nx1= B(k);
    nx2 = E(k);
    line([frame_time(nx1) frame_time(nx1)],[-1.5 1.5],'color','r','LineStyle','-');
    line([frame_time(nx2) frame_time(nx2)],[-1.5 1.5],'color','r','LineStyle','--');
end

%% 播放
% for k=1 : length(E)
%     nx1= B(k);
%     nx2 = E(k);
%     fprintf('Press any key to continue...\n');
%     pause;
%     sound(signal(round(frame_time(nx1)*fs):round(frame_time(nx2)*fs)),fs);
% end

单参数双门限检测函数


function [ B,E ] = my_vad_param1d( dist,T1,T2,MAX_TRANSITION_FRAME_LENGTH,MIN_SPEECH_FRAME_LENGTH )
%function [ output_args ] = my_vad_param1d( dist,T1,T2,MAX_TRANSITION_FRAME_LENGTH,MIN_SPEECH_FRAME_LENGTH )
%   单参数的双门限检测函数
%
%   输入参数
%       dist:每一帧的距离度量
%       T1:低门限
%       T2:高门限
%       MAX_TRANSITION_FRAME_LENGTH:最大允许的过渡段的帧数
%       MIN_SPEECH_FRAME_LENGTH:所允许的最短的语音帧数
%   返回参数
%       B:端点所在帧序号。数组。
%       E:终点所在帧序号。数组。
%
%   版本:v1.0
%   作者:qcy 
%   时间:2016年11月4日12:58:51

if nargin < 5
   MIN_SPEECH_FRAME_LENGTH = 5;
end
if nargin < 4
    MAX_TRANSITION_FRAME_LENGTH = 8;
end


SILENCE = 0;
TRANSITION = 1;
SPEECH = 2;

state = SILENCE;
speech_counter = 0;
transition_counter = 0;

B = [];
E = [];

k_speech = 0; % 语音段序号

for k = 1 : length(dist)
    dist_k = dist(k);
    switch state
        case{SILENCE,TRANSITION} % 从静音、过渡来的
            if dist_k > T2 % 当前已经进入语音段的阈值
                state = SPEECH;
                speech_counter = speech_counter + 1;
                transition_counter = 0;
                k_speech = k_speech + 1;
                B(k_speech) = k;
            elseif dist_k > T1 % 仅在过渡段,语音还没有开始
                state = TRANSITION; 
                speech_counter = 0;
%                 speech_counter = speech_counter + 1;
%                 transition_counter = 0; 
            elseif dist_k < T1 % 还是静音段
                state = SILENCE;
                speech_counter = 0;
                transition_counter = 0;
            end
        case SPEECH % 从语音段来的
            
            % 【注意】 原来写的是>T2,但效果不是很好。T2要求太高了。
            %  通常要比T1小,语音才会真正结束。观察一下短时能量的图就知道了。
            if dist_k > T1   % 比低的门限高,就认为还在语音段。
                state = SPEECH;
                speech_counter = speech_counter + 1; % 语音段继续计数
                transition_counter = 0;
            else % 低于较低的T1门限,“似乎”进入静音段。
                transition_counter = transition_counter + 1;
                % 有可能是语音结束,可能是语音间停顿,有可能是突发噪声。下面验证。
                if transition_counter < MAX_TRANSITION_FRAME_LENGTH % 小于预定的语音中最长间隔、停顿
                    state = SPEECH; % 就还认为是语音段 --> 起到 “延音” 的作用
                    speech_counter = speech_counter + 1;
                    
                else % 如果停顿很长了,就可能是语音结束了。可不可能是“突发的噪声”呢?
                    if speech_counter < MIN_SPEECH_FRAME_LENGTH % 判断是语音结束还是噪声
                        state = SILENCE; % 噪声
                        speech_counter = 0;
                        transition_counter = 0;
                        if k_speech > 0 % 刚刚如果把噪声错误地当做起点了,
                            k_speech = k_speech - 1;
                            B = B(1:end-1); % 发现是突发的噪声,就把刚刚记录的起点从起点数组中去掉
                        end
                    elseif speech_counter > MIN_SPEECH_FRAME_LENGTH % 确实是语音,不是噪声
                        state = SILENCE; % 语音结束
                        speech_counter = 0;
                        transition_counter = 0;
                        E(k_speech) = k;
                    end
                end
            end
    end
end

if length(B)>length(E) % 好像,起点总会要么跟终点个数一样多,要么多一个
    % 最后一个终点没有找到,就把最后一帧当做终点。
    E(length(E)+1) = length(dist);
end

工具函数


生成AWGN,加入信号。

function [y,noise] = Gnoisegen(x,snr)
% Gnoisegen函数是叠加高斯白噪声到语音信号x中
% [y,noise] = Gnoisegen(x,snr)
% x是语音信号,snr是设置的信噪比,单位为dB
% y是叠加高斯白噪声后的带噪语音,noise是被叠加的噪声
noise=randn(size(x));              % 用randn函数产生高斯白噪声
Nx=length(x);                      % 求出信号x长
signal_power = 1/Nx*sum(x.*x);     % 求出信号的平均能量
noise_power=1/Nx*sum(noise.*noise);% 求出噪声的能量
noise_variance = signal_power / ( 10^(snr/10) );    % 计算出噪声设定的方差值
noise=sqrt(noise_variance/noise_power)*noise;       % 按噪声的平均能量构成相应的白噪声
y=x+noise;                         % 构成带噪语音

因为有噪声,短时特征可以先平滑处理。采用多次中值滤波。

function y=multimidfilter(x,m,order)

if nargin < 3
    order = 5; % 默认使用5阶中值滤波
end

a=x;
for k=1 : m
    b=medfilt1(a, order); 
    a=b;
end
y=b;


结果示意


第一张子图里的干净信号加了AWGN后,成为了第三张子图里带有噪声的信号。
检测是在含噪声的信号中检测的。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qcyfred

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值