计算功率谱密度

时间:2014-07-11 10:59:23

标签: c signal-processing fftw

我试图通过使用fftw3库来获取真实数据集的PSD 为了测试我写了一个如下所示的小程序,它产生一个跟随正弦函数的信号

#include <stdio.h>
#include <math.h>
#define PI 3.14

int main (){
    double  value= 0.0;
    float frequency = 5;
    int i = 0 ; 
    double time = 0.0;
    FILE* outputFile = NULL;
    outputFile = fopen("sinvalues","wb+");
    if(outputFile==NULL){
        printf(" couldn't open the file \n");
        return -1;
    }

    for (i = 0; i<=5000;i++){

        value =  sin(2*PI*frequency*zeit);
        fwrite(&value,sizeof(double),1,outputFile);
        zeit += (1.0/frequency);
    }
    fclose(outputFile);
    return 0;

}

现在我正在阅读上述程序的输出文件并尝试计算其PSD,如下所示

#include <stdio.h>
#include <fftw3.h>
#include <complex.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14
int main (){
    FILE* inp = NULL;
    FILE* oup = NULL;
    double* value;// = 0.0;
    double* result;
    double spectr = 0.0 ;
    int windowsSize =512;
    double  power_spectrum = 0.0;
    fftw_plan plan;

    int index=0,i ,k;
    double multiplier =0.0;
    inp = fopen("1","rb");
    oup = fopen("psd","wb+");

    value=(double*)malloc(sizeof(double)*windowsSize);
    result = (double*)malloc(sizeof(double)*(windowsSize)); // what is the length that I have to choose here ? 
        plan =fftw_plan_r2r_1d(windowsSize,value,result,FFTW_R2HC,FFTW_ESTIMATE);

    while(!feof(inp)){

        index =fread(value,sizeof(double),windowsSize,inp);
            // zero padding 
        if( index != windowsSize){
            for(i=index;i<windowsSize;i++){
                    value[i] = 0.0;
                        }

        }


        // windowing  Hann 

        for (i=0; i<windowsSize; i++){
            multiplier = 0.5*(1-cos(2*PI*i/(windowsSize-1)));
            value[i] *= multiplier;
        }


        fftw_execute(plan);


        for(i = 0;i<(windowsSize/2 +1) ;i++){ //why only tell the half size of the window
            power_spectrum = result[i]*result[i] +result[windowsSize/2 +1 -i]*result[windowsSize/2 +1 -i];
            printf("%lf \t\t\t %d \n",power_spectrum,i);
            fprintf(oup," %lf \n ",power_spectrum);
        }

    }
    fclose(oup);
    fclose(inp);
    return 0;

}

我不确定我这样做的正确性,但以下是我获得的结果:

PSD

任何人都可以帮助我追踪上述方法的错误

提前致谢 的 * UPDATE 在hartmut回答之后我已经编辑了代码但仍然得到了相同的结果: PSD2

,输入数据如下:

sinus

更新 增加采样频率后,这里的窗口大小为2048就是我所拥有的: enter image description here更新 在这里使用ADD-ON之后,使用窗口的结果如何: enter image description here

3 个答案:

答案 0 :(得分:3)

您将错误的输出值与功率谱线组合在一起。最后windowsSize / 2 + 1result虚数值的windowsSize / 2 - 1个实数值以相反的顺序排列。这是因为第一个(0Hz)和最后一个(奈奎斯特频率)谱线的虚部是0。

int spectrum_lines = windowsSize / 2 + 1;
power_spectrum = (double *)malloc( sizeof(double) * spectrum_lines );

power_spectrum[0] = result[0] * result[0];
for ( i = 1 ; i < windowsSize / 2 ; i++ )
    power_spectrum[i] = result[i]*result[i] + result[windowsSize-i]*result[windowsSize-i];
power_spectrum[i] = result[i] * result[i];

并且存在一个小错误:您应该仅将窗口函数应用于输入信号而不应用于零填充部分。

添加-ON:

您的测试程序生成5001个正弦信号样本,然后您读取并分析该信号的前512个样本。结果是您只分析一段时间的一小部分。由于信号的硬截止,它包含了大量的能量,几乎无法预测能量水平,因为你甚至不使用PI而只使用3.41,这不足以进行任何可预测的计算。

