了解简单 Sin / Cos 的 FFT

信息处理 fft 频谱 阶段 振幅 震级
2022-01-14 05:27:55

这是我在这个论坛上的第一个问题,虽然我在这方面阅读了几个主题并用谷歌搜索了很多,但我找不到我的问题的答案(也许它太基本了)?

对于阅读此线程并提出类似问题的任何人:现在对代码进行了调整,部分答案在此问题中给出。有关解释,请查看下面的答案。

我找到了这个,但它只部分涵盖了我的答案: 通过一个简单的示例了解 FFT 相位谱 如果您有涵盖该主题的优秀文献,如果您能分享,我将不胜感激。

因此,假设我在时域中有一个非常基本的信号,例如:5*sin(3*2*pi*x-2),幅度=5,频率=3,位移为 2。

import matplotlib.pyplot as plt
import numpy as np
npts  = 100
tmax  = 10
dt = tmax / npts[![enter image description here][1]][1]
fs = 1 / dt
t     = np.linspace(0, tmax-dt, npts)
y     = 5 * np.sin(3 * 2*np.pi * t - 2)
N = y.size

在此处输入图像描述

freq = np.fft.fftshift(np.fft.fftfreq(N)*fs)
yfft = np.fft.fft(y)
yfft = np.where(abs(yfft) < 1.0e-10, 0, yfft)
magn = np.fft.fftshift(np.abs(yfft*dt)/(N*dt))
phase = np.fft.fftshift(np.angle(yfft))
psd = np.fft.fftshift(np.abs(yfft*dt)**2/(N*dt))
yfft = np.fft.fftshift(np.fft.fft(y))

如何获得频率 3 和 2 的偏移来显示?这甚至可能吗?

fig, ax = plt.subplots(3, 2)
fig.tight_layout()
ax[0,0].plot(t, y)
ax[0,0].set_title('Time-Domain')
ax[0,1].plot(freq, yfft)
ax[0,1].set_title('Frequency-Domain')
ax[1,0].plot(freq, magn)
ax[1,0].set_title('Magnitude')
ax[1,1].plot(freq, phase)
ax[1,1].set_title('Phase')
ax[2,0].plot(freq, psd)
ax[2,0].set_title('PSD')

在此处输入图像描述

最好的问候乔纳斯

3个回答

创建频率向量

fft() 输出的排列取决于您的 fft 是使用奇数点还是偶数点。我认为这篇文章很好地总结了频率的排列方式。看看它。

  • 由于您使用的是偶数个点,因此Nyquist频率$F_N = F_s/2$出现在 fft 的输出中,并且是频率向量中的最大值。您的采样频率为$N/T=100/10=10\;\textrm{Hz}$,因此向量中的最大可检测频率将为$F_N=5\;\textrm{Hz}$
  • 其次,无论您使用偶数点还是奇数点,DC 值始终是 fft 输出中的第一个条目。
  • 频率分辨率$df = F_s/N$是频率空间中 fft 输出箱之间的间隔。对你来说,这将是$0.1\;\textrm{Hz}$
  • 最后,以对称、居中的方式显示频谱也是最常见的,DC 频率位于中间 - 函数 fftshift 会为您执行此操作。

考虑到所有这些,与您的 fft 输出相对应的频率向量(在您应用 fftshift() 使其居中之后)将是 $$ f = [-F_N : df : (F_N-df)] $$ 有一些讨论关于如果您有兴趣,请在此线程中使用fftshift 。请注意,在 fftshift() 之后,对应于 Nyquist 的分量$F_N$被放置在向量的开头,因此从 fftshift() 的角度来看,它被认为是频率 - 末尾没有 Nyquist正频率中的矢量(然后相反,为了对称,直流分量被视为正频率)。

用正确的比例绘制频谱

就个人而言,我更喜欢根据波的功率(能量变化率)而不是 fft bin 中组件的幅度来处理这些事情。这只是帮助我跟踪单位,并保持物理状态。以下代码使用简单的 周期图估计为您的信号计算功率谱密度$S_{xx}$(我假设您的信号以伏特为单位 - 可能是也可能不是真的 - 但可以是任何东西:温度,股票价格,等等)。抱歉,我的代码是在 Matlab 中,而不是 Python,但希望您可以毫无问题地按照这些步骤操作。

N = 100;   % Number of samples
T = 10;    % Record window duration
dt = T/N;  % Sampling period
Fs = 1/dt; % Sampling frequency

t = 0:dt:(T-dt); % Time vector for sampling

% generate samples at the specified times
x = 5*sin(2*pi*3*t - 2); % units: [V]

df = Fs/N; % frequency resolution (bin width in frequency space)

