4、Librosa实战:音频加载与可视化、时域与频域分析、提取MFCC特征、音频数据增强

好,咱们进入第四讲。说实话,Librosa这个库,是我做语音项目时最离不开的工具之一。它就像一把瑞士军刀——加载音频、画波形图、做频谱分析、提特征、做数据增强,全都能搞定。今天这一讲,咱们就把这些核心操作挨个过一遍。

4.1 音频加载与基础可视化

先说说加载音频。Librosa加载音频用的是librosa.load(),这个函数会把音频文件读成一个numpy数组,同时返回采样率。我个人习惯把采样率固定为22050Hz,为什么呢?因为对于语音识别来说,这个采样率已经能覆盖大部分有效信息,而且计算量适中。

import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np

# 加载音频文件
audio_path = 'speech_sample.wav'
y, sr = librosa.load(audio_path, sr=22050)  # sr=None 保留原始采样率

print(f'音频长度: {len(y)} 个采样点')
print(f'采样率: {sr} Hz')
print(f'时长: {len(y)/sr:.2f} 秒')

加载完之后,第一件事就是看看波形。波形图能直观反映音频的振幅变化——哪里声音大、哪里安静、有没有爆音。

# 绘制波形图
plt.figure(figsize=(14, 5))
librosa.display.waveshow(y, sr=sr)
plt.title('音频波形图')
plt.xlabel('时间 (秒)')
plt.ylabel('振幅')
plt.show()
小技巧:如果音频太长,可以只截取前几秒来预览。用 y[:sr*3] 就能拿到前3秒的数据。我在调试语音唤醒模型时经常这么干,省时间。

4.2 时域与频域分析

波形图看的是时域信息,但语音识别真正依赖的是频域信息。为什么?因为人耳对频率的感知更敏感,而且频域特征更能区分不同的音素。

把时域信号转换到频域,靠的是短时傅里叶变换(STFT)。Librosa里用librosa.stft()一步到位。

# 计算STFT
D = librosa.stft(y, n_fft=2048, hop_length=512, win_length=2048)
# 转换为幅度谱(dB单位)
D_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)

# 绘制频谱图
plt.figure(figsize=(14, 5))
librosa.display.specshow(D_db, sr=sr, hop_length=512, x_axis='time', y_axis='hz')
plt.colorbar(format='%+2.0f dB')
plt.title('频谱图 (Spectrogram)')
plt.show()

嗯,这里要注意一个参数:n_fft。它决定了频率分辨率。n_fft越大,频率分辨率越高,但时间分辨率会下降。我一般用2048,对于16kHz或22.05kHz的语音来说,这个值比较平衡。

核心概念:频谱图的横轴是时间,纵轴是频率,颜色深浅代表能量大小。你能看到元音部分(低频能量集中)和辅音部分(高频能量散开)的明显区别。

除了频谱图,我们还可以看梅尔频谱图。梅尔刻度模拟了人耳对频率的非线性感知——低频分辨细,高频分辨粗。Librosa里转换起来也很方便:

# 计算梅尔频谱
mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=2048, hop_length=512, n_mels=128)
mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)

plt.figure(figsize=(14, 5))
librosa.display.specshow(mel_spec_db, sr=sr, hop_length=512, x_axis='time', y_axis='mel')
plt.colorbar(format='%+2.0f dB')
plt.title('梅尔频谱图 (Mel Spectrogram)')
plt.show()

4.3 提取MFCC特征

MFCC(梅尔频率倒谱系数)是语音识别领域最经典的特征之一。说白了,它就是在梅尔频谱的基础上,再做一次离散余弦变换(DCT),提取出最能代表语音特征的系数。

我刚开始做语音识别时,以为MFCC越复杂越好,结果发现13维的MFCC就已经够用了。维度太高反而会引入噪声。

# 提取MFCC特征
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13, n_fft=2048, hop_length=512, n_mels=128)

print(f'MFCC形状: {mfcc.shape}')  # (13, 时间帧数)

# 可视化MFCC
plt.figure(figsize=(14, 5))
librosa.display.specshow(mfcc, sr=sr, hop_length=512, x_axis='time')
plt.colorbar()
plt.title('MFCC特征')
plt.show()

MFCC的每一行代表一个系数,第一行是能量项,后面的行代表频谱包络的细节。实际使用中,我们通常还会加上一阶差分和二阶差分,让特征包含动态信息。

# 计算一阶差分和二阶差分
mfcc_delta = librosa.feature.delta(mfcc)
mfcc_delta2 = librosa.feature.delta(mfcc, order=2)

# 拼接成39维特征
mfcc_features = np.vstack([mfcc, mfcc_delta, mfcc_delta2])
print(f'拼接后MFCC形状: {mfcc_features.shape}')  # (39, 时间帧数)
避坑指南:我曾经在提取MFCC时忘记做均值归一化,结果模型训练时损失一直降不下去。后来发现不同音频的能量差异太大,导致特征分布不一致。建议提取MFCC后,对每个特征维度做均值归一化:mfcc = (mfcc - np.mean(mfcc, axis=1, keepdims=True))