您需要保证整数个句点完全适合512个样本的分析窗口。因此,您应该在测试信号创建程序中对此进行更改,以使测试信号具有恰好numberOfPeriods个句点(例如numberOfPeriods=1表示sinoid的一个句点具有正好512个样本的句点,2 =&gt; ; 256,3 => 512 / 3,4 = = 128,...)。这样,您就可以在特定的谱线处产生能量。请注意,windowSize在两个程序中必须具有相同的值,因为不同的大小会使这项工作变得毫无用处。

#define PI 3.141592653589793 // This has to be absolutely exact!

int windowSize = 512;        // Total number of created samples in the test signal
int numberOfPeriods = 64;    // Total number of sinoid periods in the test signal
for ( n = 0 ; n < windowSize ; ++n ) {
    value = sin( (2 * PI * numberOfPeriods * n) / windowSize );
    fwrite( &value, sizeof(double), 1, outputFile );
}

答案 1 :(得分:3)

对预期输出功能的一些评论。

  • 您的输入是纯实际值的函数。 DFT的结果具有复杂的值。 所以你必须声明变量不是双精度而是fftw_complex * out。

  • 通常,dft输入值的数量与输出值的数量相同。 然而,dft的输出光谱包含正的复振幅 频率以及负频率。

  • 在纯实际输入的特殊情况下,正频率的幅度是 负频率幅度的共轭复数值。 为此,仅计算正谱的频率, 这意味着复数输出值的数量是一半 实际输入值的数量。

  • 如果输入是简单的正弦波,则频谱仅包含单个频率分量。 这适用于10,100,1000或甚至更多输入样本。 所有其他值均为零。因此,使用大量输入值没有任何意义。

  • 如果输入数据集包含单个句点,则复数输出值为 包含在[1]中。

  • 如果输入数据集包含M个完整句点,则在您的情况5中, 所以结果存储在out [5]

  • 我对您的代码进行了一些修改。为了使事情更清楚。


#include <iostream>
#include <stdio.h>
#include <math.h>
#include <complex.h>
#include "fftw3.h"

int performDFT(int nbrOfInputSamples, char *fileName)
{
    int nbrOfOutputSamples;
    double *in;
    fftw_complex *out;
    fftw_plan p;

    // In the case of pure real input data,
    // the output values of the positive frequencies and the negative frequencies
    // are conjugated complex values.
    // This means, that there no need for calculating both.
    // If you have the complex values for the positive frequencies,
    // you can calculate the values of the negative frequencies just by
    // changing the sign of the value's imaginary part
    // So the number of complex output values ( amplitudes of frequency components)
    // are the half of the number of the real input values ( amplitutes in time domain):
    nbrOfOutputSamples = ceil(nbrOfInputSamples/2.0);

    // Create a plan for a 1D DFT with real input and complex output
    in = (double*) fftw_malloc(sizeof(double) * nbrOfInputSamples);
    out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * nbrOfOutputSamples);
    p = fftw_plan_dft_r2c_1d(nbrOfInputSamples, in, out, FFTW_ESTIMATE);

    // Read data from input file to input array
    FILE* inputFile = NULL;
    inputFile = fopen(fileName,"r");
    if(inputFile==NULL){
        fprintf(stdout,"couldn't open the file %s\n", fileName);
        return -1;
    }
    double value;
    int     idx = 0;
    while(!feof(inputFile)){
        fscanf(inputFile, "%lf", &value);
        in[idx++] = value;       
    }
    fclose(inputFile);

    // Perform the dft
    fftw_execute(p);

    // Print output results
    char outputFileName[] = "dftvalues.txt";
    FILE* outputFile = NULL;
   outputFile = fopen(outputFileName,"w+");
    if(outputFile==NULL){
        fprintf(stdout,"couldn't open the file %s\n", outputFileName);
        return -1;
    }
    double  realVal;
    double  imagVal;
    double  powVal;
    double  absVal;

    fprintf(stdout, "     Frequency  Real       Imag        Abs       Power\n");
    for (idx=0; idx<nbrOfOutputSamples; idx++) {
        realVal = out[idx][0]/nbrOfInputSamples; // Ideed nbrOfInputSamples is correct!
        imagVal = out[idx][1]/nbrOfInputSamples; // Ideed nbrOfInputSamples is correct!
        powVal  = 2*(realVal*realVal + imagVal*imagVal);
        absVal  = sqrt(powVal/2);
        if (idx == 0) {
            powVal /=2;
        }
        fprintf(outputFile, "%10i %10.4lf %10.4lf %10.4lf %10.4lf\n", idx, realVal, imagVal, absVal, powVal);
        fprintf(stdout,     "%10i %10.4lf %10.4lf %10.4lf %10.4lf\n", idx, realVal, imagVal, absVal, powVal);
        // The total signal power of a frequency is the sum of the power of the posive and the negative frequency line.
        // Because only the positive spectrum is calculated, the power is multiplied by two.
        // However, there is only one single line in the prectrum for DC.
        // This means, the DC value must not be doubled.

    }
    fclose(outputFile);


    // Clean up
    fftw_destroy_plan(p);
    fftw_free(in); fftw_free(out);

    return 0;

}