% generate frequency vector for 2-sided spectrum (NOTE, this arrangement
% only works for even number of points - otherwise, use f = -(Fs/2-df/2):df:(Fs/2-df/2))
f = -(Fs/2):df:(Fs/2-df);

% Calculate Fourier transform (approximating CFT), and shift DC term to centre
X = fftshift(fft(x))*dt; % units: [V sec]

X((abs(X)<1e-10)) = 0; % kill values below threshold, so phase is well-behaved

% Calculate power spectral density using periodogram estimation
Sxx = (X.*conj(X))/(N*dt); % units: [V^2 / Hz]

figure; stem(f,Sxx)      % Plot power spectral density
figure; stem(f,angle(X)) % Plot phase

在此处输入图像描述 在此处输入图像描述 在此处输入图像描述

代码中的变量$X$是连续傅里叶变换积分的离散近似值,这就是为什么它乘以采样周期$dt$(近似值是黎曼和)。然后可以由此计算周期图 - 再次,在其连续对应物的离散版本中,我认为在这个答案中很好地解释了这一点注意,Matlab 也有一个内置函数 periodogram(),它也可以进行计算。

您可以看到在$\pm 3\;\textrm{Hz}$处有正确的峰值,与您的输入信号完全匹配(请记住,正弦波实际上是复指数的总和)。

-- 请注意,在信号处理中,“能量”定义为 [信号平方乘以时间],因此功率为 [能量/时间],然后是 [信号平方]。这就是为什么您在以$[V^2]$ 为单位给出的答案中看到“力量”的原因。要找到以 [Watts] 为单位的物理功率,您只需按您正在驱动的任何负载的电阻进行缩放。这通常在信号分析中设置为 1——

验证 Parseval 定理

您还可以通过检查时域和频域中的功率和能量是否相等来验证 fft 输出的缩放比例,因为它们应该符合Parseval 定理请记住,如果您对所有频率的功率谱密度进行积分,您应该获得输入信号中存在的总功率。例如,使用

Energy_timedomain = sum(x.*conj(x))*dt  = 125  [V^2 sec]
Power_timedomain = Energy_timedomain/T  = 12.5 [V^2]
Energy_freqdomain = sum(X.*conj(X))*df  = 125  [V^2 sec]
Power_freqdomain = sum(Sxx)*df          = 12.5 [V^2]

请注意,如果您为输入信号采用更长的时间窗口持续时间$T$ ,则“能量”将增加(因为它更多),但平均功率将保持不变(因为这是活力)。

恢复幅度

功率谱密度图中每个峰值的值为$62.5\;[\textrm{V}^2 / \textrm{Hz}]$要将其转换为功率,我们需要乘以 bin 宽度(本质上是对该 bin 上的功率谱密度进行积分)。在您的情况下,箱宽度为$df=F_s/N = 10\;\textrm{Hz}/100 = 0.1\;\textrm{Hz}$因此,该频率下输入信号的总功率为 $$ P = 2 \times 62.5\;[V^2/\textrm{Hz}] \times 0.1\;[\textrm{Hz}] = 12.5\; [V^2] $$ 其中$2$的因数是因为有 2 个峰值 - 频谱的负部分功率的一半和正部分的功率的一半。请注意,如果您的输入信号是真实的,那么输出频谱将始终是对称的,并且您经常会看到被丢弃的负频率(不包含任何额外信息)。如果您选择丢弃负频率,那么您应该将剩余的唯一正频率中的功率乘以 2 以进行补偿(但不要将 DC 或 Nyquist 乘以 2,因为它们中的每一个都只有一个在 fft 输出中-它们没有负面的对应物)。

我们知道,对于正弦曲线,功率和幅度$A$$P = A^2/2$相关(同样,只需将任何负载电阻设置为$1$)。因此,重新排列这个,你的 3 Hz 波的振幅是

$$A = \sqrt{2\times 12.5} = 5\;[V]$$

恢复阶段

正如 Hilmar 所说,$-2$ 的相位可以通过获取$3\;\textrm{Hz}$处的相位值(即 2.712)并加上$\pi/2$然后减去$2\pi$来恢复$\pi/2$是因为您的输入信号是正弦波,它本质上已经包含相移,因为 $$ \sin(2\pi f_0 t + \phi) = \cos(2\pi f_0 t + ( \phi - \pi/2)) = \cos(2\pi f_0 t + \phi_{\textrm{fft}}) $$ 所以你需要将$\pi/2$添加到 fft( ) 得到你的输入阶段 $$\phi = \phi_{\textrm{fft}} + \pi/2 = 2.712 + \pi/2 = 4.283.$$ 最后的$2\pi$减法是因为在执行此操作后,您会得到大于$\pi$的值,因此您只需解开相位以将其恢复到$[-\pi ... +\pi]$范围内。

