3. 时域特征提取:短时能量、短时过零率、短时自相关函数
聊完了声学基础,咱们正式进入特征提取环节。时域特征,说白了就是直接在波形上算出来的统计量。你想想看,一段语音信号,本质上就是一串随时间变化的幅度值。我们能不能直接从这些数值里挖出有用的信息?当然能。
我个人习惯把时域特征比作「信号的体检报告」。短时能量告诉你声音有多响,短时过零率告诉你声音有多「碎」,短时自相关函数则帮你找到信号里的周期性规律。这三个特征组合起来,已经能解决不少实际问题了。
核心思路:语音信号是非平稳的,但在10~30ms的短时窗内可以视为平稳。所以我们加窗、分帧,然后在每一帧上计算特征。这就是「短时」二字的由来。
3.1 短时能量
短时能量是最直观的特征。它衡量一帧信号的总能量大小。公式很简单:对一帧内所有采样点的平方求和(或者取绝对值求和)。
def short_time_energy(frame):
"""
计算一帧信号的短时能量
frame: 一维numpy数组,表示一帧语音信号
返回: 标量能量值
"""
energy = np.sum(frame ** 2)
return energy
我在项目中遇到过一个问题:直接用平方和算能量,数值范围波动很大。安静环境和嘈杂环境可能差好几个数量级。后来我习惯取对数或者做归一化处理,这样特征更稳定。
实用技巧:实际工程中常用 10 * log10(energy + eps) 转换成分贝值。加一个小常数 eps 是为了防止 log(0)。
短时能量能干什么?最经典的应用就是端点检测(VAD)。说话的时候能量高,静音段能量低。设个阈值就能把语音段切出来。但要注意——背景噪声大的时候,这个阈值不好选。我曾经在工厂环境里做语音唤醒,背景机器轰鸣声让能量阈值完全失效,后来不得不结合过零率一起判断。
3.2 短时过零率
短时过零率(ZCR)统计一帧内信号穿过零轴的次数。说白了就是看波形正负交替有多频繁。
def zero_crossing_rate(frame):
"""
计算一帧信号的短时过零率
frame: 一维numpy数组
返回: 过零次数(或归一化后的比率)
"""
# 相邻采样点符号不同则计数+1
zcr = np.sum(np.abs(np.diff(np.sign(frame)))) / 2
return zcr
你想想看,清音(比如 /s/、/f/)的波形高频成分多,过零率就高;浊音(比如 /a/、/i/)的波形低频为主,过零率就低。这个差异非常明显。
| 音素类型 | 典型过零率范围 | 波形特点 |
|---|---|---|
| 浊音(元音) | 10~40 次/帧 | 周期性明显,幅度大 |
| 清音(摩擦音) | 40~80 次/帧 | 类噪声,幅度小 |
| 静音/背景噪声 | 不稳定,通常较低 | 随机波动 |
注意:过零率对直流偏移非常敏感。如果信号有直流分量,过零率会严重失真。预处理时一定要做去直流(减去均值)操作。我曾经因为ADC采集卡有直流偏置没处理,算出来的过零率全是错的,排查了半天才发现。
短时能量 + 短时过零率联合使用,是经典的语音端点检测方案。能量判断有声/无声,过零率判断是清音还是浊音。两者互补,效果比单用能量好得多。
3.3 短时自相关函数
短时自相关函数(ACF)用来检测信号的周期性。它的思路很简单:把信号和它自己延迟 τ 个点后的版本做内积。如果 τ 正好等于基音周期,两个波形对齐得最好,相关值最大。
def short_time_autocorr(frame, max_lag=None):
"""
计算一帧信号的短时自相关函数
frame: 一维numpy数组
max_lag: 最大延迟点数,默认等于帧长
返回: 自相关序列
"""
n = len(frame)
if max_lag is None:
max_lag = n
# 零均值化
frame = frame - np.mean(frame)
# 计算自相关
acf = np.correlate(frame, frame, mode='full')
# 只取正延迟部分 [0, max_lag]
acf = acf[n-1 : n-1 + max_lag]
# 归一化(可选)
acf = acf / acf[0] # 除以零延迟值,使范围在[-1, 1]
return acf
自相关函数的第一个峰值(除了 τ=0 处)对应的延迟就是基音周期。基音频率 = 采样率 / 延迟点数。嗯,这里要注意:实际语音的基频范围大概在 80~500 Hz,所以搜索峰值时只在这个范围内找就行,别去搜那些没意义的延迟。
基音检测的坑:自相关函数在倍频处也会出现峰值。比如实际基频是 100 Hz,在 200 Hz、300 Hz 的位置也可能出现次高峰。我早期做基音检测时经常被倍频干扰,后来加了动态规划和平滑约束才稳定下来。
3.4 三个特征的工程应用
这三个特征单独用都有局限,但组合起来威力不小。我列几个实际场景:
- 语音端点检测:能量 + 过零率双阈值法。能量判断有声段,过零率区分清音和噪声。
- 基音检测:自相关函数找峰值,配合中值滤波去野点。
- 语音/音乐分类:语音的过零率变化大,音乐的过零率相对稳定。能量包络也能提供区分信息。
- 异常声音检测:短时能量的突变往往对应撞击声、爆炸声等异常事件。
我个人习惯在项目初期先用这三个时域特征快速验证想法。它们计算量小,实时性高,跑在树莓派上都没问题。等验证通过后,再上更复杂的频域特征。
工程建议:帧长一般取 20~30ms,帧移取 10ms。这样相邻帧有重叠,特征序列更平滑。采样率 16kHz 时,帧长 512 点(32ms),帧移 160 点(10ms)是个不错的起点。
好了,时域特征就聊到这儿。这三个特征虽然简单,但它们是后续所有声学特征的基石。你动手跑一遍代码,用自己录的语音试试,感受一下不同音素在能量、过零率和自相关上的差异——比光看公式直观得多。