引言

脑电信号(EEG)作为脑机接口(BCI)系统的核心输入,其质量直接决定后续特征提取与意图解码的效果。但 EEG 信号天生具有低信噪比(SNR<10dB)、非平稳性、易受干扰等特性,采集过程中不可避免会混入环境噪声(如工频干扰)和生理伪迹(如眼电、肌电)。因此,去噪是 EEG 数据预处理的第一步,也是最关键的一步。

本文将聚焦 EEG 去噪的两大核心技术 —— 滤波(去除环境噪声)和伪迹剔除(去除生理干扰),从技术原理、参数选择逻辑出发,结合完整 Python 代码示例(基于 MNE-Python),实现从原始嘈杂信号到干净数据的全流程实操,帮助读者掌握可复现的 EEG 去噪方案。

一、EEG 噪声与伪迹分类:明确去噪目标

在动手去噪前,需先明确 “敌人”——EEG 中的干扰成分主要分为两类,处理策略截然不同:

干扰类型典型代表特征与来源处理方法
环境噪声50/60Hz 工频干扰、电磁辐射周期性强、频率固定,由电网、电子设备产生陷波滤波、高通 / 低通滤波
生理伪迹眼电伪迹(EOG)幅度大(100-500μV),眨眼 / 眼球运动导致,前额电极显著ICA、阈值剔除、回归法
肌电伪迹(EMG)高频噪声(>30Hz),咀嚼 / 皱眉 / 颈部肌肉收缩导致带通滤波、ICA、小波去噪
心电伪迹(ECG)周期性(~1Hz),心脏跳动产生,颈部 / 颞区电极明显ICA、自适应滤波
基线漂移低频漂移(<0.5Hz),呼吸 / 出汗 / 电极极化导致高通滤波、基线校正

核心原则:滤波针对 “频率已知、周期性强” 的噪声,伪迹剔除针对 “幅度大、非平稳” 的生理干扰。

二、核心去噪技术原理详解

1. 滤波技术:频率域的 “噪声筛选”

滤波的本质是通过频率域的 “选通”,保留 EEG 有效频段(0.5-45Hz,覆盖 δ-γ 波),剔除特定频率的噪声。常用滤波类型及参数选择逻辑如下:

(1)高通滤波(High-Pass Filter)

  • 作用:去除低频干扰(如基线漂移、呼吸干扰)。
  • 参数选择:截止频率通常设为 0.5-1Hz(过低无法有效去漂移,过高会丢失 δ 波等低频有效信号)。
  • 实现方式:FIR 滤波(线性相位,无信号失真,推荐)或 IIR 滤波(计算快,轻微相位失真)。

(2)低通滤波(Low-Pass Filter)

  • 作用:去除高频噪声(如肌电、电子设备辐射),同时满足抗混叠要求。
  • 参数选择:截止频率设为采样率的 1/3~1/2(如 250Hz 采样率→截止频率 80Hz);若需保留 γ 波(>30Hz),截止频率需≥100Hz。

(3)陷波滤波(Notch Filter)

  • 作用:针对性去除 50Hz(国内电网)或 60Hz(欧美电网)工频干扰及谐波(100Hz、150Hz)。
  • 参数选择:中心频率 = 50/60Hz,带宽 = 2-4Hz(过窄可能遗漏谐波,过宽会影响邻近频段)。

2. 伪迹剔除技术:基于 “成分分离” 的精准去噪

生理伪迹(如眼电)幅度远大于 EEG 有效信号,且频率与有效频段重叠(如眼电包含 α/β 波频段),无法通过简单滤波去除。需通过 “成分分离” 技术将伪迹从原始信号中剥离,常用方法:

(1)独立成分分析(ICA)

  • 核心原理:假设 EEG 信号是 “有效脑电 + 多种伪迹” 的线性混合,通过 ICA 分解出相互独立的成分(ICs),识别并剔除伪迹对应的成分,再重构信号。
  • 优势:不依赖伪迹的频率特征,能精准分离眼电、肌电、心电等多种伪迹,是科研中最常用的伪迹剔除方法。
  • 关键步骤:数据预处理(去趋势、滤波)→ ICA 分解 → 伪迹成分识别(自动 + 手动)→ 信号重构。

