欢迎光临
我们一直在努力

OpenBCI-信号预处理:滤波、去噪与伪迹去除

头图

OpenBCI-信号预处理:滤波、去噪与伪迹去除

一、引言:信号预处理的重要性

原始脑电信号非常微弱,通常被各种噪声和伪迹污染。信号预处理的目标是去除噪声、伪迹,保留有用的脑电信号特征,为后续的特征提取和模式识别打下良好基础。

二、噪声来源分析

2.1 噪声类型分类

#mermaid-svg-lT9UMIwIlglqmCIJ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lT9UMIwIlglqmCIJ .error-icon{fill:#552222;}#mermaid-svg-lT9UMIwIlglqmCIJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lT9UMIwIlglqmCIJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lT9UMIwIlglqmCIJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lT9UMIwIlglqmCIJ .marker.cross{stroke:#333333;}#mermaid-svg-lT9UMIwIlglqmCIJ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lT9UMIwIlglqmCIJ p{margin:0;}#mermaid-svg-lT9UMIwIlglqmCIJ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster-label text{fill:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster-label span{color:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster-label span p{background-color:transparent;}#mermaid-svg-lT9UMIwIlglqmCIJ .label text,#mermaid-svg-lT9UMIwIlglqmCIJ span{fill:#333;color:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ .node rect,#mermaid-svg-lT9UMIwIlglqmCIJ .node circle,#mermaid-svg-lT9UMIwIlglqmCIJ .node ellipse,#mermaid-svg-lT9UMIwIlglqmCIJ .node polygon,#mermaid-svg-lT9UMIwIlglqmCIJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lT9UMIwIlglqmCIJ .rough-node .label text,#mermaid-svg-lT9UMIwIlglqmCIJ .node .label text,#mermaid-svg-lT9UMIwIlglqmCIJ .image-shape .label,#mermaid-svg-lT9UMIwIlglqmCIJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-lT9UMIwIlglqmCIJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lT9UMIwIlglqmCIJ .rough-node .label,#mermaid-svg-lT9UMIwIlglqmCIJ .node .label,#mermaid-svg-lT9UMIwIlglqmCIJ .image-shape .label,#mermaid-svg-lT9UMIwIlglqmCIJ .icon-shape .label{text-align:center;}#mermaid-svg-lT9UMIwIlglqmCIJ .node.clickable{cursor:pointer;}#mermaid-svg-lT9UMIwIlglqmCIJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lT9UMIwIlglqmCIJ .arrowheadPath{fill:#333333;}#mermaid-svg-lT9UMIwIlglqmCIJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lT9UMIwIlglqmCIJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lT9UMIwIlglqmCIJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lT9UMIwIlglqmCIJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lT9UMIwIlglqmCIJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lT9UMIwIlglqmCIJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster text{fill:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ .cluster span{color:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lT9UMIwIlglqmCIJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lT9UMIwIlglqmCIJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-lT9UMIwIlglqmCIJ .icon-shape,#mermaid-svg-lT9UMIwIlglqmCIJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lT9UMIwIlglqmCIJ .icon-shape p,#mermaid-svg-lT9UMIwIlglqmCIJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lT9UMIwIlglqmCIJ .icon-shape .label rect,#mermaid-svg-lT9UMIwIlglqmCIJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lT9UMIwIlglqmCIJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lT9UMIwIlglqmCIJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lT9UMIwIlglqmCIJ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

噪声来源

外部噪声

生理伪迹

设备噪声

工频干扰

电磁辐射

环境振动

眼电伪迹

肌电伪迹

心电伪迹

呼吸伪迹

电极噪声

放大器噪声

ADC量化噪声

2.2 各类噪声特征

噪声类型来源频率特征典型处理方法
工频干扰 电源 50/60Hz尖峰 陷波滤波
眼电伪迹 眨眼、眼动 0.5-4Hz大振幅 ICA、独立成分分析
肌电伪迹 肌肉活动 20-100Hz高频 带通滤波、阈值检测
心电伪迹 心脏活动 1-10Hz ICA、模板匹配
基线漂移 电极极化 <0.5Hz 高通滤波、多项式拟合

三、滤波技术详解

