由于项目需要，我要使用Python对语音进行端点检测，在之前的博客使用短时能量和谱质心特征进行端点检测中，我使用MATLAB实现了一个语音端点检测算法，下面我将使用Python重新实现这个这个算法，并将其封装到VAD类中，如下是运行结果：
软件环境
Python3.8、scipy、pyaudio、matplotlib
程序
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import numpy as np
import sys
from collections import deque
import matplotlib.pyplot as plt
import scipy.signal
import pyaudio
import struct as st

def ShortTimeEnergy(signal, windowLength, step):
"""
计算短时能量
Parameters
----------
signal : 原始信号.
windowLength : 帧长.
step : 帧移.

Returns
-------
E : 每一帧的能量.
"""
signal = signal / np.max(signal) # 归一化
curPos = 0
L = len(signal)
numOfFrames  = np.asarray(np.floor((L-windowLength)/step) + 1, dtype=int)
E = np.zeros((numOfFrames, 1))
for i in range(numOfFrames):
window = signal[int(curPos):int(curPos+windowLength-1)];
E[i] = (1/(windowLength)) * np.sum(np.abs(window**2));
curPos = curPos + step;
return E

def SpectralCentroid(signal,windowLength, step, fs):
"""
计算谱质心
Parameters
----------
signal : 原始信号.
windowLength : 帧长.
step : 帧移.
fs : 采样率.

Returns
-------
C : 每一帧的谱质心.
"""
signal = signal / np.max(signal) # 归一化
curPos = 0
L = len(signal)
numOfFrames  = np.asarray(np.floor((L - windowLength) / step) + 1, dtype=int)
H = np.hamming(windowLength)
m = ((fs / (2 * windowLength)) * np.arange(1, windowLength, 1)).T
C = np.zeros((numOfFrames, 1))
for i in range(numOfFrames):
window = H * (signal[int(curPos) : int(curPos + windowLength)])
FFT = np.abs(np.fft.fft(window, 2 * int(windowLength)))
FFT = FFT[1 : windowLength]
FFT = FFT / np.max(FFT)
C[i] = np.sum(m * FFT) / np.sum(FFT)
if np.sum(window**2) < 0.010:
C[i] = 0.0
curPos = curPos + step;
C = C / (fs/2)
return C

def findMaxima(f, step):
"""
寻找局部最大值
Parameters
----------
f : 输入序列.
step : 搜寻窗长.

Returns
-------
Maxima : 最大值索引 最大值
countMaxima : 最大值的数量
"""
## STEP 1: 寻找最大值
countMaxima = 0
Maxima = []
for i in range(len(f) - step - 1): # 对于序列中的每一个元素:
if i >= step:
if (np.mean(f[i - step : i]) < f[i]) and (np.mean(f[i + 1 : i + step + 1]) < f[i]):
# IF the current element is larger than its neighbors (2*step window)
# --> keep maximum:
countMaxima = countMaxima + 1
Maxima.append([i, f[i]])
else:
if (np.mean(f[0 : i + 1]) <= f[i]) and (np.mean(f[i + 1 : i + step + 1]) < f[i]):
# IF the current element is larger than its neighbors (2*step window)
# --> keep maximum:
countMaxima = countMaxima + 1
Maxima.append([i, f[i]])

## STEP 2: 对最大值进行进一步处理
MaximaNew = []
countNewMaxima = 0
i = 0
while i < countMaxima:
# get current maximum:

curMaxima = Maxima[i][0]
curMavVal = Maxima[i][1]

tempMax = [Maxima[i][0]]
tempVals = [Maxima[i][1]]
i = i + 1

# search for "neighbourh maxima":
while (i < countMaxima) and (Maxima[i][0] - tempMax[len(tempMax) - 1] < step / 2):

tempMax.append(Maxima[i][0])
tempVals.append(Maxima[i][1])
i = i + 1

MM = np.max(tempVals)
MI = np.argmax(tempVals)
if MM > 0.02 * np.mean(f): # if the current maximum is "large" enough:
# keep the maximum of all maxima in the region:
MaximaNew.append([tempMax[MI], f[tempMax[MI]]])
countNewMaxima = countNewMaxima + 1   # add maxima
Maxima = MaximaNew
countMaxima = countNewMaxima

return Maxima, countMaxima

