频率响应和频谱的卷积和乘法之间的区别

信息处理 自由度 卷积
2022-01-17 23:47:56

假设我有类似 [1/25,2/25,3/25,4/25,5/25,4/25,3/25,2/25,1/25] 的脉冲响应。我对 600 个测试信号样本进行了卷积(似乎我做了一些过滤)。然后我在频域中计算了同样的东西:

  1. 找到测试信号的频谱和频率响应。
  2. 将它们相乘并通过 IDFT 转换回时域。

然后我绘制了这两个信号之间的差异,我得到这些信号之间的差异约为|0.4|。

可以吗?这对我来说似乎有点大——差异大约是十分之一。

function generateConvolution2(x, impResp)
{
    var xVal = x();
    var irVal = impResp();

    var convolution = [];
    var data = [];

    for (var i = 0; i < xVal.length + irVal.length - 1; i++)
    {
        convolution[i] = 0;
        for (var j = 0; j < irVal.length; j++)
        {
            if (i - j < 0) convolution[i] += 0;
            else convolution[i] += irVal[j] * xVal[i - j];
        }

        data.push(convolution[i]);
    }
    return data;
}

//calculate dft magnitude and spectrum of a function
function magnitudeAndPhase(length, dftLength, func)
{
    var data = [];
    for (var j = 0; j <= dftLength; j++)
    {
        var f = 0;
        var re = 0;
        var im = 0;

        for (var i = 0; i < length; i++)
        {
            f = func(i);
            re += f * Math.cos(2 * Math.PI * j * i / length);
            im -= f * Math.sin(2 * Math.PI * j * i / length);
        }

        //calculate magnitude
        var mag = Math.sqrt(re * re + im * im);

        //calculate phase
        var phase;
        if (re === 0)
        {
            re = 1e-20;
        }
        phase = Math.atan(im / re);
        if (re < 0 && im < 0) phase -= Math.PI;
        if (re < 0 && im >= 0) phase += Math.PI;
        data.push({mag: mag, phase: phase});
    }
    return data;
}


function multDFt(length, dftLength, func1, func2)
{
    var data1 = magnitudeAndPhase(length, dftLength, func1);
    var data2 = magnitudeAndPhase(length, dftLength, func2);
    var result = [];
    //1)multiply magnitudes
    //2)add phase
    for (var i = 0; i <= dftLength; i++)
    {
        result.push({mag: data1[i].mag * data2[i].mag, phase: data1[i].phase + data2[i].phase });
    }

    return result;
}

//using magnitude and phase calculate IDFT
function IDFT2(mainLength, length, dftLength, testSignal, imprResp)
{
    var rea = [];
    var ima = [];

    var dftMagResult = multDFt(length, dftLength, testSignal, imprResp);
    //create re and im part of coefficients
    for (var i = 0; i <= dftLength; i++)
    {
        rea.push(dftMagResult[i].mag * Math.cos(dftMagResult[i].phase));
        ima.push(dftMagResult[i].mag * Math.sin(dftMagResult[i].phase));
    }

    //prepare re and im coefficients
    rea[0] /= length;
    rea[dftLength] /= length;
    for (var j = 1; j < dftLength; j++)
    {
        rea[j] /= (length / 2  );
        ima[j] /= -1 * (length / 2  );
    }
    var vala = [];
    for (var i = 0; i < mainLength; i++)
    {
        vala.push(0);
    }
    //calculate idft
    for (var j = 0; j <= dftLength; j++)
    {
        for (var i = 0; i < length; i++)
        {
            vala[i] += rea[j] * Math.cos(2 * Math.PI * j * i / length);
            vala[i] += ima[j] * Math.sin(2 * Math.PI * j * i / length);
        }
    }

    return vala;
}

更新:添加了我的 js 代码。不要为我的 js 风格(我是初学者)和一些不一致的命名而烦恼。每个功能。返回数组,因为它是绘制库所必需的。

提前致谢。

2个回答