3.1 滤波器类型

#mermaid-svg-ZnizNilwo29OoR8q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZnizNilwo29OoR8q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZnizNilwo29OoR8q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZnizNilwo29OoR8q .error-icon{fill:#552222;}#mermaid-svg-ZnizNilwo29OoR8q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZnizNilwo29OoR8q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZnizNilwo29OoR8q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZnizNilwo29OoR8q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZnizNilwo29OoR8q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZnizNilwo29OoR8q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZnizNilwo29OoR8q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZnizNilwo29OoR8q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZnizNilwo29OoR8q .marker.cross{stroke:#333333;}#mermaid-svg-ZnizNilwo29OoR8q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZnizNilwo29OoR8q p{margin:0;}#mermaid-svg-ZnizNilwo29OoR8q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZnizNilwo29OoR8q .cluster-label text{fill:#333;}#mermaid-svg-ZnizNilwo29OoR8q .cluster-label span{color:#333;}#mermaid-svg-ZnizNilwo29OoR8q .cluster-label span p{background-color:transparent;}#mermaid-svg-ZnizNilwo29OoR8q .label text,#mermaid-svg-ZnizNilwo29OoR8q span{fill:#333;color:#333;}#mermaid-svg-ZnizNilwo29OoR8q .node rect,#mermaid-svg-ZnizNilwo29OoR8q .node circle,#mermaid-svg-ZnizNilwo29OoR8q .node ellipse,#mermaid-svg-ZnizNilwo29OoR8q .node polygon,#mermaid-svg-ZnizNilwo29OoR8q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZnizNilwo29OoR8q .rough-node .label text,#mermaid-svg-ZnizNilwo29OoR8q .node .label text,#mermaid-svg-ZnizNilwo29OoR8q .image-shape .label,#mermaid-svg-ZnizNilwo29OoR8q .icon-shape .label{text-anchor:middle;}#mermaid-svg-ZnizNilwo29OoR8q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ZnizNilwo29OoR8q .rough-node .label,#mermaid-svg-ZnizNilwo29OoR8q .node .label,#mermaid-svg-ZnizNilwo29OoR8q .image-shape .label,#mermaid-svg-ZnizNilwo29OoR8q .icon-shape .label{text-align:center;}#mermaid-svg-ZnizNilwo29OoR8q .node.clickable{cursor:pointer;}#mermaid-svg-ZnizNilwo29OoR8q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ZnizNilwo29OoR8q .arrowheadPath{fill:#333333;}#mermaid-svg-ZnizNilwo29OoR8q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZnizNilwo29OoR8q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZnizNilwo29OoR8q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZnizNilwo29OoR8q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZnizNilwo29OoR8q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZnizNilwo29OoR8q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ZnizNilwo29OoR8q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZnizNilwo29OoR8q .cluster text{fill:#333;}#mermaid-svg-ZnizNilwo29OoR8q .cluster span{color:#333;}#mermaid-svg-ZnizNilwo29OoR8q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZnizNilwo29OoR8q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZnizNilwo29OoR8q rect.text{fill:none;stroke-width:0;}#mermaid-svg-ZnizNilwo29OoR8q .icon-shape,#mermaid-svg-ZnizNilwo29OoR8q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZnizNilwo29OoR8q .icon-shape p,#mermaid-svg-ZnizNilwo29OoR8q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ZnizNilwo29OoR8q .icon-shape .label rect,#mermaid-svg-ZnizNilwo29OoR8q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZnizNilwo29OoR8q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZnizNilwo29OoR8q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZnizNilwo29OoR8q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

滤波器类型

低通滤波器

高通滤波器

带通滤波器

陷波滤波器

保留低频

保留高频

保留特定频段

去除特定频率

3.2 巴特沃斯滤波器

from scipy.signal import butter, filtfilt

def butter_filter(data, lowcut, highcut, fs, btype='band', order=4):
"""设计并应用巴特沃斯滤波器"""
nyquist = 0.5 * fs

if btype == 'low':
b, a = butter(order, lowcut / nyquist, btype='low')
elif btype == 'high':
b, a = butter(order, highcut / nyquist, btype='high')
elif btype == 'band':
b, a = butter(order, [lowcut/nyquist, highcut/nyquist], btype='band')
elif btype == 'stop':
b, a = butter(order, [lowcut/nyquist, highcut/nyquist], btype='bandstop')

