
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, [(freq–1)/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 核心技术总结
10.2 下一步计划
在下一篇文章中,我们将深入学习特征提取技术,包括频域分析和时频分析方法。
**继续探索信号处理技术!**下一篇我们将学习特征提取方法。
本文是《OpenBCI从入门到精通》系列的第8篇。
关键字:信号预处理、滤波、去噪、伪迹去除、ICA、小波分析