(2)阈值法(Artifact Rejection)

  • 核心原理:基于 “伪迹幅度远大于 EEG 有效信号” 的特点,设定电压阈值(如 ±100μV),剔除超过阈值的信号段(Epochs)。
  • 适用场景:电极移动、突发噪声等强伪迹,常与 ICA 结合使用。

(3)回归法(EOG Regression)

  • 核心原理:以眼电电极(如 Fp1、Fp2)记录的信号为自变量,原始 EEG 信号为因变量,通过线性回归剔除与眼电相关的成分。
  • 优势:操作简单,无需分解信号,但依赖眼电参考电极的质量。

三、完整代码实操:EEG 去噪全流程

以下代码基于公开 EEG 数据集(BCI Competition IV 2a),实现 “加载数据→滤波去噪→ICA 伪迹剔除→阈值筛选→结果验证” 的完整流程,代码可直接运行复现。

1. 环境准备

安装必要依赖库(MNE-Python 为科研级 EEG 处理工具,支持滤波、ICA、可视化等全功能):

bash

pip install mne numpy scipy matplotlib

2. 数据加载与探索性分析

首先加载原始 EEG 数据,观察噪声和伪迹的分布特点:

python

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

# 加载公开数据集(BCI Competition IV 2a,运动想象任务)
# 若未下载,MNE会自动下载(约50MB)
from mne.datasets import bcic4_2a
raw_fname, event_fname = bcic4_2a.data_path(subject=1)  # 加载受试者1的数据

# 读取原始数据(EDF格式,64通道,250Hz采样率)
raw = mne.io.read_raw_edf(raw_fname, preload=True)

# 数据标准化:设置电极位置(10-20系统)、参考电极
mne.datasets.bcic4_2a.standardize(raw)
raw.set_montage('standard_1005')  # 加载电极位置模板

# 查看数据基本信息
print("原始数据形状(通道数×时间点):", raw.get_data().shape)
print("采样率:", raw.info['sfreq'], "Hz")
print("通道名称(前10个):", raw.info['ch_names'][:10])
print("数据时长:", round(raw.times[-1], 2), "秒")

# 可视化原始信号(前10秒,选择易受干扰的电极)
picks = ['Fp1', 'Fp2', 'C3', 'C4', 'O1', 'O2']  # Fp1/Fp2易受眼电影响,C3/C4为运动区
raw.plot(
    duration=10,
    n_channels=len(picks),
    scalings=dict(eeg=100e-6),  # 缩放为100μV,便于观察伪迹
    title='原始EEG信号(含噪声与伪迹)',
    show=True,
    block=True
)

运行后可观察到:原始信号中存在明显的基线漂移(缓慢上下波动)、眼电伪迹(Fp1/Fp2 电极的尖峰信号)和工频干扰(微小的周期性波动)。

3. 滤波去噪:针对性剔除环境噪声

采用 “高通 + 低通 + 陷波” 组合滤波,分步去除低频漂移、高频噪声和工频干扰:

python

# ---------------------- 步骤1:高通滤波(去除基线漂移) ----------------------
raw_highpass = raw.copy().filter(
    l_freq=1.0,  # 截止频率1Hz
    h_freq=None,
    method='fir',  # 采用FIR滤波(线性相位,无失真)
    fir_window='hamming',  # 汉明窗,优化滤波效果
    verbose=False
)

# ---------------------- 步骤2:低通滤波(去除高频噪声) ----------------------
raw_bandpass = raw_highpass.copy().filter(
    l_freq=None,
    h_freq=80.0,  # 截止频率80Hz(采样率250Hz,满足1/3采样率要求)
    method='fir',
    fir_window='hamming',
    verbose=False
)

