Audio音频入门

在反复纠结两个月后,本人最后还是选择之后整音频方向,以后会将自己学音频相关的笔记放到博客上,先学习音频分类吧。

最后吐槽一下:密码学真的是Bar又高又吃不饱饭,虽然有20和21年很多大厂在政策影响下招了隐私计算岗,但这几年已经不会再招多少人了(几乎只有蚂蚁和华为有位置),区块链厂更不说了主打的就是不稳定。

1. 音频基本介绍

音频一般指人耳可以听到的频率在20Hz-20kHz之间的声波,也可以指像.WAV这样存储音频信息的文件。它有如下三要素:

  1. 振幅:声波振动大小,也就是响度
  2. 频率:声波振动的频率,可以理解为音调
  3. 波形:决定声波的形状,可以决定声音的音色

1.1. 音频数据的存储方式

一般来说,使用脉冲编码调制(PCM)来编码音频,它对连续变化的模拟信号进行抽样、量化和编码产生数字信号。

  • 抽样:将连续时间模拟信号变为离散时间、连续幅度的抽样信号
  • 量化:将抽样信号变为离散时间、离散幅度的数字信号
  • 编码:对每组数据的幅度进行编码

此外还有几个相关概念:

  • 采样率:记录声音时每秒的采样个数,它用赫兹(Hz)来表示。
  • 量化格式(采样深度):指记录声音的动态范围,它以位(Bit)为单位。
  • 声道数:通道的数目
  • 比特率:每秒传输的数据量,比特率/码率 = 采样率 × 采样深度 × 通道数。kbps
  • 音频编码:仅仅由0和1构成的编码表示不同的振幅
  • 声道数:每次生成一个声波数据成为单声道,每次生成两个声波数据称为双声道
  • 音频率:比特率/8
音频数据在计算机中的表示

1.2. 语谱图、Fbank、MFCC之间的关系

一般来说,将音频数据输入到神经网络中需要将其预处理为语谱图(log谱)、Fbank或者MFCC,目前为了让神经网络接收到的信息足够多,一般还需要将音频的相位谱也作为输入传输到神经网络中,此时神经网络的输入大小为$B\times 2\times W\times T$,$B$为batch,2为语谱图/Fbank/MFCC和相位谱,$W$为频率范围,$T$为时间。

语谱图、Fbank、MFCC之间的关系

2. 从音频到语谱图

预加重、预增强

预增强以帧单位进行,目的在于加强高频,增加语音的高频分辨率。预加重后的结果为

$$ s(x)=s(x)-k*s(x-1) $$

$k$是与增强系数,范围$[0,1)$,常用0.97,$x$是提取的wav时域数组的项

分帧

分帧是将不定长的音频切分成固定长度的小段,现实中大多数信号都是非平稳的,但大多数短时间内可以近似看做是平稳的,可以用短时傅里叶变换表现非平稳信号频域特征。 需要分帧是因为后续的傅里叶变换适用于分析平稳的信号,而语音信号是变化迅速的 。

为了避免窗边界对信号的遗漏,因此对帧做偏移时候,帧间要有重叠一部分。通常的选择是帧长25ms,帧移为10ms。接下来的操作是对单帧进行的。要分帧是因为语音信号是快速变化的,而傅里叶变换适用于分析平稳的信号。帧和帧之间的时间差常常取为10ms,这样帧与帧之间会有重叠,否则,由于帧与帧连接处的信号会因为加窗而被弱化,这部分的信息就丢失了。

分帧示意

加窗

傅里叶变换要求输入信号是平稳的,但是语音信号从整体上来讲是不平稳的。每帧信号通常要与一个平滑的窗函数相乘,让帧两端平滑地衰减到零,这样可以降低傅里叶变换后旁瓣的强度,取得更高质量的频谱。

分帧截断时域信号的两旁会出现旁瓣,导致频谱泄露。因为声音信号是非周期信号,截取任意有限长的序列都不能代表原信号,矩形窗的频域是Sa函数,旁瓣起伏大,会产生频谱失真。所以一般采用汉明窗,它具有较小的旁瓣。以下是一个窗函数的示例

窗函数

将窗函数和原始信号相乘就可以获得截取后的信号 $y(t)$。

$$
y(t)=x(t)\cdot w(t-\tau)
$$

之后对分帧后的每个帧都用这样的窗函数处理,就可以将每帧两侧的旁瓣减弱,经过傅里叶变换后,可以获得更高质量的频谱。

窗函数移动

快速傅里叶变换

即使是分帧过后极短时间的声音,仍是很多高低频声音的混杂,此时的数据是时域,通过傅里叶变换转换为频域可以将复杂声波分成各种频率的声波,方便神经网络进行学习。最终结果是个频率范围内的重要程度(能量)。

因为我们用的是数字音频,所以我们用到的是离散傅里叶变换。我们现在可以在每一帧上做N点FFT来计算频谱,也称为短时傅里叶变换(Short-Time Fourier-Transform, STFT),其中N通常为256或512,NFFT = 512。公式如下所示,其中$x_i$为信号$x$的第$i$帧:

