【问题标题】:Two dimensional FFT showing unexpected frequencies above Nyquisit limit二维 FFT 显示超出 Nyquisit 极限的意外频率
【发布时间】:2018-01-11 19:00:41
【问题描述】:

注意:这个问题是基于我的另一个问题: Two dimensional FFT using python results in slightly shifted frequency

我有一些数据,基本上是一个函数 E(x,y),其中 (x,y) 是 R^2 的(离散)子集,映射到实数。对于 (x,y) 平面,我在 x- 和 y 方向 (0,2) 上的数据点之间具有固定距离。我想使用 python 使用二维快速傅立叶变换 (FFT) 分析我的 E(x,y) 信号的频谱。

据我所知,无论我的信号中实际包含哪些频率,使用 FFT,我只能看到低于 Nyquisit 限制 Ny 的信号,即 Ny = 采样频率 / 2。就我而言,我实际间距为 0,2,导致采样频率为 1 / 0,2 = 5,因此我的 Nyquisit 限制为 Ny = 5 / 2 = 2,5。

如果我的信号的频率确实高于 Nyquisit 限制,它们将被“折叠”回 Nyquisit 域,从而导致错误结果(混叠)。但即使我可能会以太低的频率进行采样,但理论上应该不可能看到任何高于 Niquisit 限制的频率,对吗?

所以这是我的问题:分析我的信号只能导致最大 2.5 的频率,但我显然会得到更高的频率。鉴于我对这里的理论非常确定,我的代码中肯定有一些错误。我将提供一个缩短的代码版本,仅提供此问题的必要信息:

simulationArea =...  # length of simulation area in x and y direction
x = np.linspace(0, simulationArea, numberOfGridPointsInX, endpoint=False)
y = x
xx, yy = np.meshgrid(x, y)
Ex = np.genfromtxt('E_field_x100.txt')  # this is the actual signal to be analyzed, which may have arbitrary frequencies
FTEx = np.fft.fft2(Ex)  # calculating fft coefficients of signal
dx = x[1] - x[0]  # calculating spacing of signals in real space. 'print(dx)' results in '0.2'

sampleFrequency = 1.0 / dx
nyquisitFrequency = sampleFrequency / 2.0
half = len(FTEx) / 2

fig, axarr = plt.subplots(2, 1)

im1 = axarr[0, 0].imshow(Ex,
                         origin='lower',
                         cmap='jet',
                         extent=(0, simulationArea, 0, simulationArea))
axarr[0, 0].set_xlabel('X', fontsize=14)
axarr[0, 0].set_ylabel('Y', fontsize=14)
axarr[0, 0].set_title('$E_x$', fontsize=14)
fig.colorbar(im1, ax=axarr[0, 0])

im2 = axarr[1, 0].matshow(2 * abs(FTEx[:half, :half]) / half,
                          aspect='equal',
                          origin='lower',
                          interpolation='nearest')
axarr[1, 0].set_xlabel('Frequency wx')
axarr[1, 0].set_ylabel('Frequency wy')
axarr[1, 0].xaxis.set_ticks_position('bottom')
axarr[1, 0].set_title('$FFT(E_x)$', fontsize=14)
fig.colorbar(im2, ax=axarr[1, 0])

这样的结果是:

这怎么可能?当我对非常简单的信号使用相同的代码时,它工作得很好(例如,具有特定频率的 x 或 y 方向的正弦波)。

【问题讨论】:

  • 底部图的轴只是像素,而不是频率!!!关于使用 2D FFT,您还需要了解一些约定,例如如何构建 X 和 Y 频率的向量等,但是在这个答案中,我给出了一个使用复指数和 2D FFT 的非常简单的示例,但是在Matlab:stackoverflow.com/a/39774496/500207 看看你能不能把它改成 Python,如果不能,请告诉我,我会移植它。
  • 在 Python 中它更容易一些,因为 Numpy 提供了 fftfreq 函数。如果您可以上传Ex 的一些(真实或虚假)数据和simulationArea 等的完整值集,那么向您展示这应该是什么样子会很容易且令人信服。
  • 感谢您的回答!参考您在stackoverflow.com/a/39774496/500207 中的答案,我将如何在python 中正确使用'fftreq',以获得x 和y 的适当频率空间?我想它可以用来转换 'Nfft = 4 * 2 .^ nextpow2(size(im)); imF = fftshift(fft2(im, Nfft(1), Nfft(2))) / numel(im);'进入python代码。

标签: python fft


【解决方案1】:

好的,我们开始吧!这里有几个简单的函数和一个完整的例子,你可以使用:它有一点额外的与绘图和数据生成相关的麻烦,但第一个函数 makeSpectrum 向你展示了如何使用 fftfreqfftshift加上fft2 来实现你想要的。如果您有任何问题,请告诉我。

import numpy as np
import numpy.fft as fft
import matplotlib.pylab as plt