# ---------------------- 步骤3:陷波滤波(去除50Hz工频干扰) ----------------------
raw_filtered = raw_bandpass.copy().notch_filter(
    freqs=50.0,  # 中心频率50Hz
    notch_widths=2.0,  # 带宽2Hz
    method='fir',
    verbose=False
)

# 对比滤波前后的信号(以Fp1电极为例,观察基线漂移和工频干扰变化)
fp1_idx = raw.ch_names.index('Fp1')
times = raw.times[:int(10 * raw.info['sfreq'])]  # 前10秒数据
raw_fp1 = raw.get_data()[fp1_idx, :int(10 * raw.info['sfreq'])]
filtered_fp1 = raw_filtered.get_data()[fp1_idx, :int(10 * raw.info['sfreq'])]

plt.figure(figsize=(14, 6))
plt.subplot(2, 1, 1)
plt.plot(times, raw_fp1, color='#E74C3C', alpha=0.7)
plt.title('滤波前:Fp1电极信号(含基线漂移+工频干扰)', fontsize=12)
plt.ylabel('电压(μV)', fontsize=10)
plt.grid(True, alpha=0.3)

plt.subplot(2, 1, 2)
plt.plot(times, filtered_fp1, color='#27AE60', alpha=0.7)
plt.title('滤波后:Fp1电极信号(基线稳定+工频去除)', fontsize=12)
plt.xlabel('时间(秒)', fontsize=10)
plt.ylabel('电压(μV)', fontsize=10)
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 可视化滤波后的全通道信号
raw_filtered.plot(
    duration=10,
    n_channels=len(picks),
    scalings=dict(eeg=100e-6),
    title='滤波后EEG信号(环境噪声已去除)',
    show=True,
    block=True
)

滤波效果验证:滤波后信号基线趋于平稳,工频干扰导致的微小波动消失,但 Fp1/Fp2 电极仍存在明显的眼电伪迹(尖峰信号),需通过 ICA 进一步剔除。

4. ICA 伪迹剔除:精准分离生理干扰

基于滤波后的数据进行 ICA 分解,重点识别并剔除眼电、肌电伪迹:

python

# ---------------------- 步骤1:ICA预处理(提升分解效果) ----------------------
# ICA对低频漂移敏感,需先去除趋势(已通过高通滤波处理)
raw_ica = raw_filtered.copy().detrend(axis=-1)  # 去除线性趋势

# ---------------------- 步骤2:ICA分解 ----------------------
ica = mne.preprocessing.ICA(
    n_components=20,  # 分解为20个独立成分(64通道数据,取1/3左右避免过拟合)
    random_state=42,  # 固定随机种子,结果可复现
    max_iter='auto',  # 自动调整迭代次数,确保收敛
    method='fastica'  # 快速ICA算法,效率高
)
ica.fit(raw_ica)  # 基于预处理后的数据拟合ICA模型

# 可视化所有独立成分(ICs)的空间拓扑图(便于手动识别伪迹)
ica.plot_components(
    picks=range(20),
    title='ICA分解后的20个独立成分(拓扑图)',
    show=True,
    block=True
)

# ---------------------- 步骤3:自动识别伪迹成分 ----------------------
# 1. 自动识别眼电伪迹(基于Fp1/Fp2电极的相关性)
eog_indices, eog_scores = ica.find_bads_eog(
    raw_filtered,
    ch_name=['Fp1', 'Fp2'],  # 前额电极作为眼电参考
    threshold=2.0  # 相关性阈值(越高筛选越严格)
)

# 2. 自动识别肌电伪迹(基于高频功率特征)
emg_indices = ica.find_bads_muscle(raw_filtered, threshold=4.0)

# 合并伪迹成分索引(去重)
bad_ics = list(set(eog_indices + emg_indices))
print("自动识别的伪迹成分索引:", bad_ics)

