时频分析法——短时傅里叶变换(STFT)

时频分析法是对时序信号的一种时间-频率联合分析手段,也是将一维时间序列信号转化为二维图像的一种方法。其中,短时傅里叶变换、连续小波变换、希尔伯特-黄变换是时频分析法中最常用的三种方法。本文将以心电信号(ECGs)为例子来着重介绍STFT,相关的代码和数据也将在下面提供。

所使用的数据来自于公开的心电信号数据库,获取网址如下:PTB Diagnostic ECG Database v1.0.0 (physionet.org)

本文所有插图均由其中同一份经过预处理的数据得到,有想复现的小伙伴可以下载:文章顶部的"processed_ecg segment_csdn"

STFT的原理解释:

为了更直观的了解STFT,我们先画出时序心电信号图,以及它的由傅里叶变换(FFT)得到的频谱图。

时序信号图及绘制代码:

import matplotlib.pyplot as plt
import json
import matplotlib

# 全局字体和大小
matplotlib.rcParams['font.family'] = 'Times New Roman'
matplotlib.rcParams['font.size'] = 26

#加载ECG数据
with open(r"F:\ecg segment_csdn.json", "r") as file:   #将地址"F:\ecg segment_csdn.json"换成你的本地储存地址
    ecg_data = json.load(file)
signal_data = ecg_data['data']
fs = ecg_data['fs']  #加载采样频率 hz=1000

# 获取v1导联的数据(有12个导联数据,其中一个叫V1)
signal = signal_data['V1']

# 创建图表并绘制V1导联
plt.figure(figsize=(15, 5))
plt.plot(signal)
plt.title('V1 Lead ECG Signal')
plt.xlabel('Time (samples)')
plt.ylabel('Amplitude')
plt.show()

频谱图及绘制代码: 

import numpy as np
import matplotlib.pyplot as plt
import json
import matplotlib

matplotlib.rcParams['font.family'] = 'Times New Roman'
matplotlib.rcParams['font.size'] = 26

#加载ECG数据
with open(r"F:\ecg segment_csdn.json", "r") as file:
    ecg_data = json.load(file)
signal_data = ecg_data['data']
fs = ecg_data['fs']  # 加载采样频率

#获取V1导联的数据
signal = signal_data['V1']

#傅里叶变换(FFT)
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(len(signal), 1/fs)

#获取频率
half_n = len(fft_result) // 2
fft_magnitude = np.abs(fft_result[:half_n]) * 2 / len(fft_result)
frequencies = frequencies[:half_n]

# 创建图表并绘制频谱图
plt.figure(figsize=(15, 5))
plt.plot(frequencies, fft_magnitude)
plt.title('Frequency Spectrum of V1 Lead ECG Signal')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude')
plt.xlim(0, fs/2)
plt.show()

 通过上面的操作我们对整个信号进行FFT得到它的频谱图,假设信号为x(t),则FFT的计算公式:X(\partial ) = \int_{-\infty }^{+\infty }x(t) e^{-j\partial t}dt

既然FFT能够得到信号的频率,那么我们使用类似于积分的办法对每一小段信号进行FFT是不是就可以视为得到该信号每一个时刻的频率分布呢?没错,你已经get到STFT的核心思想了。PS:重要的事再说一遍“STFT就是对一小段一小段的信号进行FFT,然后再全部连起来”。                        

于是为了将整条信号分成多个小段,引入了一个窗函数,使得我们可以在对窗口内的信号段进行FFT,并且通过移动实现分窗,如下图所示:                                                                                                  

 在这里我们使用流行的汉明窗口,M是窗口长度,它的公式如下:  w(t) = 0.54 - 0.46\cos (\frac{2\pi t}{M-1})

假设窗口中心在t = \lambda,那么将要在该窗口下进行FFT的信号则可表示为y(t); y(t) = x(t) * \omega (t-\lambda )

最终我们对窗口信号做FFT得到时频图S(t, f):

 X(t ,f) = \int_{-\infty }^{+\infty }\omega (t-\lambda )x(\lambda )e^{-j\alpha \lambda }d\lambda

S(t, f) = \left | X(t, f) \right |^{2}

详细的实现STFT的代码如下,代码解释可以看注释 :                                                                                                          

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import spectrogram
import json
import matplotlib

matplotlib.rcParams['font.family'] = 'Times New Roman'
matplotlib.rcParams['font.size'] = 26

#加载ECG数据
with open(r"F:\ecg segment_csdn.json", "r") as file:
    ecg_data = json.load(file)
fs = ecg_data['fs']  #加载采样频率

#短时傅里叶函数实现     其中,neperseg是窗口长度,noverlap是窗口移动长度,这里设置了一半重叠
def compute_stft(signal, fs):
    signal = np.array(signal)  
    frequencies, times, Sxx = spectrogram(signal, fs=fs, window='hamming', nperseg=500, noverlap=250)  
    return frequencies, Sxx 

lead = 'V1'
signal = np.array(ecg_data['data'][lead])
frequencies, Sxx = compute_stft(signal, fs)

#限制频率范围至0到80Hz进行绘图
idx_max = np.max(np.where(frequencies <= 80)[0])
frequencies_clipped = frequencies[:idx_max+1]
Sxx_clipped = Sxx[:idx_max+1, :]

#绘制STFT频谱图
plt.figure(figsize=(15, 5))
plt.imshow(Sxx_clipped, aspect='auto', cmap='jet', origin='lower', extent=[0, 4, 0, 80])
plt.title(f'Spectrogram of Lead {lead}')
plt.xlabel('Time (s)')
plt.ylabel('Frequency (Hz)')
plt.colorbar(label='Magnitude (dB)')
plt.show()

最终运行得到的时频图如下:                                                                                                                                                                                           

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值