回声消除(AEC)原理、算法及实战——频域块LMS自适应滤波算法(FDAF)

        块LMS自适应滤波算法中的线性卷积和线性相关均可以采用快速傅里叶变换(FFT)来实现。因此,块LMS自适应滤波算法的有效实现方法实际上是利用FFT算法在频域上完成滤波器系数的自适应。这样实现的块LMS自适应滤波算法称为频域块LMS自适应滤波算法(FDAF,Frequency-Domain Block Least MeanSquare Adaptive Filter)。

        另一方面,数字信号处理中常用的重叠存储法和重叠相加法为快速卷积运算提供了强有力的工具,即利用离散傅里叶变换计算线性卷积。重叠存储方法是非自适应滤波两种方法中更为常用的一种方法。此外,值得注意的是,尽管滤波器能够以任意数量的重叠来实现,但当以50%重叠时运算效率达到最高。

 基于重叠存储法的频域块LMS自适应滤波算法的信号流程图

问题:针对回声路径很长且复杂,并且回声延迟较高时,时域自适应滤波算法计算复杂度高的问题,

解决:提出了频域分块滤波(FDAF)算法,FDAF算法将长度为L的自适应滤波器分成FFT长度的整数倍个子块,对输入信号的每个子块进行频域内的LMS算法。

优点:当回声路径很长且复杂时计算量小,并且在收敛速度方面有略微提升。
  相对于时域,FDAF具有许多优势。除了能够通过在频域中相乘来执行滤波器卷积之外,该变换还有效地减小了自适应滤波器的长度。因此,降低了自适应算法的计算复杂度。此外,为了降低计算复杂度,FDAF还可以提高收敛速度。这是由于滤波器更新中信号的自相关矩阵的特征值分布减小所导致的。

  FDAF的这些优势最终需要权衡。FDAF的主要成本是增加的延迟和增加的内存需求。延迟成本来自需要通过频域滤波器延迟期望信号(或回声消除中的麦克风信号)。这导致在时域方法上增加了存储器存储,因为需要同时存储激励信号和期望信号。FDAF的早期方法使FFT的阶数与脉冲响应的大小大致相同。但是如前所述,回声消除等应用回波路径较长,会导致较大的延迟和存储需求。该缺点可以通过诸如多延迟自适应滤波器之类的方法来克服。在这种方法中,块大小可以小于所需的时域自适应滤波器,并且可以应用每个频点中的自适应滤波器来代替单个系数。因此,可以减轻FDAF的缺点,同时保持降低的计算复杂性和提高的收敛速度。

代码如下:

import numpy as np
import librosa
import soundfile as sf
import pyroomacoustics as pra

from scipy.linalg import hankel


from numpy.fft import rfft as fft
from numpy.fft import irfft as ifft

def fdaf(x, d, M, mu=0.05, beta=0.9):
  H = np.zeros(M+1,dtype=np.complex128)
  norm = np.full(M+1,1e-8)

  window =  np.hanning(M)
  x_old = np.zeros(M)

  num_block = min(len(x),len(d)) // M
  e = np.zeros(num_block*M)

  for n in range(num_block):
    x_n = np.concatenate([x_old,x[n*M:(n+1)*M]])
    d_n = d[n*M:(n+1)*M]
    x_old = x[n*M:(n+1)*M]

    X_n = fft(x_n)
    y_n = ifft(H*X_n)[M:]
    e_n = d_n-y_n
    e[n*M:(n+1)*M] = e_n

    e_fft = np.concatenate([np.zeros(M),e_n*window])
    E_n = fft(e_fft)

    norm = beta*norm + (1-beta)*np.abs(X_n)**2
    G = mu*E_n/(norm+1e-3)
    H = H + X_n.conj()*G

    h = ifft(H)
    h[M:] = 0
    H = fft(h)

  return e

# x 原始参考信号
# v 理想mic信号 
# 生成模拟的mic信号和参考信号
def creat_sim_sound(x,v):
    rt60_tgt = 0.08
    room_dim = [2, 2, 2]

    e_absorption, max_order = pra.inverse_sabine(rt60_tgt, room_dim)
    room = pra.ShoeBox(room_dim, fs=sr, materials=pra.Material(e_absorption), max_order=max_order)
    room.add_source([1.5, 1.5, 1.5])
    room.add_microphone([0.1, 0.5, 0.1])
    room.compute_rir()
    rir = room.rir[0][0]
    rir = rir[np.argmax(rir):]
    # x 经过房间反射得到 y
    y = np.convolve(x,rir)
    scale = np.sqrt(np.mean(x**2)) /  np.sqrt(np.mean(y**2))
    # y 为经过反射后到达麦克风的声音
    y = y*scale

    L = max(len(y),len(v))
    y = np.pad(y,[0,L-len(y)])
    v = np.pad(v,[L-len(v),0])
    x = np.pad(x,[0,L-len(x)])
    d = v + y
    return x,d

if __name__ == "__main__":
    x_org, sr  = librosa.load('female.wav',sr=8000)
    v_org, sr  = librosa.load('male.wav',sr=8000)

    x,d = creat_sim_sound(x_org,v_org)

    e =  fdaf(x, d,M=256,mu=0.1)

    sf.write('x.wav', x, sr, subtype='PCM_16')
    sf.write('d.wav', d, sr, subtype='PCM_16')
    sf.write('fdaf.wav', e, sr, subtype='PCM_16')

参考文献:

https://blog.csdn.net/qq_34218078/article/details/108666894?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168345854216800215089748%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168345854216800215089748&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-108666894-null-null.article_score_rank_blog&utm_term=%E5%9B%9E%E5%A3%B0&spm=1018.2226.3001.4450

https://www.bilibili.com/video/BV16U4y1z7Pq/?spm_id_from=333.999.0.0&vd_source=77c874a500ef21df351103560dada737

猜你喜欢

转载自blog.csdn.net/qq_42233059/article/details/130546590
今日推荐