# 可视化伪迹成分的时间序列(验证识别准确性)
if bad_ics:
    ica.plot_sources(
        raw_filtered,
        picks=bad_ics,
        title=f'伪迹成分时间序列(索引:{bad_ics})',
        show=True,
        block=True
    )

    # ---------------------- 步骤4:手动修正伪迹成分(关键步骤) ----------------------
    # 自动识别可能存在遗漏,需结合拓扑图和时间序列手动调整
    # 示例:若发现索引5也是眼电成分,添加到bad_ics中
    # bad_ics.append(5)
    # bad_ics = list(set(bad_ics))  # 去重

    # ---------------------- 步骤5:重构信号(剔除伪迹成分) ----------------------
    raw_cleaned = raw_filtered.copy()
    ica.apply(raw_cleaned, exclude=bad_ics)

    # 对比ICA处理前后的信号(以Fp1电极为例,观察眼电伪迹变化)
    ica_before_fp1 = raw_filtered.get_data()[fp1_idx, :int(10 * raw.info['sfreq'])]
    ica_after_fp1 = raw_cleaned.get_data()[fp1_idx, :int(10 * raw.info['sfreq'])]

    plt.figure(figsize=(14, 6))
    plt.subplot(2, 1, 1)
    plt.plot(times, ica_before_fp1, color='#F39C12', alpha=0.7)
    plt.title('ICA处理前:Fp1电极信号(含眼电伪迹)', fontsize=12)
    plt.ylabel('电压(μV)', fontsize=10)
    plt.grid(True, alpha=0.3)

    plt.subplot(2, 1, 2)
    plt.plot(times, ica_after_fp1, color='#3498DB', alpha=0.7)
    plt.title(f'ICA处理后:Fp1电极信号(剔除伪迹成分{bad_ics})', fontsize=12)
    plt.xlabel('时间(秒)', fontsize=10)
    plt.ylabel('电压(μV)', fontsize=10)
    plt.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    # 可视化最终去噪后的信号
    raw_cleaned.plot(
        duration=10,
        n_channels=len(picks),
        scalings=dict(eeg=100e-6),
        title='最终去噪后EEG信号(环境噪声+生理伪迹均去除)',
        show=True,
        block=True
    )
else:
    print("未自动识别到伪迹成分,需手动从ICA成分图中选择!")
    # 手动选择伪迹成分后,执行重构:
    # bad_ics = [2, 5]  # 示例:手动选择的伪迹成分索引
    # raw_cleaned = raw_filtered.copy()
    # ica.apply(raw_cleaned, exclude=bad_ics)

ICA 伪迹识别关键技巧

  • 眼电伪迹成分:拓扑图中前额电极(Fp1/Fp2)权重高,时间序列中存在与眨眼同步的尖峰;
  • 肌电伪迹成分:拓扑图中颞区 / 额区电极权重高,时间序列中为高频锯齿波;
  • 心电伪迹成分:拓扑图中颈部 / 颞区电极权重高,时间序列中为周期性脉冲。

5. 阈值筛选:剔除残留强伪迹

ICA 处理后可能仍存在少量突发伪迹(如电极移动),通过阈值法剔除异常信号段:

python

# ---------------------- 步骤1:提取事件标记(运动想象任务) ----------------------
events = mne.read_events(event_fname)
event_id = {'left_hand': 1, 'right_hand': 2, 'foot': 3, 'tongue': 4}  # 任务类型

# ---------------------- 步骤2:创建Epochs(按事件分段) ----------------------
epochs = mne.Epochs(
    raw_cleaned,
    events,
    event_id=event_id,
    tmin=-0.2,  # 事件前200ms(基线期)
    tmax=0.8,   # 事件后800ms(任务期)
    baseline=(-0.2, 0),  # 基线校正
    preload=True,
    verbose=False
)

# ---------------------- 步骤3:阈值筛选(剔除异常Epochs) ----------------------
# 方法1:基于峰峰值阈值(EEG有效信号峰峰值通常<100μV)
reject_criteria = dict(eeg=100e-6)  # 峰峰值>100μV的Epochs视为异常
epochs_cleaned = epochs.copy().drop_bad(reject=reject_criteria)

