第三节:脑电数据预处理——把脏数据洗干净

各位同学,欢迎来到预处理环节。

说实话,脑电数据预处理这步,我当年刚入行时觉得特别枯燥。不就是滤波、去噪、校正嘛,有什么好讲的?直到我第一次拿着没做ICA去伪迹的数据去跑分类模型,结果准确率只有55%——跟抛硬币差不多。嗯,从那以后我再也不敢小看预处理了。

今天我们要聊的内容,说白了就是四个步骤:带通滤波、陷波滤波、ICA去伪迹、基线校正。每一步都有它的道理,也有它的坑。我尽量把我在项目中踩过的雷都告诉你。

脑电数据预处理流程 步骤一 带通滤波 0.5 - 45 Hz 步骤二 陷波滤波 50 Hz 工频 步骤三 ICA 去伪迹 眼电/肌电 步骤四 基线校正 去直流偏移 原始脑电 → 带通滤波 → 陷波滤波 → ICA去伪迹 → 基线校正 → 干净数据 💡 小提示 顺序很重要!先带通滤波再陷波滤波,最后做ICA,效果最好

3.1 带通滤波:把不要的频率切掉

脑电信号里,真正有用的频率范围其实很窄。我个人习惯用0.5到45 Hz这个区间。为什么?

  • 低于0.5 Hz:主要是基线漂移和慢电位,跟认知活动关系不大
  • 高于45 Hz:肌电噪声(EMG)开始大量出现,还有各种高频干扰

你想想看,如果不做带通滤波,低频漂移会让你的数据看起来像过山车,高频噪声又会让波形变得毛刺刺的。我在做运动想象脑电时遇到过这种情况——没滤波之前,C3和C4导联的ERD/ERS模式根本看不出来,滤波之后才清晰可见。

核心参数:

  • 低截止频率:0.5 Hz(有时用0.1 Hz,看具体任务)
  • 高截止频率:45 Hz(或40 Hz,看你的采样率)
  • 滤波器阶数:我一般用4阶Butterworth,效果够用

# Python代码示例 - MNE库实现带通滤波
import mne

# 加载原始数据
raw = mne.io.read_raw_fif('subject01_raw.fif', preload=True)

# 带通滤波 0.5-45 Hz
raw.filter(0.5, 45, fir_design='firwin')
print('带通滤波完成!')

⚠️ 注意:滤波顺序有讲究。我建议先做带通滤波,再做陷波滤波。如果反过来,陷波滤波可能会把带通滤波边缘附近的频率也削掉一部分,造成不必要的信号损失。

3.2 陷波滤波:干掉50 Hz工频干扰

工频干扰,说白了就是市电带来的噪声。在中国是50 Hz,在美国是60 Hz。这个频率的干扰在脑电数据里特别明显——你会看到所有导联上都有规律的50 Hz正弦波叠加在上面。

我记得有一次做静息态脑电分析,没做陷波滤波就直接跑功率谱密度,结果50 Hz那里冒出一个巨大的尖峰,把alpha波(8-12 Hz)的峰值都压下去了。嗯,从那以后我每次预处理都会检查一下功率谱,确认50 Hz被干掉没有。

💡 我的经验:陷波滤波的带宽不要设太宽。我一般用48-52 Hz,带宽4 Hz就够了。带宽太宽会把附近的脑电活动也滤掉,特别是如果你研究的是gamma频段(30-50 Hz),那就要格外小心。


# 陷波滤波 50 Hz
raw.notch_filter(50, picks='eeg', fir_design='firwin')
print('50 Hz 工频干扰已去除')

3.3 ICA去伪迹:把眼电和肌电分离出来

独立成分分析(ICA),这名字听起来挺高大上,其实原理不复杂。它假设脑电信号是由多个独立源混合而成的——脑电活动是一个源,眨眼是一个源,肌肉活动又是一个源。ICA就是把这些源分离开。

我刚开始用ICA时犯过一个错误:把所有成分都保留,只手动剔除那些明显是眼电的成分。后来发现,有些成分看起来像脑电,其实是肌电伪迹。怎么办?我现在的做法是:先用EOG通道做参考,自动识别眼电成分,再手动检查剩下的成分。

ICA去伪迹的关键步骤:

  1. 先做带通滤波和陷波滤波
  2. 运行ICA算法(我常用FastICA或Infomax)
  3. 查看各成分的拓扑图和时序图
  4. 剔除眼电成分(前额叶分布、低频高幅)
  5. 剔除肌电成分(后部分布、高频特征)
  6. 重建干净的脑电信号

# ICA去伪迹示例
from mne.preprocessing import ICA

# 初始化ICA,设置成分数量
ica = ICA(n_components=20, random_state=42)

# 拟合ICA
ica.fit(raw, picks='eeg')

# 自动识别眼电成分(如果有EOG通道)
eog_indices, eog_scores = ica.find_bads_eog(raw, ch_name='EOG', threshold=3.0)

# 标记并剔除眼电成分
ica.exclude = eog_indices
raw_clean = ica.apply(raw)

print(f'已剔除 {len(eog_indices)} 个眼电成分')

⚠️ 我曾经踩过的坑:有一次我一次性剔除了10个成分,结果发现脑电信号变得很奇怪——alpha波都没了。后来才意识到,ICA成分里有些是脑电活动本身,不能盲目剔除。我的建议是:每次剔除前先看拓扑图和频谱,确认它是伪迹再动手。

3.4 基线校正:让数据站在同一起跑线上

基线校正,说白了就是把每个trial的基线期(通常是刺激出现前的一段时间)的平均值减掉。为什么要做?因为不同trial的直流偏移不一样,如果不校正,你看到的ERP波形可能不是真正的脑电反应,而是基线差异造成的假象。

我一般用刺激前200毫秒作为基线期。比如刺激在0时刻出现,我就取-200 ms到0 ms这段数据,计算平均值,然后从整个trial里减掉。

💡 小技巧:基线校正后,记得检查一下所有trial的基线期是否都接近0微伏。如果某个trial的基线期偏离很大,说明这个trial可能还有残留噪声,可以考虑剔除。


# 基线校正示例
# 假设epochs已经创建,基线期为[-0.2, 0]秒
epochs = mne.Epochs(raw, events, event_id, tmin=-0.2, tmax=0.8, baseline=(-0.2, 0))

# 或者对已有的epochs做基线校正
epochs.apply_baseline(baseline=(-0.2, 0))
print('基线校正完成!')

3.5 预处理流程总结

好了,四个步骤都讲完了。我整理了一个表格,方便你对照使用:

步骤 目的 关键参数 注意事项
带通滤波 去除低频漂移和高频噪声 0.5-45 Hz,4阶Butterworth 先做带通,再做陷波
陷波滤波 去除50 Hz工频干扰 48-52 Hz,带宽4 Hz 带宽不要太宽,避免影响gamma频段
ICA去伪迹 分离眼电、肌电等伪迹 n_components=15-25 逐个检查成分,不要批量剔除
基线校正 消除trial间直流偏移差异 基线期[-0.2, 0]秒 校正后检查基线是否归零

一句话总结:预处理做得好,后续分析事半功倍。我见过太多人花大量时间调模型参数,却忽略了预处理——结果数据质量差,模型怎么调都调不好。记住,干净的输入数据,才是好模型的基石。


公众号:蓝海资料掘金营,微信deep3321