最后的注意事项 在这种情况下一切都很好,因为由于采样率、点数、时间窗口和输入频率波的组合,所有信号能量都恰好落在单个 fft bin 中(参见此处)。如果在将输入信号传递给 fft() 之前对输入信号应用了任何窗口零填充,事情就会变得更加困难。当你走到那一步时,这里这里都有一些启发性的答案。

知道 FFT 中的每个 bin 是指数$e^{j\omega t}$的值,而不是正弦或余弦。

因此,对于 OP 的情况$5sin(2\pi 3 x-2)$就指数而言(参见欧拉恒等式),这是:

$$\frac{5}{2j}e^{j(6\pi x -2)} - \frac{5}{2j}e^{-j(6\pi x -2)}$$

每个都是 DFT bin,其幅度和相位由上述指数给出,只要 DFT 通过$1/N$归一化。$1/j$只是$e^{-j\pi/2}$的附加相移,因此例如,第一个 bin 的幅度为$2.5$,相位为$-2-\pi/2美元)。

此外,DFT 中的频率轴具有$k$的索引,对于 DFT 中的$ N$个样本,它从$0$$N-1$,对应于从$0$$(N-1)的频率轴/N$ Hz,循环旋转,使得频谱的上部同样是负频率。这意味着对于$10$ Hz的采样率和$100$的样本,上面给出的$-3$ Hz 频率等价于$+7$ Hz:

$e^{-j(2\pi fx -\phi)}$采样时为$e^{(-j2\pi fn/f_s - \phi)}$

所以采样时$ \frac{5}{2j}e^{-j(6\pi x -2)}$上面的值是$\frac{5}{2j} e^{-j(2\pi 3 n/10 -2)}$

这相当于$\frac{5}{2j}e^{-j(2\pi (10-3)n/10 -2)} = \frac{5}{2j}e^{-j(2\ π 7 n/10 -2)} $

所以底线是,对应于 3 Hz 和 7 Hz 的两个区间将具有由给定正弦波的欧拉展开给出的幅度和相位,当 DFT 适当地按$1/N$缩放时。


为 OP 的示例显示此内容:

对于您的情况,您的$N= 100$的采样率似乎为 $f_s = 10$,因此要确保整数个周期(下面的 MATLAB 代码):

N = 100
fs = 10
n= 0:N-1;
f = 3;
y = 5*sin(2*pi*f*n/fs-2);

如果您使用归一化的 DFT,它将答案按$1/N$缩放,您会得到以下幅值图,我们可以清楚地看到上面推导的 2.5 系数和两个音调(由于 DFT 的周期性属性超过 5 Hz 的上频谱表示负频率轴:5 Hz 至 10 Hz 与 -5 Hz 至 0 Hz 相同)。在这里,我使用 n/fs 将 bin 编号 k 转换为频率:

震级

我们可以从图形上看到这两个 bin 的相位从这些 FFT bin 的相位中得到,如下所示,通过 fft 的复数(实数-虚数)图。我们将这两个 bin 中的每一个视为幅度为 2.5 且相位为$\pm 2.71$弧度的相量,这与$\pm 2\pi-2.71 = 3.57$弧度或预期的$\pm(- 2-\pi/2) = \pm 3.57$弧度:

复杂情节

有几点需要考虑。

如果您创建一个在时间窗口中具有整数个周期的正弦波,这一切都会变得容易得多。您可以通过将时间向量构建为

t = np.linspace(0, tmax*(1-1/npts), npts) 

如果你这样做,你可以按如下方式处理:

  1. 您正在使用长度为 10 秒的时间窗口,通过 100 个抽头进行采样,这为您提供了 0.1 秒的时间分辨率或 10 赫兹的采样率。
  2. FFT 的结果有 100 个频率分档,分档间距为 0.1Hz。您将在 bin 30 = $3Hz/0.1Hz$ AND bin 70 = $(10Hz-3Hz)/0.1Hz$中找到与您的 3 Hz 正弦波对应的 FFT 值 仓 30 表示正频率分量,仓 70 表示负频率。
  3. 每个 bin 中的幅度将为 FFTSize*Amplitude/2 = 250。
  4. 正 bin 处的相位将为 2.7124。为了获得 -2 的原始相位,您需要添加$\pi/2$以补偿正弦和余弦的差异,并减去$2\pi$以展开相位。

如果你的正弦波没有整数个周期,事情会变得复杂得多,在你完全理解整数情况之前我不会去那里。