2. EEG数据预处理实战:数据导入与格式转换
好,咱们直接进入正题。预处理这一步,说白了就是给原始EEG数据「洗澡」。你想想看,从放大器里直接捞出来的数据,那叫一个脏——工频干扰、眼动伪迹、肌肉活动,啥都有。我刚开始做ERP研究那会儿,就吃过没好好预处理的亏,结果统计结果全是假的,白忙活两周。
今天这一章,咱们就把预处理流程走一遍。从数据导入到坏段剔除,每一步我都会告诉你:为什么要做、怎么做、以及我踩过的坑。
2.1 数据导入与格式转换
EEG数据格式五花八门,EDF、BDF、CNT、SET……不同厂商各玩各的。我个人习惯用MNE库来处理,它几乎通吃所有格式。
核心原则:不管原始格式是什么,最终都要转成统一的内部数据结构(比如MNE的Raw对象),方便后续处理。
2.1.1 常见格式一览
| 格式 | 扩展名 | 特点 | 典型来源 |
|---|---|---|---|
| EDF | .edf | 欧洲数据格式,16位整数 | Brain Products、Neuroscan |
| BDF | .bdf | EDF的扩展版,24位整数 | BioSemi系统 |
| CNT | .cnt | Neuroscan专有格式 | Neuroscan放大器 |
| SET | .set + .fdt | EEGLAB格式 | EEGLAB用户 |
2.1.2 实战代码:用MNE导入数据
import mne
# 导入EDF文件
raw = mne.io.read_raw_edf('subject01.edf', preload=True)
# 导入BDF文件(BioSemi系统)
raw = mne.io.read_raw_bdf('subject01.bdf', preload=True)
# 导入CNT文件(Neuroscan)
raw = mne.io.read_raw_cnt('subject01.cnt', preload=True)
# 查看基本信息
print(raw.info)
print(raw.ch_names)
我的经验:preload=True这个参数很重要。如果不加,MNE会采用懒加载模式,后续操作会变慢。我一般直接preload,省心。
2.2 电极定位与通道选择
电极定位,说白了就是告诉软件:每个通道的电极在头皮上哪个位置。没有这个信息,后续的拓扑图、源定位都做不了。
2.2.1 标准电极位置系统
最常用的是10-20系统。我建议你直接用MNE自带的模板,省得自己手动配。
# 设置标准10-20电极位置
montage = mne.channels.make_standard_montage('standard_1020')
raw.set_montage(montage)
# 验证电极位置
raw.plot_sensors(show_names=True)
注意:不同厂商的电极命名可能不一样。比如「Cz」有的叫「Cz」,有的叫「CZ」。我曾经因为大小写问题,折腾了半小时才发现是命名不匹配。
2.2.2 通道选择策略
不是所有通道都要保留。我个人习惯:
- 保留EEG通道:Fz、Cz、Pz、Oz等关键位置
- 剔除EOG通道:眼电通道单独处理
- 剔除参考通道:比如M1、M2(乳突参考)
- 剔除坏通道:阻抗过高的通道
# 选择特定通道
eeg_channels = ['Fz', 'Cz', 'Pz', 'Oz', 'F3', 'F4', 'C3', 'C4', 'P3', 'P4']
raw.pick_channels(eeg_channels)
# 或者按类型选择
raw.pick_types(eeg=True, eog=False, stim=False)
2.3 参考电极设置
参考电极的选择,直接影响ERP波形的形态。这里我重点讲三种常用方式。
2.3.1 平均参考
把全脑所有电极的平均值作为参考。这是目前最常用的方法,尤其适合高密度EEG。
# 设置平均参考
raw.set_eeg_reference('average', projection=False)
为什么用平均参考?因为它能减少单点参考带来的偏差。我做过对比实验,平均参考下的N170波形比单侧乳突参考更干净。
2.3.2 双极参考
两个相邻电极相减,得到局部电位差。适合分析特定脑区。
# 创建双极参考(Cz - Fz)
bipolar_ref = mne.set_bipolar_reference(raw, anode='Cz', cathode='Fz')
2.3.3 常见参考方式对比
| 参考方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 平均参考 | 全局无偏 | 高密度时计算量大 | 大多数ERP研究 |
| 双极参考 | 局部精准 | 丢失全局信息 | 特定脑区分析 |
| 乳突参考 | 传统标准 | 可能引入偏侧化 | 经典ERP范式 |
2.4 滤波:让信号变干净
滤波是预处理的重头戏。说白了,就是把不需要的频率成分滤掉,留下我们关心的频段。
2.4.1 四种滤波类型
- 高通滤波:滤掉低频漂移(0.1-1 Hz)
- 低通滤波:滤掉高频噪声(30-100 Hz)
- 带通滤波:同时滤掉高低频(比如0.1-30 Hz)
- 陷波滤波:专门滤掉50/60 Hz工频干扰
# 带通滤波:0.1 - 30 Hz(ERP常用)
raw.filter(0.1, 30, fir_design='firwin')
# 陷波滤波:滤掉50 Hz工频
raw.notch_filter(50, fir_design='firwin')
我的建议:先做高通再做低通,顺序别搞反。另外,陷波滤波要谨慎使用,它可能会扭曲附近的频率成分。我一般先看频谱,确认有工频干扰才用。
2.4.2 滤波参数选择
| ERP成分 | 推荐滤波范围 | 说明 |
|---|---|---|
| N170、P100 | 0.1 - 30 Hz | 视觉ERP标准设置 |
| MMN | 0.5 - 20 Hz | 低频成分为主 |
| P300 | 0.1 - 10 Hz | P300是慢波 |
2.5 坏段剔除与插值
数据里总有一些「脏段」——眨眼、肌肉活动、电极脱落。这些段必须处理掉,否则会污染ERP平均结果。
2.5.1 手动剔除坏段
我习惯先肉眼扫一遍数据,标记明显的坏段。
# 手动标记坏段(交互式)
raw.plot(duration=10, n_channels=30)
# 用代码剔除
raw.crop(tmin=10, tmax=100) # 只保留10-100秒
2.5.2 自动检测坏段
用阈值法自动标记。我一般设置±100 μV为阈值。
# 自动标记坏段
annotations = mne.preprocessing.annotate_amplitude(raw,
peak=100e-6, # 100 μV
min_duration=0.01)
raw.set_annotations(annotations)
避坑指南:我曾经遇到过一个问题——自动检测把正常的ERP成分也标记成了坏段。后来发现是阈值设得太低了。建议先看数据分布,再设阈值。
2.5.3 坏通道插值
如果某个通道全程都是坏的,别直接删掉,用周围通道插值补全。
# 标记坏通道
raw.info['bads'] = ['Fz'] # 假设Fz坏了
# 插值修复
raw.interpolate_bads(reset_bads=True)
插值的原理:用周围3-4个通道的加权平均来估计坏通道的值。权重由电极间的距离决定。这个方法对单个坏通道效果很好,但如果一片区域都坏了,那就别插了。
知识体系总览
下面这张图,把整个预处理流程串起来了。你可以把它当作检查清单。
好了,这一章的内容就到这儿。预处理虽然繁琐,但它是ERP分析的基石。你想想看,如果数据本身就有问题,后面再花哨的分析也是白搭。我个人习惯把预处理流程写成脚本,每次跑数据直接调用,省时省力。
记住一句话:预处理做得好,ERP分析没烦恼。