代码之家  ›  专栏  ›  技术社区  ›  kilojoules

快速傅立叶变换在短时间内寻找低频

  •  2
  • kilojoules  · 技术社区  · 7 年前

    我有1个时间单位的信号历史。我的主频是1/100时间单位。当我使用numpy的fft函数时,我的分辨率受到信号历史范围的限制。 如何在不破坏信号的情况下提高频率梳的分辨率?

    import numpy as np
    import matplotlib.pyplot as plt
    '''
    I need to caputre a low-frequency oscillation with only 1 time unit of data.
    So far, I have not been able to find a way to make the fft resolution < 1.
    '''
    timeResolution = 10000
    mytimes = np.linspace(0, 1, timeResolution)
    mypressures = np.sin(2 * np.pi * mytimes / 100)
    
    
    fft = np.fft.fft(mypressures[:])
    T = mytimes[1] - mytimes[0]
    N = mypressures.size
    
    # fft of original signal is limitted by the maximum time
    f = np.linspace(0, 1 / T, N)
    filteredidx = f > 0.001
    freq = f[filteredidx][np.argmax(np.abs(fft[filteredidx][:N//2]))]
    print('freq bin is is ', f[1] - f[0]) # 1.0
    print('frequency is ', freq) # 1.0
    print('(real frequency is 0.01)')
    

    import numpy as np
    import matplotlib.pyplot as plt
    
    timeResolution = 10000
    mytimes = np.linspace(0, 1, timeResolution)
    mypressures = np.sin(2 * np.pi * mytimes / 100)
    
    # glue data to itself to make signal articicially longer
    timesby = 1000
    newtimes = np.concatenate([mytimes * ii for ii in range(1, timesby + 1)])
    newpressures = np.concatenate([mypressures] * timesby)
    
    
    fft = np.fft.fft(newpressures[:])
    T = newtimes[1] - newtimes[0]
    N = newpressures.size
    
    # fft of original signal is limitted by the maximum time
    f = np.linspace(0, 1 / T, N)
    filteredidx = f > 0.001
    freq = f[filteredidx][np.argmax(np.abs(fft[filteredidx][:N//2]))]
    print('freq bin is is ', f[1] - f[0]) # 0.001
    print('frequency is ', freq) # 1.0
    print('(real frequency is 0.01)') 
    
    1 回复  |  直到 7 年前
        1
  •  3
  •   Paul Panzer    7 年前

    即使在最简单的情况下(干净的正弦波,你的例子),数据看起来很像一条直线(下面的左面板)。只有在去趋势化之后,我们才能看到一点点的曲率(右面板下面,注意非常小的y值),这就是所有假设的算法可以通过的。尤其是在我看来,英国《金融时报》是行不通的。

    enter image description here

    f = c + sin(om * t ---那么一阶导数和三阶导数 om * cos(om * t) -om^3 * cos(om * t) 如果信号足够简单和干净,这与稳健的数值微分可以用来恢复频率ω。

    在下面的演示代码中,我使用了一个SavGol滤波器来获得导数,同时去除了一些添加到信号中的高频噪声(下面的蓝色曲线)(橙色曲线)。可能存在其他(更好的)数值微分方法。

    enter image description here

    运行示例:

    Estimated freq clean signal:   0.009998
    Estimated freq noisy signal:   0.009871
    

    我们可以看到,在这个非常简单的情况下,频率恢复正常。

    也许可以使用更多的导数和一些线性分解巫毒来恢复多个频率,但我不打算在这里探讨这个问题。

    代码:

    import numpy as np
    import matplotlib.pyplot as plt
    '''
    I need to caputre a low-frequency oscillation with only 1 time unit of data.
    So far, I have not been able to find a way to make the fft resolution < 1.
    '''
    timeResolution = 10000
    mytimes = np.linspace(0, 1, timeResolution)
    mypressures = np.sin(2 * np.pi * mytimes / 100)
    
    
    fft = np.fft.fft(mypressures[:])
    T = mytimes[1] - mytimes[0]
    N = mypressures.size
    
    # fft of original signal is limitted by the maximum time
    f = np.linspace(0, 1 / T, N)
    filteredidx = f > 0.001
    freq = f[filteredidx][np.argmax(np.abs(fft[filteredidx][:N//2]))]
    print('freq bin is is ', f[1] - f[0]) # 1.0
    print('frequency is ', freq) # 1.0
    print('(real frequency is 0.01)')
    
    import scipy.signal as ss
    
    plt.figure(1)
    plt.subplot(121)
    plt.plot(mytimes, mypressures)
    plt.subplot(122)
    plt.plot(mytimes, ss.detrend(mypressures))
    plt.figure(2)
    
    mycorrupted = mypressures + 0.00001 * np.random.normal(size=mypressures.shape)
    plt.plot(mytimes, ss.detrend(mycorrupted))
    plt.plot(mytimes, ss.detrend(mypressures))
    
    width, order = 8999, 3
    hw = (width+3) // 2
    dsdt = ss.savgol_filter(mypressures, width, order, 1, 1/timeResolution)[hw:-hw]
    d3sdt3 = ss.savgol_filter(mypressures, width, order, 3, 1/timeResolution)[hw:-hw]
    est_freq_clean = np.nanmean(np.sqrt(-d3sdt3/dsdt) / (2 * np.pi))
    
    dsdt = ss.savgol_filter(mycorrupted, width, order, 1, 1/timeResolution)[hw:-hw]
    d3sdt3 = ss.savgol_filter(mycorrupted, width, order, 3, 1/timeResolution)[hw:-hw]
    est_freq_noisy = np.nanmean(np.sqrt(-d3sdt3/dsdt) / (2 * np.pi))
    
    print(f"Estimated freq clean signal: {est_freq_clean:10.6f}")
    print(f"Estimated freq noisy signal: {est_freq_noisy:10.6f}")