fft在短时间历史中发现低频

时间:2018-09-30 15:38:33

标签: numpy signal-processing fft

我有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)')

我认为我可以通过端到端粘贴信号并进行fft来人为地增加时程长度(从而减小频率梳的宽度)。由于某些我不明白的原因,这对我不起作用:

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 个答案:

答案 0 :(得分:3)

您的目标是从“太短”(即<< sample_rate / frequency_of_interest,窗口)中恢复频谱信息。

即使在最简单的情况下(例如正弦波),数据看起来也很像一条直线(下面的左图)。只有在去趋势化之后,我们才能看到很小的曲率(下面的右面板,请注意非常小的y值),这是所有假设算法都可以通过的。特别是,FT-(据我所知-)将无法工作。

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}")