def makeSpectrum(E, dx, dy, upsample=10):
    """
    Convert a time-domain array `E` to the frequency domain via 2D FFT. `dx` and
    `dy` are sample spacing in x (left-right, 1st axis) and y (up-down, 0th
    axis) directions. An optional `upsample > 1` will zero-pad `E` to obtain an
    upsampled spectrum.

    Returns `(spectrum, xf, yf)` where `spectrum` contains the 2D FFT of `E`. If
    `Ny, Nx = spectrum.shape`, `xf` and `yf` will be vectors of length `Nx` and
    `Ny` respectively, containing the frequencies corresponding to each pixel of
    `spectrum`.

    The returned spectrum is zero-centered (via `fftshift`). The 2D FFT, and
    this function, assume your input `E` has its origin at the top-left of the
    array. If this is not the case, i.e., your input `E`'s origin is translated
    away from the first pixel, the returned `spectrum`'s phase will *not* match
    what you expect, since a translation in the time domain is a modulation of
    the frequency domain. (If you don't care about the spectrum's phase, i.e.,
    only magnitude, then you can ignore all these origin issues.)
    """
    zeropadded = np.array(E.shape) * upsample
    F = fft.fftshift(fft.fft2(E, zeropadded)) / E.size
    xf = fft.fftshift(fft.fftfreq(zeropadded[1], d=dx))
    yf = fft.fftshift(fft.fftfreq(zeropadded[0], d=dy))
    return (F, xf, yf)


def extents(f):
    "Convert a vector into the 2-element extents vector imshow needs"
    delta = f[1] - f[0]
    return [f[0] - delta / 2, f[-1] + delta / 2]


def plotSpectrum(F, xf, yf):
    "Plot a spectrum array and vectors of x and y frequency spacings"
    plt.figure()
    plt.imshow(abs(F),
               aspect="equal",
               interpolation="none",
               origin="lower",
               extent=extents(xf) + extents(yf))
    plt.colorbar()
    plt.xlabel('f_x (Hz)')
    plt.ylabel('f_y (Hz)')
    plt.title('|Spectrum|')
    plt.show()


if __name__ == '__main__':
    # In seconds
    x = np.linspace(0, 4, 20)
    y = np.linspace(0, 4, 30)
    # Uncomment the next two lines and notice that the spectral peak is no
    # longer equal to 1.0! That's because `makeSpectrum` expects its input's
    # origin to be at the top-left pixel, which isn't the case for the following
    # two lines.
    # x = np.linspace(.123 + 0, .123 + 4, 20)
    # y = np.linspace(.123 + 0, .123 + 4, 30)

    # Sinusoid frequency, in Hz
    x0 = 1.9
    y0 = -2.9

    # Generate data
    im = np.exp(2j * np.pi * (y[:, np.newaxis] * y0 + x[np.newaxis, :] * x0))

    # Generate spectrum and plot
    spectrum, xf, yf = makeSpectrum(im, x[1] - x[0], y[1] - y[0])
    plotSpectrum(spectrum, xf, yf)

    # Report peak
    peak = spectrum[:, np.isclose(xf, x0)][np.isclose(yf, y0)]
    peak = peak[0, 0]
    print('spectral peak={}'.format(peak))

结果如下图,并打印出spectral peak=(1+7.660797103157986e-16j),这正是纯复指数频率处频谱的正确值。

【讨论】:

  • 你太棒了,非常感谢你 ;-) 最后一个问题:在最后一步中,我使用上面的代码示例并替换了 "x = np.linspace(0, 4, 20); y = np.linspace(0, 4, 30)" with " x = np.linspace(0, simulationArea, numberOfGridPointsInX, endpoint = False); y = x" 当然还有我的真实数据的 exp() E(真实物理数据)。结果看起来不错,因为它不再显示高于 Nyquisit 限制的频率。但它也显示负频率,我有(与你的例子相反)一个纯粹的真实信号。那我该如何摆脱那些多余的负频率呢?
  • 所以我试图通过以纯真实信号为例来解决负频率问题:“np.sin(2 * np.pi * (y[:, np.newaxis] * y0)) * np.sin(2 * np.pi * x[:, np.newaxis] * x0)" 作为时域函数,然后在 "plotSpectrum()" 内部使用: "imshow( 2 * abs(F[ :half, :half])..." 然后是 "plt.xlim(0, max(extents(xf)))" 和 "plt.xlim(0, max(extents(xf)))",但不幸的是这个结果基本上什么都没有显示(只是非常弱的频率,定期出现,所以绝对不是我想要的)
  • 同样知道高斯的 FT 会导致另一个高斯函数,我使用了 "im = np.exp(-(np.power(x[:, np.newaxis] - mu, 2. ) + np.power(y[:, np.newaxis] - mu, 2.)) / (2 * np.power(sig, 2.)))",其中“mu = 0.5”和“sig = 1” ,从中可以看出,它不会导致高斯。对于真实信号,这里需要做哪些调整?
  • 关于真实信号。如果你绘制一个二维sin,你会得到两个正确的峰值,一个在频率(x0, y0),幅度为 1/2j,另一个在频率(-x0, -y0),幅度为 -1/2j,所以要看到两者,你我想保留完整的频率范围。是的,对于一个真实的信号,确实存在冗余,就像rfftn(或rfft2)的文档字符串解释说:你可以隐藏一半的频率平面,假设另一半是共轭转置的一半你确实展示了。
  • 我认为你的代码中有一个错误:im 是一个 (500,1) 向量而不是 (500, 500) 数组,因为你需要 x[np.newaxis, :],但是你反过来。请注意,在我的示例中,x[np.newaxis, :]y[:, np.newaxis] 的区别是如何精心选择括号内的参数以将 x 扩展为 2D row 向量和 y 扩展为 2D column 向量,使两个broadcast 变成一个2D 500 x 500 数组。总之——用x[np.newaxis, :]?重新运行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
  • 2013-01-13
  • 2011-05-04
  • 1970-01-01
  • 1970-01-01
  • 2020-09-22
  • 2012-01-09
相关资源
最近更新 更多