常见的语音增强算法有谱减法,MMSE和维纳滤波等。谱减法虽然实现简单,运算量小,但效果比较差,容易出现刺耳的“音乐噪声”。MMSE和维纳滤波虽然原理较复杂,运算量也相对较大,但效果着实不错,甚至可以完全减除“音乐噪声”。由于实验室需要,对语音增强算法相对有点了解,但不够深入。实验室项目增强部分算法采用的是欧洲ETSI的ASR(自动语音识别)的前端去噪。采用的是两级维纳滤波,对于平稳的噪声语音去噪效果相当可观。官网也给出了C语言的实现和文档说明。虽然程序已移植到项目中,但由于文档主要是介绍了方法和一些经验参数,并没有具体说明为什么这样做,总体感觉就是只知道其中的算法流程。下图是ETSI的两级维纳滤波的程序框图。可以看出这个程序的维纳滤波是在梅尔频域上做的,估计是因为梅尔频率更符合人耳特性吧。就像语音识别中提取的特征参数是梅尔倒谱系数MFCC。
由于想进一步了解下语音增强算法的原理,这段时间着重看了MMSE的实现原理,尤其是对数的幅度谱误差平方MMSE-LSA的原理,并对这个算法进行C语言实现,最终结果与matlab代码一致。下面简要介绍下原理:
根据MMSE-LSA的开山始祖的文章Ephraim, Y. and Malah, D. (1985).《 Speech enhancement using a minimum
mean-square error log-spectral amplitude estimator.》假设带噪信号y(n) = x(n) + d(n),其中x(n)为纯净信号,d(n)为噪声信号,转换为频域,Y(w) = X(w) + D(w)。根据论文推导出最优的对数MMSE估计器为:
其中就是对数MMSE估计器的增益函数。在这个函数中有两个未知量,分别是先验信噪比(可以被看作是第k个频谱分量的实际信噪比)和后验信噪比(可以看作是加入噪声后第k个频谱分量测得的信噪比)。
其中 表示纯净信号谱的第k个频谱分量的方差。表示噪声频谱的第k个频谱分量的方差。从上面式子当中,只有Yk是已知的,即就是带噪信号的频域分量。如果假设噪声是平稳信号,噪声方差可以用VAD检测非语音段来不断更新。而纯净信号的方差则需要通过一定的方法估算出来,文章给出的是判决引导法,推导的结果是
其中a是平滑系数,一般取0.98。一些文章对上式提出改进方法,比如限定先验信噪比的最小值等。下面是MMSE-LSA的程序框图
MMSE-LSA算法实现步骤
1、对带噪信号分帧,加窗(这里是汉明窗)
3、估计后验信噪比 : 这里 是在非语音片段(语音开始之前几帧或语音间隙)估计的噪声能量谱;然后用判决引导法估计先验信噪比;
4、用最优的MMSE-LSA估计器的公式估计增强信号幅度;
5、重建增强信号谱,然后计算增强信号谱的IFFT,得到对应输入语音帧的增强的时域信号x(n)。
下面是matlab代码的程序(来自《语音增强-理论与实践》光盘,这本书是语音大牛Philipos C.Loizou的大作。这本书对语音增强方面的主流算法原理逐一介绍和推导,更难能可贵的是针对每一个算法给出了Matlab实例,是本难得好书)
function logmmse(filename,outfile) % % Implements the logMMSE algorithm [1]. % % Usage: logmmse(noisyFile, outputFile) % % infile - noisy speech file in .wav format % outputFile - enhanced output file in .wav format % % % Example call: logmmse('sp04_babble_sn10.wav','out_log.wav'); % % References: % [1] Ephraim, Y. and Malah, D. (1985). Speech enhancement using a minimum % mean-square error log-spectral amplitude estimator. IEEE Trans. Acoust., % Speech, Signal Process., ASSP-23(2), 443-445. % % Authors: Philipos C. Loizou % % Copyright (c) 2006 by Philipos C. Loizou % $Revision: 0.0 $ $Date: 10/09/2006 $ %------------------------------------------------------------------------- if nargin<2 fprintf('Usage: logmmse(noisyfile.wav,outFile.wav) \n\n'); return; end [x, Srate, bits]= wavread( filename); %nsdata is a column vector % =============== Initialize variables =============== len=floor(20*Srate/1000); % Frame size in samples if rem(len,2)==1, len=len+1; end; PERC=50; % window overlap in percent of frame size len1=floor(len*PERC/100); len2=len-len1; win=hamming(len); % define window % Noise magnitude calculations - assuming that the first 6 frames is % noise/silence nFFT=2*len; noise_mean=zeros(nFFT,1); j=1; for m=1:2 noise_mean=noise_mean+abs(fft(win.*x(j:j+len-1),nFFT)); j=j+len; end noise_mu=noise_mean/6; noise_mu2=noise_mu.^2; %--- allocate memory and initialize various variables x_old=zeros(len1,1); Nframes=floor(length(x)/len2)-floor(len/len2); xfinal=zeros(Nframes*len2,1); %=============================== Start Processing ======================================================= % k=1; aa=0.98; mu=0.98; eta=0.15; ksi_min=10^(-25/10); for n=1:Nframes insign=win.*x(k:k+len-1); spec=fft(insign,nFFT); sig=abs(spec); % compute the magnitude sig2=sig.^2; gammak=min(sig2./noise_mu2,40); % limit post SNR to avoid overflows if n==1 ksi=aa+(1-aa)*max(gammak-1,0); else ksi=aa*Xk_prev./noise_mu2 + (1-aa)*max(gammak-1,0); % a priori SNR ksi=max(ksi_min,ksi); % limit ksi to -25 dB end log_sigma_k= gammak.* ksi./ (1+ ksi)- log(1+ ksi); vad_decision= sum(log_sigma_k)/ len; if (vad_decision< eta) % noise only frame found noise_mu2= mu* noise_mu2+ (1- mu)* sig2; end % ===end of vad=== A=ksi./(1+ksi); % Log-MMSE estimator vk=A.*gammak; ei_vk=0.5*expint(vk); hw=A.*exp(ei_vk); sig=sig.*hw; Xk_prev=sig.^2; xi_w= ifft( hw .* spec,nFFT); xi_w= real( xi_w); xfinal(k:k+ len2-1)= x_old+ xi_w(1:len1); x_old= xi_w(len1+ 1: len); k=k+len2; end wavwrite(xfinal,Srate,16,outfile);
C语言实现代码github地址 https://github.com/willhope/Noise-reduction ‘
鉴于很多人需要光盘,我在这里分享下 http://download.csdn.net/detail/zwhlxl/8789731 设置了2个积分,实在没积分的可以留下QQ邮箱,我发给你链接,因为资料很大,我放在了腾讯的云盘里。