# 方法2:基于Z-score阈值(剔除偏离均值3个标准差的Epochs)
# epochs_cleaned = epochs.copy().drop_bad(z_threshold=3.0)

# 统计剔除效果
print(f"原始Epochs数量: {len(epochs)}")
print(f"阈值筛选后Epochs数量: {len(epochs_cleaned)}")
print(f"剔除比例: {1 - len(epochs_cleaned)/len(epochs):.2%}")

# 可视化筛选前后的Epochs质量(ERP对比)
left_erp_before = epochs['left_hand'].average()
left_erp_after = epochs_cleaned['left_hand'].average()

plt.figure(figsize=(12, 4))
left_erp_before.plot(picks=['C3'], axes=plt.gca(), label='筛选前', color='#E74C3C', linewidth=2)
left_erp_after.plot(picks=['C3'], axes=plt.gca(), label='筛选后', color='#27AE60', linewidth=2)
plt.title('C3电极左手运动想象ERP(阈值筛选前后对比)', fontsize=12)
plt.xlabel('时间(秒)', fontsize=10)
plt.ylabel('电压(μV)', fontsize=10)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# 保存去噪后的数据(用于后续特征提取)
epochs_cleaned.save('eeg_cleaned-epo.fif', overwrite=True)
print("去噪后的数据已保存为:eeg_cleaned-epo.fif")

阈值筛选注意事项

  • 峰峰值阈值需根据数据特点调整(如儿童 EEG 信号幅度较小,可设为 ±80μV);
  • 剔除比例不宜过高(建议 < 20%),否则会丢失有效数据,影响后续分析。

6. 去噪效果量化评估

仅靠可视化不够,需通过量化指标验证去噪效果,常用指标包括信噪比(SNR)、功率谱密度(PSD)、ERP 波形平滑度

python

# ---------------------- 指标1:计算信噪比(SNR) ----------------------
def calculate_snr(data, sfreq, signal_band=(0.5, 45), noise_band=(55, 65)):
    """
    计算SNR:信号频段功率 / 噪声频段功率
    data: EEG数据(n_channels, n_times)
    sfreq: 采样率
    signal_band: 有效信号频段
    noise_band: 噪声频段(无有效信号)
    """
    from mne.time_frequency import psd_array_welch
    psd, freqs = psd_array_welch(data, sfreq=sfreq, fmin=0.5, fmax=70, n_fft=1024)
    
    # 计算信号功率和噪声功率
    signal_idx = np.logical_and(freqs >= signal_band[0], freqs <= signal_band[1])
    noise_idx = np.logical_and(freqs >= noise_band[0], freqs <= noise_band[1])
    signal_power = np.mean(psd[:, signal_idx], axis=1)
    noise_power = np.mean(psd[:, noise_idx], axis=1)
    snr = 10 * np.log10(signal_power / noise_power)
    return np.mean(snr)  # 返回所有通道的平均SNR

# 计算各阶段SNR
raw_snr = calculate_snr(raw.get_data(), raw.info['sfreq'])
filtered_snr = calculate_snr(raw_filtered.get_data(), raw.info['sfreq'])
cleaned_snr = calculate_snr(raw_cleaned.get_data(), raw.info['sfreq'])
epochs_snr = calculate_snr(epochs_cleaned.get_data().mean(axis=0), raw.info['sfreq'])

print("\n去噪效果量化评估(SNR):")
print(f"原始数据SNR: {raw_snr:.2f} dB")
print(f"滤波后SNR: {filtered_snr:.2f} dB")
print(f"ICA去伪迹后SNR: {cleaned_snr:.2f} dB")
print(f"阈值筛选后SNR: {epochs_snr:.2f} dB")

