将FFT转换为频谱图

13

我有一个音频文件,我正在遍历该文件并在每个步骤中取512个样本,然后通过FFT传递它们。

我将数据输出为一个514个浮点数的块(使用IPP的ippsFFTFwd_RToCCS_32f_I),其中实部和虚部交错存放。

我的问题是:一旦我拥有这些复数,我该怎么做?目前,我正在对每个值执行以下操作

const float realValue   = buffer[(y * 2) + 0];
const float imagValue   = buffer[(y * 2) + 1];
const float value       = sqrt( (realValue * realValue) + (imagValue * imagValue) );
这样得到的结果可用,但我更希望找到一种方式将值限制在0到1范围内。上述方法的问题在于峰值最终会回到大约9或更高。这意味着事情会变得非常饱和,然后频谱图的其他部分几乎不会出现,尽管当我通过Audition的频谱图运行音频时它们看起来相当强大。我完全承认我并不百分百确定FFT返回的数据是什么(除了它表示我传入的512个样本的长块的频率值)。特别是我对复数表示的理解缺乏。

非常感谢任何建议和帮助!

编辑:仅澄清。我的主要问题是FFT返回的值没有任何意义,如果没有对比例有所了解。有人能指点我如何计算比例吗?

编辑2:通过以下步骤,我得到了非常漂亮的结果:

size_t count2   = 0;
size_t max2     = kFFTSize + 2;
while( count2 < max2 )
{
    const float realValue   = buffer[(count2) + 0];
    const float imagValue   = buffer[(count2) + 1];
    const float value   = (log10f( sqrtf( (realValue * realValue) + (imagValue * imagValue) ) * rcpVerticalZoom ) + 1.0f) * 0.5f;
    buffer[count2 >> 1] = value;
    count2 += 2;
}

在我看来,这甚至比我看过的大多数其他频谱图实现都要好。

我所做的有什么主要错误吗?


1
你在获取复数的大小方面做得很正确。你只需要找出这些(复数)数字的比例尺(0-1,0-255,..?),请参阅FFT函数的文档。如果范围太大,不符合您的喜好,那么像下面建议的那样取幂函数的对数(log())应该会有所帮助。 - Wim
可能对您的使用不是很重要,但您也可以通过将频域值(即从FFT获得的值)除以FFT宽度来归一化它们。(即,FFT越宽,各个频率桶中的值就越大) - Trevor Harrison
5个回答

11

通常为了使FFT的所有信息可见,需要对其幅值取对数。

因此,输出缓冲区的位置告诉您检测到的频率。 复数的大小(L2范数)告诉您检测到的频率有多强,而相位(arctangent)提供的信息在图像空间中比音频空间中更加重要。由于FFT是离散的,所以频率从0到奈奎斯特频率运行。在图像中,第一个项(DC)通常是最大的,因此如果您的目的是归一化,则可以将其用作良好的候选项。我不知道这是否也适用于音频(我怀疑)。


有趣的回应。只需注意,在音频中,通常没有直流值(如果通过您的放大器传递,它会损坏您的扬声器),它纯粹是交流电。 - Wim
无论如何,寻找最大值是一个相当简短的操作(与FFT相比)。 - Raphaël Saint-Pierre
同意使用对数刻度(并找到最大值) - peterchen
@Wim 我很高兴听到我的直觉并不完全失灵。 - McBeth
在音频中,相位对于能够从频谱恢复到原始信号是很重要的,也就是说,你仅从频谱中无法重建出原始信号。但这通常不是你使用频谱的目的 :) - Juan Macek
1
log10( sqrt( real^2 + imag^2 ) ) 绝对会给出更好看的结果... - Goz

7

对于每个512个样本的窗口,您可以像以前一样计算FFT的幅度。每个值表示信号中相应频率的幅度。

mag
 /\
 |
 |      !         !
 |      !    !    !
 +--!---!----!----!---!--> freq
 0          Fs/2      Fs

现在我们需要计算频率。
由于输入信号是实数,FFT 在中间(奈奎斯特分量)是对称的,第一项是直流分量。知道信号采样频率 Fs,奈奎斯特频率为 Fs/2。因此,对于索引 k,相应的频率是 k*Fs/512。
因此,对于每个长度为 512 的窗口,我们可以得到指定频率处的幅度。连续窗口中这些幅度组成了频谱图。

6

提醒大家,我已经在这个问题上做了很多工作。我发现FFT变换之后需要进行归一化处理。

具体方法是将窗口向量中的所有值相加求平均数,得到一个略小于1的值(如果使用矩形窗口则为1)。然后将该数字除以FFT变换后的频率区间数量。

最后将FFT返回的实际数字除以归一化数字即可。振幅值应该在-Inf到1范围内。可以根据需要进行对数等操作,但仍需在已知范围内进行。


5

有几件事情我认为你会觉得很有帮助。

正向傅里叶变换(FFT)的输出结果通常比输入数据要大。可以将其理解为某个特定频率上的所有强度都集中在一个位置,而不是分布在整个数据集中。这是否重要?可能不太重要,因为您总是可以缩放数据以适应您的需求。我曾经编写过一个基于整数的FFT / IFFT对,每次通过都需要重新缩放以防止整数溢出。

您的实际输入数据被转换为几乎是复杂的东西。事实证明,buffer [0]和buffer [n / 2]是实数且独立的。这里有一篇很好的讨论: here

输入数据是随时间等间隔采取的声音强度值。适当地说,它们处于时域。 FFT的输出被称为频域,因为水平轴是频率。垂直比例尺仍然是强度。虽然从输入数据中并不明显,但输入中也存在相位信息。尽管所有声音都是正弦波,但没有任何东西可以固定正弦波的相位。这个相位信息在频域中出现为单个复数的相位,但通常我们不关心它(有时我们也会!)。这取决于您正在做什么。计算

const float value = sqrt((realValue * realValue) + (imagValue * imagValue));

该操作会获取强度信息,但会舍弃相位信息。取对数实质上只是减小了大峰值的幅度。

希望这能有所帮助。


那么,如果我不丢弃相位信息,我该如何使用它?相位如何适用于频谱图? - Goz

1

如果你得到了奇怪的结果,那么需要检查FFT库的文档以查看输出是如何打包的。一些例程使用打包格式,其中实数/虚数值是交错的,或者它们可能从N/2元素开始并绕回。

为了进行健全性检查,建议创建具有已知特征的示例数据,例如Fs/2、Fs/4(Fs =采样频率),并将FFT例程的输出与您预期的内容进行比较。尝试同时创建正弦和余弦,因为这些应该在频谱中具有相同的幅度,但具有不同的相位(即realValue/imagValue将不同,但平方和应该相同)。

如果您打算使用FFT,则确实需要了解其数学原理,否则您可能会遇到其他奇怪的问题,例如混叠。


好的,我已经检查过了该个人资料。我的问题是,如果没有任何关于刻度代表什么的概念,从FFT中得到的数字是毫无意义的。我将更新我的原始问题。 - Goz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接