# 使用filtfilt实现零相位滤波
filtered_data = filtfilt(b, a, data)
return filtered_data

# 使用示例
fs = 250 # 采样率

# 带通滤波(0.5-50Hz)
filtered = butter_filter(raw_data, 0.5, 50, fs, btype='band')

# 陷波滤波(去除50Hz)
filtered = butter_filter(filtered, 49, 51, fs, btype='stop')

3.3 FIR滤波器

from scipy.signal import firwin, lfilter

def fir_filter(data, cutoff, fs, btype='low', numtaps=101):
"""设计并应用FIR滤波器"""
nyquist = 0.5 * fs

if btype == 'low':
taps = firwin(numtaps, cutoff/nyquist, pass_zero=True)
elif btype == 'high':
taps = firwin(numtaps, cutoff/nyquist, pass_zero=False)
elif btype == 'band':
taps = firwin(numtaps, [cutoff[0]/nyquist, cutoff[1]/nyquist], pass_zero=False)
elif btype == 'stop':
taps = firwin(numtaps, [cutoff[0]/nyquist, cutoff[1]/nyquist], pass_zero=True)

filtered_data = lfilter(taps, [1.0], data)
return filtered_data

# 使用示例
# FIR带通滤波
filtered_fir = fir_filter(raw_data, [0.5, 50], fs, btype='band')

3.4 卡尔曼滤波

import numpy as np

class KalmanFilter:
def __init__(self, process_noise=1e-5, measurement_noise=1e-3):
self.Q = process_noise # 过程噪声协方差
self.R = measurement_noise # 测量噪声协方差
self.P = 1.0 # 估计误差协方差
self.x = 0.0 # 状态估计

def update(self, measurement):
# 预测
P_pred = self.P + self.Q

# 更新
K = P_pred / (P_pred + self.R) # 卡尔曼增益
self.x = self.x + K * (measurement self.x)
self.P = (1 K) * P_pred

return self.x

# 使用示例
kf = KalmanFilter()
filtered_data = [kf.update(x) for x in raw_data]

四、伪迹去除技术

4.1 眼电伪迹去除

import numpy as np

def detect_eye_blinks(eeg_data, threshold=150, min_duration=50):
"""检测眨眼伪迹"""
# 使用差分检测快速变化
diff = np.abs(np.diff(eeg_data))
blink_start = np.where(diff > threshold)[0]

# 合并相邻的检测点
blinks = []
i = 0
while i < len(blink_start):
start = blink_start[i]
end = start
while i < len(blink_start) and blink_start[i] end < min_duration:
end = blink_start[i]
i += 1
blinks.append((start, end))

return blinks

def remove_eye_blinks(eeg_data, blinks, window_size=100):
"""去除眨眼伪迹(插值法)"""
cleaned = eeg_data.copy()

for start, end in blinks:
# 确定插值窗口
win_start = max(0, start window_size)
win_end = min(len(eeg_data), end + window_size)

# 获取插值数据
before_data = cleaned[win_start:start]
after_data = cleaned[end:win_end]

if len(before_data) > 0 and len(after_data) > 0:
# 线性插值
before_mean = np.mean(before_data)
after_mean = np.mean(after_data)
interpolated = np.linspace(before_mean, after_mean, end start + 1)
cleaned[start:end+1] = interpolated

return cleaned

# 使用示例
blinks = detect_eye_blinks(eeg_data[0])
cleaned_data = remove_eye_blinks(eeg_data[0], blinks)

4.2 独立成分分析(ICA)

from sklearn.decomposition import FastICA
import numpy as np

def ica_artifact_removal(eeg_data):
"""使用ICA去除伪迹"""
# 数据形状:(通道数, 采样点数) -> 转换为(采样点数, 通道数)
data = eeg_data.T

# 应用ICA
ica = FastICA(n_components=data.shape[1], random_state=42)
components = ica.fit_transform(data)

# 识别伪迹成分(基于方差或其他特征)
# 这里简化处理,手动选择要保留的成分
# 在实际应用中,需要根据成分特征识别伪迹

