阶段二 · 核心分析能力 · 模块 4 · 第 10 课 / 共 28 课

基于 STFT 的时频分析(上):原理与实现

FFT 把整段信号压成一张"频率清单",但清单上没有时间。 STFT 给频谱装上时间轴:让窗在信号上滑动,逐段做 FFT。

🕐 45 分钟🎯 难度:进阶✅ 前置:4.1

🎯 本节学习目标

  • 理解纯 FFT 丢失时间信息、ERP 平均丢失非锁时成分的双重困境
  • 掌握 STFT 的滑动窗原理与输出矩阵的含义
  • 会用 Matlab 的 spectrogram 函数完成标准时频分析
  • 搞清窗长、nfft、重叠率三个参数各自控制什么
  • 能正确解读一张时频图:横轴、纵轴、颜色各是什么

一、两种"看不见":为什么需要时频分析

到目前为止我们有两大工具,但它们各有盲区:

  • FFT / PSD 的盲区:整段信号压成一张频谱清单,完全丢失时间信息。你只知道"这段 10 秒里有 α 节律",不知道它是前 3 秒有还是后 3 秒有、是持续还是阵发。
  • ERP 的盲区:平均只保留锁时锁相于刺激的成分。而大脑另一大类活动——比如刺激诱发但相位随机的节律增强或抑制(ERD/ERS,统称"诱导响应")——在平均中被正负抵消,彻底消失。
📌 诱发现象举例

手握把手准备运动时,对侧运动皮层的 8-12Hz μ 节律会去同步(幅度下降,ERD);这在单试次里清晰可见,但把试次平均后一无所得——因为每次的相位是随机的。要捕捉它,必须逐试次做时频分析。

解决思路朴素而优雅:给频谱装上时间轴——用一个短窗在信号上滑动, 每个位置做一次 FFT,把每次的频谱按时间排开。

二、STFT 原理:滑动窗 + 逐段 FFT

短时傅里叶变换(STFT)的完整流程只有三步:

  1. 取一个固定长度的窗(如 500ms 的 Hann 窗);
  2. 把它乘在信号当前位置,对窗内数据做 FFT,得到这一小段的频谱;
  3. 窗向右滑动一段距离(如 50ms),重复,直到滑过整段信号。

把每次得到的频谱按时间排成一排,就得到一个频率 × 时间的功率矩阵。 频率是纵轴、时间是横轴、功率用颜色表示——这就是时频图(spectrogram)。

时频图(spectrogram)

一个二维矩阵:纵轴频率、横轴时间、颜色深浅表示该时刻该频率的功率。它回答"什么时间、什么频率、多强"。

三、Matlab 实现:spectrogram 函数

先用一段"频率随时间变化"的仿真信号练手:

Matlab
% 构造信号:前 1 秒 10Hz,后 1 秒突然变成 25Hz
fs = 500;
t  = 0 : 1/fs : 2 - 1/fs;
x  = [sin(2*pi*10*t(1:500)), sin(2*pi*25*t(501:end))];

figure;
subplot(2,1,1); plot(t, x); xlim([0.9 1.1]);
title('时域:1 秒处频率从 10Hz 跳到 25Hz');

subplot(2,1,2);
spectrogram(x, hann(250), 125, 512, fs, 'yaxis');
title('时频图:两条水平能量带,跳变时刻一目了然');
ylim([0 50]);

时域图上你只能看到波形突然变密;而时频图上,10Hz 和 25Hz 各自是一条水平的能量带,切换发生在 1 秒处,清楚得像红绿灯。

spectrogram 也可以用输出参数形式调用,拿到矩阵自己画:

Matlab
[s, f, tt] = spectrogram(x, hann(250), 125, 512, fs);
% s  : 频率 x 时间 的复数谱;功率 = abs(s).^2
% f  : 纵轴频率向量
% tt : 横轴各窗中心时刻

P = abs(s).^2;
figure; imagesc(tt, f, 10*log10(P)); axis xy;
xlabel('时间 (s)'); ylabel('频率 (Hz)');
colorbar; ctitle_str = '功率 (dB)'; ylim([0 50]);

四、三个关键参数