4.4 音频数据增强

数据增强是提升模型泛化能力的关键手段。语音领域常用的增强方法有三种:加噪、变速、移调。我自己的经验是,加噪能有效提升模型在嘈杂环境下的表现,变速和移调则能让模型对说话人差异更鲁棒。

4.4.1 加噪

加噪就是在原始音频上叠加一段噪声。噪声可以是白噪声,也可以是实际环境噪声(比如咖啡馆、街道的录音)。

def add_noise(y, noise_factor=0.005):
    """给音频添加白噪声"""
    noise = np.random.randn(len(y))
    y_noisy = y + noise_factor * noise
    # 防止裁剪
    y_noisy = np.clip(y_noisy, -1.0, 1.0)
    return y_noisy

# 测试
y_noisy = add_noise(y, noise_factor=0.01)
print('加噪完成,噪声系数: 0.01')

噪声系数怎么选?我一般控制在0.005到0.02之间。太大了语音会被淹没,太小了没效果。你可以先试听一下,找到合适的平衡点。

4.4.2 变速

变速改变音频的播放速度,但不改变音调。Librosa里用librosa.effects.time_stretch()实现。

def change_speed(y, rate=1.2):
    """改变音频速度,rate>1 变快,rate<1 变慢"""
    y_stretched = librosa.effects.time_stretch(y, rate=rate)
    return y_stretched

# 测试
y_fast = change_speed(y, rate=1.2)   # 变快20%
y_slow = change_speed(y, rate=0.8)   # 变慢20%
print(f'原始长度: {len(y)}, 变快后: {len(y_fast)}, 变慢后: {len(y_slow)}')

变速的范围我建议在0.8到1.2之间。超过这个范围,语音听起来就不自然了,模型反而学不到有用的特征。

4.4.3 移调

移调改变音调高低,但不改变语速。用librosa.effects.pitch_shift()实现。

def shift_pitch(y, sr, n_steps=2):
    """移调,n_steps为正数升高音调,负数降低音调"""
    y_shifted = librosa.effects.pitch_shift(y, sr=sr, n_steps=n_steps)
    return y_shifted

# 测试
y_higher = shift_pitch(y, sr, n_steps=2)   # 升高2个半音
y_lower = shift_pitch(y, sr, n_steps=-2)   # 降低2个半音
print('移调完成')

移调的步数我一般用±2到±4。你想想看,如果移调太多,语音就变成卡通音了,反而会误导模型。

4.5 综合实战:构建数据增强流水线

在实际项目中,我们通常会把多种增强方法组合起来。下面是我常用的一个数据增强函数:

def audio_augment(y, sr, noise_factor=0.005, speed_range=(0.9, 1.1), pitch_range=(-2, 2)):
    """音频数据增强流水线"""
    augmented = []
    
    # 原始音频
    augmented.append(('original', y))
    
    # 加噪
    y_noisy = add_noise(y, noise_factor)
    augmented.append(('noisy', y_noisy))
    
    # 变速
    for rate in [0.9, 1.1]:
        y_speed = change_speed(y, rate)
        augmented.append((f'speed_{rate}', y_speed))
    
    # 移调
    for n_steps in [-2, 2]:
        y_pitch = shift_pitch(y, sr, n_steps)
        augmented.append((f'pitch_{n_steps}', y_pitch))
    
    # 组合增强:变速+移调
    y_combined = change_speed(y, 1.1)
    y_combined = shift_pitch(y_combined, sr, 2)
    augmented.append(('speed_1.1_pitch_2', y_combined))
    
    return augmented

# 执行增强
augmented_audios = audio_augment(y, sr)
print(f'共生成 {len(augmented_audios)} 个增强版本')
经验之谈:数据增强不是越多越好。我见过有人把增强倍数开到50倍,结果模型训练时间翻了好几倍,效果提升却微乎其微。建议先做5-10倍增强,观察模型效果,再决定是否增加。

4.6 本章小结

这一讲的内容比较多,但都是实打实的干货。我们来捋一下重点:

  • 音频加载与可视化:用librosa.load()加载音频,用waveshow()看波形,用specshow()看频谱。
  • 时域与频域分析:STFT把时域信号转到频域,梅尔频谱模拟人耳感知。
  • MFCC特征提取:13维MFCC + 一阶/二阶差分 = 39维特征,记得做均值归一化。
  • 数据增强:加噪、变速、移调,组合使用效果更好。

下一讲,我们会把这些特征输入到深度学习模型中,真正开始训练语音识别系统。到时候你会发现,今天打下的基础有多重要。

课后练习:找一个你自己的音频文件,用本章学到的知识完成以下任务:
1. 加载并可视化波形图和频谱图
2. 提取39维MFCC特征并做归一化
3. 生成5个增强版本(至少包含加噪、变速、移调各一种)
4. 对比原始音频和增强音频的MFCC特征差异

好,今天就到这里。有什么问题,欢迎在课程群里交流。咱们下节课见。