# 重构信号
cleaned_data = ica.inverse_transform(components)

return cleaned_data.T

# 使用示例
cleaned_eeg = ica_artifact_removal(eeg_data)

4.3 小波去噪

import pywt

def wavelet_denoising(data, wavelet='db4', level=5):
"""小波去噪"""
# 小波分解
coeffs = pywt.wavedec(data, wavelet, level=level)

# 阈值处理
threshold = np.sqrt(2 * np.log(len(data)))

# 对细节系数应用软阈值
coeffs_thresholded = [coeffs[0]] # 近似系数保留
for i in range(1, len(coeffs)):
coeffs_thresholded.append(pywt.threshold(coeffs[i], threshold, mode='soft'))

# 重构
denoised = pywt.waverec(coeffs_thresholded, wavelet)

return denoised[:len(data)] # 确保长度一致

# 使用示例
denoised_data = wavelet_denoising(eeg_data[0])

五、基线漂移校正

5.1 多项式拟合

import numpy as np

def baseline_correction_poly(data, order=3):
"""多项式拟合基线校正"""
n = len(data)
x = np.arange(n)

# 拟合基线
coeffs = np.polyfit(x, data, order)
baseline = np.polyval(coeffs, x)

# 去除基线
corrected = data baseline

return corrected

# 使用示例
corrected_data = baseline_correction_poly(eeg_data[0], order=3)

5.2 滑动窗口中值滤波

import numpy as np
from scipy.signal import medfilt

def baseline_correction_median(data, window_size=501):
"""滑动窗口中值滤波校正基线"""
# 使用中值滤波估计基线
baseline = medfilt(data, kernel_size=window_size)

# 去除基线
corrected = data baseline

return corrected

# 使用示例
corrected_data = baseline_correction_median(eeg_data[0])

六、完整预处理流程

#mermaid-svg-NK7bEaO644W8KBKm{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NK7bEaO644W8KBKm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NK7bEaO644W8KBKm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NK7bEaO644W8KBKm .error-icon{fill:#552222;}#mermaid-svg-NK7bEaO644W8KBKm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NK7bEaO644W8KBKm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NK7bEaO644W8KBKm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NK7bEaO644W8KBKm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NK7bEaO644W8KBKm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NK7bEaO644W8KBKm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NK7bEaO644W8KBKm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NK7bEaO644W8KBKm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NK7bEaO644W8KBKm .marker.cross{stroke:#333333;}#mermaid-svg-NK7bEaO644W8KBKm svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NK7bEaO644W8KBKm p{margin:0;}#mermaid-svg-NK7bEaO644W8KBKm .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-NK7bEaO644W8KBKm .cluster-label text{fill:#333;}#mermaid-svg-NK7bEaO644W8KBKm .cluster-label span{color:#333;}#mermaid-svg-NK7bEaO644W8KBKm .cluster-label span p{background-color:transparent;}#mermaid-svg-NK7bEaO644W8KBKm .label text,#mermaid-svg-NK7bEaO644W8KBKm span{fill:#333;color:#333;}#mermaid-svg-NK7bEaO644W8KBKm .node rect,#mermaid-svg-NK7bEaO644W8KBKm .node circle,#mermaid-svg-NK7bEaO644W8KBKm .node ellipse,#mermaid-svg-NK7bEaO644W8KBKm .node polygon,#mermaid-svg-NK7bEaO644W8KBKm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NK7bEaO644W8KBKm .rough-node .label text,#mermaid-svg-NK7bEaO644W8KBKm .node .label text,#mermaid-svg-NK7bEaO644W8KBKm .image-shape .label,#mermaid-svg-NK7bEaO644W8KBKm .icon-shape .label{text-anchor:middle;}#mermaid-svg-NK7bEaO644W8KBKm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NK7bEaO644W8KBKm .rough-node .label,#mermaid-svg-NK7bEaO644W8KBKm .node .label,#mermaid-svg-NK7bEaO644W8KBKm .image-shape .label,#mermaid-svg-NK7bEaO644W8KBKm .icon-shape .label{text-align:center;}#mermaid-svg-NK7bEaO644W8KBKm .node.clickable{cursor:pointer;}#mermaid-svg-NK7bEaO644W8KBKm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NK7bEaO644W8KBKm .arrowheadPath{fill:#333333;}#mermaid-svg-NK7bEaO644W8KBKm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NK7bEaO644W8KBKm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NK7bEaO644W8KBKm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NK7bEaO644W8KBKm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NK7bEaO644W8KBKm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NK7bEaO644W8KBKm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NK7bEaO644W8KBKm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NK7bEaO644W8KBKm .cluster text{fill:#333;}#mermaid-svg-NK7bEaO644W8KBKm .cluster span{color:#333;}#mermaid-svg-NK7bEaO644W8KBKm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NK7bEaO644W8KBKm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NK7bEaO644W8KBKm rect.text{fill:none;stroke-width:0;}#mermaid-svg-NK7bEaO644W8KBKm .icon-shape,#mermaid-svg-NK7bEaO644W8KBKm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NK7bEaO644W8KBKm .icon-shape p,#mermaid-svg-NK7bEaO644W8KBKm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NK7bEaO644W8KBKm .icon-shape .label rect,#mermaid-svg-NK7bEaO644W8KBKm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NK7bEaO644W8KBKm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NK7bEaO644W8KBKm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NK7bEaO644W8KBKm :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

