一、语音识别基础(1.1 语音特征的提取)

整个流程:

语音 → 特征(Fbank/MFCC) → 编码器(Transformer/Conformer) → CTC/Attention → 解码 → 文本

fbank

一、 概念

Fbank = Mel Filter Bank Energies

本质上:

把语音信号切成许多帧 → 做 FFT → 过一组 Mel 滤波器 → 得到每个滤波器的能量 → log → Fbank 特征

二、过程

2.1 预加重(Pre-emphasis)

y[t] = x[t] -- α * x[t-1]

系统 α
Kaldi 0.97
Librosa 默认 0.97
WeNet 默认关闭!
MFCC 论文(Rabiner) 0.95~0.97

参数α的解释:

问题 答案
预加重公式固定吗? 形式固定,但参数可调
α = 0.97 是必须的吗? 不是。只是习惯值
WeNet 用吗? 默认不使用
必须使用预加重吗? 现代 ASR 不一定需要

2.2 分帧(Framing)

把连续语音切成一帧一帧处理:

帧长:25ms(WeNet 默认)

帧移:10ms(WeNet 默认)

举例:16kHz 采样率

一帧:25ms = 400 samples 16k * 25 / 1000 = 400

帧移:10ms = 160 samples

原因:语音在 20~30ms 内可以视为稳定信号(短时平稳性)。

帧移 概念:

就是解码是一段一段的。但是不是严格连续的。比如我解第一帧的时间是0ms~25ms,第二帧的时间就是10ms ~35ms

复制代码
可以看到帧是重叠的?那有没有可能这两帧输出的一个东西呢?
答案是会的。
比如我第一帧和第二帧解码结果都是a
怎么保持正常:
1. Encoder 是累积序列建模的
   Transformer / Conformer 会把整段特征序列作为输入
   即使帧有 overlap,Decoder / CTC 看到的是 序列顺序
   前面的帧信息已经"被记住",不会单独把同一个时间段解码两次(有时间戳)
2. CTC 的特性
    CTC 输出是一串字符序列 + blank
	它会 自动合并重复的符号
	比如输入帧0和帧1都可能对应同一音素 "a",CTC 会输出:
	a a → a
	所以不会重复输出
3. Attention / Decoder 的上下文感知
   对于 Attention-based decoder,输出当前字符时,会参考 整个已经编码的序列
  当前帧不会单独"看"前一帧,而是整个上下文
时间 帧编号 特征 CTC 输出
0--25ms F0 f0 blank
10--35ms F1 f1 "你"
20--45ms F2 f2 "好"

2.3 加窗(Windowing)

给每一帧乘上一个窗,例如 Hamming 窗,让边缘更平滑

frame = frame * hamming_window

目的:避免信号突然截断带来的频域泄漏。

2.4 FFT(快速傅里叶变换) + 功率谱(Power Spectrum)

对每帧做 FFT,通常补零到 512/1024 点。

power = |FFT(frame)|^2

这种以时间为横坐标的表示,就是时域。时域信号告诉我们"什么时候发生了什么事",但它很难直接告诉我们这个信号是由哪些'基础频率'组成的。

傅里叶变换 的终极思想就是:任何复杂的时域信号,都可以分解为一系列不同频率、不同幅度、不同相位的简单正弦波(或余弦波)的叠加。

FFT 就是一种高效计算离散傅里叶变换的算法,是连接时域和频域的桥梁。

全称:Fast Fourier Transform。

本质:是一种算法,用于快速计算 DFT(离散傅里叶变换)。

功能:它将一个离散的时域信号(例如一段数字音频的采样点)作为输入,输出该信号的频域表示。

2.5 Mel 滤波器组(Filter Bank)

这是 Fbank 的核心

Mel 滤波器组是一系列 三角滤波器,数量通常为:

80(WeNet 默认)

40(传统配置)

每个滤波器响应不同频段,频率越高滤波器越稀疏(符合人耳感知)。

计算方法:

对每个三角滤波器 i:

fbank[i] = sum(power[k] * mel_filter[i][k] over k)

也就是:

把频谱能量按照滤波器形状加权求和 → 得到各频带能量

2.6 log 取对数

log_fbank = log(fbank + eps)

示例代码

py 复制代码
import numpy as np
import scipy.io.wavfile as wav
# import matplotlib.pyplot as plt
"""
1. 读取 wav
"""
wav_path = "./wavs/test.wav"
sr, y = wav.read(wav_path)  # y: int16
y = y.astype(np.float32)
y = y / np.max(np.abs(y))  # 归一化到 [-1, 1]

