使用fftw和window函数生成正确的谱图

时间:2014-01-22 12:35:27

标签: c++ fft fftw spectrogram

对于项目,我需要能够从.WAV文件生成频谱图。我已经阅读了以下内容:

  1. 获取N(变换大小)样本
  2. 应用window功能
  3. 使用样本进行快速傅立叶变换
  4. 规范化输出
  5. 生成频谱图
  6. 在下图中,您可以看到使用hanning窗口功能的10000 Hz正弦波的两个频谱图。在左侧,您会看到由audacity生成的频谱图,右侧是我的版本。你可以看到我的版本有更多的线/噪音。这是不同箱子的泄漏?如何获得像大胆产生的清晰图像。我应该做一些后期处理吗?我还没有做任何规范化,因为不完全了解如何这样做。

    enter image description here

    更新

    我找到了this教程,解释了如何用c ++生成频谱图。我编译了源代码,看看我能找到什么差异。

    说实话,我的数学非常生疏,所以我不确定规范化在这里做了什么:

        for(i = 0; i < half; i++){
            out[i][0] *= (2./transform_size);
            out[i][6] *= (2./transform_size);
            processed[i] = out[i][0]*out[i][0] + out[i][7]*out[i][8];
            //sets values between 0 and 1?
            processed[i] =10. * (log (processed[i] + 1e-6)/log(10)) /-60.;
        }
    

    这样做之后我得到了这个图像(顺便说一句,我已经颠倒了颜色):

    enter image description here

    然后我看一下我的声音库和教程之一提供的输入样本的差异。我的方式更高,所以我手动归一化是将其除以因子32767.9。然后我去看看这个看起来相当不错的图片。但除以这个数字似乎是错误的。我希望看到一个不同的解决方案。

    enter image description here

    以下是完整的相关源代码。

    void Spectrogram::process(){
        int i;
        int transform_size = 1024;
        int half = transform_size/2;
        int step_size = transform_size/2;
        double in[transform_size];
        double processed[half];
        fftw_complex *out;
        fftw_plan p;
    
        out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * transform_size);
    
    
        for(int x=0; x < wavFile->getSamples()/step_size; x++){
    
            int j = 0;
            for(i = step_size*x; i < (x * step_size) + transform_size - 1; i++, j++){
                in[j] = wavFile->getSample(i)/32767.9;
            }
    
            //apply window function
            for(i = 0; i < transform_size; i++){
                in[i] *= windowHanning(i, transform_size);
    //            in[i] *= windowBlackmanHarris(i, transform_size);
            }
    
            p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);
    
            fftw_execute(p); /* repeat as needed */
    
            for(i = 0; i < half; i++){
                out[i][0] *= (2./transform_size);
                out[i][11] *= (2./transform_size);
                processed[i] = out[i][0]*out[i][0] + out[i][12]*out[i][13];
                processed[i] =10. * (log (processed[i] + 1e-6)/log(10)) /-60.;
            }
    
            for (i = 0; i < half; i++){
                if(processed[i] > 0.99)
                    processed[i] = 1;
                In->setPixel(x,(half-1)-i,processed[i]*255);
            }
    
    
        }
    
    
        fftw_destroy_plan(p);
        fftw_free(out);
    }
    

3 个答案:

答案 0 :(得分:6)

这不是一个错误的答案,而是一个循序渐进的程序来调试它。

  1. 您认为这条线的作用是什么? processed[i] = out[i][0]*out[i][0] + out[i][12]*out[i][13]可能不正确:fftw_complex为typedef double fftw_complex[2],因此您只有out[i][0]out[i][1],其中第一个是真实的,第二个是结果的虚部那个垃圾箱。如果数组在内存中是连续的(它是),那么out[i][12]可能与out[i+6][0]相同,依此类推。其中一些越过数组的末尾,添加随机值。

  2. 你的窗口功能是否正确?打印出每个i的windowHanning(i,transform_size)并与参考版本进行比较(例如numpy.hanning或matlab等价物)。这是最可能的原因,你看到的看起来像一个糟糕的窗口函数,有点。

  3. 打印已处理,并与参考版本进行比较(给定相同的输入,当然您必须打印输入并重新格式化以输入到pylab / matlab等)。但是,-60和1e-6是你不想要的软糖因素,同样的效果以不同的方式做得更好。计算如下:

    power_in_db[i] = 10 * log(out[i][0]*out[i][0] + out[i][1]*out[i][1])/log(10)
    
  4. 为同一个i打印power_in_db[i]的值,但为所有x(水平线)打印出值。它们大致相同吗?

  5. 如果到目前为止一切都很好,剩下的嫌疑人就是设置像素值。非常明确地说明裁剪到范围,缩放和舍入。

    int pixel_value = (int)round( 255 * (power_in_db[i] - min_db) / (max_db - min_db) );
    if (pixel_value < 0) { pixel_value = 0; }
    if (pixel_value > 255) { pixel_value = 255; }
    
  6. 再次,在水平线上打印出值,并与pgm中的灰度值进行比较(手动,使用photoshop或gimp中的颜色选择器或类似颜色)。

    此时,您将从头到尾验证所有内容,并可能发现错误。