几点注意事项:

  1. 注意 DFT 中的“因素”。一些使用 $ \frac{1}{2 \pi} $ 一些使用其他的,注意你相应地标准化。12π some use others, pay attention you normalize accordingly.
  2. DFT 的乘法相当于循环卷积
    您应该相应地填充信号以获得“经典卷积”。

通常线性和循环卷积是两种不同的操作,但在某些条件下可以使它们相等,从而通过 FFT 加速卷积。

分别有两个长度为 $N$ 和 $L$ 的输入向量 $x$ 和 $h$,我们计算线性卷积 $y_l=x\ast h$。结果向量的长度为 $M=N+L-1$。x and h of length N and L respectively, we compute linear convolution yl=xh. A resulting vector has length M=N+L1.

如果我们想使用 FFT 计算相同的结果,那么您必须用零填充两个信号,直到长度为 $M$。最后一步是使用循环卷积的公式:M. Last step is to use the formula for circular convolution:

yc=IFFT{FFT{xpad}×FFT{hpad}}

所以现在两个结果相等,$y_c=y_l$。yc=yl.

下面您可以在 MATLAB 中找到一些示例,说明您的滤波器系数和信号是白噪声。请记住,该fft函数会自动将信号用零填充到指定为第二个参数的长度。

% Signals used
randn('seed',0) % still prefer an old-way...
x = randn(1,600);
h = [1/25,2/25,3/25,4/25,5/25,4/25,3/25,2/25,1/25];

% Linear convolution
y_l = conv(x,h);

% Linear convolution through circular
M = length(x)+length(h)-1;
y_c = ifft(fft(x, M).*fft(h, M)); % pad to M

% Plotting of results
hold on
plot(y_l,'b')
plot(y_c, 'r--')
grid on
xlabel('Sample')
ylabel('Amplitude')
title('Result of linear and circular convolution')
legend({'linear','circular'})
display(sprintf('Max difference is: %e', max(abs((y_l-y_c)))))

您可以看到结果是相等的(请注意,红线是虚线,所以可以看到蓝色的线 - 有些人倾向于认为这是错误的),并且 MATLAB 给出的最大差异是:4.440892e-16非常接近相对浮点精度。

在此处输入图像描述

另外请记住,如果您使用任何外部库,请检查正在使用的缩放因子。据我所知,FFTW 没有执行任何缩放,一切都取决于你。


人们可能会注意到,对于长信号,它可能会产生使 FFT 算法效率不高的长度,即不是 2 的幂。因此,我们可以将信号填充到最接近的 2 幂以加快计算速度。

在您的情况下,您提到了 1024。这将在第 608 个样本之后产生尾随零,应将其删除。这是情节:

在此处输入图像描述

所以回答你的问题;可以使用 $M=1024$ 而不是 $M=600+9-1=608$。请记住将输出截断为 $M=608$ 个样本。M=1024 instead of M=600+91=608. Just remember to truncate the output to M=608 samples.

我还包括一个 Python 代码,用于与某些人没有 MATLAB 相同的过程。填充到 2 的幂的结果代码可能类似于:

import numpy as np
import matplotlib.pyplot as plt

def nextpow2(N):
    """ Function for finding the next power of 2 """
    n = 1
    while n < N: n *= 2
    return n

if __name__ == "__main__":
    # Signals used
    x = np.random.randn(600)
    h = np.array([1,2,3,4,5,4,3,2,1])/25.0

    # Linear convolution
    y_l = np.convolve(x, h)

    # Circular convolution
    M = x.size + h.size - 1
    M2 = nextpow2(M)
    y_c = np.fft.ifft(np.fft.fft(x,M2) * np.fft.fft(h,M2))
    # Truncate the output and get rid of small imaginary parts
    y_c = np.real(y_c[0:M])

    # Plotting of results
    plt.plot(y_l, 'b', label='linear')
    plt.grid(True)
    plt.hold(True)
    plt.plot(y_c, 'r--', label='circular')
    plt.title('Result of linear and circular convolution')
    plt.xlabel('Samples')
    plt.ylabel('Amplitude')
    plt.legend()
    plt.show()