【微实验】倒谱算法(Cepstrum)深度解析:原理、数学推导与代码实现

文章目录

      • [1. 问题的根源:人声共振峰如何掩盖基频](#1. 问题的根源:人声共振峰如何掩盖基频)
        • [1.1 人声信号的产生模型](#1.1 人声信号的产生模型)
        • [1.2 频域中的困境](#1.2 频域中的困境)
        • [1.3 分离的关键:将乘法变为加法](#1.3 分离的关键:将乘法变为加法)
      • [2. 倒谱的数学定义与推导](#2. 倒谱的数学定义与推导)
        • [2.1 倒谱的定义](#2.1 倒谱的定义)
        • [2.2 倒谱中的分量分离](#2.2 倒谱中的分量分离)
      • [3. 完整的倒谱基频检测算法](#3. 完整的倒谱基频检测算法)
        • [步骤 1:分帧加窗](#步骤 1:分帧加窗)
        • [步骤 2:计算对数幅度谱](#步骤 2:计算对数幅度谱)
        • [步骤 3:计算倒谱](#步骤 3:计算倒谱)
        • [步骤 4:在倒谱域中搜索峰值](#步骤 4:在倒谱域中搜索峰值)
      • [4. 完整代码实现](#4. 完整代码实现)
      • [5. 倒谱算法的工程限制](#5. 倒谱算法的工程限制)
        • [5.1 为什么倒谱不擅长时变信号](#5.1 为什么倒谱不擅长时变信号)
        • [5.2 倒谱峰的分辨率限制](#5.2 倒谱峰的分辨率限制)
        • [5.3 与 HPS 的对比](#5.3 与 HPS 的对比)
      • [6. 现代替代方案](#6. 现代替代方案)

1. 问题的根源:人声共振峰如何掩盖基频

1.1 人声信号的产生模型

人声的产生可以被建模为激励源-滤波器 结构。声带振动产生准周期的脉冲序列,这是激励源,决定了基频 f 0 f_0 f0。这个脉冲序列经过声道(咽喉、口腔、鼻腔构成的共鸣腔)时,声道会对不同频率分量施加不同的增益,形成若干个共振峰 F 1 , F 2 , F 3 . . . F_1, F_2, F_3... F1,F2,F3...。

在时域中,这个过程的数学表达为卷积:

s ( t ) = e ( t ) ∗ h ( t ) s(t) = e(t) * h(t) s(t)=e(t)∗h(t)

其中 s ( t ) s(t) s(t) 是最终的声音信号, e ( t ) e(t) e(t) 是声带激励(准周期脉冲), h ( t ) h(t) h(t) 是声道的冲激响应, ∗ * ∗ 表示卷积。

卷积在时域中混合了两个分量,直接观察 s ( t ) s(t) s(t) 无法分离它们。 这就是共振峰干扰基频检测的根本原因。

1.2 频域中的困境

对 s ( t ) s(t) s(t) 做傅里叶变换,卷积变为乘法:

S ( f ) = E ( f ) ⋅ H ( f ) S(f) = E(f) \cdot H(f) S(f)=E(f)⋅H(f)

其中:

  • S ( f ) S(f) S(f) 是语音信号的频谱
  • E ( f ) E(f) E(f) 是激励源的频谱,表现为间隔为 f 0 f_0 f0 的谐波梳状结构
  • H ( f ) H(f) H(f) 是声道的频率响应,表现为缓慢变化的包络,共振峰是包络上的峰值

在 S ( f ) S(f) S(f) 中, E ( f ) E(f) E(f) 的精细谐波结构被 H ( f ) H(f) H(f) 的包络调制。如果共振峰 H ( f ) H(f) H(f) 在某一频段的增益很大,该频段的谐波会被放大,导致直接搜索最大峰时可能锁定共振峰频率而非基频

1.3 分离的关键:将乘法变为加法

倒谱算法的突破在于将频谱中的乘法关系转化为加法关系,使激励分量和声道分量在倒谱域中占据不同区域,从而可以分离。

对频谱取对数:

log ⁡ S ( f ) = log ⁡ E ( f ) + log ⁡ H ( f ) \log S(f) = \log E(f) + \log H(f) logS(f)=logE(f)+logH(f)

此时两个分量从相乘变为相加。 log ⁡ E ( f ) \log E(f) logE(f) 是周期性的梳状函数(周期为 f 0 f_0 f0),而 log ⁡ H ( f ) \log H(f) logH(f) 是缓慢变化的包络。两者在频域中的"变化速率"差异巨大。


2. 倒谱的数学定义与推导

2.1 倒谱的定义

倒谱(Cepstrum) 是对数幅度谱的傅里叶变换:

c ( τ ) = F − 1 { log ⁡ ∣ S ( f ) ∣ } c(\tau) = \mathcal{F}^{-1}\left\{\log |S(f)|\right\} c(τ)=F−1{log∣S(f)∣}

其中 τ \tau τ 称为倒频率(Quefrency),具有时间的量纲,单位是秒。

这个定义可以分解为几个步骤:

  1. 对信号 s ( t ) s(t) s(t) 做傅里叶变换得到 S ( f ) S(f) S(f)
  2. 取幅度谱 ∣ S ( f ) ∣ |S(f)| ∣S(f)∣
  3. 取自然对数 log ⁡ ∣ S ( f ) ∣ \log |S(f)| log∣S(f)∣
  4. 对对数幅度谱做逆傅里叶变换,得到倒谱 c ( τ ) c(\tau) c(τ)

注意 :这里的逆傅里叶变换是在频域数据上的变换,结果是时间量纲的函数,但物理含义与原始时间轴不同 。倒频率 τ \tau τ 反映的是频谱中"变化周期"的快慢 ------低频分量(快速变化的谐波结构)对应大 τ \tau τ,高频分量(缓慢变化的包络)对应小 τ \tau τ。

2.2 倒谱中的分量分离

将 log ⁡ S ( f ) = log ⁡ E ( f ) + log ⁡ H ( f ) \log S(f) = \log E(f) + \log H(f) logS(f)=logE(f)+logH(f) 代入倒谱定义:

c ( τ ) = F − 1 { log ⁡ E ( f ) } + F − 1 { log ⁡ H ( f ) } c(\tau) = \mathcal{F}^{-1}\{\log E(f)\} + \mathcal{F}^{-1}\{\log H(f)\} c(τ)=F−1{logE(f)}+F−1{logH(f)}

定义:

  • c e ( τ ) = F − 1 { log ⁡ E ( f ) } c_e(\tau) = \mathcal{F}^{-1}\{\log E(f)\} ce(τ)=F−1{logE(f)} ------ 激励分量的倒谱
  • c h ( τ ) = F − 1 { log ⁡ H ( f ) } c_h(\tau) = \mathcal{F}^{-1}\{\log H(f)\} ch(τ)=F−1{logH(f)} ------ 声道分量的倒谱

关键性质

E ( f ) E(f) E(f) 是周期为 f 0 f_0 f0 的梳状函数, log ⁡ E ( f ) \log E(f) logE(f) 仍然是周期为 f 0 f_0 f0 的函数。周期函数的傅里叶变换是离散冲激序列,冲激出现在:

τ k = k f 0 , k = 1 , 2 , 3 , . . . \tau_k = \frac{k}{f_0}, \quad k = 1, 2, 3, ... τk=f0k,k=1,2,3,...

因此, c e ( τ ) c_e(\tau) ce(τ) 在 τ = 1 / f 0 , 2 / f 0 , 3 / f 0 . . . \tau = 1/f_0, 2/f_0, 3/f_0... τ=1/f0,2/f0,3/f0... 处有显著的峰值。第一个峰的位置 τ = 1 / f 0 \tau = 1/f_0 τ=1/f0 直接给出基频的倒数

H ( f ) H(f) H(f) 是缓慢变化的包络,其傅里叶变换 c h ( τ ) c_h(\tau) ch(τ) 集中在 τ \tau τ 很小的区域(通常 τ < 2 ms \tau < 2\text{ ms} τ<2 ms)。这是因为包络在频域中的变化速率很低,对应的倒频率分量只在低倒频区出现。

分离策略 :在倒谱域中,低频倒频区( τ \tau τ 小)由声道分量主导,高频倒频区( τ \tau τ 大)由激励分量主导。通过设置阈值,可以提取激励分量的峰值位置,从而估计基频。


3. 完整的倒谱基频检测算法

步骤 1:分帧加窗

对语音信号 s n sn sn 进行分帧,每帧长度 N N N,加汉明窗:

s w n = s n ⋅ w n , w n = 0.54 − 0.46 cos ⁡ ( 2 π n N − 1 ) s_wn = sn \cdot wn, \quad wn = 0.54 - 0.46\cos\left(\frac{2\pi n}{N-1}\right) swn=sn⋅wn,wn=0.54−0.46cos(N−12πn)

步骤 2:计算对数幅度谱

S k = ∣ ∑ n = 0 N − 1 s w n e − j 2 π k n / N ∣ Sk = \left| \sum_{n=0}^{N-1} s_wn e^{-j2\pi kn/N} \right| Sk= n=0∑N−1swne−j2πkn/N

L k = log ⁡ ( S k + ϵ ) Lk = \log(Sk + \epsilon) Lk=log(Sk+ϵ)

其中 ϵ \epsilon ϵ 是小的正数,防止对零取对数。

步骤 3:计算倒谱

c n = 1 N ∑ k = 0 N − 1 L k e j 2 π k n / N cn = \frac{1}{N} \sum_{k=0}^{N-1} Lk e^{j2\pi kn/N} cn=N1k=0∑N−1Lkej2πkn/N

即对对数幅度谱做逆 FFT。

步骤 4:在倒谱域中搜索峰值

设定搜索范围 τ m i n , τ m a x \\tau_{min}, \\tau_{max} τmin,τmax,对应基频范围 f m i n , f m a x f_{min}, f_{max} fmin,fmax 的倒数:

τ m i n = 1 f m a x , τ m a x = 1 f m i n \tau_{min} = \frac{1}{f_{max}}, \quad \tau_{max} = \frac{1}{f_{min}} τmin=fmax1,τmax=fmin1

在倒谱 c n cn cn 的该区间内找最大值对应的索引 n p e a k n_{peak} npeak,基频估计为:

f ^ 0 = 1 n p e a k / f s = f s n p e a k \hat{f}0 = \frac{1}{n{peak} / f_s} = \frac{f_s}{n_{peak}} f^0=npeak/fs1=npeakfs

其中 f s f_s fs 是采样率, n p e a k n_{peak} npeak 是倒谱索引。


4. 完整代码实现

python 复制代码
import numpy as np
from scipy.signal import get_window
import matplotlib.pyplot as plt

def cepstrum_pitch_detection(
    signal_frame: np.ndarray,
    sample_rate: float,
    f0_min: float = 60.0,
    f0_max: float = 400.0,
    use_lifter: bool = True,
    lifter_cutoff: float = 0.002,
):
    """
    倒谱基频检测(单帧)
    
    参数:
        signal_frame: 单帧时域信号
        sample_rate: 采样率 (Hz)
        f0_min: 最小基频 (Hz)
        f0_max: 最大基频 (Hz)
        use_lifter: 是否使用倒谱提升(去除低倒频分量)
        lifter_cutoff: 低倒频截止阈值 (秒),小于该值的倒谱分量被置零
    
    返回:
        estimated_f0: 估计基频 (Hz)
        cepstrum: 完整倒谱序列
    """
    N = len(signal_frame)
    
    # 1. 加窗
    window = get_window('hamming', N)
    signal_windowed = signal_frame * window
    
    # 2. 计算幅度谱
    spectrum = np.abs(np.fft.rfft(signal_windowed))
    
    # 3. 对数幅度谱(防止 log(0))
    eps = 1e-10
    log_spectrum = np.log(spectrum + eps)
    
    # 4. 补全频谱(对称)后做逆 FFT 得到倒谱
    # rfft 只给出正频率,需要重建对称频谱
    log_spectrum_full = np.concatenate([log_spectrum, log_spectrum[-2:0:-1]])
    cepstrum = np.fft.ifft(log_spectrum_full).real
    
    # 5. 倒谱提升(去除声道分量)
    if use_lifter:
        lifter_samples = int(lifter_cutoff * sample_rate)
        cepstrum[:lifter_samples] = 0
        cepstrum[-lifter_samples:] = 0
    
    # 6. 在有效倒频范围内搜索峰值
    tau_min = int(sample_rate / f0_max)  # 对应最高基频
    tau_max = int(sample_rate / f0_min)  # 对应最低基频
    
    if tau_max >= N // 2:
        tau_max = N // 2 - 1
    
    search_range = cepstrum[tau_min:tau_max]
    peak_offset = np.argmax(search_range)
    peak_index = tau_min + peak_offset
    
    estimated_f0 = sample_rate / peak_index
    
    return estimated_f0, cepstrum


def cepstrum_pitch_tracking(
    signal: np.ndarray,
    sample_rate: float,
    frame_size: int = 2048,
    hop_size: int = 512,
    f0_min: float = 60.0,
    f0_max: float = 400.0,
):
    """
    逐帧倒谱基频跟踪
    """
    n_frames = (len(signal) - frame_size) // hop_size + 1
    f0_track = np.zeros(n_frames)
    
    for i in range(n_frames):
        start = i * hop_size
        frame = signal[start:start + frame_size]
        
        f0, _ = cepstrum_pitch_detection(
            frame, sample_rate, f0_min, f0_max,
            use_lifter=True, lifter_cutoff=0.002
        )
        f0_track[i] = f0
    
    return f0_track

使用示例

python 复制代码
# 生成模拟人声信号:基频 150 Hz,带共振峰
sample_rate = 16000
duration = 0.3
t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)

f0_true = 150.0

# 激励:脉冲序列
excitation = np.zeros_like(t)
pulse_period = int(sample_rate / f0_true)
for n in range(0, len(t), pulse_period):
    excitation[n] = 1.0

# 声道滤波器(模拟共振峰 F1=500Hz, F2=1500Hz)
from scipy.signal import lfilter
b = [1.0, -0.8, 0.2]  # 简化声道模型
a = [1.0]
speech = lfilter(b, a, excitation)

# 加噪声
noise = 0.01 * np.random.randn(len(t))
speech_noisy = speech + noise

# 单帧检测
frame = speech_noisy[:2048]
f0_est, cep = cepstrum_pitch_detection(frame, sample_rate)

print(f"真实基频: {f0_true:.1f} Hz")
print(f"倒谱估计: {f0_est:.1f} Hz")
print(f"误差: {abs(f0_est - f0_true):.2f} Hz")

# 对比:朴素最大峰法
spectrum = np.abs(np.fft.rfft(frame * get_window('hamming', 2048)))
naive_freq = np.argmax(spectrum) * sample_rate / 2048
print(f"朴素最大峰: {naive_freq:.1f} Hz (可能被共振峰干扰)")

5. 倒谱算法的工程限制

5.1 为什么倒谱不擅长时变信号

倒谱算法假设单帧内信号是稳态的,即基频在帧长范围内保持不变。当基频变化时(说话、唱歌、滑音),帧内会出现多个频率分量,倒谱峰变得模糊,甚至分裂成多个峰值。

对于连续语音,帧长通常取 20-30 ms,在这个尺度内基频可能已经发生了明显变化 。倒谱方法无法应对这种非平稳性,导致估计结果在帧间剧烈跳动

5.2 倒谱峰的分辨率限制

倒谱检测基频的分辨率受 FFT 点数的约束。倒谱索引 n n n 对应的频率为 f = f s / n f = f_s / n f=fs/n,相邻倒谱样本的频率差:

Δ f = ∣ f s n − f s n + 1 ∣ = f s n ( n + 1 ) ≈ f 2 f s \Delta f = \left|\frac{f_s}{n} - \frac{f_s}{n+1}\right| = \frac{f_s}{n(n+1)} \approx \frac{f^2}{f_s} Δf= nfs−n+1fs =n(n+1)fs≈fsf2

在基频 100 Hz、采样率 16 kHz 时,分辨率约为 0.6 Hz。这个精度对于稳态分析足够,但无法满足实时跟踪的平滑性要求。

5.3 与 HPS 的对比
特性 倒谱 HPS
共振峰抑制 ✅ 天然分离 ❌ 需要额外处理
基频缺失乐器 ❌ 效果差 ✅ 专项解决
噪声鲁棒性 ⚠️ 对加性噪声敏感 ⚠️ 对频谱错位敏感
计算复杂度 一次 FFT + 一次 IFFT 一次 FFT + R 次降采样
时变信号 ❌ 不适用 ❌ 不适用

6. 现代替代方案

倒谱算法在语音信号处理领域仍有应用,但基频检测的任务已大量转向:

  • YIN / pYIN:基于自相关差分,帧间平滑,适合连续语音
  • SWIPE:基于锯齿波匹配,对噪声鲁棒
  • CREPE / SPICE:卷积神经网络直接预测基频分布,在真实录音上表现优秀

如果你需要处理真实的人声录音,尤其是带有音高变化的语音或歌声,优先考虑 YIN 或深度学习方案。倒谱算法更适合教学、稳态元音分析、以及需要对激励-声道进行分量的应用场景。

相关推荐
研华科技Advantech2 小时前
案例 | 从离线巡检到在线看护:研华PHM解决方案赋能石油钻井平台智能运维
运维·人工智能·边缘计算
Elastic 中国社区官方博客2 小时前
你的 AI agent 不需要你的 API 密钥:使用 OAuth 2.1 对 Elasticsearch MCP 服务器进行身份验证
大数据·运维·人工智能·elasticsearch·搜索引擎·全文检索
airobotcn2 小时前
AI Agent下工厂:大模型如何驱动巡检机器人自主决策
人工智能·机器人·自动化·无人机
能源革命2 小时前
AI+能源前沿2026-08-15
人工智能·能源
Nil2082 小时前
leetcode 189轮转数组
数据结构·算法·leetcode
Dawson Zhu2 小时前
为什么智能体 Agent 需要本体 Ontology
人工智能·语言模型·架构·制造·agi
-dzk-3 小时前
【动态规划】LC 118.杨辉三角
算法·动态规划
恣逍信点3 小时前
《凌微经》静态自悖——变化之自因
人工智能·科技·算法·机器学习·业界资讯·拓扑学·哲学
在世修行3 小时前
深度图像数据格式与RAW文件解析:从字节到三维世界的桥梁
人工智能·数码相机·计算机视觉