如何使用FFT计算数据的频率?

15

我想知道数据的频率。我有一点想法,可以使用FFT来完成,但我不确定如何做。一旦我将整个数据传递给FFT,它就会给我2个峰值,但我该如何得到频率?

非常感谢提前。


1
FFT将为您提供信号正弦分量的频率。如果您想测量实际信号(任何形状)的频率,则必须忘记FFT并使用样本扫描进行零交叉或峰峰搜索等...这在很大程度上取决于信号的形状和偏移量。顺便说一下,在FFT上,您会得到2个峰值,其中一个是第一个峰值的镜像(如果输入信号在实域上),因此请忽略FFT的后半部分。 - Spektre
6个回答

17

以下是您可能要查找的内容:

当您谈论计算信号频率时,可能不太关心组成正弦波。这就是FFT给您的东西。例如,如果您求和sin(2*pi*10x)+sin(2*pi*15x)+sin(2*pi*20x)+sin(2*pi*25x),您可能想将“频率”检测为5(查看此函数的图表)。但是,此信号的FFT将检测到频率5的0幅度。

您可能更感兴趣的是信号的周期性。也就是说,信号变得最像自己的间隔。因此,您最有可能想要的是自相关。查一下。这基本上会给您一个度量,即经过一定量的移动后,信号与自身相似程度的度量。因此,如果在自相关中找到峰值,则表示信号在移位一定量后与自身匹配良好。它背后有很多很酷的数学,如果您有兴趣,请查阅,但如果您只想让它工作,请执行以下操作:

  1. 使用平滑窗口(余弦函数即可),对信号进行加权。窗口的大小至少应是你希望检测到的最大周期的两倍,如果为三倍,则结果更佳(如有疑问,请参考http://zone.ni.com/devzone/cda/tut/p/id/4844)。

  2. 进行FFT变换(确保FFT大小是窗口大小的两倍,第二个半部分用零填充。如果FFT大小只是窗口大小,则实际上会进行圆形自相关,这不是你想要的。请参考https://en.wikipedia.org/wiki/Discrete_Fourier_transform#Circular_convolution_theorem_and_cross-correlation_theorem)。

  3. 将FFT的所有系数替换为它们的平方值(real^2+imag^2)。这实际上是进行自相关。

  4. 进行iFFT变换。

  5. 找到iFFT中的最大峰值。这是波形的最强周期性。你可以在选择峰值时更聪明一些,但对于大多数目的来说,这应该足够了。要找到频率,只需取f=1/T。


感谢您在这里提供清晰的答案,我查阅了很多关于这个主题的信息,这为我解决了更多的问题。 - Adamski

13
假设 x [n] = cos(2*pi*f0*n/fs) ,其中f0是您正弦波的频率(以赫兹为单位),n = 0:N-1fs是每秒样本数时的采样率。
X = fft(x)xX都具有长度N。 假设Xn0N-n0处具有两个峰值。
正弦波频率f0 = fs * n0 / N 赫兹。 示例fs为每秒8000个样本,N为16000个样本。 因此,x持续两秒钟。
假设X = fft(x)在2000和14000(= 16000-2000)处有峰值。 因此,f0 = 8000 * 2000/16000 = 1000 Hz。

这是正确的。但请注意,由于fft设计(蝴蝶网络)的原因,fft算法给出的数据经常会错位。在解释这些值之前,必须先进行移位。 - ypnos

8
如果您有一个频率为一的信号(例如:

y = sin(2 pi f t)

使用:

  • y时间信号
  • f中心频率
  • t时间

然后你会得到两个峰值,一个在与f对应的频率处,另一个在与-f对应的频率处。

因此,为了得到一个频率,可以丢弃负频率部分。它位于正频率部分之后。此外,数组中的第一个元素是直流偏移量,因此频率为0。(请注意,此偏移通常大于0,因此其他频率成分可能会被压制。)

代码示例(我用Python编写了它,但在C#中同样简单):

import numpy as np
from pylab import * 
x = np.random.rand(100) # create 100 random numbers of which we want the fourier transform
x = x - mean(x) # make sure the average is zero, so we don't get a huge DC offset.
dt = 0.1 #[s] 1/the sampling rate
fftx = np.fft.fft(x) # the frequency transformed part
# now discard anything  that we do not need..
fftx = fftx[range(int(len(fftx)/2))]
# now create the frequency axis: it runs from 0 to the sampling rate /2
freq_fftx = np.linspace(0,2/dt,len(fftx))
# and plot a power spectrum
plot(freq_fftx,abs(fftx)**2)
show()

现在频率位于最大峰值处。

将平均值减去1来驯服直流偏移。在我看来,如果您使用采样频率来索引频率而不是采样周期,那么会更清晰明了。 - Colbert Sesanker
1
不错!但是答案中有一个笔误:绘图线应该是plot(freq_fftx,abs(fftx)**2)。 - Michael Tiemann
谢谢!已经修复了。 - Dirklinux

6
如果您正在查看最常用的FFT类型的幅度结果,则实际数据的强正弦频率成分将显示在两个位置上,一次在底部的半部分,再加上其复共轭镜像在顶部的半部分。这两个峰都代表相同的谱峰和相同的频率(对于严格的实际数据)。如果FFT结果bin编号从0(零)开始,则由FFT结果底部的bin表示的正弦成分的频率最有可能。
Frequency_of_Peak = Data_Sample_Rate * Bin_number_of_Peak / Length_of_FFT ;

请确保在上述方程中使用正确的单位(以获得每秒,每两周,每千秒差距等周期单位)。请注意,除非数据波长是FFT长度的精确整数子倍,否则实际峰值将位于bin之间,因此在多个相邻的FFT结果bin之间分配能量。 因此,您可能需要进行插值以更好地估计频率峰值。 为了找到更精确的频率估计,常见的插值方法是三点抛物线和Sinc卷积(几乎与使用零填充更长的FFT相同)。

1
假设您使用离散傅里叶变换来查看频率,则必须小心地将标准化频率解释为物理频率(即赫兹)。
根据 FFTW教程关于如何计算信号功率谱的说明:
#include <rfftw.h>
...
{
     fftw_real in[N], out[N], power_spectrum[N/2+1];
     rfftw_plan p;
     int k;
     ...
     p = rfftw_create_plan(N, FFTW_REAL_TO_COMPLEX, FFTW_ESTIMATE);
     ...
     rfftw_one(p, in, out);
     power_spectrum[0] = out[0]*out[0];  /* DC component */
     for (k = 1; k < (N+1)/2; ++k)  /* (k < N/2 rounded up) */
          power_spectrum[k] = out[k]*out[k] + out[N-k]*out[N-k];
     if (N % 2 == 0) /* N is even */
          power_spectrum[N/2] = out[N/2]*out[N/2];  /* Nyquist freq. */
     ...
     rfftw_destroy_plan(p);
}

注意它可以处理不是偶数长度的数据。特别要注意如果给定数据长度,FFTW将会给出对应于奈奎斯特频率(采样率除以2)的"bin"。否则,你就得不到它(即最后一个"bin"刚好在奈奎斯特频率下方)。 MATLAB示例类似,但他们选择了长度为1000(一个偶数)的例子。
N = length(x);
xdft = fft(x);
xdft = xdft(1:N/2+1);
psdx = (1/(Fs*N)).*abs(xdft).^2;
psdx(2:end-1) = 2*psdx(2:end-1);
freq = 0:Fs/length(x):Fs/2;

一般而言,这取决于实现(DFT的)。您应该创建一个已知频率的测试纯正弦波,然后确保计算得出相同的数字。

-9

频率=速度/波长。

波长是两个峰之间的距离。


频域中的两个峰值。 - Steve Tjoa
Steve:不完全正确。如果您的数据确实是具有单个最大值的周期性数据,则峰值之间的距离将给出确切的1/f。然而,通常您处理的是半周期性数据,对于这种数据,应用于周期性数据的标准数学分析是不起作用的。 - Jeremy Salwen
我早期评论中的意思是,作者在频域获得了两个峰值。这个回答错误地将两个峰值解释为时域中的。 - Steve Tjoa

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