第1章:神经信号预处理实战——从原始脑电到干净数据

大家好,我是你们的老朋友。今天咱们来聊聊神经信号预处理这件事。

说实话,我刚入行那会儿,觉得预处理就是走个过场。直到有一次,我花了两周时间训练一个深度学习模型,结果发现性能差得离谱。排查了半天,最后发现是原始数据里有个50Hz的工频干扰没滤干净。嗯,从那以后,我再也不敢小看预处理了。

预处理说白了,就是把原始脑电信号里的「脏东西」清理掉。你想想看,头皮上采集到的信号,除了大脑活动,还有眼动、肌肉收缩、心跳、电源干扰……这些乱七八糟的东西混在一起,模型根本学不到真正有用的特征。

核心观点:预处理不是可选项,而是必选项。数据质量决定了模型性能的上限。

1.1 预处理流程总览

我个人习惯把预处理分成五个步骤,按顺序执行:

  1. 带通滤波——保留目标频段,去掉低频漂移和高频噪声
  2. 陷波滤波——干掉50Hz或60Hz的工频干扰
  3. 伪迹去除——干掉眼电、肌电这些大块头噪声
  4. 基线校正——把信号拉回零均值
  5. 重参考——选择合适的参考电极

下面这张图可以帮你快速理解整个流程:

神经信号预处理流程图 原始EEG数据 ① 带通滤波 (0.5-45Hz) ② 陷波滤波 (50Hz) ③ 伪迹去除 (ICA/阈值法)

1.2 带通滤波——把信号「框」在目标频段

脑电信号的能量主要集中在0.5Hz到45Hz之间。低于0.5Hz的是基线漂移,高于45Hz的基本是肌电噪声。带通滤波就是只让这个频段的信号通过。

我在项目中遇到过一个问题:滤波器的阶数选太高,结果信号边缘出现了振铃效应。后来我学乖了,一般用4阶或5阶的巴特沃斯滤波器,效果比较稳。

import mne
import numpy as np

# 读取原始数据
raw = mne.io.read_raw_fif('sample_raw.fif', preload=True)

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

小技巧:滤波时建议用fir_design='firwin',它比默认的'firwin2'更稳定,尤其适合短数据段。

1.3 陷波滤波——干掉烦人的工频干扰

50Hz的工频干扰是脑电信号里最常见的噪声源。你想想看,实验室里到处都是电源线、插座、设备,这些都会产生50Hz的电磁场。陷波滤波就是专门针对这个频率的「狙击枪」。

我曾经见过一个同学,滤波参数设得太窄,结果50Hz附近的脑电活动也被干掉了。嗯,这里要注意:陷波滤波的带宽一般设0.5-1Hz就够了,太宽会损伤有用信号。

# 陷波滤波:干掉50Hz
raw.notch_filter(50, picks='eeg', fir_design='firwin')
print('陷波滤波完成')

注意:如果数据采样率低于100Hz,陷波滤波可能会失效。因为奈奎斯特频率只有50Hz,刚好卡在边界上。建议采样率至少250Hz以上。

1.4 伪迹去除——眼电和肌电的「大扫除」

伪迹去除是预处理里最考验经验的一步。眼电伪迹通常出现在前额电极,频率在0-4Hz;肌电伪迹频率高,一般在20Hz以上,而且能量大、持续时间短。

我个人习惯先用ICA(独立成分分析)来分离伪迹。ICA的原理很简单:把混合信号分解成统计独立的成分,然后手动识别并剔除那些明显是伪迹的成分。

# 使用ICA去除眼电伪迹
ica = mne.preprocessing.ICA(n_components=20, random_state=42)
ica.fit(raw)

# 自动识别眼电成分
eog_indices, eog_scores = ica.find_bads_eog(raw, ch_name='Fp1', threshold=3.0)
ica.exclude = eog_indices

# 应用ICA,去除伪迹
raw_clean = ica.apply(raw)
print(f'去除了{len(eog_indices)}个眼电成分')

避坑指南:我曾经一次性去除了太多ICA成分,结果把真正的脑电活动也干掉了。建议每次只去除1-2个最明显的伪迹成分,然后检查一下信号是否正常。

1.5 基线校正——把信号拉回零均值

基线校正说白了,就是把每个epoch(试次)的信号减去基线期的平均值。基线期一般取刺激出现前的100-200毫秒。这样做的好处是:消除不同试次之间的直流偏移,让信号在零附近波动。

# 创建epochs,基线校正
events = mne.find_events(raw, stim_channel='STI 014')
epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.8, 
                    baseline=(-0.2, 0), preload=True)
print('基线校正完成')

你想想看,如果没有基线校正,不同试次的信号起点不一样,叠加平均后会出现奇怪的波形。我刚开始做实验时就踩过这个坑,平均后的ERP波形怎么看怎么不对劲。

1.6 重参考——选择合适的参考电极

参考电极的选择直接影响信号质量。常见的参考方式有三种:

参考方式 说明 适用场景
平均参考 所有电极的平均值作为参考 电极数较多时(≥32导)
双极参考 相邻电极相减 局部电位分析
单极参考 固定一个电极(如Cz)作为参考 传统ERP研究
# 重参考:使用平均参考
raw.set_eeg_reference('average', projection=False)
print('重参考完成')

我的建议:如果电极数少于32导,用平均参考效果不太好。这时候我一般用双极参考或者干脆不重参考,直接用原始参考。

1.7 完整预处理流水线

最后,我把整个预处理流程封装成一个函数,方便复用:

def preprocess_eeg(raw_path, l_freq=0.5, h_freq=45, notch_freq=50):
    """
    完整的EEG预处理流水线
    """
    # 读取数据
    raw = mne.io.read_raw_fif(raw_path, preload=True)
    
    # 1. 带通滤波
    raw.filter(l_freq, h_freq, fir_design='firwin')
    
    # 2. 陷波滤波
    raw.notch_filter(notch_freq, fir_design='firwin')
    
    # 3. ICA去伪迹
    ica = mne.preprocessing.ICA(n_components=20, random_state=42)
    ica.fit(raw)
    eog_indices, _ = ica.find_bads_eog(raw, ch_name='Fp1', threshold=3.0)
    ica.exclude = eog_indices
    raw = ica.apply(raw)
    
    # 4. 重参考
    raw.set_eeg_reference('average', projection=False)
    
    return raw

# 使用示例
raw_clean = preprocess_eeg('sample_raw.fif')
print('预处理完成,数据已就绪')

好了,这就是神经信号预处理的完整流程。说实话,预处理看起来步骤多,但每一步都有它的道理。你只要按照这个流程走一遍,基本能拿到干净的数据。

记住一句话:预处理做得好,模型训练没烦恼。下次咱们再聊特征提取的事。


专注资料整理