为什么重采样会改变这个 FFT 输出?

信息处理 fft Python
2022-02-12 15:00:33

我一直在研究以下代码,但仍然无法解释其输出。我真的希望有人能解释一下。

让我从基线开始。假设我有一个 20 秒的正弦曲线,频率分量为 [1,2,3,4....500Hz]。该信号的 Fs=1024Hz,所以它的 FFT 输出如下所示。FFT 幅度也被缩放以保持 Parseval 定理。在这种情况下,频率分辨率为 Fs/nfft = 1.0 Hz/bin,因此 FFT 输出自身模糊,使输出看起来像一条平线,这是预期的。

# Python code

import numpy as np
import matplotlib.pyplot as plt

dur = 20
fs = 1024
t1 = np.arange(0, dur, 1/fs)    
x1 = np.zeros_like(t1)
for f in np.arange(1, 500):
    x1 += np.sin(2*np.pi*f*t1)


plt.figure(figsize=(8,4))
plt.title('Signal')
plt.plot(t1[:100], x1[:100])
plt.xlabel('sec')
plt.grid()
plt.show()


nfft = 1024
df = fs / nfft
X1 = np.fft.fft(x1, nfft)[:nfft//2]

# Scale FFT amplitude to preserve Parseval's Theorem
X1 = X1 * np.sqrt(2 / nfft)
f1 = np.arange(0, nfft//2) * df

plt.figure(figsize=(8,4))
plt.title(f'Baseline FFT, Freq Resolution={df}Hz')
plt.plot(f1, np.abs(X1))
plt.grid()
plt.xlim([0, nfft/2])
plt.xlabel('Hz')
plt.show()

p1 = np.sum(x1[:nfft]**2)
p2 = np.sum(np.abs(X1)**2)
print('Parseval check:', np.allclose(p1, p2))

在此处输入图像描述

棘手的部分来了。信号按以下方式重新采样x2 = x1[::10](下采样 10 倍?)。如果这真的是下采样,那么新的有效 Fs 应该变为 1024/10 = 102.4Hz,然后奈奎斯特频率变为,因此 FFT 输出应该只包含高达 51Hz 的频率分量。至少从理论上来说是这样。但是,以下输出表明并非如此。我可以看到频率分辨率从 1.0Hz 显着提高到 0.1Hz,但输出仅包含高达 12Hz 的频率分量,而不是 51Hz。为什么会这样?我想念什么?任何人都可以解释一下吗?谢谢102.4/2=51.2Hz

step = 10
x2 = x1[::step]

plt.figure(figsize=(8,4))
plt.title('Downsampled Signal')
plt.plot(t1[:100], x2[:100])
plt.xlabel('sec')
plt.grid()
plt.show()


nfft = 1024
fs2 = fs / step
df2 = fs2 / nfft
X2 = np.fft.fft(x2, nfft)[:nfft//2]

# To preserve Parseval's Theorem
X2 = X2 * np.sqrt(2/nfft)
f2 = np.arange(0, nfft//2) * df2

plt.figure(figsize=(8,4))
plt.title(f'Downsampled FFT, Freq Resolution={df2}Hz')
plt.plot(f2, np.abs(X2))
plt.grid()
plt.xlim([0, 50])
plt.show()

p1 = np.sum(x2[:nfft]**2)
p2 = np.sum(np.abs(X2)**2)
print('Parseval check:', np.allclose(p1, p2))

在此处输入图像描述

1个回答

我认为这里发生了几件事。

首先,基线图的平坦度有点欺骗性,因为您添加的音调 (1 - 500 Hz) 几乎都是 fft 基频的精确倍数 (fs/nfft = 1.024 Hz/bin)。

例如,如果您将 nfft 增加 10 倍,则零填充 FFT 的更精细频率分辨率将显示类似于您的新 FFT 图的内容。这是与nfft = 10240

在此处输入图像描述

因为音调很好地集中在 FFT 箱上,所以没有频谱泄漏。如果您要更改音调,例如使它们处于 0.75 Hz 的倍数,您会看到如下内容:

在此处输入图像描述

它看起来像一团糟,但真正发生的是每个音调都被涂抹在几个垃圾箱中,并且这些音调具有建设性和破坏性的干扰。这是 0.75 Hz 的单音nfft=10240

在此处输入图像描述

发生这种频谱泄漏是因为 FFT 假设其输入永远重复,因此,如果您有一个周期是 FFT 基频的倍数的正弦曲线,那么一切看起来都如您所料,但如果不是,则会出现不连续性在第一个样本和最后一个样本之间。可以使用窗口函数减轻这种影响:https ://en.wikipedia.org/wiki/Window_function 。

但是,您看到的主要问题是混叠的结果。如果要避免混叠,则需要在下采样之前对信号进行低通滤波。否则,原始信号中高于下采样信号的奈奎斯特速率的任何频率成分都会混叠。在您的情况下,下采样后的采样率为 1024/10 = 102.4 Hz,因此原始信号中高于 51.2 Hz 的所有音调将混叠到低于 51.2 Hz 的频率,并干扰这些频率的真实信号。

将您的下采样因子更改为 8 会使效果更容易展示,因为混叠音调将落在其他真实音调之上。如果您的原始信号仅具有低于 64 Hz 的音调,则所有音调都存在于下采样信号中:

for f in np.arange(1, 64):
...
step = 8
x2 = x1[::step]   # down-sampling by a factor of 10??
fs2 = fs/step

在此处输入图像描述

现在让我们添加一个 65 Hz 的音调:

for f in np.arange(1, 66):

63 Hz 或 65 Hz 不再有任何音调。65 Hz 音调与 63 Hz 的真实音调发生混叠和破坏性干扰,以将其抵消:

在此处输入图像描述

现在,让我们将原始音调的相位改变 180 度:

for f in np.arange(1, 66):
x1 += np.sin(2*np.pi*f*t1 + np.pi/2)

在此处输入图像描述

65 Hz 处仍然没有音调,但 65 Hz 处的音调现在正在建设性干扰,使 63 Hz 箱中的幅度增加一倍。

因此,如果将其扩展到 500 Hz,您就可以开始看到发生了什么。解决方法是在下采样之前应用低通滤波器。低通滤波和下采样的组合步骤称为抽取。这是执行此操作的 scipy 函数:https ://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.decimate.html 。