win = 0.05
step = 0.05
Eor = ShortTimeEnergy(signal, int(win * fs), int(step * fs));
Cor = SpectralCentroid(signal, int(win * fs), int(step * fs), fs);
E = scipy.signal.medfilt(Eor[:, 0], 5)
E = scipy.signal.medfilt(E, 5)
C = scipy.signal.medfilt(Cor[:, 0], 5)
C = scipy.signal.medfilt(C, 5)

E_mean = np.mean(E);
Z_mean = np.mean(C);
Weight = 100 # 阈值估计的参数
# 寻找短时能量的阈值
Hist = np.histogram(E, bins=10) # 计算直方图
HistE = Hist[0]
X_E = Hist[1]
MaximaE, countMaximaE = findMaxima(HistE, 3) # 寻找直方图的局部最大值
if len(MaximaE) >= 2: # 如果找到了两个以上局部最大值
T_E = (Weight*X_E[MaximaE[0][0]] + X_E[MaximaE[1][0]]) / (Weight + 1)
else:
T_E = E_mean / 2

# 寻找谱质心的阈值
Hist = np.histogram(C, bins=10)
HistC = Hist[0]
X_C = Hist[1]
MaximaC, countMaximaC = findMaxima(HistC, 3)
if len(MaximaC)>=2:
T_C = (Weight*X_C[MaximaC[0][0]]+X_C[MaximaC[1][0]]) / (Weight+1)
else:
T_C = Z_mean / 2

# 阈值判断
Flags1 = (E>=T_E)
Flags2 = (C>=T_C)
flags = np.array(Flags1 & Flags2, dtype=int)

## 提取语音片段
count = 1
segments = []
while count < len(flags): # 当还有未处理的帧时
# 初始化
curX = []
countTemp = 1
while ((flags[count - 1] == 1) and (count < len(flags))):
if countTemp == 1: # 如果是该语音段的第一帧
Limit1 = np.round((count-1)*step*fs)+1 # 设置该语音段的开始边界
if Limit1 < 1:
Limit1 = 1
count = count + 1 		# 计数器加一
countTemp = countTemp + 1	# 当前语音段的计数器加一

if countTemp > 1: # 如果当前循环中有语音段
Limit2 = np.round((count - 1) * step * fs) # 设置该语音段的结束边界
if Limit2 > len(signal):
Limit2 = len(signal)
# 将该语音段的首尾位置加入到segments的最后一行
segments.append([int(Limit1), int(Limit2)])
count = count + 1

# 合并重叠的语音段
for i in range(len(segments) - 1): # 对每一个语音段进行处理
if segments[i][1] >= segments[i + 1][0]:
segments[i][1] = segments[i + 1][1]
segments[i + 1, :] = []
i = 1

return segments

if __name__ == "__main__":
CHUNK = 1600
FORMAT = pyaudio.paInt16
CHANNELS = 1 # 通道数
RATE = 16000 # 采样率
RECORD_SECONDS = 3 # 时长
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
frames = [] # 音频缓存
while True:
frames.append(data)
if(len(frames) > RECORD_SECONDS * RATE / CHUNK):
del frames[0]
datas = b''
for i in range(len(frames)):
datas = datas + frames[i]
if len(datas) == RECORD_SECONDS * RATE * 2:
fmt = "<" + str(RECORD_SECONDS * RATE) + "h"
signal = np.array(st.unpack(fmt, bytes(datas))) # 字节流转换为int16数组
segments = VAD(signal, RATE) # 端点检测
# 可视化
index = 0
for seg in segments:
if index < seg[0]:
x = np.linspace(index, seg[0], seg[0] - index, endpoint=True, dtype=int)
y = signal[index:seg[0]]
plt.plot(x, y, 'g', alpha=1)
x = np.linspace(seg[0], seg[1], seg[1] - seg[0], endpoint=True, dtype=int)
y = signal[seg[0]:seg[1]]
plt.plot(x, y, 'r', alpha=1)
index = seg[1]
x = np.linspace(index, len(signal), len(signal) - index, endpoint=True, dtype=int)
y = signal[index:len(signal)]
plt.plot(x, y, 'g', alpha=1)
plt.ylim((-32768, 32767))
plt.show()


运行结果
下面是语音“语音信号处理”的端点检测结果： 
展开全文
• 主要介绍了详解python的webrtc库实现语音端点检测,小编觉得挺不错的，现在分享给大家，也给大家做个参考。一起跟随小编过来看看吧
• python实现语音端点检测(Voice Activity Detection,VAD) 1.准备环境 https://github.com/marsbroshok/VAD-python 里面的vad.py文件 2.具体代码 from vad import VoiceActivityDetector import wave if __name__ =...
用python实现语音端点检测(Voice Activity Detection,VAD)
2.具体代码
from vad import VoiceActivityDetector
import wave

if __name__ == "__main__":
save_file = "process.wav"
raw_detection = v.detect_speech()
speech_labels, point_labels = v.convert_windows_to_readible_labels(raw_detection)
if len(point_labels) != 0:
# 根据节点音频分割并连接
data = v.data
cut_data = []
Fs = v.rate
for start, end in point_labels:
cut_data.extend(data[int(start):int(end)])

# 保存音频
f = wave.open(save_file, 'w')
nframes = len(cut_data)
f.setparams((1, 2, Fs, nframes, 'NONE', 'NONE'))  # 声道，字节数，采样频率，*，*
wavdata = np.array(cut_data)
wavdata = wavdata.astype(np.int16)
f.writeframes(wavdata)  # outData
f.close()

class VoiceActivityDetector():
""" Use signal energy to detect voice activity in wav file """

def __init__(self, wave_input_filename):
#沿音频数据移动 20 毫秒的窗口。
self.sample_window = 0.02  # 20 ms
self.sample_overlap = 0.01  # 10ms
#应用长度为 0.5s 的中值滤波器来平滑检测到的语音区域。
self.speech_window = 0.5  # half a second
#计算语带能量与窗口总能量的比值。如果比率大于阈值（默认为 0.6），则将窗口标记为语音
self.speech_energy_threshold = 0.6  # 60% of energy in voice band
#中值滤波器（滤波保留2000-8000hz）
self.speech_start_band = 2000
self.speech_end_band = 8000
self.data_speech = []

4.实现效果 原音频  切割保留后的音频 
展开全文
• 【语音识别】语音端点检测Python实现一、语音信号的分帧处理二、端点检测方法2.1、短时能量2.2、短时过零率三、Python实现 从接收的语音信号中准确检测出人声开始和结束的端点是进行语音识别的前提。本博文介绍...


【语音识别】语音端点检测及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.

二、端点检测方法
端点检测是指找出人声开始和结束的端点。利用人声信号短时特性与非人声信号短时特性的差异可以有效地找出人声开始和结束的端点，本博文介绍短时能量和短时过零率结合进行端点检测的方法。
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}}

包含人声信号的帧的短时平均能量大于非人声信号的帧。
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)}

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.

三、Python实现
import wave
import numpy as np
import matplotlib.pyplot as plt

'''读取语音信号
'''
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
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)

端点检测结果：

展开全文
py-webrtcvad 语音端点检测
算法说明
p(xk|z,rk)={1/sqrt(2*pi*sita^2)} * exp{ - (xk-uz) ^2/(2 * sita ^2 )}

1.设定模式 根据hangover、单独判决和全局判决门限将VAD检测模式分为以下4类 0-quality mode
1-low bitrate mode
2-aggressive mode
3-very aggressive mode
2.webrtc的VAD只支持帧长10ms，20ms和30ms，为此事先要加以判断，不符合条件的返回-13.webrtc的VAD核心计算只支持8KHz采样率，所以当输入信号采样率为32KHz或者16KHz时都要先采样到8KHz4.在8KHz采样率上分为两个步骤
4.1 计算子带能量 子带分为80~250Hz，250~500Hz，500~1000Hz，1000~2000Hz，2000~3000Hz，3000~4000Hz

需要分别计算上述子带的能量feature_vector
4.2通过高斯混合模型分别计算语音和非语音的概率，使用假设检验的方法确定信号的类型 首先通过高斯模型计算假设检验中的H0和H1(c代码是用h0_test和h1_test表示)，通过门限判决vadflag

然后更新概率计算所需要的语音均值(speech_means)、噪声的均值(noise_means)、语音方差(speech_stds)和噪声方差(noise_stds)

实例代码
import collections
import contextlib
import sys
import wave

with contextlib.closing(wave.open(path, 'rb')) as wf:
num_channels = wf.getnchannels()
assert num_channels == 1
sample_width = wf.getsampwidth()
assert sample_width == 2
sample_rate = wf.getframerate()
assert sample_rate in (8000, 16000, 32000)
return pcm_data, sample_rate

