为什么要对 FFT 使用窗口函数?

信息处理 fft 频率 声音 窗函数 语音处理
2022-01-04 23:28:36

所以我刚刚使用谐波积谱算法修改了我的音高计算算法。我只是好奇为什么谐波乘积频谱的解释表明您需要对数据集实施汉宁窗。在数据集上实现其他窗口函数(然后对其进行 FFT)会有什么影响?哪个 Windowing 函数实际上最适合频率检测?以下是我在代码中使用的相关方法:

/**
 * Calculates the Frequency based off of the byte array,
 * @param bytes The audioData you want to analyze
 * @return The calculated frequency in Hertz.
 */
private int getFrequency(byte[] bytes){
    double[] audioData = this.bytesToDoubleArray(bytes);
    audioData = applyHanningWindow(audioData);
    Complex[] complex = new Complex[audioData.length];
    for(int i = 0; i<complex.length; i++){
        complex[i] = new Complex(audioData[i], 0);
    }
    Complex[] fftTransformed = FFT.fft(complex);
    //return calculateFrequency(fftTransformed);
    System.out.println("Max size:" + (fftTransformed.length*getFFTBinSize(fftTransformed.length)/4));
    return calculateFundamentalFrequency(fftTransformed,4);
}

private double[] applyHanningWindow(double[] data){
    return applyHanningWindow(data, 0, data.length);
}

private double[] applyHanningWindow(double[] signal_in, int pos, int size)
{
    for (int i = pos; i < pos + size; i++)
    {
        int j = i - pos; // j = index into Hann window function
        signal_in[i] = (double)(signal_in[i] * 0.5 * (1.0 - Math.cos(2.0 * Math.PI * j / size)));
    }
    return signal_in;
}


/**
 * Harmonic Product Spectrum
 * @param fftData
 * @param n
 * @return
 */
private int calculateFundamentalFrequency(Complex[] fftData, int n){
    Complex[][] data = new Complex[n][fftData.length/n];
    for(int i = 0; i<n; i++){
        for(int j = 0; j<data[0].length; j++){
            data[i][j] = fftData[j*(i+1)];
        }
    }
    Complex[] result = new Complex[fftData.length/n];//Combines the arrays
    for(int i = 0; i<result.length; i++){
        Complex tmp = new Complex(1,0);
        for(int j = 0; j<n; j++){
            tmp = tmp.times(data[j][i]);
        }
        result[i] = tmp;
    }
    //Calculates Maximum Magnitude of the array
    double max = Double.MIN_VALUE;
    int index = -1;
    for(int i = 0; i<result.length; i++){
        Complex c = result[i];
        double tmp = c.getMagnitude();
        if(tmp>max){
            max = tmp;;
            index = i;
        }
    }
    return index*getFFTBinSize(fftData.length);
}
1个回答

FFT 只能在有限的数据块上执行。基本数学是基于时域信号是周期性的假设,即您的数据块在时间上重复。这通常会导致块边缘出现主要的不连续性。

让我们看一个简单的例子:FFT 大小 = 1000 点,采样率 = 1000 Hz,频率分辨率 = 1Hz。如果你有一个 10 Hz 的正弦波,你就没有不连续性,因为恰好 10 个周期适合你的 FFT 窗口,并且边缘的值(和导数)是相同的。该信号的 FFT 将为零,除了 bin #10 中的单个值。这也适用于 11 Hz 正弦波。

但是,对于 10.3 HZ 正弦波,您最终会遇到很多不连续性,并且 FFT 将在所有 bin 中具有能量,最大值约为 10 或 11,然后“裙子”会滚到两侧。因此,频率的微小变化会导致 FFT 图片发生巨大变化。

窗口化用于避免这种情况。Windows 确保边缘的数据为零,因此没有不连续性。然而,时域中的乘法是频域中的卷积,这会导致谱线和旁瓣变宽。窗口的选择控制了主瓣宽度与旁瓣间距和高度之间的权衡。您的应用程序特定要求决定了要使用的窗口,并且有数十种选择。汉宁只是其中之一。它基本上是“如果您没有更好的想法,可以选择的窗口”。就个人而言,我更喜欢 Kaiser 窗口,因为它们有一个连续的参数,可以在很宽的范围内控制窗口的行为。

一般来说,FFT 并不是一个很好的音高检测方法。对于大多数音频信号,频谱中的最大值不是基频(通常谐波具有更高的能量),为了获得不错的分辨率,您需要很长的数据,但这使得算法对变化的响应非常缓慢和迟缓。更好的选择是相位环、延迟环、自相关、最大/最小跟踪器、过零跟踪器等。