AUTOVC 代码解析 —— make_spect.py
简介
本项目一个基于 AUTOVC 模型的语音转换项目,它是使用 PyTorch 实现的(项目地址)。
AUTOVC 遵循自动编码器框架,只对自动编码器损耗进行训练,但它引入了精心调整的降维和时间下采样来约束信息流,这个简单的方案带来了显著的性能提高。(详情请参阅 AUTOVC 的详细介绍)。
由于 AUTOVC 项目较大,代码较多。为了方便学习与整理,将按照工程文件的结构依次介绍。
本文将介绍项目中的 make_spect.py 文件:读取音频文件,并转换为 Mel 频谱保存。
函数解析
butter_highpass
该函数的作用是: 创建一个高通滤波器,得到滤波器系数:分子(’ b ‘)和分母(’ a ')多项式
输入参数:
cutoff : 滤波器截止频率
fs : 样本采样率
order : 滤波器阶数
输出参数:
b, a : IIR 滤波器的分子(' b ')和分母(' a ')多项式
代码详解:
def butter_highpass(cutoff, fs, order=5):
# 取一半的 fs 标量,fs 是采样率, nyq 与尼奎斯特定理有关
nyq = 0.5 * fs
# cutoff 是截止频率,需要标准化(我也不懂,想问问神奇海螺)
normal_cutoff = cutoff / nyq
# 设计一个 n 阶数字或模拟巴特沃斯滤波器并返回滤波器系数,其中:
# order 是 int 型整数,代表设计的滤波器的阶数;
# normal_cutoff 为临界频率,这里作为高通滤波器的标量,对于数字滤波器, normal_cutoff 与 fs (采样率)的单位相同。
# 默认情况下, fs 是 2 个半周期/样本,所以这些从 0 到 1 标准化,其中 1 是尼奎斯特频率。(因此, normal_cutoff 是半周期/样本。)(我也没看懂,等待大佬相助);
# btype 为可选过滤器的类型,在这里默认为 'high' ,选择高通滤波器;
# analog 为滤波器返回标志:当为 True 时,返回一个模拟滤波器,否则返回一个数字滤波器。这里代表设计的是一个数字滤波器;
# 综上所述,这里创建了一个 order 阶临界频率为 normal_cutoff 的高通滤波器,并返回了 IIR 滤波器的分子(' b ')和分母(' a ')多项式。
b, a = signal.butter(order, normal_cutoff, btype='high', analog=False)
# 返回IIR 滤波器的分子(' b ')和分母(' a ')多项式
return b, a
pySTFT
该函数的作用是: 实现给定样本片段的短时傅里叶变换
输入参数:
x : 音频样本
fft_length : 傅里叶变换的长度
hop_length : 跳跃的长度
输出参数:
np.abs(result) : 傅里叶变换的结果
代码详解:
def pySTFT(x, fft_length=1024, hop_length=256):
# 填补样本,前后长度为 fft_length//2 ,以反射的方式填充
x = np.pad(x, int(fft_length//2), mode='reflect')
# 重叠部分的长度
noverlap = fft_length - hop_length
# 计算样本 x 分割后的形状;
# 每隔 hop_length 在样本 x 上取 fft_length 个数据,共有 (x.shape[-1]-noverlap)//hop_length 个这样的数据组;
# 至于为什么前面要加上 x.shape[:-1] (计算结果为'()'),是为了保留原有的维度,仅对最后一个维度做更改
shape = x.shape[:-1]+((x.shape[-1]-noverlap)//hop_length, fft_length)
# 计算每次取上述形状的数据需要跳过的字节长度;
# x.strides 是 x 数组中元素间的距离,单位为字节,与元素类型有关。运行中元素类型为 float64 ,x.strides 为 8
# 在外部,每隔 hop_length 个元素跳跃一次;在内部,每隔 1 个元素跳跃一次
strides = x.strides[:-1]+(hop_length*x.strides[-1], x.strides[-1])
# 组装上述样本、形状与距离参数,执行样本数据的分割,获得 result ,形状为 shape
result = np.lib.stride_tricks.as_strided(x, shape=shape,
strides=strides)
# 创建一个长度为 fft_length 的 hann 周期窗口,准备与' ifftshift '一起使用,并乘以FFT的结果
fft_window = get_window('hann', fft_length, fftbins=True)
# 计算实际输入的一维离散傅里叶变换,并转置数组;
# 这个函数通过一种称为快速傅立叶变换(FFT)的高效算法计算实值数组的一维 n 点离散傅立叶变换(DFT)
result = np.fft.rfft(fft_window * result, n=fft_length).T
# 将傅里叶变换的结果取绝对值并返回
return np.abs(result)
main
该函数的作用是: 计算音频文件目录下音频文件的 Mel 频谱,并保存在对应的频谱目录
输入参数: 无
输出参数: 无
代码详解:
# 创建一个 Mel 过滤器组
# 这将产生一个线性变换矩阵来将 FFT 箱投射到梅尔频率箱上
mel_basis = mel(16000, 1024, fmin=90, fmax=7600, n_mels=80).T
# 设计一个最小等级的标量
min_level = np.exp(-100 / 20 * np.log(10))
# 调用 butter_highpass 创建一个高通滤波器,得到滤波器系数:分子(' b ')和分母(' a ')多项式
b, a = butter_highpass(30, 16000, order=5)
# 音频文件目录
rootDir = './wavs'
# 频谱图目录
targetDir = './spmel'
# 获取音频文件目录及该目录下的子目录列表
dirName, subdirList, _ = next(os.walk(rootDir))
# 打印音频文件目录名
print('Found directory: %s' % dirName)
# 按顺序遍历音频文件子目录
for subdir in sorted(subdirList):
# 打印当前目录
print(subdir)
# 若目标频谱图子目录不存在,则创建对应目录
if not os.path.exists(os.path.join(targetDir, subdir)):
os.makedirs(os.path.join(targetDir, subdir))
# 访问当前音频文件子目录,获取该目录下的所有文件名
_,_, fileList = next(os.walk(os.path.join(dirName,subdir)))
# 同一个音频文件子目录下,设定生成的随机数序列相同
prng = RandomState(int(subdir[1:]))
# 顺序遍历所有的文件名
for fileName in sorted(fileList):
# 以 NumPy 数组的形式从声音文件中提供音频数据
x, fs = sf.read(os.path.join(dirName,subdir,fileName))
# 信号滤波,消除漂移噪声
y = signal.filtfilt(b, a, x)
# 为了模型的鲁棒性,添加一点随机噪声
wav = y * 0.96 + (prng.rand(y.shape[0])-0.5)*1e-06
# 调用 pySTFT 进行短时傅里叶变化后转置数组,计算频谱
D = pySTFT(wav).T
# 转换为 mel 并标准化
D_mel = np.dot(D, mel_basis)
D_db = 20 * np.log10(np.maximum(min_level, D_mel)) - 16
S = np.clip((D_db + 100) / 100, 0, 1)
# 在指定目录下保存 mel 频谱
np.save(os.path.join(targetDir, subdir, fileName[:-4]),
S.astype(np.float32), allow_pickle=False)