int main(int argc, const char * argv[]) {
    // Set basic parameters
    float   timeIntervall = 1.0;     // in seconds
    int     nbrOfSamples = 50;      //  number of Samples per time intervall, so the unit is S/s
    double  timeStep = timeIntervall/nbrOfSamples; // in seconds
    float   frequency = 5;          // frequency in Hz

    // The period time of the signal is 1/5Hz = 0.2s
    // The number of samples per period is: nbrOfSamples/frequency = (50S/s)/5Hz = 10S
    // The number of periods per time intervall is: frequency*timeIntervall = 5Hz*1.0s = (5/s)*1.0s = 5



    // Open file for writing signal values
    char fileName[] = "sinvalues.txt";
    FILE* outputFile = NULL;
    outputFile = fopen(fileName,"w+");
    if(outputFile==NULL){
        fprintf(stdout,"couldn't open the file %s\n", fileName);
        return -1;
    }

    // Calculate signal values and write them to file
    double  time;
    double  value;
    double  dcValue = 0.2;
    int idx = 0;
    fprintf(stdout, "     SampleNbr   Signal value\n");
    for (time = 0; time<=timeIntervall; time += timeStep){
        value =  sin(2*M_PI*frequency*time) + dcValue;
        fprintf(outputFile, "%lf\n",value);
        fprintf(stdout, "%10i %15.5f\n",idx++, value);
    }
    fclose(outputFile);

    performDFT(nbrOfSamples, fileName);
    return 0;

}
  • 如果dft的输入是纯实的,则输出在任何情况下都是复杂的。 所以你必须使用计划r2c(RealToComplex)。
  • 如果信号为sin(2 * pi * f * t),从t = 0开始,频谱包含单个频率线 在f,这是纯粹的想象。
  • 如果符号具有相位偏移,例如sin(2 * pi * f * t + phi),则单行的值很复杂。
  • 如果采样频率为fs,则输出频谱的范围为-fs / 2 ... + fs / 2。
  • 正负频率的实部是相同的。
  • 正负频率的虚部具有相反的符号。 这被称为共轭复合物。
  • 如果您具有正谱的复数值,则可以计算其值 通过改变虚部的符号来消极。 因此,不需要计算正和负的扫描。
  • 一个边带保存所有信息。 因此,计划r2c中的输出样本数是该数字的一半+ 1 输入样本。
  • 要获得频率的功率,您还必须考虑正频率 作为负频率。但是,计划r2c仅提供正确的正半 光谱。所以你必须将正面的功率加倍才能获得总功率。

    顺便说一句,fftw3软件包的文档很好地描述了计划的用法。 你应该花时间来阅读手册。

答案 2 :(得分:1)

我不确定你的问题是什么。根据提供的信息,您的结果似乎合理。

您必须知道,PSD是自相关函数的傅里叶变换。使用正弦波输入,您的AC功能将是周期性的,因此PSD将具有色调,就像您绘制的那样。

我的“回答”实际上是一些关于调试的启动者。如果我们可以发布方程式,那么所有参与者都会更容易。你可能知道这些天SE上有一个信号处理部分。

首先,您应该给我们一个AC功能图。您显示的PSD的反向FT将是周期性音调的线性组合。

其次,尝试删除窗口,只需将其设置为方框或跳过该步骤即可。

第三,尝试用FFT替换DFT(我只浏览了fftw3库文档,也许这是一个选项)。

最后,尝试输入白噪声。您可以使用Bernoulli dist,或仅使用Gaussian dist。 AC将是delta函数,尽管样本AC不会。这应该给你一个(样本)白色PSD分布。

我希望这些建议有所帮助。