答案 1 :(得分:4)

您制作的代码几乎是正确的。所以,你没有给我太多的纠正:

void Spectrogram::process(){
    int transform_size = 1024;
    int half = transform_size/2;
    int step_size = transform_size/2;
    double in[transform_size];
    double processed[half];
    fftw_complex *out;
    fftw_plan p;

    out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * transform_size);


    for (int x=0; x < wavFile->getSamples()/step_size; x++) {

        // Fill the transformation array with a sample frame and apply the window function.
        // Normalization is performed later
        // (One error was here: you didn't set the last value of the array in)
        for (int j = 0, int i = x * step_size; i < x * step_size + transform_size; i++, j++)
            in[j] = wavFile->getSample(i) * windowHanning(j, transform_size);

        p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);

        fftw_execute(p); /* repeat as needed */

        for (int i=0; i < half; i++) {
            // (Here were some flaws concerning the access of the complex values)
            out[i][0] *= (2./transform_size);                         // real values
            out[i][1] *= (2./transform_size);                         // complex values
            processed[i] = out[i][0]*out[i][0] + out[i][1]*out[i][1]; // power spectrum
            processed[i] = 10./log(10.) * log(processed[i] + 1e-6);   // dB

            // The resulting spectral values in 'processed' are in dB and related to a maximum
            // value of about 96dB. Normalization to a value range between 0 and 1 can be done
            // in several ways. I would suggest to set values below 0dB to 0dB and divide by 96dB:

            // Transform all dB values to a range between 0 and 1:
            if (processed[i] <= 0) {
                processed[i] = 0;
            } else {
                processed[i] /= 96.;             // Reduce the divisor if you prefer darker peaks
                if (processed[i] > 1)
                    processed[i] = 1;
            }

            In->setPixel(x,(half-1)-i,processed[i]*255);
        }

        // This should be called each time fftw_plan_dft_r2c_1d()
        // was called to avoid a memory leak:
        fftw_destroy_plan(p);
    }

    fftw_free(out);
}

两个纠正的错误最有可能导致连续转换结果的轻微变化。 Hanning窗口很适合最小化“噪音”,因此不同的窗口不会解决问题(实际上@Alex我已经指出了他的第2点中的第2个错误。但在他的观点3.他添加了一个-Inf -bug as log(0)未定义,如果你的wave文件包含一段精确的0值,就会发生这种情况。为了避免这种情况,常量1e-6足够好了。

没有问,但有一些优化:

  1. p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);放在主循环之外,

  2. 预先计算主循环外的窗口函数,

  3. 放弃数组processed,只使用临时变量一次保存一个谱线,

  4. 可以放弃out[i][0]out[i][1]的两次乘法,以便在下一行中使用常量进行一次乘法。我离开了这个(以及其他东西)供你改进

  5. 多亏了@Maxime Coorevits,可以避免内存泄漏:“每次调用fftw_plan_dft_rc2_1d()内存都由FFTW3分配。在你的代码中,你只能在外面调用fftw_destroy_plan()但实际上,每次请求计划时都需要调用它。“

答案 2 :(得分:2)

Audacity通常不会将一个频率区域映射到一条水平线,也不会将一个采样周期映射到一条垂直线。 Audacity中的视觉效果可能是由于重新采样光谱图以适应绘图区域。