导库:
1 | import torch |
空气振动 ===> 时域波形
声波的采样
声音通过空气传播,被麦克风采集,存储为音频文件,其中一种格式叫做.wav。
声波是模拟量,是连续的,要将其转化为数字量就需要采样。并且这个转化一定会有损失存在。
一共涉及到两个重要参数,一个是采样率,另一个是采样深度。采样率越高,单位时间采集的点就越多,采样深度越大,点的大小刻度就越细。一般想要还原一段音频,采样率必须高于这段音频最大频率分量的2倍。麦克风硬件完成模拟量到数字量的转换。
用torchaudio 读取.wav 文件可以得到:
1 | wave_path = "./bed_0b56bcfe_nohash_0.wav" |
1 | AudioMetaData(sample_rate=16000, num_frames=16000, num_channels=1, bits_per_sample=16, encoding=PCM_S) |
表示这个文件保存的音频数据的采样率为16000Hz, 总共有16000 个点,所以时长为1s.那么里面的音频数据具体是怎样的呢?数据位数为16,那数据类型呢,可以是有符号整数也可以是无符号。encoding=PCM_S
, 中的S 就表示signed.所以每个数据点的范围为[-32768, 32767].
读取wav 文件并可视化
有了点的个数和点的值就能画出这个wav 文件所代表的时域波形。
先利用torchaudio 解析wav 文件,转化为tensor:
1 | wave = torchaudio.load(wave_path, normalize=False) |
1 | (tensor([[ 6, 11, 15, ..., 7, 12, 11]], dtype=torch.int16), 16000) |
这里参数normalize 默认为True, 会将样本点的数据类型转化到torch.float32 并且缩放到[-1, 1]
画图:
1 | wave = [] |
左边是没有缩放和标准化的,右边是是标准化后的。由于在同一个词语,在不同响度的情况下,波形幅度大小不同,但它们都是同一个词语,所以我们一般采用标准化后的波形。
时域波形 ===> 频谱图
与上文同思路,找出画出频谱图的必须条件,比如:横纵坐标以及数据点的具体值,就能画出频谱图,画出来了,也就理解了。
傅里叶变换具体原理,我个人还不太理解,先插个眼在这里,如果以后有契机再补全吧。傅里叶变换原理
频谱图表示一段音频中各个频率的分量的大小。横坐标就是频率,范围就是0hz 到采样频率(FS),分辨率就是采样率(FS)除以样本点个数(N)。纵坐标分两种,功率谱时为功率,能量谱时为能量。
这里没有明白为什么对时域信号进行傅里叶变换后,得到的横坐标长度为采样频率,以及纵坐标为功率和能量,估计是因为没有去具体学习傅里叶变换。
对上文的wave 进行傅里叶变换:
1 | specgram = [] |
1 | 16000 |
首先看横坐标,傅里叶变换后的横坐标个数就等于变换前样本点的个数,只不过数据左右对称,所以一般只取一半。横坐标的最小间距是采样率(FS)/样本点个数,只是这里恰好等于1而已。
然后就是纵坐标,这里的纵坐标主要跟变换前的时域信号相关,没有标准化的纵坐标非常大,标准化后的相对较小,一般取标准化后的。
这里我会有直觉上的疑惑:假如我对一个幅值为10 的正弦波进行傅里叶变换,那么变换后的频谱图(振幅图)中对应的尖刺峰值应该为10 才对。但是经过实验发现,峰值为几百。查询资料发现,这个峰值包含了多个样本点的能量.处理方法如下:
直流分量(0 Hz):得到的振幅需要除以N,N 为样本点个数
非直流分量:得到的振幅需要除以(N/2),N 为样本点个数
频谱图 ===> 时频图
对于两个读音完全不相同的词语来说,他们的时域图肯定不相同,但是频谱图可能相同(极端情况)。虽然完全相同的情况不多,但相似的情况绝对不少。对于关键词分类来说,频谱图的信息诚然比时域图的多,但这种相似情况却让其不能作为分类模型的输入。所以时频图诞生了,它既保留了频域信号有包含时域信号,是折中的选择。
大致流程是,规定一个长度,从时域图上等距取出此长度的数据(这些数据可以重合),每个此长度的数据简称为窗口。每个窗口都包含一部分时域数据,窗口与窗口之间有重合部分。对这些窗口分别进行傅里叶变换,得到这一小段时间内的频谱图,再将所有的二维频谱图合成一张三维图片,这张图片就是时频图。
1 | # 这里是直接从时域图到时频图,一步到位 |
1 | 时域信号:tensor([0.0002, 0.0003, 0.0005, ..., 0.0002, 0.0004, 0.0003]) |
窗口大小为600,所以单个傅里叶变换的样本个数就为600,所以频率个数就为301,而频率最小间隔就为:16000/600 = 26.66 Hz
.窗口大小为600,窗口滑动步长为300,可以计算得到窗口个数为52(剩下的数据不够一个窗口,舍去)。
画图:
1 | fig, axes = plt.subplots(1, 1, figsize=(20, 5)) |
这里的三维图片的具体数据是由标准化到[-1, 1]的时域数据傅里叶变换而来,这些数据差距非常大(有的分量的值非常大,有的非常小),这就导致较小的值之间的差距很小所以全为黑色。下面通过对数变换一下,将差距减小,以便于分辨这些细小的差距。
1 | fig, axes = plt.subplots(1, 1, figsize=(20, 5)) |
梅尔频谱图
。。。。。。。