参数它决定什么典型值
窗长 window频率分辨率与时间分辨率的平衡点250–500ms(EEG 常用)
重叠 overlap时间轴平滑度;窗与窗衔接是否留缝窗长的 50%–90%
nfft频轴采样密度(补零插值)≥ 窗长,常取 2 的幂

逐个说透:

窗长:分辨率的天平

窗内必须容纳至少一个完整周期才能"看见"该频率。500Hz 采样下:250ms 窗的频率分辨率是 4Hz——α 和 β 的细节全糊在一起;500ms 窗是 2Hz。但窗越长,时间上越"迟钝":快变的事件会被抹平。你不可能同时要高时间分辨率和高频率分辨率,这就是不确定性原理,4.3 展开。

重叠:让时间轴连续

若窗每次只滑动"一个窗长",相邻两窗不重叠,时间轴像珠串一样有缝。50% 重叠是惯例;追踪快速动态时可用 90%。代价只是计算量。

nfft:只是插值,不是魔法

nfft 大于窗长的部分是补零,让频轴点更密、图画得更平滑。真实频率分辨率不变——与 4.1 的结论一致。

💡 参数速记

分析 EEG 诱发/诱导响应的常用起点:400ms 窗、90% 重叠、nfft=512。γ 频段研究可缩短窗;关注 δ/θ 可加长窗。

五、怎么读一张时频图

拿到任何时频图,按固定清单过一遍:

  1. 横轴范围与零点:时间零点通常对齐刺激/事件;零之前是基线期;
  2. 纵轴范围:是否只画到关心的频段(如 2-45Hz),γ 之上多半是肌电;
  3. 颜色刻度:线性功率还是 dB?dB 刻度把动态范围压进可见区间,是论文标准;
  4. 有没有做基线校正:未校正的图里 1/f 背景会"下亮上暗",像瀑布(4.3 教你校正);
  5. 能量块的走向:水平条带=节律性持续活动;短促竖条=瞬态;斜向条纹多为伪迹。

六、从仿真走向 EEG:epoch 的时频分析骨架

Matlab
% 输入:epochs (ntrials x nchans x ntimes),fs = 500
% 对每个试次做 STFT,再跨试次平均(抓住诱导响应的关键!)
fs = 500; ntr = size(epochs, 1);
win = hann(round(0.4*fs));         % 400ms 窗
nov = round(0.9 * length(win));     % 90% 重叠
nfft = 512;

for tr = 1:ntr
    [s, f, tt] = spectrogram(squeeze(epochs(tr,1,:)), win, nov, nfft, fs);
    P(:, :, tr) = abs(s).^2;        % 每试次的功率
end
Pavg = mean(P, 3);                  % 跨试次平均 —— 诱导响应在这里显形

figure; imagesc(tt, f, 10*log10(Pavg)); axis xy;
xlabel('时间 (s)'); ylabel('频率 (Hz)'); colorbar;
ylim([2 45]); title('试次平均时频图');
⚠️ ERP 思维残留

新手最常见的错误:先平均波形再算时频。这只能得到相位锁定成分——等于放弃了 STFT 的看家本领。正确顺序是"逐试次时频 → 跨试次平均功率",诱导响应才会出现。

📌 本节小结

  • FFT 无时间轴、ERP 平均抹掉非锁时成分——时频分析补上"诱导响应"盲区
  • STFT = 固定窗滑动 + 逐段 FFT,输出频率×时间×功率矩阵
  • 窗长决定时间/频率分辨率的平衡;重叠率决定时间轴平滑;nfft 只是插值
  • 读图清单:零点、频段范围、dB 刻度、是否基线校正、能量块走向
  • 正确顺序:逐试次时频变换 → 跨试次平均功率,而不是先平均波形

✏️ 课后练习

  1. 构造"前 1 秒 10Hz + 后 1 秒 25Hz"的仿真信号,用 spectrogram 画时频图;再把窗长从 250ms 改成 1000ms,观察两条频带的边缘是变锐利还是变模糊。
  2. 把重叠率从 50% 改成 90% 重画,观察时间轴的平滑度变化,并记录两次运行的耗时差异。
  3. 对一份 epochs 数据(或仿真 50 个试次、每试次内含随机相位的 10Hz 幅度增强)验证:先平均再时频 vs 逐试次时频再平均,两张图的差别。