对于项目,我需要能够从.WAV文件生成频谱图。我已经阅读了以下内容:
在下图中,您可以看到使用hanning窗口功能的10000 Hz正弦波的两个频谱图。在左侧,您会看到由audacity生成的频谱图,右侧是我的版本。你可以看到我的版本有更多的线/噪音。这是不同箱子的泄漏?如何获得像大胆产生的清晰图像。我应该做一些后期处理吗?我还没有做任何规范化,因为不完全了解如何这样做。
更新
我找到了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.;
}
这样做之后我得到了这个图像(顺便说一句,我已经颠倒了颜色):
然后我看一下我的声音库和教程之一提供的输入样本的差异。我的方式更高,所以我手动归一化是将其除以因子32767.9。然后我去看看这个看起来相当不错的图片。但除以这个数字似乎是错误的。我希望看到一个不同的解决方案。
以下是完整的相关源代码。
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);
}
答案 0 :(得分:6)
这不是一个错误的答案,而是一个循序渐进的程序来调试它。
您认为这条线的作用是什么? 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]
相同,依此类推。其中一些将越过数组的末尾,添加随机值。
你的窗口功能是否正确?打印出每个i的windowHanning(i,transform_size)并与参考版本进行比较(例如numpy.hanning或matlab等价物)。这是最可能的原因,你看到的看起来像一个糟糕的窗口函数,有点。
打印已处理,并与参考版本进行比较(给定相同的输入,当然您必须打印输入并重新格式化以输入到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)
为同一个i打印power_in_db[i]
的值,但为所有x(水平线)打印出值。它们大致相同吗?
如果到目前为止一切都很好,剩下的嫌疑人就是设置像素值。非常明确地说明裁剪到范围,缩放和舍入。
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; }
再次,在水平线上打印出值,并与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足够好了。
没有问,但有一些优化:
将p = fftw_plan_dft_r2c_1d(transform_size, in, out, FFTW_ESTIMATE);
放在主循环之外,
预先计算主循环外的窗口函数,
放弃数组processed
,只使用临时变量一次保存一个谱线,
可以放弃out[i][0]
和out[i][1]
的两次乘法,以便在下一行中使用常量进行一次乘法。我离开了这个(以及其他东西)供你改进
多亏了@Maxime Coorevits,可以避免内存泄漏:“每次调用fftw_plan_dft_rc2_1d()
内存都由FFTW3分配。在你的代码中,你只能在外面调用fftw_destroy_plan()
但实际上,每次请求计划时都需要调用它。“
答案 2 :(得分:2)
Audacity通常不会将一个频率区域映射到一条水平线,也不会将一个采样周期映射到一条垂直线。 Audacity中的视觉效果可能是由于重新采样光谱图以适应绘图区域。