# ---------------------- 指标2:功率谱密度(PSD)对比 ----------------------
# 对比原始数据和去噪后数据的PSD(C3电极)
c3_idx = raw.ch_names.index('C3')
psd_raw, freqs = mne.time_frequency.psd_array_welch(
    raw.get_data()[c3_idx:c3_idx+1], sfreq=raw.info['sfreq'], fmin=0.5, fmax=50, n_fft=1024
)
psd_cleaned, _ = mne.time_frequency.psd_array_welch(
    raw_cleaned.get_data()[c3_idx:c3_idx+1], sfreq=raw.info['sfreq'], fmin=0.5, fmax=50, n_fft=1024
)

plt.figure(figsize=(10, 4))
plt.semilogy(freqs, psd_raw[0], label='原始数据', color='#E74C3C', alpha=0.7)
plt.semilogy(freqs, psd_cleaned[0], label='去噪后数据', color='#27AE60', alpha=0.7)
plt.axvline(x=50, color='black', linestyle='--', alpha=0.5, label='50Hz工频')
plt.title('C3电极功率谱密度(PSD)对比', fontsize=12)
plt.xlabel('频率(Hz)', fontsize=10)
plt.ylabel('功率谱密度(μV²/Hz)', fontsize=10)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

合格去噪标准:去噪后 SNR 提升≥3dB,PSD 图中 50Hz 工频干扰峰值消失,ERP 波形平滑无尖峰伪迹。

四、去噪实操关键技巧与避坑指南

1. 参数选择技巧

  • 滤波参数:高通滤波截止频率≤1Hz(避免丢失 δ 波),低通滤波截止频率 = 采样率 / 3~1/2(抗混叠),陷波滤波带宽 = 2~4Hz(平衡工频去除和信号保留);
  • ICA 成分数:通常为通道数的 1/3~1/2(如 64 通道→20~30 个成分),成分数过多易过拟合,过少无法分离伪迹;
  • 阈值设置:峰峰值阈值建议 ±80~120μV(根据数据实际幅度调整),Z-score 阈值 = 3.0(经典异常值筛选标准)。

2. 常见坑与解决方案

问题表现解决方案
ICA 分解效果差成分拓扑图模糊,无法识别伪迹1. 先进行高通滤波(≥1Hz);2. 去除数据趋势;3. 增加 ICA 迭代次数
伪迹剔除过度ERP 波形失真,有效信号丢失1. 减少伪迹成分数量;2. 降低自动识别阈值;3. 结合手动验证
工频干扰未完全去除PSD 图中 50Hz 仍有明显峰值1. 扩大陷波带宽(如 3Hz);2. 增加谐波陷波(如 100Hz);3. 检查采集环境接地
阈值筛选后数据量过少剔除比例 > 30%1. 放宽阈值(如 ±120μV);2. 先手动剔除明显坏段,再进行阈值筛选

3. 不同场景的去噪方案适配

  • 科研场景(高密度 EEG,64/128 通道):采用 “FIR 滤波 + ICA + 阈值筛选”,追求高精度去噪;
  • 消费级场景(低通道 EEG,4/8 通道):采用 “IIR 滤波 + 回归法 + 简单阈值”,兼顾速度和效果;
  • 实时 BCI 场景:采用 “在线陷波滤波 + 滑动窗口 ICA + 实时阈值”,优化计算效率(如使用 mne.realtime 模块)。

五、总结

EEG 去噪是数据预处理的核心,其本质是 “针对性分离有效信号与干扰成分”:滤波负责解决 “频率已知的环境噪声”,ICA 负责解决 “频率重叠的生理伪迹”,阈值筛选负责兜底 “残留的突发伪迹”。

本文通过完整代码实现了从原始数据到干净数据的全流程,关键在于:

  1. 理解噪声 / 伪迹的特征,选择合适的去噪方法;
  2. 精细化调整参数(如滤波截止频率、ICA 成分数、阈值);
  3. 结合可视化和量化指标验证去噪效果,避免过度去噪或去噪不彻底。

掌握这套去噪流程后,可直接应用于运动想象、P300、稳态视觉诱发电位(SSVEP)等各类 BCI 任务,为后续特征提取(如 CSP、时域特征、频域特征)和模型训练(如 SVM、CNN)奠定坚实基础。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