Python语音增强

野哥李 / 2023-05-06 / 原文

简介

音频时域波形具有以下特征:音调,响度,质量。我们在进行数据增强时,最好只做一些小改动,使得增强数据和源数据存在较小差异即可,切记不能改变原有数据的结构,不然将产生“脏数据”,通过对音频数据进行数据增强,能有助于我们的模型避免过度拟合并变得更加通用。

经过实验发现对声波的以下改变是有用的:Noise addition(增加噪音)、Add reverb(增加混响)、Time shifting(时移)、Pitch shifting(改变音调)和Time stretching(时间拉伸)。

本文需要使用的python库:

  • matplotlib:绘制图像
  • librosa:音频数据处理
  • numpy:矩阵数据处理

常见的失真有:

  1. 加性声学噪声:加性噪声与期望信号不相干,平稳加性噪声(背景环境声音、嗡嗡声、功放噪音),非平稳加性噪声(媒体干扰、非期望语音干扰和一些电子干扰)
  2. 声学混响:多径反射引起的叠加效应(与期望信号相关)
  3. 卷积信道效应:导致不均匀或带宽限制响应,为了去除信道脉冲响应,做信道均衡时对通信信道没有有效建模
  4. 非线性失真:信号输入时不适当的增益,常出现与幅度限制、麦克风功放等加性宽带电子噪声电器干扰
  5. 编码失真:比如压缩编码
  6. 录音仪器引起的失真:麦克风频率响应不足

先画出原始语音数据的语谱图和波形图:

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()

img

时域增强

噪声增强

第一种:控制噪声因子

def add_noise1(clean, noise, gain=0.004):
    # gain:噪声增益因子
    noisy = clean + gain * noise
    return noisy

img

第二种:根据SNR生成noisy

通过信噪比的公式推导出噪声的增益系数k。

\[SNR=10*log_{10}(\frac{S^2}{(kN)^2}) \Rightarrow k=\sqrt{\frac{S^2}{N^2*10^{\frac{SNR}{10}}}} \]

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增益:

\[dB=10*log_{10}(kS)^2\Rightarrow k=\sqrt{\frac{10^{\frac{dB}{10}}}{S^2}} \]

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

\[dB=20*log_{10}kS\Rightarrow k=\frac{10^{\frac{db}{20}}}{A} \]

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

步骤:

  1. 创建房间(定义房间大小、所需的混响时间、墙面材料、允许的最大反射次数、)
  2. 在房间内创建信号源
  3. 在房间内放置麦克风
  4. 创建房间冲击响应
  5. 模拟声音传播
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)      # 控制信噪比

img
img

方法二:Image Source Method 算法讲解

从这里要讲算法和原理了,

代码参考:matlab版本:(RIR-Generator)https://github.com/ehabets/RIR-Generator[https://github.com/audiolabs/rir-generator]

镜像源法简介:
img

将反射面等效为一个虚像,或者说镜像。比如说,在一个开放空间里有一面平整墙面,那么一个声源可以等效为2两个声源;一个开放空间里有两面垂直的平整墙面,那么一个声源可以等效为4个;同理三面的话是8个。原理上就是这样,但是封闭的三维空间里情况有那么点复杂,一般来说,家里的空房间可以一定程度上近似为矩形盒子,假设房间尺寸为:

\[L=\left[x_{r}, y_{r}, z_{r}\right] \]