3. 能量门限与过零率联合静音检测
任务目标
实现一个前端语音活动检测(VAD)函数,利用短时能量和过零率(ZCR)的双门限联合判断,从连续音频流中区分静音帧与语音帧。该检测器可作为关键词唤醒系统的前置模块,用于降低后端计算功耗。
核心原理
静音帧通常具有低短时能量和相对稳定的过零率;语音帧(尤其是清音/摩擦音)能量较高或过零率波动明显。本案例采用双门限策略:
- 帧能量低于能量低门限
E_low且过零率低于ZCR门限Z_th→ 判为静音 - 帧能量高于能量高门限
E_high→ 判为语音(浊音段) - 介于两者之间时,利用过零率辅助判断(清音段ZCR较高)
完整实现
import numpy as np
def compute_energy(frame):
"""计算帧短时能量(归一化)"""
return np.mean(frame ** 2)
def compute_zcr(frame):
"""计算帧过零率(单位:次/样本)"""
if len(frame) < 2:
return 0.0
signs = np.signbit(frame)
return np.mean(signs[1:] != signs[:-1])
def vad_double_threshold(audio, sr=16000, frame_len_ms=25, frame_shift_ms=10,
E_low=0.001, E_high=0.01, Z_th=0.3):
"""
基于短时能量和过零率的双门限VAD
参数:
audio: 一维float数组,范围[-1, 1]
sr: 采样率
frame_len_ms / frame_shift_ms: 帧长/帧移
E_low / E_high: 能量低/高门限
Z_th: 过零率门限
返回:
vad_flags: bool数组,True表示语音帧
"""
frame_len = int(sr * frame_len_ms / 1000)
frame_shift = int(sr * frame_shift_ms / 1000)
n_frames = (len(audio) - frame_len) // frame_shift + 1
if n_frames <= 0:
return np.array([], dtype=bool)
vad_flags = np.zeros(n_frames, dtype=bool)
for i in range(n_frames):
start = i * frame_shift
frame = audio[start:start + frame_len]
energy = compute_energy(frame)
zcr = compute_zcr(frame)
if energy > E_high:
vad_flags[i] = True # 浊音/高能量段
elif energy > E_low and zcr > Z_th:
vad_flags[i] = True # 清音/摩擦音段(低能量但高ZCR)
# 其余情况保持False(静音)
return vad_flags
# ---- 最小验证示例 ----
if __name__ == "__main__":
sr = 16000
t = np.arange(sr * 1.0) / sr # 1秒音频
# 构造测试信号:0.2s静音 + 0.4s语音(带清音成分) + 0.4s静音
silence = np.zeros(int(0.2 * sr))
voiced = 0.5 * np.sin(2 * np.pi * 200 * t[:int(0.4 * sr)])
# 叠加高频噪声模拟清音(高ZCR)
unvoiced = 0.05 * np.sin(2 * np.pi * 3000 * t[:int(0.4 * sr)])
speech = voiced + unvoiced
tail_silence = np.zeros(int(0.4 * sr))
test_audio = np.concatenate([silence, speech, tail_silence])
flags = vad_double_threshold(test_audio)
# 统计各段语音帧比例(预期:中间段比例高,两端接近0)
mid_start = int(0.2 * sr / 160) # 帧移10ms=160样本
mid_end = int(0.6 * sr / 160)
voiced_ratio = np.mean(flags[mid_start:mid_end])
silence_ratio = np.mean(flags[:mid_start])
print(f"语音段激活帧比例: {voiced_ratio:.2f}")
print(f"静音段激活帧比例: {silence_ratio:.2f}")
print(f"总帧数: {len(flags)}")
运行与验证
- 依赖:仅需
numpy(pip install numpy) - 运行:直接执行脚本
python vad_demo.py - 通过条件:观察输出——语音段激活帧比例应明显高于静音段(例如语音段>0.8,静音段<0.1)。若比例接近,可调整
E_low/E_high/Z_th门限适配实际音频范围
关键参数调优建议
- 能量门限:先计算纯静音段平均能量作为
E_low基准,E_high设为该值的5~10倍 - ZCR门限:清音/摩擦音ZCR通常>0.3,浊音段通常<0.1,静音接近0
- 帧长/帧移:25ms/10ms为语音处理常用配置,唤醒场景可适当增大帧移至20ms降低计算量
常见问题
- 环境噪声导致误检:建议先做高通滤波(截止频率80~100Hz)去除直流和低频哼声
- 门限不通用:不同麦克风增益下能量范围差异大,可在运行时用前200ms估计噪声底,动态更新门限
- 端点毛刺:可在帧级判决后增加中值滤波(窗口3~5帧)平滑VAD输出