def write_wave(path, audio, sample_rate):
with contextlib.closing(wave.open(path, 'wb')) as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(sample_rate)
wf.writeframes(audio)

class Frame(object):
def __init__(self, bytes, timestamp, duration):
self.bytes = bytes
self.timestamp = timestamp
self.duration = duration

def frame_generator(frame_duration_ms, audio, sample_rate):
n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
offset = 0
timestamp = 0.0
duration = (float(n) / sample_rate) / 2.0
while offset + n < len(audio):
yield Frame(audio[offset:offset + n], timestamp, duration)
timestamp += duration
offset += n

triggered = False
voiced_frames = []
for frame in frames:
sys.stdout.write(
'1' if vad.is_speech(frame.bytes, sample_rate) else '0')
if not triggered:
ring_buffer.append(frame)
num_voiced = len([f for f in ring_buffer
if num_voiced > 0.9 * ring_buffer.maxlen:
sys.stdout.write('+(%s)' % (ring_buffer[0].timestamp,))
triggered = True
voiced_frames.extend(ring_buffer)
ring_buffer.clear()
else:
voiced_frames.append(frame)
ring_buffer.append(frame)
num_unvoiced = len([f for f in ring_buffer
if not vad.is_speech(f.bytes, sample_rate)])
if num_unvoiced > 0.9 * ring_buffer.maxlen:
sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
triggered = False
yield b''.join([f.bytes for f in voiced_frames])
ring_buffer.clear()
voiced_frames = []
if triggered:
sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
sys.stdout.write('\n')
if voiced_frames:
yield b''.join([f.bytes for f in voiced_frames])

def main(args):
if len(args) != 2:
sys.stderr.write(
'Usage: example.py <aggressiveness> <path to wav file>\n')
sys.exit(1)
audio, sample_rate = read_wave(args[1])
frames = frame_generator(30, audio, sample_rate)
frames = list(frames)
segments = vad_collector(sample_rate, 30, 300, vad, frames)
for i, segment in enumerate(segments):
path = 'chunk-%002d.wav' % (i,)
print(' Writing %s' % (path,))
write_wave(path, segment, sample_rate)

if __name__ == '__main__':
main(sys.argv[1:])

参考：
http://blog.csdn.net/u012931018/article/details/16903027
GitHub地址：
https://github.com/wiseman/py-webrtcvad
展开全文
• 所谓的端点检测其实就是将语音进行分段，分为轻音，浊音，静音等。主要依据的短时能量以及短时过零率。 短时能量表示为： En=∑m=1Nxn2(m)E_n=\sum\limits_{m=1}^Nx_n^2(m)En​=m=1∑N​xn2​(m) 短时过零率表示为：...
• python的webrtc库实现语音端点检测

万次阅读 多人点赞 2017-05-26 16:54:31
语音端点检测最早应用于电话传输和检测系统当中,用于通信信道的时间分配,提高传输线路的利用效率.端点检测属于语音处理系统的前端操作,在语音检测领域意义重大. 但是目前的语音端点检测,尤其是检测 人声 开始和结束...
• 双门限法语音端点检测（Python实现）

万次阅读 多人点赞 2018-10-23 19:34:22
本文介绍一下利用双门限法进行语音端点检测的方法，该方法主要利用了语音的短时能量和短时过零率。 端点检测就是在一段包含语音的信号中，准确地确定语音的起始点和终止点，将语音段和非语音段区分开。我们知道，一...
• 端点检测就是检测语音信号的起点和终点，因此也叫起止点识别。它是语音处理技术中的一个重要方面，一个关键性问题，端点检测的准确与否，在很大程度上影响语音识别系统的性能。这里我们来研究下采用短时能量和短时...
• 语音端点检测文档包含一个python项目和17个技术文档相关资料，包括一些论文，我估计是网站里目前最全的资料
• 1.Python 基于Keras 训练的样本模型。 2.采用 C# 使用 Python 训练的样本模型，进行语音端点检测
• 基于倒谱特征的带噪语音端点检测，根据倒谱特征的优越性对语音有效部分进行提取和标注。
• Matlab写的一个基于谱熵算法的语音端点检测方法的实现

...

python 订阅