webrtc中的噪声抑制之三:基于信号存在概率的递归平均噪声估计
最朴素的噪声估计思想
结合VaD算法,在信号的无声段来估计和更新噪声是最安全和靠谱的。但为什么商业算法里这么复杂?是因为vad的方法虽然可以跟踪稳态噪声,但对于非稳态尤其是剧烈变化的噪声是不尽如人意的,所以各路大神抛出了非常多的想法和算法,就是希望对于偶发和非稳态噪声也能很好的滤除。
承上启下
上文学习了webrtc中利用当前帧经过STFT得到频率幅值,结合quantile算法,先进行一轮初始噪声估计,但这还没完,接下来利用这个估计的噪声和频率幅值带入到ComputeSnr中,计算先验信噪比和后验信噪比,
1.先验信噪比估计值是通过前一帧保存的幅值(未去噪)和最终的噪声估计值,在乘以一个平滑因子得到的,平滑因子是维纳滤波器的增益。
2.后验信噪比就是利用当前帧频率幅值和quantile估算的噪声来得到的。
3.最后使用DD(Directed decision)来更新先验信噪比。这个DD是估算先验信噪比的一种方法。
接下来需要更新当前帧的feature和计算话音出现概率,根据这个概率更新噪声估计,语音概率的分析本节不做展开,重点关注的是UpdateNoiseEstimate这个函数干的事情。
static void UpdateNoiseEstimate(NoiseSuppressionC* self,
const float* magn,
const float* snrLocPrior,
const float* snrLocPost,
float* noise) {
size_t i;
float probSpeech, probNonSpeech;
// Time-avg parameter for noise update.
float gammaNoiseTmp = NOISE_UPDATE;
float gammaNoiseOld;
float noiseUpdateTmp;
for (i = 0; i < self->magnLen; i++) {
probSpeech = self->speechProb[i];
probNonSpeech = 1.f - probSpeech;
// Temporary noise update:
// Use it for speech frames if update value is less than previous.
noiseUpdateTmp = gammaNoiseTmp * self->noisePrev[i] +
(1.f - gammaNoiseTmp) * (probNonSpeech * magn[i] +
probSpeech * self->noisePrev[i]);
// Time-constant based on speech/noise state.
gammaNoiseOld = gammaNoiseTmp;
gammaNoiseTmp = NOISE_UPDATE;
// Increase gamma (i.e., less noise update) for frame likely to be speech.
if (probSpeech > PROB_RANGE) {
gammaNoiseTmp = SPEECH_UPDATE;
}
// Conservative noise update.
if (probSpeech < PROB_RANGE) {
self->magnAvgPause[i] += GAMMA_PAUSE * (magn[i] - self->magnAvgPause[i]);
}
// Noise update.
if (gammaNoiseTmp == gammaNoiseOld) {
noise[i] = noiseUpdateTmp;
} else {
noise[i] = gammaNoiseTmp * self->noisePrev[i] +
(1.f - gammaNoiseTmp) * (probNonSpeech * magn[i] +
probSpeech * self->noisePrev[i]);
// Allow for noise update downwards:
// If noise update decreases the noise, it is safe, so allow it to
// happen.
if (noiseUpdateTmp < noise[i]) {
noise[i] = noiseUpdateTmp;
}
}
} // End of freq loop.
}
函数本身写的很简洁,初始化第一个频率分量默认设置NOISE_UPDATE,根据此平滑因子计算出当前帧noiseUpdateTmp,如果语音概率超过门限,平滑因子SPEECH_UPDATE重新计算一遍noiseUpdateTmp。(中间插播了一个计算magnAvgPause的过程,用在语音出现概率计算中的)最后根据初始设定的平滑因子是否改变决定是用噪声下平滑计算的噪声估计,还是取两次计算结果的最小值来更新。该算法从第二个循环开始,平滑因子更新是采用相邻频率语音概率来决定的,该方法进一步的利用临近频率相关性的原则。
我们前文已经提到过信噪比计算是利用STSA(short-time spectral amplitude)方法,但具体使用哪种准则呢? 查阅比较流行的paper,MMSE,OMLSA,IMCRA那种准则是webrtc ns所采用的呢?下面先了解一下STSA框架下的几种自适应准则:
MMSE-Minimum Mean-Square Error
这个方法是出自《Speech Enhancement Using a Minimum Mean-Square Error Short-Time Spectral Amplitude Estimator》,该算法建立在高斯统计模型的假设基础之上,通过一系列复杂的推导,得出
A
k
^
=
Γ
(
1.5
)
v
k
γ
k
M
(
−
0.5
;
1
;
−
v
k
)
R
k
\hat{A_k}=\Gamma(1.5) \frac{\sqrt{v_k}}{\gamma_k} M(-0.5;1;-v_k)R_k
Ak^=Γ(1.5)γkvkM(−0.5;1;−vk)Rk
这里只强调一下
R
k
R_k
Rk是当前帧频率
k
k
k的傅立叶变换系数,
A
k
^
\hat{A_k}
Ak^是从
R
k
R_k
Rk计算得出的估计值。
LSA-Log Spectral Amplitude
还有一种是对数谱估计方法的MMSE(MMSE-LSA ),得出的估计公式:
A
k
^
=
ξ
k
1
+
ξ
k
e
x
p
[
1
2
∫
0
∞
e
−
t
t
d
t
]
R
k
\hat{A_k}=\frac{\xi_k}{1+\xi_k} exp[\frac{1}{2}\int_0^\infty {\frac{e^{-t}}{t}} \,{\rm d}t]R_k
Ak^=1+ξkξkexp[21∫0∞te−tdt]Rk
MCRA-Minima Controlled Recursive Averaging
如果当前估计出来的语音出现概率为
p
(
k
,
t
)
p(k,t)
p(k,t),其中k标识傅立叶频率系数,t标识当前帧,则可以更新噪声估计
λ
d
^
(
k
,
t
+
1
)
\hat{\lambda_d}(k,t+1)
λd^(k,t+1):
λ
d
^
(
k
,
t
+
1
)
=
λ
d
^
(
k
,
t
)
p
(
k
,
t
)
+
[
α
d
λ
d
^
(
k
,
t
)
+
(
1
−
α
d
)
∣
Y
(
k
,
t
)
∣
2
]
(
1
−
p
(
k
,
t
)
)
\begin{aligned} \hat{\lambda_d}(k,t+1)&=\hat{\lambda_d}(k,t)p(k,t)+[\alpha_d\hat{\lambda_d}(k,t)+(1-\alpha_d)|Y(k,t)|^2](1-p(k,t)) \end{aligned}
λd^(k,t+1)=λd^(k,t)p(k,t)+[αdλd^(k,t)+(1−αd)∣Y(k,t)∣2](1−p(k,t))
《Speech enhancement for non-stationary noise environments》一文中又提出了两种方法:
- OMLSA-Optimally Modified Log-Spectral Amplitude Estimator
- IMCRA-Improved Minima Controlled Recursive Averaging
不过也是前面方法的改进,就不列举公式了。到此我们感觉和MCRA匹配度最高,那不妨把更新噪声估计的方法抽象成公式看看:
λ
d
^
(
k
,
t
+
1
)
=
λ
d
^
(
k
,
t
)
γ
d
(
k
,
t
)
+
(
1
−
γ
d
(
k
,
t
)
)
[
(
1
−
p
(
k
,
t
)
)
∣
Y
(
k
,
t
)
∣
+
p
(
k
,
t
)
λ
d
^
(
k
,
t
)
]
\begin{aligned} \hat{\lambda_d}(k,t+1)&=\hat{\lambda_d}(k,t)\gamma_d(k,t)+(1-\gamma_d(k,t))[(1-p(k,t))|Y(k,t)|+p(k,t)\hat{\lambda_d}(k,t)] \end{aligned}
λd^(k,t+1)=λd^(k,t)γd(k,t)+(1−γd(k,t))[(1−p(k,t))∣Y(k,t)∣+p(k,t)λd^(k,t)]
此处的
γ
d
(
k
,
t
)
\gamma_d(k,t)
γd(k,t)也是一个平滑因子,但这个平滑因子会根据语音/非语音发生改变,并且会受到临频(低频->高频)的影响。为了看着方便,直接换成
α
d
\alpha_d
αd。比较一下
λ
d
^
(
k
,
t
+
1
)
=
λ
d
^
(
k
,
t
)
α
d
+
(
1
−
α
d
)
[
(
1
−
p
(
k
,
t
)
)
∣
Y
(
k
,
t
)
∣
+
p
(
k
,
t
)
λ
d
^
(
k
,
t
)
]
\begin{aligned} \hat{\lambda_d}(k,t+1)&=\hat{\lambda_d}(k,t)\alpha_d+(1-\alpha_d)[(1-p(k,t))|Y(k,t)|+p(k,t)\hat{\lambda_d}(k,t)] \end{aligned}
λd^(k,t+1)=λd^(k,t)αd+(1−αd)[(1−p(k,t))∣Y(k,t)∣+p(k,t)λd^(k,t)]
比较有意思的地方是平滑因子和语音概率嵌套的方式调换了一下,中心思想应该都是希望用这两个因子自适应的跟踪噪声。
再回到维纳滤波器
其实维纳滤波器函数ComputeDdBasedWienerFilter和计算信噪比函数ComputeSnr非常相似,最后得到的增益也是非常简单的公式套用,在第一个博文里已经阐述,所以推测webrtc中只是简单的维纳滤波器方法,并没有引入MMSA LSA等方法。而在噪声估计的时候也采用了软判决的策略,避免VAD的过硬策略。
小结
在webrtc的代码里畅游了一圈,大致摸清了算法的一些基本原理,接下来向最后一个堡垒进发,语音噪声概率估计。