文章目录
- 概述
- Preprocessline模块实现以及代码讲解
- Loader模块
- Padder模块
- LogSpectrogramExtractor模块
- MinMaxNormaliser模块
- Saver模块
- PreprocessPipeLine模块
- 知识补充
- property修饰词
- 总结
概述
- 这部分是将原来基于mnist手写数据集生成模型,一个用到基于FSDD音频数据集的声音生成模型的关键。二者差别,就在于多了这一步。
- 通过这一步,将音频文件转为频谱图,所以,后半部分生成图片的就可以重复利用了,这部分是关键。
- 具体流程如下图,就是多了前后的预处理这部分。
Preprocessline模块实现以及代码讲解
- 这部分需要一些基础知识,需要自己学一下,这里也会做相关介绍:
- 总的课程笔记
- 快速理解短时傅立叶变换
- 理解声音密度、强度和音色等基础声音特征
省流,直接看流程图
预处理流程
- 在代码中,作者将预处理分成了几个模块,并且每一个模块都负责一个功能,然后再用一个preprocess类,按照需求将各个模块进行组装。
- 具体功能如下
- Loader模块:负责读取音频文件,并返回对应的信号序列
- Padder模块:对信号的进行补充,确保每一个信号的长度都是统一长度相同
- LogSpectrogramExtractor模块:从数据序列中,提取出对应的频谱数据,并返回
- MinMaxNormaliser模块:正则化模块,对频谱数据,按照要求,将之隐射到对应的范围之内
- Saver模块:保存模块,将数据进行保存,主要保存两种特征,分别如下
- 数据的最值,这个是用来进行数据恢复的,因为数据还需要从正则化之后的数据恢复到原来的数据
- 提取出来的特征:将提取出来的频谱数据和声音的振幅,以及声音的时间序列进行保存
下面几个部分也是按照流程图的顺序,逐个实现的
Loader模块
class Loader:
""" 关于这个函数还是有一些不懂的地方,需要学习一下,相关的音频知识 """
def __init__(self,
sample_rate,
duration,
mono # 这是单声道还是双声道
):
self.sample_rate = sample_rate
self.duration = duration
self.mono = mono
def load(self,file_path):
""" 加载文件,这里加载的是什么东西 """
signal = librosa.load(file_path,
sr = self.sample_rate,
duration = self.duration,
mono = self.mono)[0]
return signal
- 参数说明
- sample_rate:采样频率
- duration:加载的音频时长,默认是完全加载的
- mono:是否将音频转换为单声道
- 返回值
- numpy数组:音频的时间序列,支持多通道
- 采样率
Padder模块
class Padder:
""" padder这个类别是用来对数组进行填补,保证数据的大小一致 """
def __init__(self,mode ="constant"):
self.mode = mode
def left_pad(self,array,num_missing_items):
paded_array = np.pad(array,
(num_missing_items,0),
mode = self.mode)
return paded_array
def right_pad(self, array, num_missing_items):
paded_array = np.pad(array,
( 0,num_missing_items),
mode=self.mode)
return paded_array
- 很常见的就是对数组进行扩充,是的数组能够保证大小一致
- 这里就是根据需要添加对应的列,使得所有的array的shape一致
LogSpectrogramExtractor模块
class LogSpectrogramExtractor:
""" LogSpectrogram是提取了时间序列中的频谱信息(以DB为单位) """
def __init__(self,frame_size,hop_length):
self.frame_size = frame_size
self.hop_length = hop_length
def extract(self,signal):
stft = librosa.stft(signal,
n_fft = self.frame_size,
hop_length=self.hop_length)[:-1] # 注意,这里为了能够输入模型训练,原来是(257,64)变成了(256,64),取出了最后一列
spectrogram = np.abs(stft) # 取绝对值,化成频谱图
log_spectrogram = librosa.amplitude_to_db(spectrogram) # 加上分贝图
return log_spectrogram
-
这部分主要是负责提取时域中音频信息的频域信息,主要用了两种方法,具体介绍如下
-
librosa.stft:
- 这部分是用来短时傅立叶变换,返回一个复数矩阵
- 参数说明
- n_fft:进行傅立叶变化的时域窗口
- hop_length:帧移动的跳数,时域窗口的每一次迭代的移动步数
- 返回
- 复数矩阵,这里仅仅不需要最后一个输出,具体执行如下效果图,
-
原来生成复数矩阵是(257,64),这里去除了最后一列
-
短时傅立叶如何将波形图转成频谱图的
-
librosa.amplitude_to_db:
- 将幅度频谱转换为dB标度频谱,就是用分贝表示幅度
- 参数
- 输入幅度
- 返回
- 复数序列,将原来的数据替换为对应的dB,下面是转换之后的db图
-
在网上找了一下,关于这块,这部分介绍的是比较详细的
- 链接
- 一般来说,这里需要了解一下对应的log-spectrogram数据到底是什么样的,有什么特征
-
振幅和分贝的关系,为啥需要这样转换
MinMaxNormaliser模块
class MinMaxNormaliser:
""" 对数据进行正则化 """
def __init__(self,min_val,max_val):
self.min = min_val
self.max = max_val
def nomalise(self,array):
"""" 将原来的数组映射到min_val,max_val之间,这部分是用来将声音提取出来,提取特征"""
norm_array = (array - array.min) / (array.max - array.min)
norm_array = norm_array * (self.max - self.min) + self.min
return norm_array
def denormalise(self,norm_array,original_min,original_max):
""" 这个是用来还原的,后续生成声音 """
array = (norm_array -self.min) / (self.max - self.min)
array = array * (original_max - original_min) * original_min
return array
- 这部分使用来对数据进行正则化的,我觉得他大部分函数都是自己实现的,可能就是让你学习一下的
- normalise:对数据进行正则化,将原来的数据映射到新的min和max之间
- denormalise:对数据进行还原
Saver模块
class Saver:
""" 保存特征和对应的min和max """
def __init__(self,feature_save_dir,min_max_values_save_dir):
self.feature_save_dir = feature_save_dir # 这是特征保存的路径,和原来的路径并不相同
self.min_max_values_save_dir = min_max_values_save_dir
def save_feature(self,feature,file_path):
""" 经过处理之后的特征 """
save_path = self._generate_save_path(file_path)
np.save(save_path,feature)
def save_min_max_values(self,min_max_values):
""" 保存音频文件对应的最大最小值 """
save_path = os.path.join(self.min_max_values_save_dir,"min_max_values.pkl")
self._save(min_max_values,save_path)
@staticmethod
def _save(data,save_path):
with open(save_path,"wb") as f:
pickle.dump(data,f)
def _generate_save_path(self,file_path):
file_name = os.path.split(file_path)[1]
save_path = os.path.join(self.feature_save_dir,file_name + ".npy")
return save_path
- 这个模块是负责保存提取出来的特征和保存原数据的最值
- min_max_values.pkl
- 将提取出来的数据,获取其最大值和最小值,保存为对应的pkl文件
- 文件名.npy
- 将提取出来的频谱特征保存为对应的npy文件
- min_max_values.pkl
PreprocessPipeLine模块
class PreprocessingPipeline:
""" 将上述的每一个文件经过下述流程处理:
1、加载音频文件
2、对数据进行padding,确保等长
3、从数据中提取出log频谱图
4、将频谱图进行正则花
5、保存频谱图
store the min max values for all the log spectrogram
"""
def __init__(self):
""" 这里并没有写死,考虑到了代码的鲁棒性,可以通用于不同的预处理模块 """
self.padder = None
self.extractor = None
self.normaliser = None
self.saver = None
self._loader = None
self._num_expected_samples = None
self.min_max_value = dict()
# 这部分是用来判定是否需要进行padding的
# 将成员变量定义为padder,判定是否需要进行
@property
def loader(self):
return self._loader
@loader.setter
def loader(self,loader):
self._loader = loader
self._num_expected_samples = int(loader.sample_rate * loader.duration)
def process(self,audio_files_dir):
for root, _, files in os.walk(audio_files_dir):
for file in files:
file_path = os.path.join(root, file)
self._process_file(file_path)
print(f"Processed file {file_path}")
self.saver.save_min_max_values(self.min_max_value)
# 这里还需要保存对应每一个音频文件对应的最大和最小值,便于还原
self.saver.save_min_max_values(self.min_max_value)
def _process_file(self,file_path):
""" 处理单个文件的过程 """
signal = self.loader.load(file_path) # 加载信号
if self._is_padding_necessary(signal): # 判定是否需要进行padding
signal = self._apply_padding(signal)
feature = self.extractor.extract(signal) # 提取特定的特征,这里保留一个通用的函数
norm_feature = self.normaliser.normalise(feature) # 对特征进行正则化
save_path = self.saver.save_feature(norm_feature,file_path)
self._store_min_max_value(save_path,feature.min(),feature.max())
# 4.3 逐步实现上述的方法
def _is_padding_necessary(self,signal):
""" 判定是否需要对这个array进行扩充 """
if len(signal) < self._num_expected_samples:
return True
return False
def _apply_padding(self,signal):
""" 对array进行padding """
padding_signal = self.padder.right_pad(signal,self._num_expected_samples - len(signal))
return padding_signal
def _store_min_max_value(self,save_path,min_value,max_value):
self.min_max_value[save_path] = {
"min" :min_value,
"max":max_value
}
- 数据预处理的完整流程,主要是调用的process方法实现,然后处理单个文件,是调用私有函数_process方法
知识补充
property修饰词
- 参考连接 , property修饰词的说明
- 作用
- 使用property来创建只读属性,将方法转为相同名称的只读属性,防止属性被修改。
总结
- 这段代码的复用性还是很强的,如果你是实现不同的预处理流程,只需要实现特定阶段的类即可,然后将新的对象,组装到新的preprocess pipeline即可,这里需要学习一下。说到这里,还是需要了解一下设计模式,毕竟是前人总结出来,针对不同情况的编程的习惯。现在的编码习惯太差了,一点都不好,无论是python、java还是C++都是可以面向对象编程,我忽然间想起,我在程序精英编程大赛中,居然还使用一个一个函数进行实现功能,羞愧。
- 这部分是对音频进行预处理,将之变为图片,然后音频生成就转成了图片的生成问题。
- 这里预处理的流程,涉及到很多环节,都是音频处理的基础知识,需要好好学习一下。
- log spectrogram和spectrogram的关系
- 短时傅立叶怎么把波形图转成频谱图的
- 。。。。
- 现在会的都是一些皮毛。这篇博客,也仅仅会讲述这个项目中会用到的相关知识,多的并不会讲,但是如果想去专门了解一下这个版块,还是需要专门学习一下相关的基础知识。