原始EEG数据

高通滤波

陷波滤波

基线校正

ICA伪迹去除

小波去噪

带通滤波

预处理后数据

6.1 预处理管道

class EEGPreprocessor:
def __init__(self, fs=250):
self.fs = fs

def process(self, data):
"""完整预处理流程"""
result = data.copy()

# 1. 高通滤波(去除基线漂移)
result = self.highpass_filter(result, 0.5)

# 2. 陷波滤波(去除50Hz)
result = self.notch_filter(result, 50)

# 3. 带通滤波(保留0.5-50Hz)
result = self.bandpass_filter(result, 0.5, 50)

# 4. 基线校正
result = self.baseline_correction(result)

# 5. 小波去噪
result = self.wavelet_denoising(result)

return result

def highpass_filter(self, data, cutoff):
from scipy.signal import butter, filtfilt
nyquist = 0.5 * self.fs
b, a = butter(4, cutoff/nyquist, btype='high')
return filtfilt(b, a, data)

def notch_filter(self, data, freq):
from scipy.signal import butter, filtfilt
nyquist = 0.5 * self.fs
b, a = butter(4, [(freq1)/nyquist, (freq+1)/nyquist], btype='bandstop')
return filtfilt(b, a, data)

def bandpass_filter(self, data, low, high):
from scipy.signal import butter, filtfilt
nyquist = 0.5 * self.fs
b, a = butter(4, [low/nyquist, high/nyquist], btype='band')
return filtfilt(b, a, data)

def baseline_correction(self, data):
from scipy.signal import medfilt
baseline = medfilt(data, 501)
return data baseline

def wavelet_denoising(self, data):
import pywt
coeffs = pywt.wavedec(data, 'db4', level=5)
threshold = np.sqrt(2 * np.log(len(data)))
coeffs_thresh = [coeffs[0]]
for i in range(1, len(coeffs)):
coeffs_thresh.append(pywt.threshold(coeffs[i], threshold, mode='soft'))
return pywt.waverec(coeffs_thresh, 'db4')[:len(data)]

# 使用示例
preprocessor = EEGPreprocessor(fs=250)
cleaned_data = preprocessor.process(raw_eeg_data)

七、质量评估

7.1 信噪比计算

def calculate_snr(signal, noise):
"""计算信噪比(SNR)"""
signal_power = np.var(signal)
noise_power = np.var(noise)
snr = 10 * np.log10(signal_power / noise_power)
return snr

# 使用示例
# 假设我们有干净的信号和噪声估计
snr = calculate_snr(cleaned_signal, noise_estimate)
print(f"SNR: {snr:.2f} dB")

7.2 信号质量指标

def signal_quality_metrics(data):
"""计算信号质量指标"""
metrics = {}

# RMS值
metrics['rms'] = np.sqrt(np.mean(data ** 2))

# 峰峰值
metrics['peak_to_peak'] = np.max(data) np.min(data)

# 方差
metrics['variance'] = np.var(data)

# 过零率
metrics['zero_crossing_rate'] = np.sum(np.diff(np.sign(data)) != 0) / len(data)

