代码之家  ›  专栏  ›  技术社区  ›  Trap

基于FFT的频域DSP滤波

  •  20
  • Trap  · 技术社区  · 14 年前

    我一直在玩弄FFT的外皮质实现,但我遇到了一些问题。

    每当我在调用ifft之前修改频率仓的振幅时,产生的信号包含一些点击和弹出,特别是当信号中存在低频时(如鼓或低音)。但是,如果我用相同的系数衰减所有垃圾箱,则不会发生这种情况。

    让我举一个4样本FFT的输出缓冲区的例子:

    // Bin 0 (DC)
    FFTOut[0] = 0.0000610351563
    FFTOut[1] = 0.0
    
    // Bin 1
    FFTOut[2] = 0.000331878662
    FFTOut[3] = 0.000629425049
    
    // Bin 2
    FFTOut[4] = -0.0000381469727
    FFTOut[5] =  0.0
    
    // Bin 3, this is the first and only negative frequency bin.
    FFTOut[6] =  0.000331878662
    FFTOut[7] = -0.000629425049
    

    输出由成对的浮点数组成,每个浮点数表示单个容器的真实部分和想象部分。因此,bin 0(数组索引0,1)表示直流频率的实部和虚部。如你所见,1号和3号箱都有相同的值(除了IM部分的符号),所以我猜3号箱是第一个负频率,最后一个指数(4,5)是最后一个正频率箱。

    然后,要衰减频率bin 1,我要做的是:

    // Attenuate the 'positive' bin
    FFTOut[2] *= 0.5;
    FFTOut[3] *= 0.5;
    
    // Attenuate its corresponding negative bin.
    FFTOut[6] *= 0.5;
    FFTOut[7] *= 0.5;
    

    对于实际的测试,我使用的是1024长度的FFT,我总是提供所有的样本,所以不需要0填充。

    // Attenuate
    var halfSize = fftWindowLength / 2;
    float leftFreq = 0f;
    float rightFreq = 22050f; 
    for( var c = 1; c < halfSize; c++ )
    {
        var freq = c * (44100d / halfSize);
    
        // Calc. positive and negative frequency indexes.
        var k = c * 2;
        var nk = (fftWindowLength - c) * 2;
    
        // This kind of attenuation corresponds to a high-pass filter.
        // The attenuation at the transition band is linearly applied, could
        // this be the cause of the distortion of low frequencies?
        var attn = (freq < leftFreq) ? 
                        0 : 
                        (freq < rightFreq) ? 
                            ((freq - leftFreq) / (rightFreq - leftFreq)) :
                            1;
    
        // Attenuate positive and negative bins.
        mFFTOut[ k ] *= (float)attn;
        mFFTOut[ k + 1 ] *= (float)attn;
        mFFTOut[ nk ] *= (float)attn;
        mFFTOut[ nk + 1 ] *= (float)attn;
    }
    

    显然我做错了什么,但不知道是什么。

    我不想使用FFT输出作为生成一组FIR系数的方法,因为我正试图实现一个非常基本的动态均衡器。

    在频域过滤的正确方法是什么?我错过了什么?

    此外,是否真的需要衰减负频率?我看到了一个fft实现,其中neg。频率值在合成前归零。

    事先谢谢。

    4 回复  |  直到 10 年前
        1
  •  35
  •   Mark Ransom    10 年前

    有两个问题:使用FFT的方式和特定的过滤器。

    滤波传统上是作为卷积在时间域中实现的。你说得对,输入信号和滤波器信号的频谱相乘是等价的。然而,当您使用离散傅立叶变换(DFT)(使用快速傅立叶变换算法实现速度)时,实际上是计算真实频谱的采样版本。这有很多含义,但与过滤最相关的一个含义是时域信号是周期性的。

    下面是一个例子。考虑一个正弦输入信号 x 和一个简单的低通滤波器 h。在matlab/octave语法中:

    n=1024;
    n=(1:n)'-1;%'定义时间索引
    x=sin(2*pi*1.5*n/n);%输入,每1024点1.5个周期
    H=汉宁(129)。*sinc(0.25*(-64:1:64)';%'窗口sinc lpf,fc=pi/4
    H=[H./和(H)];%标准化直流增益
    
    y=ifft(fft(x).*fft(h,n));%采样光谱乘积的倒数ft
    y=实数(y);%由于数字误差,y有一个很小的虚部
    %#根据您的FT/IFT实现,可能需要在这里按n或1/n进行扩展。
    情节(Y);
    < /代码> 
    
    

    这是图表:

    块开头的故障根本不是我们所期望的。但是,如果考虑到fft(x),它是有意义的。离散傅立叶变换假定信号在变换块内是周期性的。据DFT所知,我们要求对这一时期的一个时期进行转换:

    这导致了使用dfts过滤时的第一个重要考虑因素:您实际上正在实现循环卷积,而不是线性卷积。所以当你考虑数学的时候,第一个图表中的“小故障”并不是真正的小故障。那么问题就变成了:有没有办法解决周期性问题?答案是肯定的:使用重叠保存处理。本质上,你计算n长的产品,如上面所述,但只保留n/2分。

    nproc=512;
    xproc=零(2*nproc,1);%初始化临时缓冲区
    idx=1:nproc;%初始化半缓冲区索引
    ycorrect=零(2*nproc,1);%初始化目标
    对于ctr=1:(长度(x)/nproc)%每次迭代x 512点
    xproc(1:nproc)=xproc((nproc+1):结束);%将上一个迭代的下半部分移动到该迭代的上半部分
    xproc((nproc+1):end)=x(idx);%用新数据填充此迭代的下半部分
    yproc=ifft(fft(xproc)。*fft(h,2*nproc));%计算新缓冲区
    ycorrect(idx)=real(yproc((nproc+1):end));%保留新缓冲区的后半部分
    idx=idx+nproc;%半步缓冲索引
    结束
    < /代码> 
    
    

    以下是ycorrect的图表:

    这张图片是有意义的-我们期望从滤波器的启动瞬态,然后结果沉淀到稳态正弦响应。请注意,现在x可以任意长。限制是nproc>2*min(length(x),length(h))

    现在讨论第二个问题:特定的过滤器。在您的循环中,您创建了一个基本上频谱为h=[0(1:511)/512 1(511:-1:1)/512]';if you dohraw=real(ifft(h));plot(hraw),you get:

    很难看清,但是在图的最左边缘有一组非零点,然后在极右方的边缘有更多的零点。使用Octave的内置freqzfunction查看我们看到的频率响应(by doingfreqz(hraw)):

    幅度响应从高通包络线到零点有很多波动。同样,DFT固有的周期性正在发挥作用。就DFT而言,hrawrepeats一遍又一遍。但是,如果您使用一个周期的hraw,asfreqzdoes,它的光谱与周期性版本有很大的不同。

    因此,让我们定义一个新的信号:hrot=[hraw(513:end);hraw(1:512)];we simply rotate the raw dft output to make it continuous within the block.现在,让我们使用freqz(hrot)来查看频率响应。:

    好多了。理想的信封就在那里,没有任何涟漪。当然,现在的实现并不那么简单,你必须做一个完全复杂的乘法,而不是仅仅缩放每个复杂的bin,但至少你会得到正确的答案。

    请注意,对于速度,您通常会预先计算填充物的DFTh,我将其单独留在循环中,以便更容易与原始数据进行比较。

    呃。

    滤波传统上是在时域中作为卷积来实现的。你说得对,输入信号和滤波器信号的频谱相乘是等价的。然而,当您使用离散傅立叶变换(DFT)(使用快速傅立叶变换算法实现速度)时,实际上是计算真实频谱的采样版本。这有很多含义,但与滤波最相关的是时域信号是周期性的。

    下面是一个例子。考虑一个正弦输入信号x周期内有1.5个周期和一个简单的低通滤波器h. 在matlab/octave语法中:

    N = 1024;
    n = (1:N)'-1; %'# define the time index
    x = sin(2*pi*1.5*n/N); %# input with 1.5 cycles per 1024 points
    h = hanning(129) .* sinc(0.25*(-64:1:64)'); %'# windowed sinc LPF, Fc = pi/4
    h = [h./sum(h)]; %# normalize DC gain
    
    y = ifft(fft(x) .* fft(h,N)); %# inverse FT of product of sampled spectra
    y = real(y); %# due to numerical error, y has a tiny imaginary part
    %# Depending on your FT/IFT implementation, might have to scale by N or 1/N here
    plot(y);
    

    下面是图表: IFFT of product

    块开头的故障根本不是我们所期望的。但是如果你考虑fft(x),这是有道理的。离散傅立叶变换假定信号在变换块内是周期性的。据DFT所知,我们要求对这一时期的一个时期进行转换: Aperiodic input to DFT

    这导致了使用dfts过滤时的第一个重要考虑:您实际上正在实现circular convolution不是线性卷积。所以当你考虑数学的时候,第一个图表中的“小故障”并不是真正的小故障。那么问题就变成了:有没有一种方法可以绕过周期性?答案是肯定的:使用overlap-save processing. 本质上,你计算n长的产品,如上面所述,但只保留n/2分。

    Nproc = 512;
    xproc = zeros(2*Nproc,1); %# initialize temp buffer
    idx = 1:Nproc; %# initialize half-buffer index
    ycorrect = zeros(2*Nproc,1); %# initialize destination
    for ctr = 1:(length(x)/Nproc) %# iterate over x 512 points at a time
        xproc(1:Nproc) = xproc((Nproc+1):end); %# shift 2nd half of last iteration to 1st half of this iteration
        xproc((Nproc+1):end) = x(idx); %# fill 2nd half of this iteration with new data
        yproc = ifft(fft(xproc) .* fft(h,2*Nproc)); %# calculate new buffer
        ycorrect(idx) = real(yproc((Nproc+1):end)); %# keep 2nd half of new buffer
        idx = idx + Nproc; %# step half-buffer index
    end
    

    这是ycorrect: ycorrect

    这张图片是有意义的-我们期望从滤波器的启动瞬态,然后结果沉淀到稳态正弦响应。注意现在X可以任意长。限制是Nproc > 2*min(length(x),length(h)).

    现在讨论第二个问题:特定的过滤器。在你的循环中,你创建了一个过滤器,它的光谱本质上是H = [0 (1:511)/512 1 (511:-1:1)/512]';如果你这样做了hraw = real(ifft(H)); plot(hraw)你得到: hraw

    很难看清,但是在图的最左边缘有一组非零点,然后在极右方的边缘有更多的零点。使用八度音阶内置freqz函数来查看我们看到的频率响应(通过执行freqz(hraw)): freqz(hraw)

    幅度响应从高通包络线到零点有很多波动。同样,DFT固有的周期性正在发挥作用。就DFT而言,hraw一遍又一遍地重复。但是如果你用一段时间原材料作为数字滤波器频率响应是的,它的光谱和周期版本有很大的不同。

    那么让我们定义一个新的信号:hrot = [hraw(513:end) ; hraw(1:512)];我们只需旋转原始DFT输出,使其在块内连续。现在让我们来看看频率响应freqz(hrot): freqz(hrot)

    好多了。理想的信封就在那里,没有任何涟漪。当然,现在实现不是那么简单,你必须做一个完全复杂的乘法fft(hrot)不要只是缩放每个复杂的垃圾箱,但至少你会得到正确的答案。

    注意,对于速度,您通常会预先计算填充物的干膜厚度。H我把它单独放在循环中,以便与原来的相比更容易。

        2
  •  11
  •   tom10    14 年前

    你的主要问题是 在短时间间隔内频率定义不好 . 这对于低频尤其如此,这就是为什么你最注意到问题所在。

    因此,当你把很短的片段从声音序列中取出,然后过滤这些片段时,过滤后的片段不会以产生连续波形的方式过滤,你会听到片段之间的跳跃,这就是产生点击的原因。

    例如,取一些合理的数字:我从27.5赫兹(钢琴上的a0)的波形开始,在44100赫兹的频率下数字化,它看起来像这样(红色部分长1024个样本):

    alt text http://i48.tinypic.com/zim802.png

    所以首先我们将从40Hz的低通开始。因此,由于原始频率小于40Hz,一个40Hz截止的低通滤波器不应该真的有任何影响,我们将得到一个几乎完全匹配输入的输出。对吗? 错,错,错 “这基本上是你问题的核心。问题是,对于较短的部分, 主意 27.5赫兹的频率定义不清楚,在离散傅立叶变换中不能很好地表示出来。

    从下图中的DFT可以看出,27.5赫兹在短段中没有特别的意义。请注意,虽然较长段的dft(黑点)在27.5赫兹时显示峰值,但短段(红点)没有。

    alt text http://i50.tinypic.com/14w6luw.png

    显然,低于40Hz的滤波只会捕获直流偏移,40Hz低通滤波器的结果显示为绿色。

    alt text http://i48.tinypic.com/2vao21w.png

    蓝色曲线(采用200赫兹截止频率)开始匹配得更好。但要注意的是,并不是低频率使它匹配得很好,而是包含了高频。直到我们将短段中可能的每一个频率,高达22kHz,我们才最终得到原始正弦波的良好表示。

    所有这些的原因是27.5赫兹正弦波的一小部分 27.5赫兹正弦波,它的dft与27.5赫兹没有太多关系。

        3
  •  2
  •   Jason B    14 年前

    您是否将直流频率采样值减至零?在您的示例中,似乎您根本没有减弱它。由于要实现高通滤波器,因此还需要将dc值设置为零。

    这可以解释低频失真。如果直流值由于大的跃迁而非零值,那么低频时的频率响应会产生很大的纹波。

    下面是matlab/octave中的一个示例,演示可能发生的情况:

    N = 32;
    os = 4;
    Fs = 1000;
    X = [ones(1,4) linspace(1,0,8) zeros(1,3) 1 zeros(1,4) linspace(0,1,8) ones(1,4)];
    x = ifftshift(ifft(X));
    Xos = fft(x, N*os);
    f1 = linspace(-Fs/2, Fs/2-Fs/N, N);
    f2 = linspace(-Fs/2, Fs/2-Fs/(N*os), N*os);
    
    hold off;
    plot(f2, abs(Xos), '-o');
    hold on;
    grid on;
    plot(f1, abs(X), '-ro');
    hold off;
    xlabel('Frequency (Hz)');
    ylabel('Magnitude');
    

    frequency response http://www.freeimagehosting.net/uploads/e10109e535.png

    注意,在我的代码中,我创建了一个DC值非零的例子,随后突然变为零,然后是一个斜坡。然后我用ifft转换成时间域。然后我对这个时间域信号执行一个零填充的fft(当你传入一个大于输入信号的fft大小时,matlab会自动完成)。时间域中的零填充导致频域中的插值。使用这个,我们可以看到过滤器如何在过滤器样本之间做出响应。

    要记住的最重要的一点是,即使通过衰减DFT的输出来设置给定频率下的滤波器响应值,这也不能保证采样点之间发生的频率。这意味着你的变化越突然,样品之间的过冲和振荡就越多。

    现在回答您的问题,关于如何进行过滤。有很多方法,但最容易实现和理解的方法之一是窗口设计方法。当前设计的问题是过渡宽度很大。大多数情况下,你会想要尽可能快的过渡,尽可能少的涟漪。

    在下一个代码中,我将创建一个理想的过滤器并显示响应:

    N = 32;
    os = 4;
    Fs = 1000;
    X = [ones(1,8) zeros(1,16) ones(1,8)];
    x = ifftshift(ifft(X));
    Xos = fft(x, N*os);
    f1 = linspace(-Fs/2, Fs/2-Fs/N, N);
    f2 = linspace(-Fs/2, Fs/2-Fs/(N*os), N*os);
    
    hold off;
    plot(f2, abs(Xos), '-o');
    hold on;
    grid on;
    plot(f1, abs(X), '-ro');
    hold off;
    xlabel('Frequency (Hz)');
    ylabel('Magnitude'); 
    

    frequency response http://www.freeimagehosting.net/uploads/c86f5f1700.png

    请注意,突然的变化会引起很多振荡。

    FFT或离散傅立叶变换是傅立叶变换的采样版本。傅立叶变换适用于连续范围内的信号-无穷大到无穷大,而DFT适用于有限数量的样本。由于我们只处理有限数量的样本,因此在使用dft时,这实际上会导致在时间域中出现方形窗口(截断)。不幸的是,方波的dft是sinc型函数(sin(x)/x)。

    在您的过滤器中有一个尖锐的转换(在一个示例中从0快速跳转到1)的问题是,在时间域中有一个非常长的响应,该响应被一个方形窗口截断。为了帮助最小化这个问题,我们可以将时间域信号乘以一个更渐变的窗口。如果我们将汉宁窗乘以线:

    x = x .* hanning(1,N).';
    

    服用IFFT后,我们得到以下回复:

    frequency response http://www.freeimagehosting.net/uploads/944da9dd93.png

    因此,我建议尝试实现窗口设计方法,因为它相当简单(有更好的方法,但它们会变得更复杂)。由于您正在实现均衡器,我假设您希望能够即时更改衰减,因此我建议您在参数发生变化时计算并将滤波器存储在频域中,然后您只需将其应用到每个输入音频缓冲区,方法是取输入缓冲区的fft,乘以您的fre。序列域过滤样本,然后执行ifft返回到时间域。这将比您为每个样本所做的所有分支都有效得多。

        4
  •  1
  •   leonbloy    14 年前

    首先,关于规范化:这是一个已知的(非)问题。DFT/IDFT需要一个系数 1 /平方英尺(n) (除了标准的余弦/正弦因子)在每一个(直接一个逆),使他们西米和真正可逆。另一种可能是将它们中的一个(直接或反向)除以 n ,这是一个方便和品味的问题。通常,FFT例程不执行这种规范化,用户应该了解它,并根据自己的喜好进行规范化。 See

    第二:在16分的DFT中,你称之为 斌0 与零频率(dc)相对应, 斌1 低频率 斌4 中等频率, 斌8 达到最高频率 垃圾桶9…15 到“负频率”。在你的例子中, 斌1 实际上是低频和中频。除此之外,你的“均衡”在概念上没有任何错误。我不明白你的意思 “信号在低频时失真” . 你怎么观察到的?