$$
P=\frac{|FFT(x_i)|^2}{N}
$$

将得到的每一帧的变换按轴频率轴拼接在一起就成了语谱图。纵轴表示频率,横轴表示时间,颜色的深浅来代替频谱强度。

语谱图

3. 从语谱图到Fbank

人耳对声音频谱的响应是非线性的,如果我们能类似于人耳的方式对音频进行处理,可以提高语音识别的性能。耳蜗的滤波作用是在对数频率尺度上进行的,在1000HZ以下为线性尺度,1K HZ以上为对数尺度,使得人耳对低频信号敏感,高频信号不敏感。FilterBank就是这样的一种算法。FBank特征提取要在预处理之后进行,这时语音已经分帧,我们需要逐帧提取FBank特征。生成Fbank需要对频谱图进行对Mel滤波和对数运算。

Mel滤波器组

梅尔标度被提出,它是频率Hz的非线性变换,对于以mel scale为单位的信号,可以做到人们对于相同频率差别的信号的感知能力几乎相同。梅尔频率与实际频率的关系为:Hz ($f$) 和 Mel ($m$),$m=F_{mel}(f)=2595\cdot log_{10}(1+\frac{f}{700}), f=F^{-1}_{mel}(m)=700(10^{m/2595}-1)$

计算fbank的最后一步是在得到的功率谱上应用三角形滤波器,通常是40个滤波器。滤波器如下所示:

Mel滤波器组

  • 当声波频率小于$f(0)$或大于$f(6)$时,滤波器组的和等于0,该声波会被完全过滤掉

确定这些$f(1)~f(6)$需要以最低频率$f_l$,最高频率$f_h$,傅里叶变换时的长度$N$,音频采样率$f_s$,对于第$m$个点$f(m)$计算如下:

$$
f(m)=(\frac{N}{f_s})F^{-1}{mel}(F{mel}(f_l)+m\frac{F_{mel}(f_h)-F_{mel}(f_l)}{M+1})
$$

将Mel滤波器组应用于信号的语谱图(也就是语谱图的矩阵和Mel滤波器的矩阵相乘),然后过滤后的语谱图取$log_{10}$再乘20,得到fbank,fbank的纵坐标表示在某个帧内,不同的滤波器的过滤后并取对数的结果:$s(m)=20*log(f(m))$

fbank

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def mel_filter(frame_pow, fs, n_filter, nfft):
"""
mel 滤波器系数计算
:param frame_pow: 分帧信号功率谱
:param fs: 采样率 hz
:param n_filter: 滤波器个数
:param nfft: fft点数
:return: 分帧信号功率谱mel滤波后的值的对数值
mel = 2595 * log10(1 + f/700) # 频率到mel值映射
f = 700 * (10^(m/2595) - 1 # mel值到频率映射
上述过程本质上是对频率f对数化
"""
mel_min = 0 # 最低mel值
mel_max = 2595 * np.log10(1 + fs / 2.0 / 700) # 最高mel值,最大信号频率为 fs/2
mel_points = np.linspace(mel_min, mel_max, n_filter + 2) # n_filter个mel值均匀分布与最低与最高mel值之间
hz_points = 700 * (10 ** (mel_points / 2595.0) - 1) # mel值对应回频率点,频率间隔指数化
filter_edge = np.floor(hz_points * (nfft + 1) / fs) # 对应到fft的点数比例上

# 求mel滤波器系数
fbank = np.zeros((n_filter, int(nfft / 2 + 1)))
for m in range(1, 1 + n_filter):
f_left = int(filter_edge[m - 1]) # 左边界点
f_center = int(filter_edge[m]) # 中心点
f_right = int(filter_edge[m + 1]) # 右边界点

for k in range(f_left, f_center):
fbank[m - 1, k] = (k - f_left) / (f_center - f_left)
for k in range(f_center, f_right):
fbank[m - 1, k] = (f_right - k) / (f_right - f_center)

# mel 滤波
# [num_frame, nfft/2 + 1] * [nfft/2 + 1, n_filter] = [num_frame, n_filter]
filter_banks = np.dot(frame_pow, fbank.T)
filter_banks = np.where(filter_banks == 0, np.finfo(float).eps, filter_banks)
# 取对数
filter_banks = 20 * np.log10(filter_banks) # dB

return filter_banks

# mel 滤波
n_filter = 40 # mel滤波器个数
filter_banks = mel_filter(frame_pow, fs, n_filter, nfft)
plot_spectrogram(filter_banks.T, ylabel='Filter Banks')

4. 从Fbank到MFCC

事实证明,前一步计算出的滤波器组系数高度相关,这在某些机器学习算法中可能存在问题。因此,我们可以应用离散余弦变换(DCT)去相关滤波器组系数并产生滤波器组的压缩表示(用cos函数的值作为系数,求对每个滤波器的取对数输出的加权和),其中$L$是MFCC系数阶数,一般取12-16。
$$
C(n)=\sum^{M-1}_{m=0}s(m)cos(\frac{\pi n(m-0.5)}{M}),n=1,2,…,L
$$
img