时域特征提取:短时能量、短时过零率、短时自相关函数、基音周期估计

语音信号说到底就是个随时间变化的波形。但直接拿原始波形去分析,信息量太大,也不够直观。所以我们要从时域里提取一些“特征”,把关键信息浓缩出来。

这一章,我带你看看四个最基础的时域特征:短时能量、短时过零率、短时自相关函数,还有基音周期估计。嗯,这些概念听起来有点多,但别急,它们之间是有逻辑链条的。

核心思路:语音是非平稳信号,但在10-30ms的短时片段里可以看作平稳。所以我们加一个窗,把信号切成帧,再对每一帧提取特征。这就是“短时”二字的由来。

时域特征提取知识体系 语音信号(分帧加窗) 短时能量 短时过零率 短时自相关函数 清/浊音判别 端点检测 清/浊音判别 端点检测 基音周期估计 共振峰分析 四个特征从不同角度描述语音帧:能量、频率、周期性

短时能量:语音的“音量表”

短时能量,说白了就是某一帧语音信号的“音量”大小。数学上就是把一帧内所有采样点的平方加起来。

公式很简单:

E_n = Σ_{m=0}^{N-1} [x_n(m)]²

其中 x_n(m) 是第 n 帧的第 m 个采样点,N 是帧长。

你可能会问:为什么不用绝对值?我刚开始也这么想。后来发现平方操作能放大高能量部分,让清音和浊音的区分更明显。浊音的能量通常比清音高一个数量级。

我的经验:做端点检测(VAD)时,短时能量是最常用的特征之一。但我建议别只用能量,容易把噪声误判成语音。我一般会结合过零率一起用,效果会好很多。

实际应用中,我们通常用对数能量,因为人耳对声音的感知是对数的:

E_log = 10 * log10(E_n + ε)

加个 ε 是为了防止静音帧能量为零导致 log 爆炸。这个 ε 我一般取 1e-10。

短时过零率:频率的“粗糙估计”

短时过零率,就是在一帧内,信号波形穿过零轴的次数。说白了,就是看信号正负交替有多频繁。

公式:

Z_n = 0.5 * Σ_{m=1}^{N-1} |sgn[x_n(m)] - sgn[x_n(m-1)]|

其中 sgn 是符号函数,正数返回1,负数返回-1。

过零率能反映信号的频率特性。高频信号过零快,低频信号过零慢。清音(如 /s/、/f/)的过零率很高,浊音(如 /a/、/i/)的过零率很低。

注意:过零率对噪声非常敏感。背景噪声稍微大一点,过零率就会飙升。我曾经在一个工厂环境里做语音活动检测,结果过零率完全失效——机器轰鸣声让所有帧的过零率都差不多。后来我加了预加重和带通滤波,才把问题解决。

过零率的典型取值范围:

语音类型 过零率(8kHz采样,帧长20ms)
浊音(元音) 10-40
清音(摩擦音) 60-120
静音/噪声 20-80(取决于环境)

短时自相关函数:寻找“重复模式”

自相关函数,就是看信号和自己错开一个时间延迟后,有多“像”。如果信号有周期性,那么在周期整数倍的位置上,自相关会出现峰值。

公式:

R_n(k) = Σ_{m=0}^{N-1-k} x_n(m) * x_n(m + k)

k 是延迟量,取值范围从 0 到 N-1。

自相关函数有几个重要性质:

  • R(0) 就是短时能量,是最大值
  • R(k) 是偶函数,R(k) = R(-k)
  • 对于周期信号,R(k) 在 k = T, 2T, 3T... 处出现峰值

你想想看,如果一段语音是浊音,声带振动会产生周期性的脉冲。自相关函数就能把这个周期“抓”出来。

关键点:自相关函数是基音周期估计的基础。但直接计算自相关计算量很大——O(N²) 的复杂度。实际中我们常用快速傅里叶变换(FFT)来加速,利用维纳-辛钦定理:自相关函数等于功率谱的逆傅里叶变换。

基音周期估计:找到声带的“振动频率”

基音周期,就是声带振动的周期。基音频率就是它的倒数。成年男性的基频大概在 80-200 Hz,女性在 150-350 Hz,儿童更高。

基音周期估计的方法有很多,我重点讲最经典的——基于自相关函数的方法。

步骤很简单:

  1. 对语音帧计算短时自相关函数 R(k)
  2. 在合理的基音周期范围内(比如 2ms-20ms,对应 50-500 Hz)寻找 R(k) 的最大峰值
  3. 峰值对应的 k 就是基音周期(以采样点数为单位)
  4. 基音频率 F0 = 采样率 / k

但这里有个坑——R(0) 永远是最大值。所以我们要避开 k=0 附近的小延迟区域,从最小基音周期对应的延迟开始搜索。

def pitch_estimation(frame, fs, f0_min=50, f0_max=500):
    N = len(frame)
    # 计算自相关
    R = np.correlate(frame, frame, mode='full')
    R = R[N-1:]  # 取正延迟部分
    
    # 确定搜索范围
    k_min = int(fs / f0_max)
    k_max = int(fs / f0_min)
    
    # 在范围内找最大峰值
    k_peak = np.argmax(R[k_min:k_max]) + k_min
    
    # 基音频率
    f0 = fs / k_peak
    return f0, k_peak

我曾经踩过的坑:自相关法对共振峰的干扰很敏感。有时候第一共振峰(F1)的周期会被误判为基音周期,导致结果翻倍或减半。这就是所谓的“倍频/半频错误”。

解决办法:可以用倒谱法或平均幅度差函数(AMDF)做辅助判断。我个人的习惯是同时算自相关和AMDF,取两者一致的结果。

四种特征的对比与选择

特征 计算复杂度 主要用途 抗噪能力
短时能量 O(N) 端点检测、清浊音判别 中等
短时过零率 O(N) 清浊音判别、语音/音乐区分 弱
短时自相关函数 O(N²) 或 O(N log N) 用FFT 基音周期估计、共振峰分析 较强
基音周期 取决于估计方法 语音合成、说话人识别、情感分析 中等

实际项目中,我很少只用单一特征。比如做语音唤醒,我会用短时能量做粗检,再用过零率排除噪声,最后用基音周期确认是不是人声。三个特征串起来,误唤醒率能降不少。

一个小技巧:计算短时特征前,记得做预加重。预加重能提升高频成分,让清音的过零率更稳定,也让自相关函数的峰值更清晰。预加重系数我一般取 0.97。