# 频域特征
from scipy.signal import welch
freqs, psd = welch(data, fs=250, nperseg=256)

# 50Hz功率(工频干扰指标)
mask_50hz = (freqs >= 49) & (freqs <= 51)
metrics['power_50hz'] = np.sum(psd[mask_50hz])

# 脑电频段总功率
mask_eeg = (freqs >= 0.5) & (freqs <= 50)
metrics['power_eeg'] = np.sum(psd[mask_eeg])

return metrics

# 使用示例
quality = signal_quality_metrics(cleaned_data)
print("信号质量指标:", quality)

八、实战示例

8.1 预处理完整流程

import numpy as np
import matplotlib.pyplot as plt
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds

# 1. 采集数据
params = BrainFlowInputParams()
params.serial_port = "COM3"
board = BoardShim(BoardIds.CYTON_BOARD.value, params)
board.prepare_session()
board.start_stream()

import time
time.sleep(10)
data = board.get_board_data()
board.stop_stream()
board.release_session()

# 2. 获取EEG数据
eeg_channels = BoardShim.get_eeg_channels(BoardIds.CYTON_BOARD.value)
eeg_data = data[eeg_channels, :]

# 3. 预处理
preprocessor = EEGPreprocessor(fs=250)
cleaned_data = np.array([preprocessor.process(ch) for ch in eeg_data])

# 4. 对比显示
fig, axes = plt.subplots(2, 1, figsize=(12, 8))

# 原始数据
axes[0].plot(eeg_data[0][:500])
axes[0].set_title('原始EEG数据')
axes[0].set_ylabel('μV')
axes[0].grid(True)

# 预处理后数据
axes[1].plot(cleaned_data[0][:500])
axes[1].set_title('预处理后EEG数据')
axes[1].set_xlabel('Samples')
axes[1].set_ylabel('μV')
axes[1].grid(True)

plt.tight_layout()
plt.show()

8.2 实时预处理

class RealTimePreprocessor:
def __init__(self, fs=250, buffer_size=500):
self.fs = fs
self.buffer = []
self.buffer_size = buffer_size
self.preprocessor = EEGPreprocessor(fs=fs)

def add_data(self, data):
"""添加新数据"""
self.buffer.extend(data)

# 保持缓冲区大小
if len(self.buffer) > self.buffer_size:
self.buffer = self.buffer[self.buffer_size:]

def get_processed(self):
"""获取预处理后的数据"""
if len(self.buffer) < self.buffer_size:
return None

return self.preprocessor.process(np.array(self.buffer))

# 使用示例
rt_preprocessor = RealTimePreprocessor()

# 在实时循环中
while True:
current_data = board.get_current_board_data(10)
rt_preprocessor.add_data(current_data[1]) # 通道1

processed = rt_preprocessor.get_processed()
if processed is not None:
# 处理数据…
pass

time.sleep(0.01)

九、常见问题与解决方案

9.1 滤波参数选择

#mermaid-svg-yOt17k48PNZvirvd{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yOt17k48PNZvirvd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yOt17k48PNZvirvd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yOt17k48PNZvirvd .error-icon{fill:#552222;}#mermaid-svg-yOt17k48PNZvirvd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yOt17k48PNZvirvd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yOt17k48PNZvirvd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yOt17k48PNZvirvd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yOt17k48PNZvirvd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yOt17k48PNZvirvd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yOt17k48PNZvirvd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yOt17k48PNZvirvd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yOt17k48PNZvirvd .marker.cross{stroke:#333333;}#mermaid-svg-yOt17k48PNZvirvd svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yOt17k48PNZvirvd p{margin:0;}#mermaid-svg-yOt17k48PNZvirvd .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-yOt17k48PNZvirvd .cluster-label text{fill:#333;}#mermaid-svg-yOt17k48PNZvirvd .cluster-label span{color:#333;}#mermaid-svg-yOt17k48PNZvirvd .cluster-label span p{background-color:transparent;}#mermaid-svg-yOt17k48PNZvirvd .label text,#mermaid-svg-yOt17k48PNZvirvd span{fill:#333;color:#333;}#mermaid-svg-yOt17k48PNZvirvd .node rect,#mermaid-svg-yOt17k48PNZvirvd .node circle,#mermaid-svg-yOt17k48PNZvirvd .node ellipse,#mermaid-svg-yOt17k48PNZvirvd .node polygon,#mermaid-svg-yOt17k48PNZvirvd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yOt17k48PNZvirvd .rough-node .label text,#mermaid-svg-yOt17k48PNZvirvd .node .label text,#mermaid-svg-yOt17k48PNZvirvd .image-shape .label,#mermaid-svg-yOt17k48PNZvirvd .icon-shape .label{text-anchor:middle;}#mermaid-svg-yOt17k48PNZvirvd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yOt17k48PNZvirvd .rough-node .label,#mermaid-svg-yOt17k48PNZvirvd .node .label,#mermaid-svg-yOt17k48PNZvirvd .image-shape .label,#mermaid-svg-yOt17k48PNZvirvd .icon-shape .label{text-align:center;}#mermaid-svg-yOt17k48PNZvirvd .node.clickable{cursor:pointer;}#mermaid-svg-yOt17k48PNZvirvd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yOt17k48PNZvirvd .arrowheadPath{fill:#333333;}#mermaid-svg-yOt17k48PNZvirvd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yOt17k48PNZvirvd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yOt17k48PNZvirvd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yOt17k48PNZvirvd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yOt17k48PNZvirvd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yOt17k48PNZvirvd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yOt17k48PNZvirvd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yOt17k48PNZvirvd .cluster text{fill:#333;}#mermaid-svg-yOt17k48PNZvirvd .cluster span{color:#333;}#mermaid-svg-yOt17k48PNZvirvd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yOt17k48PNZvirvd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yOt17k48PNZvirvd rect.text{fill:none;stroke-width:0;}#mermaid-svg-yOt17k48PNZvirvd .icon-shape,#mermaid-svg-yOt17k48PNZvirvd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yOt17k48PNZvirvd .icon-shape p,#mermaid-svg-yOt17k48PNZvirvd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yOt17k48PNZvirvd .icon-shape .label rect,#mermaid-svg-yOt17k48PNZvirvd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yOt17k48PNZvirvd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yOt17k48PNZvirvd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yOt17k48PNZvirvd :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

脑电信号

α波分析

去除工频

去除基线漂移

选择滤波参数

应用场景

带通: 0.5-50Hz

带通: 8-13Hz

陷波: 50/60Hz

高通: 0.5Hz

9.2 参数建议

参数建议值说明
带通滤波 0.5-50Hz 保留脑电信号主要频段
陷波滤波 50或60Hz 根据地区选择
FIR滤波器阶数 50-200 阶数越高过渡带越窄
ICA成分数 等于通道数 完整分解

9.3 性能优化

# 优化建议
# 1. 使用filtfilt而非lfilter(虽然慢但零相位)
# 2. 在预处理前降采样(如果允许)
# 3. 使用多线程并行处理多通道
# 4. 预计算滤波器系数

# 示例:预计算滤波器
from scipy.signal import butter

fs = 250
nyquist = 0.5 * fs
b, a = butter(4, [0.5/nyquist, 50/nyquist], btype='band')

# 多次使用同一滤波器
filtered1 = filtfilt(b, a, data1)
filtered2 = filtfilt(b, a, data2)

十、总结

信号预处理是BCI系统中至关重要的一环,直接影响后续分析的准确性。

10.1 核心技术总结

  • ✅ 滤波技术(巴特沃斯、FIR、卡尔曼)
  • ✅ 伪迹去除(ICA、小波去噪、阈值检测)
  • ✅ 基线校正(多项式拟合、中值滤波)
  • ✅ 质量评估(SNR、RMS、频域分析)
  • 10.2 下一步计划

    在下一篇文章中,我们将深入学习特征提取技术,包括频域分析和时频分析方法。


    **继续探索信号处理技术!**下一篇我们将学习特征提取方法。

    本文是《OpenBCI从入门到精通》系列的第8篇。

    关键字:信号预处理、滤波、去噪、伪迹去除、ICA、小波分析


    博客签名2021

    赞(0)
    未经允许不得转载:171主机测评 » OpenBCI-信号预处理:滤波、去噪与伪迹去除
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址