"""
2. 预加重
"""
pre_emphasis = 0.97
y_preemph = np.append(y[0], y[1:] - pre_emphasis * y[:-1])

"""
3. 分帧 + 加窗
"""
frame_length = 0.025  # 25ms 帧长
frame_shift  = 0.01   # 10ms 帧移
n_fft = 512 #FFT 点数,决定频率分辨率。通常 ≥ win_length。
n_mels = 80 #Mel 滤波器数量,每帧输出 80 个特征。

win_length = int(frame_length * sr)  # 400 samples 窗长 0.025×16000
hop_length = int(frame_shift * sr)   # 160 samples 10ms对应的采样点数
num_frames = int(np.ceil((len(y_preemph) - win_length) / hop_length)) + 1 #计算该音频有多少帧数 ceil 向上取整
print(f"num_frams:{num_frames}")  # 1103
"""
pad 末尾 加窗
"""
pad_len = (num_frames-1)*hop_length + win_length
y_pad = np.append(y_preemph, np.zeros(pad_len - len(y_preemph)))

frames = np.zeros((num_frames, win_length))
for i in range(num_frames):
    start = i * hop_length
    frames[i] = y_pad[start:start+win_length]
"""
Hamming 窗
"""
frames *= np.hamming(win_length)

"""
4. FFT + 功率谱
FFT
"""
fft_frames = np.fft.rfft(frames, n=n_fft)
power_frames = np.abs(fft_frames)**2

"""
5. Mel 滤波器
"""
def hz_to_mel(hz):
    return 2595 * np.log10(1 + hz/700)

def mel_to_hz(mel):
    return 700 * (10**(mel/2595) - 1)
"""
Mel 滤波器
"""
def mel_filterbank(n_mels=80, n_fft=512, sr=16000, fmin=0, fmax=None):
    if fmax is None:
        fmax = sr/2
    # Mel scale
    mels = np.linspace(hz_to_mel(fmin), hz_to_mel(fmax), n_mels+2)
    hz = mel_to_hz(mels)
    bins = np.floor((n_fft+1) * hz / sr).astype(int)

    fb = np.zeros((n_mels, n_fft//2+1))
    for i in range(1, n_mels+1):
        start, center, end = bins[i-1], bins[i], bins[i+1]
        for k in range(start, center):
            fb[i-1, k] = (k - start) / (center - start)
        for k in range(center, end):
            fb[i-1, k] = (end - k) / (end - center)
    return fb

fb = mel_filterbank(n_mels=n_mels, n_fft=n_fft, sr=sr)
fbank = np.dot(power_frames, fb.T)

"""
6. 对数
"""
fbank = np.log(fbank + 1e-6)
num_print = 10
print("Fbank 前 10 帧特征:")
for i in range(min(num_print, fbank.shape[0])):
    print(f"Frame {i}: {fbank[i]}")
print("Fbank shape:", fbank.shape)  # (num_frames, n_mels)

"""
7. 可视化
"""
"""
plt.figure(figsize=(10,4))
plt.imshow(fbank.T, origin='lower', aspect='auto', cmap='jet')
plt.xlabel("Frame")
plt.ylabel("Mel Filter")
plt.title("Fbank Feature")
plt.colorbar()
plt.show()
"""
相关推荐
线束线缆组件品替网2 小时前
Conxall 防水线缆在户外工控中的布线实践
运维·人工智能·汽车·电脑·材料工程·智能电视
皇族崛起2 小时前
【视觉多模态】基于视觉AI的人物轨迹生成方案
人工智能·python·计算机视觉·图文多模态·视觉多模态
dundunmm2 小时前
【每天一个知识点】本体论
人工智能·rag·本体论
nimadan122 小时前
**免费有声书配音软件2025推荐,高拟真度AI配音与多场景
人工智能·python
jkyy20142 小时前
汽车×大健康融合:智慧健康监测座舱成车企新赛道核心布局
大数据·人工智能·物联网·汽车·健康医疗
可触的未来,发芽的智生2 小时前
完全原生态思考:从零学习的本质探索→刻石头
javascript·人工智能·python·神经网络·程序人生
凤希AI伴侣2 小时前
重构与远见:凤希AI伴侣的看图升级与P2P算力共享蓝图-凤希AI伴侣-2026年1月12日
人工智能·重构·凤希ai伴侣
叫我:松哥2 小时前
基于Flask+ECharts+Bootstrap构建的微博智能数据分析大屏
人工智能·python·信息可视化·数据分析·flask·bootstrap·echarts
倔强的石头1063 小时前
什么是机器学习?—— 用 “买西瓜” 讲透核心逻辑
人工智能·机器学习