語音數據增強及python實現


博客作者:凌逆戰

博客地址:https://www.cnblogs.com/LXP-Never/p/13404523.html


  音頻時域波形具有以下特征:音調,響度,質量。我們在進行數據增強時,最好只做一些小改動,使得增強數據和源數據存在較小差異即可,切記不能改變原有數據的結構,不然將產生“臟數據”,通過對音頻數據進行數據增強,能有助於我們的模型避免過度擬合並變得更加通用。

  我發現對聲波的以下改變是有用的:Noise addition(增加噪音)、Add reverb增加混響)Time shifting(時移)Pitch shifting(改變音調)Time stretching(時間拉伸)

本章需要使用的python庫:

  • matplotlib:繪制圖像
  • librosa:音頻數據處理
  • numpy:矩陣數據處理

常見的失真有:

  1. 加性聲學噪聲:加性噪聲與期望信號不相干,平穩加性噪聲(背景環境聲音、嗡嗡聲、功放噪音),非平穩加性噪聲(媒體干擾、非期望語音干擾和一些電子干擾)
  2. 聲學混響:多徑反射引起的疊加效應(與期望信號相關)
  3. 卷積信道效應:導致不均勻或帶寬限制響應,為了去除信道脈沖響應,做信道均衡時對通信信道沒有有效建模
  4. 非線性失真:信號輸入時不適當的增益,常出現與幅度限制、麥克風功放等
  5. 加性寬帶電子噪聲
  6. 電器干擾
  7. 編碼失真:比如壓縮編碼
  8. 錄音儀器引起的失真:麥克風頻率響應不足

  使用先畫出原始語音數據的語譜圖和波形圖

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("./p225_001.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.show()

時域增強

噪聲增強

  本文添加的噪聲為均值為0,標准差為1的高斯白噪聲,有兩種方法對數據進行加噪

第一種:控制噪聲因子

def add_noise1(x, w=0.004):
    # w:噪聲因子
    output = x + w * np.random.normal(loc=0, scale=1, size=len(x))
    return output

Augmentation = add_noise1(x=wav_data, w=0.004)

第二種:控制信噪比

  通過信噪比的公式推導出噪聲。

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

def add_noise2(x, snr):
    """
    :param x:純凈語音
    :param snr: 信噪比
    :return: 生成執行信噪比的帶噪語音
    """
    P_signal = np.mean(x**2)    # 信號功率
    k = np.sqrt(P_signal / 10 ** (snr / 10.0))  # 噪聲系數 k
    return x + np.random.randn(len(x)) * k

Augmentation = add_noise2(x=wav_data, snr=50)

第三種:純凈語音+噪聲(指定信噪比)

  通過信噪比的公式推導出噪聲的增益系數k。

$$SNR=10*log_{10}(\frac{S^2}{N^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增益:

$$\left\{\begin{matrix}dB=10*log_{10}(S^2)\\\ S=kS\end{matrix}\right.\Rightarrow k=\sqrt{\frac{10^{\frac{dB}{10}}}{S^2}}$$

def dB_gain(wav, dB):
    """
    :param wav: 語音
    :param dB: 音量
    :return:返回以指定dB增益后的語音 
    """
    wav_p = np.mean(wav ** 2)  # 平均功率
    scalar = np.sqrt(10 ** (dB / 10) / (wav_p + np.finfo(np.float32).eps))
    wav *= scalar
    return wav, scalar

如果我們是想對帶噪語音進行音量增益,為了不破壞帶噪語音的信噪比,還需要對純凈語音語音進行相同的增益

noisy, noisy_scalar = dB_gain(noisy, 20)    # 得到增益后的帶噪語音和增益系數
clean *= noisy_scalar    # 為了控制snr不變,clean需要進行相同的增益

混響增強

  我這里使用的是Image Source Method(鏡像源方法)來實現語音加混響,我想用兩種方法來給大家實現,第一種是直接調用python庫—— Pyroomacoustics來實現音頻加混響,第二種就是按照公式推導一步一步來實現,兩種效果一樣,想看細節的可以參考第二種方法,只想開始實現效果的可以只看第一種方法:

方法一: Pyroomacoustics實現音頻加混響

  首先需要安裝 Pyroomacoustics,這個庫非常強大,感興趣也可以多看看其他API接口

pip install  Pyroomacoustics

步驟:

  1. 創建房間(定義房間大小、所需的混響時間、牆面材料、允許的最大反射次數、)
  2. 在房間內創建信號源
  3. 在房間內放置麥克風
  4. 創建房間沖擊響應
  5. 模擬聲音傳播
# Author:凌逆戰
# -*- coding:utf-8 -*-
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()
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()
混合ISM/射線跟蹤房間模擬器
room.simulate(reference_mic=0, snr=10)      # 控制信噪比
控制信噪比

方法二:Image Source Method 算法講解

  從這里要講算法和原理了,

代碼參考:matlab版本:RIR-Generator,python版本:rir-generator

鏡像源法簡介:

 

  將反射面等效為一個虛像,或者說鏡像。比如說,在一個開放空間里有一面平整牆面,那么一個聲源可以等效為2兩個聲源;一個開放空間里有兩面垂直的平整牆面,那么一個聲源可以等效為4個;同理三面的話是8個。原理上就是這樣,但是封閉的三維空間里情況有那么點復雜,

  一般來說,家里的空房間可以一定程度上近似為矩形盒子,假設房間尺寸為:

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

元素大小分別代表長寬高,而聲源的三維坐標為

$$S=\left[x_{s}, y_{s}, z_{s}\right]$$

麥克風的三維坐標為

$$M=\left[x_{m}, y_{m}, z_{m}\right]$$

鏡像聲源$(i,j,k)$到麥克風距離在三個坐標軸上的位置為

$$x_{i}=(-1)^{i} x_{s}+\left[i+\left(1-(-1)^{i}\right) / 2\right] x_{r}-x_{m}$$

$$y_{j}=(-1)^{j} y_{s}+\left[j+\left(1-(-1)^{j}\right) / 2\right] y_{r}-y_{m}$$

$$z_{k}=(-1)^{k} z_{s}+\left[k+\left(1-(-1)^{k}\right) / 2\right] z_{r}-z_{m}$$

那么聲源$(i,j,k)$距離麥克風的距離為

$$d_{i j k}=\sqrt{\left(x_{i}^{2}+y_{j}^{2}+z_{k}^{2}\right)}$$

相對於直達聲的到達延遲時間為

$$\tau_{i j k}=\left(d_{i j k}-r\right) / c$$

其中$c$為聲速,$r$為聲源到麥克風的直線距離。那么,混響效果等效為不同延遲的信號的疊加,即混響效果可以表示為一個FIR濾波器與信號源卷積的形式,此濾波器可寫為如下形式

$$h(t)=\sum_{i} \sum_{j} \sum_{k}\left[A_{i j k} \delta\left(t-\tau_{i j k}\right)\right]$$

濾波器的抽頭系數與鏡面的反射系數與距離相關,如果每個面的反射系數不同則形式略復雜。詳細代碼還是要看RIR-Generator,我這里只做拋轉引玉,寫一個最簡單的。

模擬鏡像源:

房間尺寸(m):4 X 4 X 3

聲源坐標(m):2 X 2 X 0

麥克風坐標(m):2 X 2 X 1.5

混響時間(s):0.2

RIR長度:512

clc;clear;
c = 340;                    % 聲速 (m/s)
fs = 16000;                 % Sample frequency (samples/s)
r = [2 2 1.5];              % 麥克風位置 [x y z] (m)
s = [2 2 0];              % 揚聲器位置 [x y z] (m)
L = [4 4 3];                % 房間大小 [x y z] (m)
beta = 0.2;                 % 混響時間 (s)
n = 512;                   % RIR長度

h = rir_generator(c, fs, r, s, L, beta, n);
disp(size(h))   % (1,4096)

[speech, fs] = audioread("./test_wav/p225_001.wav");
disp(size(speech)); % (46797,1)

y = conv(speech', h);
disp(length(y))


% 開始畫圖
figure('color','w');    % 背景色設置成白色
subplot(3,1,1)
plot(h)
title("房間沖擊響應 RIR","FontSize",14)

subplot(3,2,3)
plot(speech)
title("原語音波形","FontSize",14)

subplot(3,2,4)
plot(y)
title("加混響語音波形","FontSize",14)

subplot(3,2,5)
specgram(speech,512,fs,512,256);
title("原語音頻譜","FontSize",14)

subplot(3,2,6)
specgram(y,512,fs,512,256);
title("加混響語音頻譜","FontSize",14)

audiowrite("./test_wav/matlab_p225_001_reverber.wav",y,fs)
Image Source方法

gpuRIR:使用圖像源方法(ISM)和GPU加速進行房間脈沖響應(RIR)模擬

一種專用於機器學習應用中音頻數據增強的隨機房間脈沖響應生成方法。與圖像源或光線追蹤等幾何方法相反,這種技術不需要預先定義房間幾何形狀、吸收系數或麥克風和源位置,並且僅依賴於房間的聲學參數。該方法直觀、易於實施,並允許生成非常復雜的外殼的 RIR

基於神經網絡的快速房間脈沖響應生成器

基於生成對抗網絡 (GAN) 的房間脈沖響應發生器 (IR-GAN)

一種提高遠場語音識別合成房間脈沖響應質量的方法

在房間脈沖響應 (RIR) 生成中加入以下因素的影響:空氣吸收、真實材料的表面和頻率相關系數以及隨機光線追蹤

指定SER生成遠端語音

  SER的公式為

$$SER=10\log_{10}\frac{E\{s^2(n)\}}{E\{d^2(n)\}}$$

其中E是統計 期望操作,$s(n)$是近端語音,$d(n)$是遠端回聲,

  由於我們需要根據指定的SER求混響信號,並且近端語音和遠端混響都是已知的,我們只需要求得一個系數,來調整回聲信號的能量大小,與遠端混響相乘即可得我們想要的混響語音,即調整后的回聲信號為$kd(n)$

根據以上公式,可以推導出$k$的值

$$k=\sqrt{\frac{E\{s^2(n)\}}{E\{d^2(n)\}*10^{\frac{SER}{10}}}}$$

最終$kd(n)$即我們所求的指定SER的混響。

def add_echo_ser(near_speech, far_echo, SER):
    """根據指定的SER求回聲
    :param near_speech: 近端語音
    :param far_echo: 遠端回聲
    :param SER: 指定的SER
    :return: 指定SER的回聲
    """
    p_near_speech = np.mean(near_speech ** 2)  # 近端語音功率
    p_far_echo = np.mean(far_echo ** 2)  # 遠端回聲功率

    k = np.sqrt(p_near_speech / (10 ** (SER / 10)) / p_far_echo)

    return k * far_echo

 

波形位移

  語音波形移動使用numpy.roll函數向右移動shift距離

numpy.roll(a, shift, axis=None)

參數

  • a:數組
  • shift:滾動的長度
  • axis:滾動的維度。0為垂直滾動,1為水平滾動,參數為None時,會先將數組扁平化,進行滾動操作后,恢復原始形狀
x = np.arange(10)
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

print(np.roll(x, 2))
# array([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])

波形位移函數:

def time_shift(x, shift):
    # shift:移動的長度
    return np.roll(x, int(shift))

Augmentation = time_shift(wav_data, shift=fs//2)

諧波失真

import soundpy

y1 = sp.augment.harmonic_distortion(y, sr=sr)

 

重采樣數據增強

  重采樣后語音數據會丟失 重采樣采樣率到源采樣值之間的頻譜信息。

def augment_resample(wav, sr):
    resample_sr = np.random.uniform(sr)     # 從一個均勻分布中隨機采樣
    print("target_sr", resample_sr)
    resample = librosa.resample(wav, orig_sr=sr, target_sr=resample_sr)
    resample = librosa.resample(resample, orig_sr=resample_sr, target_sr=sr)
    return resample

編解碼數據增強

  這個我有空再來補全,參考DeepSpeech,先numpu to pcm,在轉成 opus。

頻域增強

音高增強(Pitch Shifting)

  在頻率軸上縮放頻譜圖,從而改變音高。音高修正只改變音高而不影響音速,我發現-5到5之間的步數更合適

def pitch_shifting(x, sr, n_steps, bins_per_octave=12):
    # sr: 音頻采樣率
    # n_steps: 要移動多少步
    # bins_per_octave: 每個八度音階(半音)多少步
    return librosa.effects.pitch_shift(x, sr, n_steps, bins_per_octave=bins_per_octave)

# 上移大三度(如果bins_per_octave為12,則4步)
Augmentation = pitch_shifting(wav_data, sr=fs, n_steps=4, bins_per_octave=12)
# 向下移動一個三全音(如果bins_per_octave是 12,則為六步)
Augmentation = pitch_shifting(wav_data, sr=fs, n_steps=-6, bins_per_octave=12)
# 上移 3 個四分音符
y_three_qt = librosa.effects.pitch_shift(y, sr, n_steps=3, bins_per_octave=24)

速度增強(Tempo)

在時間軸上縮放頻譜圖,從而改變播放速度。

變速不變調

方法一:ffmpeg

  在變速之前我們需要安裝 pip install ffmpeg  

from ffmpeg import audio

# 加快2倍速度
audio.a_speed("./sample/p225_001.wav",speed=2,out_file="./sample/p225_001_2.wav")

# 放慢2倍速度
audio.a_speed("./sample/p225_001.wav",speed=0.5,out_file="./sample/p225_001_0.5.wav")

ffmpeg是基於fmpeg開發的,Python的這個庫不能加載太大的文件,但是原生的fmpeg。或者我們可以直接使用原生的ffmpeg工具包

我們可以看到變速前后的波形圖和語譜圖沒變,但是他們的時間維度卻減少了一半。

方法二:SoundTorch

  SoundTouch 是一個開源音頻處理庫,用於更改音頻流或音頻文件的速度、音高和播放速率。該庫還支持估計音軌的穩定每分鍾節拍率。

命令實例見:https://www.surina.net/soundtouch/soundstretch.html

速度增加100%

soundstretch input.wav output.wav -tempo=100

速度降低50%

soundstretch input.wav output.wav -tempo=-50

變速變調

方法一:SOX

  需要在linux上運行,具體參考https://github.com/rabitt/pysox

import soundfile
import sox

sr = 16000

tfm = sox.Transformer()     # create transformer
tfm.speed(2)                # 變速2倍

# 創建輸出文件
# tfm.build_file("./sample/p225_001.wav", "./sample/pysox_2x.wav")

# 內存中以numpy數組的形式獲取輸出
array_out = tfm.build_array(input_filepath="./sample/p225_001.wav")
soundfile.write("./sample/pysox_2x.wav",data=array_out,samplerate=sr)

或者我們直接使用原生的sox工具包

$ sox input.wav output.wav speed 1.3 #速度變為原來的1.3倍
$ sox input.wav output.wav speed 0.8 #速度變為原來的0.8倍

方法二:librosa

  按固定速率對音頻系列進行時間拉伸。

def time_stretch(x, rate):
    # rate:拉伸的尺寸,
    # rate > 1 加快速度
    # rate < 1 放慢速度
    return librosa.effects.time_stretch(x, rate)

Augmentation = time_stretch(wav_data, rate=2)

  我們來觀察語譜圖和波形圖,發現形狀變了,並且變速后的語音波形振幅降低了,為什么呢?難道變速還會減少語音的音量?求解答


  SpecAugment 通過在時間方向上通過在時間方向上扭曲來增強,並屏蔽(多個)連續時間步長(垂直掩模)和 mel 頻率通道(水平掩模)的塊

  1. 幫助網絡在時間方向上的變形、頻率信息的部分丟失和輸入的小段語音的部分丟失方面具有魯棒性
  2. 防止網絡過度擬合

SpecAugment 中有三種增強策略:

  • 時間扭曲(Time Warping):在時間軸上隨機扭曲頻譜圖。與速度擾動不同,這種方法不會增加或減少持續時間,而是在局部壓縮和拉伸頻譜圖。
  • 頻率掩蔽(Frequency Mask):頻譜圖的 連續頻率bin被隨機掩蔽
  • 時間掩蔽(Time Mask):頻譜圖的 連續時間幀被掩蔽

paperwithcode:SpecAugment幾乎所有的代碼都列出來了

扭曲增強(Warp)

  將非線性圖像扭曲應用於頻譜圖。這是通過沿時間和頻率軸隨機移動均勻分布的扭曲點網格來實現的。代碼修改自:DeepSpeech

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def Warp(spectrogram, num_t=1, num_f=1, warp_t=0.1, warp_f=0.0, r=0, clock=0.0):
    """
    :param spectrogram: tensor (batch size,t,f)
    :param num_t: 
    :param num_f: 
    :param warp_t: 
    :param warp_f: 
    :param r: 波動范圍
    :param clock: 
    :return: 
    """
    size_t, size_f = spectrogram.shape

    seed = (clock * tf.int32.min, clock * tf.int32.max)

    num_t = tf_pick_value_from_range(num_t, r, clock=clock)
    num_f = tf_pick_value_from_range(num_f, r, clock=clock)

    def get_flows(n, size, warp, r):
        warp = tf_pick_value_from_range(warp, range, clock=clock)
        warp = warp * tf.cast(size, dtype=tf.float32) / tf.cast(2 * (n + 1), dtype=tf.float32)
        f = tf.random.stateless_normal([num_t, num_f], seed, mean=0.0, stddev=warp, dtype=tf.float32)
        return tf.pad(f, tf.constant([[1, 1], [1, 1]]), 'CONSTANT')  # zero flow at all edges

    flows = tf.stack([get_flows(num_t, size_t, warp_t, r), get_flows(num_f, size_f, warp_f, r)], axis=2)
    flows = tf.image.resize_bicubic(tf.expand_dims(flows, 0), [size_t, size_f])
    spectrogram_aug = tf.contrib.image.dense_image_warp(tf.expand_dims(spectrogram, -1), flows)
    spectrogram_aug = tf.reshape(spectrogram_aug, shape=(1, -1, size_f))
    return spectrogram_aug

頻率掩膜增強(Frequency Mask)

在隨機頻率下將增強樣本中的頻率間隔設置為零(靜音)。有關更多詳細信息,請參閱 SpecAugment 論文 - https://arxiv.org/abs/1904.08779

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def FrequencyMask(tensor, n=3, size=2, r=0, transcript=None, clock=0.0):
    time_max = tf.shape(tensor)[1]
    freq_max = tf.shape(tensor)[2]
    n = tf_pick_value_from_range(n, r, clock=clock)

    def body(i, size, spectrogram_aug):
        size = tf_pick_value_from_range(size, r, clock=clock)
        size = tf.math.maximum(1, tf.math.minimum(freq_max - 1, size))
        seed = tf.cast(clock * tf.int32.max, tf.int32) - i
        f0 = tf.random.stateless_uniform((), (-seed, seed), minval=0, maxval=freq_max - size, dtype=tf.dtypes.int32)
        freq_mask = tf.concat([tf.ones([1, time_max, f0]),
                               tf.zeros([1, time_max, size]),
                               tf.ones([1, time_max, freq_max - f0 - size])], axis=2)
        return i + 1, spectrogram_aug * freq_mask

    return tf.while_loop(lambda i, size, spectrogram_aug: i < n, body, (0, tensor))[1]

時間掩碼增強(Time Mask)

在隨機位置將增強樣本內的時間間隔設置為零(靜音)。代碼修改自:DeepSpeech

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def TimeMask(self, tensor, n=3, size=10.0, r=0, transcript=None, clock=0.0):
    time_max = tf.shape(tensor)[0 if self.domain == 'signal' else 1]
    n = tf_pick_value_from_range(n, r, clock=clock)

    def body(i, size, augmented):
        size = tf.cast(tf_pick_value_from_range(size, r, clock=clock) * self.units_per_ms(), dtype=tf.int32)
        size = tf.math.maximum(1, tf.math.minimum(time_max - 1, size))
        seed = tf.cast(clock * tf.int32.max, tf.int32) - i
        t0 = tf.random.stateless_uniform((), (-seed, seed), minval=0, maxval=time_max - size, dtype=tf.dtypes.int32)
        rest = time_max - t0 - size
        if self.domain == 'spectrogram':
            fm = tf.shape(tensor)[2]
            time_mask = tf.concat([tf.ones([1, t0, fm]), tf.zeros([1, size, fm]), tf.ones([1, rest, fm])], axis=1)
        elif self.domain == 'signal':
            time_mask = tf.concat([tf.ones([t0, 1]), tf.zeros([size, 1]), tf.ones([rest, 1])], axis=0)
        else:
            time_mask = tf.concat([tf.ones([1, t0]), tf.zeros([1, size]), tf.ones([1, rest])], axis=1)
        return i + 1, augmented * time_mask

    return tf.while_loop(lambda i, size, augmented: i < n, body, (0, tensor))[1]

頻譜交換(SpecSwap)

X. Song, Z. Wu, Y. Huang, D. Su, and H. Meng, "SpecSwap: A Simple Data Augmentation Method for End-to-End Speech Recognition", in INTERSPEECH, 2020.

頻譜交換提供兩個增強策略:

  • 頻率交換:隨機交換頻譜圖的兩個頻率塊
  • 時間交換:在時間軸上隨機交換頻譜圖的兩個幀塊

SpecSwap 增強策略在 E2E ASR 模型中也運行良好,但缺乏與 SpecAugment 的比較。

多領域增強

drop增強

  將目標數據表示的隨機數據點歸零。代碼修改自:DeepSpeech

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def Dropout(tensor, rate=0.05, r=0, transcript=None, clock=0.0):
    rate = tf_pick_value_from_range(rate, r, clock=clock)
    rate = tf.math.maximum(0.0, rate)
    factors = tf.random.stateless_uniform(tf.shape(tensor),
                                          (clock * tf.int32.min, clock * tf.int32.max),
                                          minval=0.0,
                                          maxval=1.0,
                                          dtype=tf.float32)
    return tensor * tf.math.sign(tf.math.floor(factors + rate))

添加增強

將從正態分布(均值為 0.0)中選取的隨機值添加到目標數據表示的所有數據點。代碼修改自:DeepSpeech

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def Add(tensor, stddev=5, r=0, transcript=None, clock=0.0):
    stddev = tf_pick_value_from_range(stddev, r, clock=clock)
    seed = (clock * tf.int32.min, clock * tf.int32.max)
    return tensor + tf.random.stateless_normal(tf.shape(tensor), seed, mean=0.0, stddev=stddev)

乘法增強

將目標數據表示的所有數據點與從正態分布(均值為 1.0)中選取的隨機值相乘。代碼修改自:DeepSpeech

def tf_pick_value_from_range(value, r, clock=None, double_precision=False):
    clock = (tf.random.stateless_uniform([], seed=(-1, 1), dtype=tf.float64) if clock is None
             else tf.maximum(tf.constant(0.0, dtype=tf.float64), tf.minimum(tf.constant(1.0, dtype=tf.float64), clock)))
    value = tf.random.stateless_uniform([],
                                        minval=value - r,
                                        maxval=value + r,
                                        seed=(clock * tf.int32.min, clock * tf.int32.max),
                                        dtype=tf.float64)
    if isinstance(value, int):
        return tf.cast(tf.math.round(value), tf.int64 if double_precision else tf.int32)
    return tf.cast(value, tf.float64 if double_precision else tf.float32)


def Multiply(self, tensor, stddev=5, r=0, transcript=None, clock=0.0):
    stddev = tf_pick_value_from_range(stddev, r=0, clock=clock)
    seed = (clock * tf.int32.min, clock * tf.int32.max)
    return tensor * tf.random.stateless_normal(tf.shape(tensor), seed, mean=1.0, stddev=stddev)

參考

【python 音頻處理庫】

【知乎文章】簡單地為語音加混響

【國際音頻實驗室EmanuëlHabets提供的代碼】International Audio Laboratories Erlangen

【Image-source method】Image-source method for room acoustics

【Image-source 原理講解】Image-source Model

【CSDN】變速變調原理與方法總結

【CSDN】音頻倍速(變速不變調)的實現

【CSDN】音頻變調算法小結

【CSDN】python 音頻變調不變速方法

【論文】用於語音識別的音頻增強

【論文】SpecAugment:一種簡單的自動語音識別數據增強方法

【CSDN】SoX 音頻處理工具使用方法

還有寫沒有跑通,但是總感覺有些價值的代碼,記錄在這里:

  • py-RIR-Generator(沒跑通的原因是我是window系統)
  • gpuRIR(這個我跑通了,但是需要較大的計算資源)
  • 去github找代碼的時候,不一定要搜索“回聲”,“混響”,也可以通過搜索"RIR"同樣可以得到想要的結果

本文畫圖代碼:

# Author:凌逆戰
# -*- coding:utf-8 -*-
import matplotlib.pyplot as plt
import librosa
import numpy as np

plt.rcParams['font.sans-serif']=['SimHei'] #用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus']=False #用來正常顯示符號


y1, _ = librosa.load("./speech.wav", sr=16000)
y2, _ = librosa.load("./guitar_16k_reverb_ISM.wav", sr=16000)


plt.subplot(2, 2, 1)
plt.specgram(y1, Fs=16000, scale_by_freq=True, sides='default', cmap="jet")
plt.title("語譜圖", fontsize=13)
plt.xlabel('時間/s', fontsize=13)
plt.ylabel('頻率/Hz', fontsize=13)

plt.subplot(2, 2, 2)
plt.plot(np.arange(len(y1)) / 16000, y1)
plt.title("波形圖", fontsize=13)
plt.xlabel('時間/s', fontsize=13)
plt.ylabel('振幅', fontsize=13)

plt.subplot(2, 2, 3)
plt.specgram(y2, Fs=16000, scale_by_freq=True, sides='default', cmap="jet")
plt.title("語譜圖(加混響)", fontsize=13)
plt.xlabel('時間/s', fontsize=13)
plt.ylabel('頻率/Hz', fontsize=13)


plt.subplot(2, 2, 4)
plt.plot(np.arange(len(y2)) / 16000, y2)
plt.title("波形圖(加混響)", fontsize=13)
plt.xlabel('時間/s', fontsize=13)
plt.ylabel('振幅', fontsize=13)

plt.tight_layout()
plt.show()
View Code

[1] J,B. Allen and D. A. Berkley, Image method for efficiently simulating small-room acoustics, J. Acoust. Soc. Am., vol. 65, no. 4, p. 943, 1979.

[2] M, Vorlaender, Auralization, 1st ed. Berlin: Springer-Verlag, 2008, pp. 1-340.

[3] D.Schroeder, Physically based real-time auralization of interactive virtual environments. PhD Thesis, RWTH Aachen University, 2011.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM