赞
踩
pyAudioKits是基于librosa和其他库的强大Python音频工作流支持。
通过pip安装:
pip install pyAudioKits
本项目的GitHub地址,如果这个项目帮助到了你,请为它点上一颗star,谢谢你的支持!如果你在使用过程中有任何问题,请在评论区留言或在GitHub上提issue,我将持续对该项目进行维护。
import pyAudioKits.audio as ak
import pyAudioKits.analyse as aly
import pyAudioKits.algorithm as alg
本节介绍从语音信号中滤除噪声,从而增强语音信号的方法。注意这里的“噪声”和我们之前定义的物理学意义上的噪音不同,它指的是不包含有用语音信息的音频成分,通常和语音信号在时域相互叠加。噪声会掩盖语音成分,使得语音信号的质量下降。人耳会难以对语音进行分辨,机器从语音中提取的特征也会受到干扰。
在时域和语音叠加的噪声若变换到频域后可以和语音信号相互分离,则使用第五节中介绍的LTI滤波器就可以对噪声进行移除。然而,若噪声在频域依然和语音信号相互叠加,使用LTI滤波器对噪声进行滤波时必然同时会导致语音信号特定频率成分的严重损失。此时,应当使用基于噪声估计或信号估计的统计型滤波方法。
本节将对谱减法、维纳滤波和卡尔曼滤波及其使用pyAudioKits的实现进行介绍。
白噪音是一种常见的噪声,音频录制时产生的电流噪声就属于白噪音。首先从"sample_audio/test.wav"读入示例音频。
s = ak.read_Audio("sample_audio/test.wav")
s.sound()
'''
outputs:
The power of the audio: 0.0007003224173257517
'''
然后在示例音频上增加信噪比为10dB的白噪音。
f = s.addWgn(10)
f.sound()
'''
outputs:
The power of the audio: 0.0007690852704655619
'''
可以听见此时背景出现了白噪音干扰。在波形图上也有体现。
s.plot(), f.plot()
分别绘制信号加噪前后的语谱图。
aly.FFT(s.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB")
白噪音的频率分布在整个频率区间上,和语音信号在频域完全无法分离。在这种情况下,无论如何设计LTI滤波器的通带阻带特性,都无法保证在尽可能保持语音信号成分的同时尽可能滤除白噪音。我们必须采用别的滤波方式。本节将介绍谱减法、维纳滤波和卡尔曼滤波三种滤波方式。
谱减法的思路非常简单:如果知道加噪信号的频谱和噪声的频谱,那么从加噪信号频谱中减去噪声的频谱即可得到原始信号的频谱。这种方法适用于加性噪声,即噪声和语音信号是线性相加的。由于傅里叶变换是线性变换,因此时域线性相加的噪声和语音信号其频谱也是线性相加的。
在进行谱减法时,我们需要先得到噪声估计。谱减法的步骤如下:
由于我们对带噪语音的每一帧都使用相同的噪声估计来运用谱减法,因此不难看出来,运用谱减法的前提是每一帧的噪声统计特性均相同。因此,谱减法要求噪声必须服从平稳随机过程。白噪音服从平稳随机过程,因此可以使用谱减法来滤除。
我们的带噪语音中后几秒是没有语音的,只存在噪声,因此我们取带噪语音的最后0.3秒作为噪声估计。
f1=alg.specSubstract(f,f[f.getDuration()-0.3:])
s.plot(), f.plot(), f1.plot() #Draw the waveform of original signal, noisy signal and spectral subtraction result in turn
从波形图上看,谱减法已经大大削除噪声的强度,同时尽可能保留了语音的强度。
aly.FFT(s.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f1.framing()).plot(freq_scale="mel", plot_type="dB") #依次绘制原始语音、带噪语音和谱减法结果的语谱图
从语谱图上来看,全频率区间上的白噪音都得到了一定程度的消除,但在全频率区间上也都依然存在颗粒状的残留。
维纳滤波是一种理论最优的滤波方法,它利用了平稳随机过程的统计特性。
已知一个含加性背景噪声的信号 x = s + v x=s+v x=s+v,目标是解出 h = min h E { e 2 } = min h E { ( s − x ∗ h ) 2 } = min h E { ( s [ n ] − ∑ m x [ n − m ] h [ m ] ) 2 } h=\displaystyle\min_hE\{e^2\}=\min_hE\{(s-x*h)^2\}=\min_hE\{(s[n]-\sum_{m}x[n-m]h[m])^2\} h=hminE{e2}=hminE{(s−x∗h)2}=hminE{(s[n]−m∑x[n−m]h[m])2},其中 h h h是维纳滤波器系统单位冲激响应, y = h ∗ x y=h*x y=h∗x。
对 h [ j ] h[j] h[j]求偏导,得 ∂ e 2 ∂ h [ j ] = 2 E { ( ∑ m x [ n − m ] h [ m ] − s [ n ] ) x [ n − j ] } = 2 E { ∑ m h [ m ] x [ n − m ] x [ n − j ] − s [ n ] x [ n − j ] } \displaystyle\frac{\partial e^2}{\partial h[j]}=2E\{(\sum_{m}x[n-m]h[m]-s[n])x[n-j]\}=2E\{\sum_{m}h[m]x[n-m]x[n-j]-s[n]x[n-j]\} ∂h[j]∂e2=2E{(m∑x[n−m]h[m]−s[n])x[n−j]}=2E{m∑h[m]x[n−m]x[n−j]−s[n]x[n−j]}
对原始信号进行分帧加窗。窗长不应该太长,以保证
x
[
n
]
x[n]
x[n]和
s
[
n
]
s[n]
s[n]在窗口内的平稳性;窗长也不应该太短,以保证可以用
x
[
n
]
x[n]
x[n]和
s
[
n
]
s[n]
s[n]在窗口内信息来估计各态历经过程的统计特性。这样就可以将上式改写为:
2
E
{
∑
m
h
[
m
]
x
[
n
−
m
]
x
[
n
−
j
]
−
s
[
n
]
x
[
n
−
j
]
}
=
2
∑
m
=
0
N
h
[
m
]
x
[
n
−
m
]
x
[
n
−
j
]
−
∑
n
=
s
[
n
]
x
[
n
−
j
]
=
2
∑
m
=
0
N
h
[
m
]
R
x
x
[
n
−
m
]
−
2
R
x
s
[
n
]
令导数为0,得到 R x s [ n ] = ∑ m h [ m ] R x x [ n − m ] , n ≥ 0 \displaystyle R_{xs}[n]=\sum_mh[m]R_{xx}[n-m],n≥0 Rxs[n]=m∑h[m]Rxx[n−m],n≥0
这样我们就可以得到FIR滤波器 h h h满足的等式: R x s [ n ] = ∑ m = 0 N − 1 h [ m ] R x x [ n − m ] R_{xs}[n]=\displaystyle\sum_{m=0}^{N-1}h[m]R_{xx}[n-m] Rxs[n]=m=0∑N−1h[m]Rxx[n−m]。只要知道 R x s R_{xs} Rxs和 R x x R_{xx} Rxx中的N个点,利用线性代数手段就可以计算出 h ( m ) h(m) h(m)的N个值,从而得到N阶的FIR滤波器 h h h。在实际应用中直接用矩阵求逆的方法来求解是比较困难的,但可以使用Levinson-Durbin迭代算法来进行求解。
为了求解 h h h,我们需要知道 R x s R_{xs} Rxs和 R x x R_{xx} Rxx,这需要通过原始信号 s s s的估计来进行计算。
f2=alg.wienerFilter(f,s)
s.plot(), f.plot(), f2.plot() #依次绘制原始信号、加噪信号和维纳滤波结果的波形图
aly.FFT(s.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f2.framing()).plot(freq_scale="mel", plot_type="dB") #依次绘制原始语音、带噪语音和维纳滤波结果的语谱图
通过波形图和语谱图可以看出来,维纳滤波的效果是非常好的。它的缺点是需要原始语音信号的估计。
在了解卡尔曼滤波之前,需要先了解马尔可夫假设:设有一个随机过程 { X t , t = 0 , 1 , . . . } \{X_t,t=0,1,...\} {Xt,t=0,1,...}满足 P ( X t ∣ X 0 : t − 1 ) = P ( X t ∣ X t − 1 ) P(X_t|X_{0:t-1})=P(X_t|X_{t-1}) P(Xt∣X0:t−1)=P(Xt∣Xt−1),其中 P ( X t ) P(X_t) P(Xt)是随机过程 X X X在时间 t t t的概率分布, X 0 : t − 1 X_{0:t-1} X0:t−1表示 { X 0 , . . . , X t − 1 } \{X_0,...,X_{t-1}\} {X0,...,Xt−1}。在这里,时间被离散化为一个个时刻。随机过程 { X t } \{X_t\} {Xt}的概率分布并非时间独立,但也并非任意时刻之间都有依赖性。 P ( X t ∣ X 0 : t − 1 ) = P ( X t ∣ X t − 1 ) P(X_t|X_{0:t-1})=P(X_t|X_{t-1}) P(Xt∣X0:t−1)=P(Xt∣Xt−1)的意义在于 t t t时刻 { X t } \{X_t\} {Xt}的概率分布只受 t − 1 t-1 t−1时刻 X X X的取值影响。我们称 P ( X t ∣ X t − 1 ) P(X_t|X_{t-1}) P(Xt∣Xt−1)为满足马尔可夫假设的转移模型。
假设 { X t , t = 0 , 2 , . . . } \{X_t,t=0,2,...\} {Xt,t=0,2,...}是隐状态变量,我们没有办法直接观察到这些变量的取值。但我们能观察到另外一组变量 { E t , t = 0 , 1 , . . . } \{E_t,t=0,1,...\} {Et,t=0,1,...}的取值,这组变量称为证据变量。证据变量构成另一个随机过程,且该随机过程和 { X t } \{X_t\} {Xt}之间是概率非独立的,有 P ( E t ∣ X 1 : t , E 1 : t − 1 ) = P ( E t ∣ X t ) P(E_t|X_{1:t},E_{1:t-1})=P(E_t|X_t) P(Et∣X1:t,E1:t−1)=P(Et∣Xt),其中 E 0 : t − 1 E_{0:t-1} E0:t−1表示 { E 0 , . . . , E t − 1 } \{E_0,...,E_{t-1}\} {E0,...,Et−1},则称 P ( E t ∣ X t ) P(E_t|X_t) P(Et∣Xt)为传感器模型。
卡尔曼滤波基于马尔可夫假设,且设所有随机变量都是连续变量,则有 P ( x t + 1 ∣ e 0 : t ) = ∫ x t P ( x t + 1 ∣ x t ) P ( x t ∣ e 0 : t ) d x t P(x_{t+1}|e_{0:t})=\displaystyle\int_{x_t}P(x_{t+1}|x_t)P(x_t|e_{0:t})dx_t P(xt+1∣e0:t)=∫xtP(xt+1∣xt)P(xt∣e0:t)dxt。它还假设所有的概率分布都是正态分布,且若 { x t } \{x_t\} {xt}和 { e t } \{e_t\} {et}是多维随机变量 { x ⃗ t } \{\vec x_t\} {x t}、 { e ⃗ t } \{\vec e_t\} {e t}的话则为多维正态分布。设每一时刻 { x ⃗ t } \{\vec x_t\} {x t}满足的多维正态分布的均值向量为 { μ ⃗ t , t = 0 , 1 , . . . } \{\vec\mu_t,t=0,1,...\} {μ t,t=0,1,...}、协方差矩阵为 { Σ t , t = 0 , 1 , . . . } \{\Sigma_t,t=0,1,...\} {Σt,t=0,1,...},则卡尔曼滤波这样对变量之间关系进行建模:
此时,任意时刻 { x ⃗ t } \{\vec x_t\} {x t}的均值向量和协方差矩阵都可以通过下面的公式来递推解出:
{
μ
t
+
1
⃗
=
F
μ
t
⃗
+
K
t
+
1
(
e
t
+
1
⃗
−
H
F
μ
t
⃗
)
Σ
t
+
1
=
(
I
−
K
t
+
1
H
)
(
F
Σ
t
F
T
+
Σ
x
)
其中 K t + 1 = ( F Σ t F T + Σ x ) H T ( H ( F Σ t F T + Σ x ) H T + Σ e ) − 1 K_{t+1}=(F\Sigma_tF^T+\Sigma_x)H^T(H(F\Sigma_tF^T+\Sigma_x)H^T+\Sigma_e)^{-1} Kt+1=(FΣtFT+Σx)HT(H(FΣtFT+Σx)HT+Σe)−1,称为卡尔曼增益矩阵
那么,如何将卡尔曼滤波的模型应用到语音增强问题中呢?对于语音增强问题,我们观察到的是含噪的信号,而我们要估计的是原始信号。也就是说我们可以把含噪信号看作是证据变量,而原始信号是隐变量。假如噪声是加性高斯噪声,则传感器方程为
y
n
⃗
=
H
x
n
⃗
+
v
n
⃗
\vec{y_n}=H\vec{x_n}+\vec{v_n}
yn
=Hxn
+vn
,其中
H
=
[
0
,
0
,
.
.
.
,
1
]
H=[0,0,...,1]
H=[0,0,...,1],
x
⃗
n
\vec x_n
x
n、
v
⃗
n
\vec v_n
v
n和
y
⃗
n
\vec y_n
y
n分别是原始信号
x
[
n
]
x[n]
x[n]、噪声
v
[
n
]
v[n]
v[n]和含噪信号
y
[
n
]
y[n]
y[n]的向量表示形式,我们先假设其长度是任意的。而为了得到转移方程,卡尔曼滤波考虑了发音模型中的系统:如果忽视嘴唇的影响,则声带和声道可以被建模为全极点系统,系统函数为
H
(
z
)
=
G
1
−
∑
m
=
1
p
z
−
m
H(z)=\frac{\displaystyle G}{\displaystyle 1-\sum_{m=1}^{p}z^{-m}}
H(z)=1−m=1∑pz−mG。对系统进行激励的气流
w
[
n
]
w[n]
w[n]近似于高斯白噪音信号,因此有产生的语音
x
[
n
]
=
∑
m
=
1
p
a
[
m
]
x
[
n
−
m
]
+
G
w
[
n
]
x[n]=\displaystyle\sum_{m=1}^{p}a[m]x[n-m]+Gw[n]
x[n]=m=1∑pa[m]x[n−m]+Gw[n],则有转移方程为
x
n
⃗
=
F
x
n
−
1
⃗
+
G
w
n
⃗
\vec{x_n}=F\vec{x_{n-1}}+G\vec{w_n}
xn
=Fxn−1
+Gwn
,其中
x
n
⃗
=
[
x
[
n
−
p
+
1
]
,
x
[
n
−
p
+
2
]
,
.
.
.
,
x
[
n
]
]
T
\vec{x_n}=[x[n-p+1],x[n-p+2],...,x[n]]^T
xn
=[x[n−p+1],x[n−p+2],...,x[n]]T且
F
=
[
0
1
.
.
.
0
⋮
⋮
⋮
⋮
0
0
.
.
.
1
a
[
p
]
a
[
p
−
1
]
.
.
.
a
[
1
]
]
F=\left[
为了计算 a a a,我们需要使用一种名为线性预测编码(LPC)的技术,它本质上就是对p阶自回归(AR)模型系数的计算。自回归模型有很多种参数估计方法,LPC所用的方法是Levinson递推法。
运用自回归模型的前提是 x [ n ] x[n] x[n]必须是平稳随机过程的一次实现,而语音信号往往是不平稳的。为此,我们需要将语音信号分帧加窗。然后在每帧内就可以使用LPC系数代替 a a a,输出误差e则为对白噪声 G w [ n ] Gw[n] Gw[n]的估计。
Σ x \Sigma_x Σx是一个对角线上元素均为e的 p × p p\times p p×p对角阵。 Σ e \Sigma_e Σe通过计算噪声估计的协方差得到。
最后,我们设初始状态 μ 0 ⃗ = [ y [ 0 ] , y [ 1 ] , . . . , y [ p − 1 ] ] T \vec{\mu_0}=[y[0],y[1],...,y[p-1]]^T μ0 =[y[0],y[1],...,y[p−1]]T,而 Σ 0 \Sigma_0 Σ0为一个对角线上元素均为噪声方差的 p × p p\times p p×p对角阵,这样就可以对 μ n ⃗ \vec{\mu_n} μn 进行迭代估计。 μ n ⃗ \vec{\mu_n} μn 是 x ⃗ n \vec x_n x n的均值,而 x n ⃗ = [ x [ n − p + 1 ] , x [ n − p + 2 ] , . . . , x [ n ] ] T \vec{x_n}=[x[n-p+1],x[n-p+2],...,x[n]]^T xn =[x[n−p+1],x[n−p+2],...,x[n]]T,也就是说我们可以用 μ n ⃗ \vec{\mu_n} μn 来估计 x [ m ] , m = n − p + 1 , n − p + 2 , . . . , n x[m],m=n-p+1,n-p+2,...,n x[m],m=n−p+1,n−p+2,...,n。注意 μ n ⃗ \vec{\mu_n} μn 和 μ n + 1 ⃗ \vec{\mu_{n+1}} μn+1 对应的信号 x [ n ] x[n] x[n]有重叠的部分,此时使用 μ n + 1 ⃗ \vec{\mu_{n+1}} μn+1 的估计来覆盖使用 μ n ⃗ \vec{\mu_n} μn 的估计。
估计要分帧进行:
f3=alg.kalmanFilter(f,f[f.getDuration()-0.3:])
s.plot(), f.plot(), f3.plot() #依次绘制原始信号、加噪信号和卡尔曼滤波结果的波形图
aly.FFT(s.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f3.framing()).plot(freq_scale="mel", plot_type="dB") #依次绘制原始语音、带噪语音和卡尔曼滤波结果的语谱图
卡尔曼滤波对于高斯白噪声有非常好的效果,这是因为高斯白噪声符合卡尔曼滤波建模时的假设。
接下来,我们从示例音频中导入另一段噪声样本。该噪声数据来自https://www.kaggle.com/datasets/bharatsahu/speech-commands-classification-dataset
noise=ak.read_Audio("sample_audio/_background_noise_/exercise_bike.wav")
aly.FFT(noise.framing()).plot(freq_scale="mel", plot_type="dB")
该噪声的频率成分分布在[0,采样率/2]上,在低频部分略大。
noise.plot()
noise.getDuration(), noise.sr, s.getDuration(), s.sr
'''
outputs:
(61.253875, 16000, 4.992290249433107, 22050)
'''
噪声样本的时长远大于示例音频时长,且采样率仅为16000Hz,小于示例音频的采样率22050Hz。因此我们需要对噪声样本进行切片使其匹配示例音频的时长,还要对噪声样本进行重采样。
noise = noise.resample(s.sr)
noise.sr
'''
outputs:
22050
'''
重采样后,噪声样本的采样率也变为了22050Hz。我们取其前5秒,使其匹配示例音频时长后,与示例音频以5dB的信噪比混合。
snr=5
f = ak.mixWithSNR(s,noise[0:s.getDuration()],snr,maintain="signal")
aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB")
重采样后的噪声信号的频率成分仍旧分布在[0,8000Hz]上,其中8000Hz是原采样率16000Hz的一半,没有随着采样率的提高而拓展到11025Hz,即22050Hz的一半。这是因为原始的噪音样本采用16000Hz采样后,最多只能保留[0,采样率/2]的频谱信息,高于采样率/2的频谱信息将会丢失,并无法被还原。采样率/2被称为奈奎斯特率,可逆的采样满足被采样信号的所有频率成分都小于奈奎斯特率,否则采样就会是非可逆的。
f3=alg.kalmanFilter(f,f[f.getDuration()-0.3:])
s.plot(), f.plot(), f3.plot()
aly.FFT(s.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f.framing()).plot(freq_scale="mel", plot_type="dB"), aly.FFT(f3.framing()).plot(freq_scale="mel", plot_type="dB")
对于非高斯白噪声,卡尔曼滤波的效果便有所下降。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。