我试图通过使用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;
}
我不确定我这样做的正确性,但以下是我获得的结果:
任何人都可以帮助我追踪上述方法的错误
提前致谢 的 * UPDATE 在hartmut回答之后我已经编辑了代码但仍然得到了相同的结果:
,输入数据如下:
更新 增加采样频率后,这里的窗口大小为2048就是我所拥有的: 的更新 在这里使用ADD-ON之后,使用窗口的结果如何:
答案 0 :(得分:3)
您将错误的输出值与功率谱线组合在一起。最后windowsSize / 2 + 1
和result
虚数值的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;
}
要获得频率的功率,您还必须考虑正频率 作为负频率。但是,计划r2c仅提供正确的正半 光谱。所以你必须将正面的功率加倍才能获得总功率。
顺便说一句,fftw3软件包的文档很好地描述了计划的用法。 你应该花时间来阅读手册。
答案 2 :(得分:1)
我不确定你的问题是什么。根据提供的信息,您的结果似乎合理。
您必须知道,PSD是自相关函数的傅里叶变换。使用正弦波输入,您的AC功能将是周期性的,因此PSD将具有色调,就像您绘制的那样。
我的“回答”实际上是一些关于调试的启动者。如果我们可以发布方程式,那么所有参与者都会更容易。你可能知道这些天SE上有一个信号处理部分。
首先,您应该给我们一个AC功能图。您显示的PSD的反向FT将是周期性音调的线性组合。
其次,尝试删除窗口,只需将其设置为方框或跳过该步骤即可。
第三,尝试用FFT替换DFT(我只浏览了fftw3库文档,也许这是一个选项)。
最后,尝试输入白噪声。您可以使用Bernoulli dist,或仅使用Gaussian dist。 AC将是delta函数,尽管样本AC不会。这应该给你一个(样本)白色PSD分布。
我希望这些建议有所帮助。