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

整个流程:

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

fbank

一、 概念

Fbank = Mel Filter Bank Energies

本质上:

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

二、过程

2.1 预加重(Pre-emphasis)

yt = xt -- α * xt-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:

fbanki = sum(powerk * mel_filterik 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()
"""
相关推荐
米小虾9 小时前
不偷权重,只问问题:蒸馏攻击是怎么把前沿模型"问"走的,以及什么真能挡住
人工智能
2601_9605544710 小时前
会议录音一键生成纪要:实测告别手工整理
人工智能
米小虾10 小时前
一周 AI 观察(9.5–9.11):AI 从"产品"变成"基础设施"
人工智能
Behaviour10 小时前
iPhone Duo 来了,苹果 Siri AI 接入定制 Gemini
人工智能·ios·语言模型·aigc·iphone
不要生病了10 小时前
扩散语言模型的“灵活性陷阱”:为什么训练时反而应该按自回归顺序探索?
人工智能·语言模型·回归
前端snow10 小时前
ai agent---语音交互
人工智能
IT·陈寒10 小时前
Vue组件props传对象给我整不会了
人工智能·大模型·api·创业·变现·简历优化
2601_9657422210 小时前
全媒体运营与短视频代运营,两者有什么区别?
大数据·数据结构·人工智能·算法·ai·媒体
Joker-Zohar11 小时前
GPT-Image-2.5 双版本上线:8 组压力测试的提示词拆解,中文渲染零乱码
人工智能·gpt
白帽黑客-晨哥11 小时前
Copilot 能换成本地吗?本地化 AI 编程助手可行性全解析
人工智能·copilot