【语音识别】语音端点检测及Python实现
- 一、语音信号的分帧处理
- 二、端点检测方法
- 2.1、短时能量
- 2.2、短时过零率
- 三、Python实现
从接收的语音信号中准确检测出人声开始和结束的端点是进行语音识别的前提。本博文介绍基于短时过零率和短时能量的基本语音端点检测方法及Python实现。如图所示为语音信号,红色方框内为人声:
一、语音信号的分帧处理
语音信号是时序信号,其具有长时随机性和短时平稳性。长时随机性指语音信号随时间变化是一个随机过程,短时平稳性指在短时间内其特性基本不变,因为人说话是肌肉具有惯性,从一个状态到另一个状态不可能瞬时完成。语音通常在10-30ms之间相对平稳,因此语音信号处理的第一步基本都是对语音信号进行分帧处理,帧长度一般取10-30ms。
语音信号的分帧处理通常采用滑动窗的方式,窗口可以采用直角窗、Hamming窗等。窗口长度决定每一帧信号中包含原始语音信号中信息的数量,窗口每次的滑动距离等于窗口长度时,每一帧信息没有重叠,当窗口滑动距离小于窗口长度时帧信息有重合。本博文采用直角窗进行语音信号的分帧处理:
直角窗:
h ( n ) = { 1 , 0 ≤ n ≤ N − 1 0 , o t h e r {\rm{h}}(n) = \left\{ {\begin{matrix} {1, 0\le n \le N - 1}\\ {0,{\rm{other}}} \end{matrix}} \right.
h
(
n
)
=
{
1
,
0
≤
n
≤
N
−
1
0
,
o
t
h
e
r
二、端点检测方法
端点检测是指找出人声开始和结束的端点。利用人声信号短时特性与非人声信号短时特性的差异可以有效地找出人声开始和结束的端点,本博文介绍短时能量和短时过零率结合进行端点检测的方法。
2.1、短时能量
第n帧信号的短时平均能量定义为:
E n = ∑ m = n − N + 1 n [ x ( m ) w ( n − m ) ] 2 {E_n} = \sum\limits_{m = n - N + 1}^n {{{\left[ {x\left( m \right)w\left( {n - m} \right)} \right]}^2}}
E
n
=
m
=
n
−
N
+
1
∑
n
[
x
(
m
)
w
(
n
−
m
)
]
2
包含人声信号的帧的短时平均能量大于非人声信号的帧。
2.2、短时过零率
过零信号指通过零值,相邻取样值改变符号即过零,过零数是样本改变符号的数量。
第n帧信号的平均短时过零数为:
Z n = ∑ m = n − N + 1 n ∣ s g n [ x ( m ) ] − s g n [ x ( m − 1 ) ] ∣ w ( n − m ) {Z_n} = \sum\limits_{m = n - N + 1}^n {\left| {{\mathop{\rm sgn}} \left[ {x\left( m \right)} \right] - {\mathop{\rm sgn}} \left[ {x\left( {m - 1} \right)} \right]} \right|w\left( {n - m} \right)}
Z
n
=
m
=
n
−
N
+
1
∑
n
∣
s
g
n
[
x
(
m
)
]
−
s
g
n
[
x
(
m
−
1
)
]
∣
w
(
n
−
m
)
w ( n ) = { 1 / ( 2 N ) , 0 ≤ n ≤ N − 1 0 , o t h e r w\left( n \right) = \left\{ {\begin{matrix} {1/\left( {2N} \right),0 \le n \le N - 1}\\ {0,other} \end{matrix}} \right. w ( n ) = { 1 / ( 2 N ) , 0 ≤ n ≤ N − 1 0 , o t h e r
三、Python实现
import
wave
import
numpy as np
import
matplotlib
.
pyplot as plt
def
read
(
data_path
)
:
''
'读取语音信号
''
'
wavepath
=
data_path
f
=
wave
.
open
(
wavepath
,
'rb'
)
params
=
f
.
getparams
(
)
nchannels
,
sampwidth
,
framerate
,
nframes
=
params
[
:
4
]
#声道数、量化位数、采样频率、采样点数
str_data
=
f
.
readframes
(
nframes
)
#读取音频,字符串格式
f
.
close
(
)
wavedata
=
np
.
fromstring
(
str_data
,
dtype
=
np
.
short
)
#将字符串转化为浮点型数据
wavedata
=
wavedata
*
1.0
/
(
max
(
abs
(
wavedata
)
)
)
#wave幅值归一化
return
wavedata
,
nframes
,
framerate
def
plot
(
data
,
time
)
:
plt
.
plot
(
time
,
data
)
plt
.
grid
(
'on'
)
plt
.
show
(
)
def
enframe
(
data
,
win
,
inc
)
:
''
'对语音数据进行分帧处理
input
:
data
(
一维array
)
:
语音信号
wlen
(
int
)
:
滑动窗长
inc
(
int
)
:
窗口每次移动的长度
output
:
f
(
二维array
)
每次滑动窗内的数据组成的二维array
''
'
nx
=
len
(
data
)
#语音信号的长度
try
:
nwin
=
len
(
win
)
except Exception as err
:
nwin
=
1
if
nwin
==
1
:
wlen
=
win
else
:
wlen
=
nwin
nf
=
int
(
np
.
fix
(
(
nx
-
wlen
)
/
inc
)
+
1
)
#窗口移动的次数
f
=
np
.
zeros
(
(
nf
,
wlen
)
)
#初始化二维数组
indf
=
[
inc
*
j
for
j in
range
(
nf
)
]
indf
=
(
np
.
mat
(
indf
)
)
.
T
inds
=
np
.
mat
(
range
(
wlen
)
)
indf_tile
=
np
.
tile
(
indf
,
wlen
)
inds_tile
=
np
.
tile
(
inds
,
(
nf
,
1
)
)
mix_tile
=
indf_tile
+
inds_tile
f
=
np
.
zeros
(
(
nf
,
wlen
)
)
for
i in
range
(
nf
)
:
for
j in
range
(
wlen
)
:
f
[
i
,
j
]
=
data
[
mix_tile
[
i
,
j
]
]
return
f
def
point_check
(
wavedata
,
win
,
inc
)
:
''
'语音信号端点检测
input
:
wavedata
(
一维array
)
:原始语音信号
output
:
StartPoint
(
int
)
:
起始端点
EndPoint
(
int
)
:
终止端点
''
'
#
1.
计算短时过零率
FrameTemp1
=
enframe
(
wavedata
[
0
:
-
1
]
,
win
,
inc
)
FrameTemp2
=
enframe
(
wavedata
[
1
:
]
,
win
,
inc
)
signs
=
np
.
sign
(
np
.
multiply
(
FrameTemp1
,
FrameTemp2
)
)
# 计算每一位与其相邻的数据是否异号,异号则过零
signs
=
list
(
map
(
lambda x
:
[
[
i
,
0
]
[
i
>
0
]
for
i in x
]
,
signs
)
)
signs
=
list
(
map
(
lambda x
:
[
[
i
,
1
]
[
i
<
0
]
for
i in x
]
,
signs
)
)
diffs
=
np
.
sign
(
abs
(
FrameTemp1
-
FrameTemp2
)
-
0.01
)
diffs
=
list
(
map
(
lambda x
:
[
[
i
,
0
]
[
i
<
0
]
for
i in x
]
,
diffs
)
)
zcr
=
list
(
(
np
.
multiply
(
signs
,
diffs
)
)
.
sum
(
axis
=
1
)
)
#
2.
计算短时能量
amp
=
list
(
(
abs
(
enframe
(
wavedata
,
win
,
inc
)
)
)
.
sum
(
axis
=
1
)
)
# # 设置门限
#
print
(
'设置门限'
)
ZcrLow
=
max
(
[
round
(
np
.
mean
(
zcr
)
*
0.1
)
,
3
]
)
#过零率低门限
ZcrHigh
=
max
(
[
round
(
max
(
zcr
)
*
0.1
)
,
5
]
)
#过零率高门限
AmpLow
=
min
(
[
min
(
amp
)
*
10
,
np
.
mean
(
amp
)
*
0.2
,
max
(
amp
)
*
0.1
]
)
#能量低门限
AmpHigh
=
max
(
[
min
(
amp
)
*
10
,
np
.
mean
(
amp
)
*
0.2
,
max
(
amp
)
*
0.1
]
)
#能量高门限
# 端点检测
MaxSilence
=
8
#最长语音间隙时间
MinAudio
=
16
#最短语音时间
Status
=
0
#状态
0
:
静音段
,
1
:
过渡段
,
2
:
语音段
,
3
:
结束段
HoldTime
=
0
#语音持续时间
SilenceTime
=
0
#语音间隙时间
print
(
'开始端点检测'
)
StartPoint
=
0
for
n in
range
(
len
(
zcr
)
)
:
if
Status
==
0
or Status
==
1
:
if
amp
[
n
]
>
AmpHigh or zcr
[
n
]
>
ZcrHigh
:
StartPoint
=
n
-
HoldTime
Status
=
2
HoldTime
=
HoldTime
+
1
SilenceTime
=
0
elif amp
[
n
]
>
AmpLow or zcr
[
n
]
>
ZcrLow
:
Status
=
1
HoldTime
=
HoldTime
+
1
else
:
Status
=
0
HoldTime
=
0
elif Status
==
2
:
if
amp
[
n
]
>
AmpLow or zcr
[
n
]
>
ZcrLow
:
HoldTime
=
HoldTime
+
1
else
:
SilenceTime
=
SilenceTime
+
1
if
SilenceTime
<
MaxSilence
:
HoldTime
=
HoldTime
+
1
elif
(
HoldTime
-
SilenceTime
)
<
MinAudio
:
Status
=
0
HoldTime
=
0
SilenceTime
=
0
else
:
Status
=
3
elif Status
==
3
:
break
if
Status
==
3
:
break
HoldTime
=
HoldTime
-
SilenceTime
EndPoint
=
StartPoint
+
HoldTime
return
StartPoint
,
EndPoint
,
FrameTemp1
if
__name__
==
'__main__'
:
data_path
=
'audio_data.wav'
win
=
240
inc
=
80
wavedata
,
nframes
,
framerate
=
read
(
data_path
)
time_list
=
np
.
array
(
range
(
0
,
nframes
)
)
*
(
1.0
/
framerate
)
plot
(
wavedata
,
time_list
)
StartPoint
,
EndPoint
,
FrameTemp
=
point_check
(
wavedata
,
win
,
inc
)
checkdata
,
Framecheck
=
check_signal
(
StartPoint
,
EndPoint
,
FrameTemp
,
win
,
inc
)
端点检测结果: