加性噪声生成不同信噪比音频数据的原理及代码

加性噪声的原理及代码

介绍三种加性噪声的数据生成方法,包括原理和python代码。

原理

我假设本文的读者已经对SNR的原理1有所了解。

对于有限的音频以及音频,如果表示的是常量,有如下公式(N表示,即噪声的方差):

如果是一个变量,我们使用平方的期望作为分子,对应的方差作为分母。这个公式用于验证加噪是否正确:

对应的代码:

1
2
3
4
def cal_snr_db(signal, noise):
Es = (signal**2).mean()
Vn = np.var(noise)
return 10*np.log10(Es/Vn)

后面我将使用这个函数去验证生成的数据是否满足要求。

如果通过相同的阻抗(同一设备)测量信号和噪声,通过计算振幅比的平方可以得到信噪比,这个公式是通过幅值调整加噪SNR的基础。

简单说一下。即均方根幅值理2

上面给出的公式并没有考虑到信号的范围问题,一般情况下,我们把信号的范围用对数函数进行缩放,即有如下公式,其中dB表示数据在分贝单位下表示:

同理,信噪比也可以用分贝表达:

所以我们得到了另一个很重要的公式。这个公式在对噪声和干净语音同时调整SNR时很有帮助。

A-SNR(使用幅值对噪声信号进行调整)

前面介绍过使用RMS的前提条件是同一设备,一般情况下应该都满足。调整噪声的功率,通过前面的公式可知:

增加一个权重变量来表示对于噪声点的调整,即下列第一个公式,那么根据给定的,就可以得到相应的权重。这里我写细一点,大家就不用推了。

到这里已经得到了我们需要的权重,下面就是用代码计算最后的权重然后和干净音频相加。

EMM,不知道读者有没有发现一个问题,计算混合噪声后的SNR,如果不知道原始的,指定的这个SNR计算也不是很准确,所以这里做了归一化,保证

归一化部分代码:

1
2
3
def norm(data):
max_amp = np.std(data) # np.max(np.abs(data))
return (data-data.mean()) / max_amp

幅值调整代码

1
2
3
4
5
6
7
8
9
10
11
def snr_mix_amplitude(clean, noise, snr):
clean_ = norm(clean)
noise_ = norm(noise)
rmsclean = (clean_**2).mean()**0.5
rmsnoise = (noise_**2).mean()**0.5
# Set the noise level for a given SNR
noisescalar = rmsclean / (10**(snr/20)) / rmsnoise
noise_ = noise_ * noisescalar
assert cal_snr_db(clean_,noise_) == snr # validation
noisy = clean_ + noise_
return scale_clean_noisy(clean_,noisy)

最后的函数scale_clean_noisy是为了将音频缩放到人耳可听的范围,并不影响SNR。对应的代码:

1
2
3
4
5
6
def scale_clean_noisy(clean,noisy):
max_amp = np.max(np.abs([clean, noisy]))
mix_scale = 1 / max_amp * (np.random.rand()*0.1 + 0.8)
X = clean * mix_scale
Y = noisy * mix_scale
return X, Y

P-SNR(不是PSNR,代表使用功率计算SNR)

幅值计算比较复杂,一般直接使用功率P计算。

同样这里需要对于乘以权重,不过注意一下的变化。

即得到如下公式,

下面就是计算

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def cal_power(signal, sr=16000):
p = (signal**2).mean()
return p

def snr_mix_power(clean, noise, snr):
clean_ = norm(clean)
noise_ = norm(noise)
pclean = cal_power(clean_)
pnoise = cal_power(noise_)
noisescalar = np.sqrt(pclean/pnoise/10**(snr/10))
noise_ = noise_ * noisescalar
assert cal_snr_db(clean_,noise_) == snr # validation
noisy = clean_ + noise_
return scale_clean_noisy(clean_,noisy)

其中计算功率的函数cal_power参考离散序列的功率计算(语音信号处理中的内容)。

干净音频和噪声音频同时调整加噪

原理

我们不仅计算噪声的权重,还要计算干净语音的权重。通过我下面的公式推导,也可以看出,这种加噪方式与使用P-SNR还是A-SNR无关:

公式推导

For P-SNR:

For A-SNR:

For A-SNR:

可以看出,两种方法得出的结论是一致的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
def snr_both_mix(clean,noise,snr):
clean_ = norm(clean)
noise_ = norm(noise)
clean_snr = snr / 2
noise_snr = -snr / 2
cleanscalar = 10 ** (clean_snr / 20)
noisescalar = 10 ** (noise_snr / 20)
clean_ = clean_ * cleanscalar
noise_ = noise_ * noisescalar
assert cal_snr_db(clean_,noise_) == snr # validation
noisy = clean_ + noise_
return scale_clean_noisy(clean_,noisy)

完整代码移步Github


[1] SNR

[2] RMS