Python语音增强
简介
音频时域波形具有以下特征:音调,响度,质量。我们在进行数据增强时,最好只做一些小改动,使得增强数据和源数据存在较小差异即可,切记不能改变原有数据的结构,不然将产生“脏数据”,通过对音频数据进行数据增强,能有助于我们的模型避免过度拟合并变得更加通用。
经过实验发现对声波的以下改变是有用的:Noise addition(增加噪音)、Add reverb(增加混响)、Time shifting(时移)、Pitch shifting(改变音调)和Time stretching(时间拉伸)。
本文需要使用的python库:
- matplotlib:绘制图像
- librosa:音频数据处理
- numpy:矩阵数据处理
常见的失真有:
- 加性声学噪声:加性噪声与期望信号不相干,平稳加性噪声(背景环境声音、嗡嗡声、功放噪音),非平稳加性噪声(媒体干扰、非期望语音干扰和一些电子干扰)
- 声学混响:多径反射引起的叠加效应(与期望信号相关)
- 卷积信道效应:导致不均匀或带宽限制响应,为了去除信道脉冲响应,做信道均衡时对通信信道没有有效建模
- 非线性失真:信号输入时不适当的增益,常出现与幅度限制、麦克风功放等加性宽带电子噪声电器干扰
- 编码失真:比如压缩编码
- 录音仪器引起的失真:麦克风频率响应不足
先画出原始语音数据的语谱图和波形图:
import librosa
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示符号
fs = 16000
wav_data, _ = librosa.load("/home/gxli/lgx/Data/gather_crop/clean1/2148_farend.wav", sr=fs, mono=True)
# ########### 画图
plt.subplot(2, 2, 1)
plt.title("语谱图", fontsize=15)
plt.specgram(wav_data, Fs=16000, scale_by_freq=True, sides='default', cmap="jet")
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('频率/Hz', fontsize=15)
plt.subplot(2, 2, 2)
plt.title("波形图", fontsize=15)
time = np.arange(0, len(wav_data)) * (1.0 / fs)
plt.plot(time, wav_data)
plt.xlabel('秒/s', fontsize=15)
plt.ylabel('振幅', fontsize=15)
plt.tight_layout()
# plt.savefig("save.png")
plt.show()
时域增强
噪声增强
第一种:控制噪声因子
def add_noise1(clean, noise, gain=0.004):
# gain:噪声增益因子
noisy = clean + gain * noise
return noisy
第二种:根据SNR生成noisy
通过信噪比的公式推导出噪声的增益系数k。
def snr2noise(clean, noise, SNR):
"""
:param clean: 纯净语音
:param far_echo: 噪音
:param SER: 指定的SNR
:return: 根据指定的SNR求带噪语音(纯净语音+噪声)
"""
p_clean = np.mean(clean ** 2) # 纯净语音功率
p_noise = np.mean(noise ** 2) # 噪声功率
scalar = np.sqrt(p_clean / (10 ** (SNR / 10)) / (p_noise + np.finfo(np.float32).eps))
noisy = clean + scalar * noise
return noisy
第三种:制造鸡尾酒效应的带噪语音
其实并没有那么玄乎,就是将纯净语音和多段带噪语音进行相加,然后控制一下信噪比。
音量增强
语音音量的单位为dB,音量增益可以基于平均音量或者最大瞬时音量,下面公式是基于平均音量推得dB增益:
def volumeAument1(wav, dB):
"""
:param wav: 语音
:param dB: 音量
:return:返回以指定dB增益后的语音
"""
power = np.mean(wav ** 2) # 平均功率
scalar = np.sqrt(10 ** (dB / 10) / (power + np.finfo(np.float32).eps))
wav *= scalar
return wav, scalar
def volumeAument2(wav, dB):
"""
:param wav: 语音
:param dB: 音量
:return:返回以指定dB增益后的语音
"""
rmswav = (wav ** 2).mean() ** 0.5
scalar = 10 ** (dB / 20) / (rmswav + np.finfo(np.float32).eps)
wav = wav * scalar
return wav, scalar
其实这两个函数都可以,都可以达到目的,本质上都一样。
"""
音量增强
"""
import numpy as np
import librosa
EPS = np.finfo(float).eps
def mean_dbfs(sample_data):
rms = np.sqrt(np.mean(np.square(sample_data, dtype=np.float64)))
dbfs = 20.0 * np.log10(max(1e-16, rms))
return dbfs
def volumeAument1(wav, dB):
"""
:param wav: 语音
:param dB: 音量
:return:返回以指定dB增益后的语音
"""
power = np.mean(wav ** 2) # 平均功率
scalar = np.sqrt(10 ** (dB / 10) / (power + np.finfo(np.float32).eps))
wav *= scalar
return wav, scalar
def volumeAument2(wav, dB):
"""
:param wav: 语音
:param dB: 音量
:return:返回以指定dB增益后的语音
"""
rmswav = (wav ** 2).mean() ** 0.5
scalar = 10 ** (dB / 20) / (rmswav + np.finfo(np.float32).eps)
wav = wav * scalar
return wav, scalar
sr = 16000
wav = librosa.load("./wavdata/TIMIT.WAV", sr=sr)[0] # (46797,)
print(wav.shape)
wav, scalar = volumeAument1(wav, dB=15)
print(mean_dbfs(wav)) # 18.0103004778581
wav, scalar = volumeAument2(wav, 15)
print(mean_dbfs(wav)) # 18.010299731550788
混响增强
我这里使用的是Image Source Method(镜像源方法)来实现语音加混响,我想用两种方法来给大家实现,第一种是直接调用python库—— Pyroomacoustics来实现音频加混响,第二种就是按照公式推导一步一步来实现,两种效果一样,想看细节的可以参考第二种方法,只想开始实现效果的可以只看第一种方法:
方法一: Pyroomacoustics实现音频加混响
首先需要安装 Pyroomacoustics,这个库非常强大,感兴趣也可以多看看其他API接口
pip install Pyroomacoustics
步骤:
- 创建房间(定义房间大小、所需的混响时间、墙面材料、允许的最大反射次数、)
- 在房间内创建信号源
- 在房间内放置麦克风
- 创建房间冲击响应
- 模拟声音传播
import pyroomacoustics as pra
import numpy as np
import matplotlib.pyplot as plt
import librosa
# 1、创建房间
# 所需的混响时间和房间的尺寸
rt60_tgt = 0.5 # 所需的混响时间,秒
room_dim = [9, 7.5, 3.5] # 我们定义了一个9m x 7.5m x 3.5m的房间,米
# 我们可以使用Sabine’s公式来计算壁面能量吸收和达到预期混响时间所需的ISM的最大阶数(RT60,即RIR衰减60分贝所需的时间)
e_absorption, max_order = pra.inverse_sabine(rt60_tgt, room_dim) # 返回 墙壁吸收的能量 和 允许的反射次数
# 我们还可以自定义 墙壁材料 和 最大反射次数
# m = pra.Material(energy_absorption="hard_surface") # 定义 墙的材料,我们还可以定义不同墙面的的材料
# max_order = 3
room = pra.ShoeBox(room_dim, fs=16000, materials=pra.Material(e_absorption), max_order=max_order)
# 在房间内创建一个位于[2.5,3.73,1.76]的源,从0.3秒开始向仿真中发出wav文件的内容
audio, _ = librosa.load("speech.wav",sr=16000) # 导入一个单通道语音作为源信号 source signal
room.add_source([2.5, 3.73, 1.76], signal=audio, delay=0.3)
# 3、在房间放置麦克风
# 定义麦克风的位置:(ndim, nmics) 即每个列包含一个麦克风的坐标
# 在这里我们创建一个带有两个麦克风的数组,
# 分别位于[6.3,4.87,1.2]和[6.3,4.93,1.2]。
mic_locs = np.c_[
[6.3, 4.87, 1.2], # mic 1
[6.3, 4.93, 1.2], # mic 2
]
room.add_microphone_array(mic_locs) # 最后将麦克风阵列放在房间里
# 4、创建房间冲击响应(Room Impulse Response)
room.compute_rir()
# 5、模拟声音传播,每个源的信号将与相应的房间脉冲响应进行卷积。卷积的输出将在麦克风上求和。
room.simulate()
# 保存所有的信号到wav文件
room.mic_array.to_wav("./guitar_16k_reverb_ISM.wav", norm=True, bitdepth=np.float32,)
# 测量混响时间
rt60 = room.measure_rt60()
print("The desired RT60 was {}".format(rt60_tgt))
print("The measured RT60 is {}".format(rt60[1, 0]))
plt.figure()
# 绘制其中一个RIR. both can also be plotted using room.plot_rir()
rir_1_0 = room.rir[1][0] # 画出 mic 1和 source 0 之间的 RIR
plt.subplot(2, 1, 1)
plt.plot(np.arange(len(rir_1_0)) / room.fs, rir_1_0)
plt.title("The RIR from source 0 to mic 1")
plt.xlabel("Time [s]")
# 绘制 microphone 1 处接收到的信号
plt.subplot(2, 1, 2)
plt.plot(np.arange(len(room.mic_array.signals[1, :])) / room.fs, room.mic_array.signals[1, :])
plt.title("Microphone 1 signal")
plt.xlabel("Time [s]")
plt.tight_layout()
plt.show()
混合ISM/射线跟踪房间模拟器
room = pra.ShoeBox(
room_dim,
fs=16000,
materials=pra.Material(e_absorption),
max_order=3,
ray_tracing=True,
air_absorption=True,
)
# 激活射线追踪
room.set_ray_tracing()
控制信噪比
room.simulate(reference_mic=0, snr=10) # 控制信噪比
方法二:Image Source Method 算法讲解
从这里要讲算法和原理了,
代码参考:matlab版本:(RIR-Generator)https://github.com/ehabets/RIR-Generator[https://github.com/audiolabs/rir-generator]
镜像源法简介:
将反射面等效为一个虚像,或者说镜像。比如说,在一个开放空间里有一面平整墙面,那么一个声源可以等效为2两个声源;一个开放空间里有两面垂直的平整墙面,那么一个声源可以等效为4个;同理三面的话是8个。原理上就是这样,但是封闭的三维空间里情况有那么点复杂,一般来说,家里的空房间可以一定程度上近似为矩形盒子,假设房间尺寸为: