OpenBCI-Python与OpenBCI:实时脑电信号采集实战

OpenBCI-Python与OpenBCI:实时脑电信号采集实战

一、引言:Python在BCI开发中的优势

Python已经成为脑机接口开发的主流语言,原因如下:

  • 丰富的科学计算库:NumPy、SciPy、Matplotlib等
  • 强大的机器学习框架:TensorFlow、PyTorch、Scikit-learn
  • 活跃的社区支持:大量教程和开源项目
  • 跨平台兼容性:Windows、macOS、Linux

本文将详细介绍如何使用Python与OpenBCI进行实时脑电信号采集和处理。

二、环境准备

2.1 安装依赖库

bash 复制代码
# 核心库
pip install numpy scipy matplotlib pandas

# BrainFlow
pip install brainflow

# 高级EEG分析
pip install mne pyriemann

# 可视化
pip install seaborn plotly

# 实时绘图
pip install pyqtgraph pyside2

2.2 验证安装

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
import brainflow

print(f"NumPy版本: {np.__version__}")
print(f"BrainFlow版本: {brainflow.__version__}")
print("环境配置完成!")

三、基础数据采集

3.1 完整采集流程

python 复制代码
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
import time
import numpy as np
import matplotlib.pyplot as plt

# 1. 配置设备参数
params = BrainFlowInputParams()
params.serial_port = "COM3"  # Windows串口
# params.serial_port = "/dev/ttyUSB0"  # Linux串口

# 2. 初始化设备
board_id = BoardIds.CYTON_BOARD.value
board = BoardShim(board_id, params)

try:
    # 3. 准备会话
    board.prepare_session()
    
    # 4. 开始数据流
    board.start_stream()
    print("开始采集数据...")
    
    # 5. 采集数据(10秒)
    time.sleep(10)
    
    # 6. 获取数据
    data = board.get_board_data()
    
    # 7. 停止数据流
    board.stop_stream()
    board.release_session()
    
    print(f"采集完成!数据形状: {data.shape}")
    
    # 8. 处理数据
    eeg_channels = BoardShim.get_eeg_channels(board_id)
    sampling_rate = BoardShim.get_sampling_rate(board_id)
    
    print(f"EEG通道索引: {eeg_channels}")
    print(f"采样率: {sampling_rate} Hz")
    
    # 9. 绘制波形
    plt.figure(figsize=(12, 8))
    for i, ch in enumerate(eeg_channels[:4]):
        plt.subplot(4, 1, i+1)
        plt.plot(data[ch][:500])
        plt.title(f"Channel {ch+1}")
        plt.ylabel("μV")
        plt.grid(True)
    
    plt.xlabel("Samples")
    plt.tight_layout()
    plt.show()
    
except Exception as e:
    print(f"采集过程中发生错误: {e}")
    if board.is_prepared():
        board.release_session()

3.2 数据格式说明

python 复制代码
# BrainFlow返回的数据格式
# 二维数组:行 = 通道,列 = 采样点

# 获取各类通道
eeg_channels = BoardShim.get_eeg_channels(board_id)        # EEG通道
accel_channels = BoardShim.get_accel_channels(board_id)    # 加速度计通道
timestamp_channel = BoardShim.get_timestamp_channel(board_id)  # 时间戳通道
marker_channel = BoardShim.get_marker_channel(board_id)   # 标记通道

# 提取数据
eeg_data = data[eeg_channels, :]       # EEG数据
accel_data = data[accel_channels, :]   # 加速度计数据
timestamps = data[timestamp_channel, :] # 时间戳
markers = data[marker_channel, :]      # 标记

print(f"EEG数据: {eeg_data.shape}")
print(f"时间戳: {timestamps.shape}")

四、实时数据处理

4.1 实时波形显示

python 复制代码
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from collections import deque

# 参数配置
fs = 250
buffer_size = 500
channels_to_display = 4

# 初始化缓冲区
buffers = [deque(maxlen=buffer_size) for _ in range(channels_to_display)]

# 设置绘图
fig, axes = plt.subplots(channels_to_display, 1, figsize=(12, 8))
lines = []

for i in range(channels_to_display):
    line, = axes[i].plot([], [], lw=1)
    lines.append(line)
    axes[i].set_ylim(-100, 100)
    axes[i].set_xlim(0, buffer_size)
    axes[i].grid(True)
    axes[i].set_ylabel(f"Ch{i+1} (μV)")

axes[-1].set_xlabel("Samples")

# 更新函数
def update(frame):
    # 获取最新数据(10个采样点)
    current_data = board.get_current_board_data(10)
    
    # 更新缓冲区
    eeg_channels = BoardShim.get_eeg_channels(board_id)
    for i in range(channels_to_display):
        ch_data = current_data[eeg_channels[i]]
        buffers[i].extend(ch_data)
        
        # 更新曲线
        lines[i].set_data(range(len(buffers[i])), buffers[i])
    
    return lines

# 启动设备
params = BrainFlowInputParams()
params.serial_port = "COM3"
board = BoardShim(board_id, params)
board.prepare_session()
board.start_stream()

# 启动动画
ani = animation.FuncAnimation(fig, update, interval=50, blit=True)

try:
    plt.show()
except KeyboardInterrupt:
    board.stop_stream()
    board.release_session()
    print("采集停止")

4.2 实时频谱分析

python 复制代码
from scipy.signal import welch
import numpy as np

class RealTimeSpectrumAnalyzer:
    def __init__(self, fs=250, window_size=2):
        self.fs = fs
        self.window_size = window_size
        self.buffer = deque(maxlen=int(fs * window_size))
        
    def add_data(self, data):
        """添加新数据"""
        self.buffer.extend(data)
    
    def compute_spectrum(self):
        """计算功率谱密度"""
        if len(self.buffer) < self.fs:
            return None, None
        
        data = np.array(self.buffer)
        freqs, psd = welch(data, fs=self.fs, nperseg=256)
        return freqs, psd

# 使用示例
analyzer = RealTimeSpectrumAnalyzer()

# 在实时循环中
while True:
    current_data = board.get_current_board_data(50)
    analyzer.add_data(current_data[1])  # 通道1
    
    freqs, psd = analyzer.compute_spectrum()
    if freqs is not None:
        print(f"α波功率: {np.mean(psd[(freqs>=8) & (freqs<=13)])}")
    
    time.sleep(0.1)

五、信号预处理

5.1 滤波操作

python 复制代码
from scipy.signal import butter, filtfilt

def butter_bandpass(lowcut, highcut, fs, order=5):
    """设计巴特沃斯带通滤波器"""
    nyquist = 0.5 * fs
    low = lowcut / nyquist
    high = highcut / nyquist
    b, a = butter(order, [low, high], btype='band')
    return b, a

def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
    """应用带通滤波"""
    b, a = butter_bandpass(lowcut, highcut, fs, order=order)
    y = filtfilt(b, a, data)
    return y

def butter_notch_filter(data, freq, fs, order=4):
    """设计陷波滤波器(去除工频干扰)"""
    nyquist = 0.5 * fs
    notch_freq = freq / nyquist
    b, a = butter(order, [notch_freq - 0.01, notch_freq + 0.01], btype='bandstop')
    y = filtfilt(b, a, data)
    return y

# 使用示例
filtered_data = butter_bandpass_filter(raw_data, 0.5, 50, fs=250)
filtered_data = butter_notch_filter(filtered_data, 50, fs=250)

5.2 伪迹去除

python 复制代码
import numpy as np

def detect_eye_blink(eeg_data, threshold=150):
    """检测眼电伪迹(眨眼)"""
    blink_indices = np.where(np.abs(eeg_data) > threshold)[0]
    return blink_indices

def remove_artifacts(eeg_data, blink_indices, window_size=100):
    """去除伪迹(用前后均值替换)"""
    cleaned_data = eeg_data.copy()
    
    for idx in blink_indices:
        # 获取周围数据
        start = max(0, idx - window_size)
        end = min(len(eeg_data), idx + window_size)
        
        # 计算前后均值
        before_mean = np.mean(cleaned_data[start:idx]) if start < idx else cleaned_data[idx]
        after_mean = np.mean(cleaned_data[idx+1:end]) if idx+1 < end else cleaned_data[idx]
        
        # 线性插值
        cleaned_data[idx] = (before_mean + after_mean) / 2
    
    return cleaned_data

# 使用示例
blink_indices = detect_eye_blink(eeg_data[0])
cleaned_data = remove_artifacts(eeg_data[0], blink_indices)

六、特征提取

6.1 频域特征

python 复制代码
from scipy.signal import welch

def extract_frequency_features(eeg_data, fs=250):
    """提取频域特征"""
    freqs, psd = welch(eeg_data, fs=fs, nperseg=256)
    
    # 定义频段
    bands = {
        'delta': (1, 4),
        'theta': (4, 8),
        'alpha': (8, 13),
        'beta': (13, 30),
        'gamma': (30, 50)
    }
    
    features = {}
    for band, (low, high) in bands.items():
        mask = (freqs >= low) & (freqs <= high)
        features[band] = np.mean(psd[mask])
    
    # 计算比值特征
    features['alpha/beta'] = features['alpha'] / (features['beta'] + 1e-10)
    features['theta/alpha'] = features['theta'] / (features['alpha'] + 1e-10)
    
    return features

# 使用示例
features = extract_frequency_features(eeg_data[0])
print("频域特征:")
for key, value in features.items():
    print(f"  {key}: {value:.4e}")

6.2 时域特征

python 复制代码
def extract_time_domain_features(eeg_data):
    """提取时域特征"""
    features = {}
    
    # 基本统计特征
    features['mean'] = np.mean(eeg_data)
    features['std'] = np.std(eeg_data)
    features['var'] = np.var(eeg_data)
    features['min'] = np.min(eeg_data)
    features['max'] = np.max(eeg_data)
    features['range'] = np.max(eeg_data) - np.min(eeg_data)
    
    # 高阶统计特征
    features['skewness'] = np.mean((eeg_data - features['mean']) ** 3) / (features['std'] ** 3 + 1e-10)
    features['kurtosis'] = np.mean((eeg_data - features['mean']) ** 4) / (features['std'] ** 4 + 1e-10)
    
    # 过零率
    features['zero_crossing_rate'] = np.sum(np.diff(np.sign(eeg_data)) != 0) / len(eeg_data)
    
    # RMS值
    features['rms'] = np.sqrt(np.mean(eeg_data ** 2))
    
    return features

# 使用示例
time_features = extract_time_domain_features(eeg_data[0])
print("时域特征:")
for key, value in time_features.items():
    print(f"  {key}: {value:.4f}")

七、完整实战项目

7.1 脑电信号实时监测系统

python 复制代码
import time
import numpy as np
import matplotlib.pyplot as plt
from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds
from collections import deque

class EEGMonitor:
    def __init__(self, board_id, port, fs=250):
        self.board_id = board_id
        self.port = port
        self.fs = fs
        self.board = None
        self.eeg_channels = []
        self.features_history = deque(maxlen=100)
        
    def connect(self):
        """连接设备"""
        params = BrainFlowInputParams()
        params.serial_port = self.port
        self.board = BoardShim(self.board_id, params)
        self.board.prepare_session()
        self.board.start_stream()
        self.eeg_channels = BoardShim.get_eeg_channels(self.board_id)
        print("设备连接成功")
        
    def disconnect(self):
        """断开连接"""
        if self.board:
            self.board.stop_stream()
            self.board.release_session()
            print("设备断开连接")
        
    def get_features(self, window_size=2):
        """获取特征"""
        data = self.board.get_current_board_data(int(self.fs * window_size))
        
        # 计算各通道频段功率
        features = []
        for ch in self.eeg_channels:
            ch_data = data[ch]
            
            # 滤波
            from scipy.signal import butter, filtfilt
            nyquist = 0.5 * self.fs
            b, a = butter(4, [0.5/nyquist, 50/nyquist], btype='band')
            ch_data = filtfilt(b, a, ch_data)
            
            # 计算频谱
            from scipy.signal import welch
            freqs, psd = welch(ch_data, fs=self.fs, nperseg=256)
            
            # 提取频段功率
            bands = [(1,4), (4,8), (8,13), (13,30), (30,50)]
            for low, high in bands:
                mask = (freqs >= low) & (freqs <= high)
                features.append(np.mean(psd[mask]))
        
        return np.array(features)
    
    def evaluate_state(self):
        """评估心理状态"""
        features = self.get_features()
        
        # 计算平均频段功率
        delta = np.mean(features[::5])
        theta = np.mean(features[1::5])
        alpha = np.mean(features[2::5])
        beta = np.mean(features[3::5])
        gamma = np.mean(features[4::5])
        
        # 计算状态指标
        alpha_beta_ratio = alpha / (beta + 1e-10)
        theta_alpha_ratio = theta / (alpha + 1e-10)
        
        # 判断状态
        if alpha_beta_ratio > 2:
            state = "放松"
            color = "green"
        elif alpha_beta_ratio > 1:
            state = "中等"
            color = "yellow"
        else:
            state = "紧张"
            color = "red"
        
        return {
            'state': state,
            'color': color,
            'alpha/beta': alpha_beta_ratio,
            'theta/alpha': theta_alpha_ratio,
            'bands': {'delta': delta, 'theta': theta, 'alpha': alpha, 'beta': beta, 'gamma': gamma}
        }

# 使用示例
if __name__ == "__main__":
    monitor = EEGMonitor(BoardIds.CYTON_BOARD.value, "COM3")
    
    try:
        monitor.connect()
        
        while True:
            state_info = monitor.evaluate_state()
            print(f"状态: {state_info['state']}")
            print(f"α/β比值: {state_info['alpha/beta']:.2f}")
            print(f"频段功率: {state_info['bands']}")
            print("-" * 40)
            time.sleep(1)
            
    except KeyboardInterrupt:
        monitor.disconnect()
        print("程序退出")

7.2 数据记录与回放

python 复制代码
import numpy as np
import os

class DataRecorder:
    def __init__(self, save_dir="data"):
        self.save_dir = save_dir
        os.makedirs(save_dir, exist_ok=True)
        
    def save_data(self, data, filename=None):
        """保存数据"""
        if filename is None:
            filename = f"eeg_data_{int(time.time())}.npy"
        
        filepath = os.path.join(self.save_dir, filename)
        np.save(filepath, data)
        print(f"数据已保存到: {filepath}")
        return filepath
    
    def load_data(self, filepath):
        """加载数据"""
        return np.load(filepath)
    
    def save_features(self, features, filename=None):
        """保存特征"""
        if filename is None:
            filename = f"features_{int(time.time())}.csv"
        
        filepath = os.path.join(self.save_dir, filename)
        import pandas as pd
        df = pd.DataFrame(features)
        df.to_csv(filepath, index=False)
        print(f"特征已保存到: {filepath}")
        return filepath

# 使用示例
recorder = DataRecorder()
recorder.save_data(data, "experiment_001.npy")

八、可视化技巧

8.1 实时动态图表

python 复制代码
import plotly.graph_objects as go
from plotly.subplots import make_subplots

# 创建实时更新的图表
fig = make_subplots(rows=2, cols=1, 
                    subplot_titles=("EEG波形", "频段功率"))

# 初始化数据
time_data = np.arange(0, 500)
eeg_data = np.zeros(500)
band_power_data = [0.2, 0.15, 0.4, 0.15, 0.1]
bands = ['delta', 'theta', 'alpha', 'beta', 'gamma']

# 添加轨迹
fig.add_trace(go.Scatter(x=time_data, y=eeg_data, 
                        mode='lines', name='EEG'), row=1, col=1)
fig.add_trace(go.Bar(x=bands, y=band_power_data, 
                    name='频段功率'), row=2, col=1)

# 更新布局
fig.update_layout(height=600, width=800, title="实时脑电监测")
fig.update_xaxes(title_text="样本", row=1, col=1)
fig.update_yaxes(title_text="电压 (μV)", row=1, col=1)
fig.update_xaxes(title_text="频段", row=2, col=1)
fig.update_yaxes(title_text="功率", row=2, col=1)

# 显示图表(在Jupyter环境中)
fig.show()

8.2 头部拓扑图

python 复制代码
import matplotlib.pyplot as plt
import numpy as np

def plot_head_topography(data, electrodes):
    """绘制头部拓扑图"""
    # 电极位置(简化版10-20系统)
    positions = {
        'Fp1': (-0.8, 0.9), 'Fp2': (0.8, 0.9),
        'F3': (-0.6, 0.5), 'F4': (0.6, 0.5),
        'C3': (-0.6, 0), 'C4': (0.6, 0),
        'P3': (-0.6, -0.5), 'P4': (0.6, -0.5),
        'O1': (-0.4, -0.9), 'O2': (0.4, -0.9),
        'Fz': (0, 0.5), 'Cz': (0, 0), 'Pz': (0, -0.5)
    }
    
    fig, ax = plt.subplots(figsize=(8, 8))
    
    # 绘制头部轮廓
    circle = plt.Circle((0, 0), 1, color='gray', fill=False, linewidth=2)
    ax.add_artist(circle)
    
    # 绘制电极
    for electrode, (x, y) in positions.items():
        if electrode in electrodes:
            idx = electrodes.index(electrode)
            value = data[idx]
            # 颜色映射
            color = plt.cm.viridis(value / np.max(data))
            ax.scatter(x, y, s=200, c=[color], edgecolors='black')
            ax.text(x, y+0.05, electrode, ha='center', fontsize=8)
    
    # 添加颜色条
    sm = plt.cm.ScalarMappable(cmap='viridis', 
                               norm=plt.Normalize(vmin=np.min(data), vmax=np.max(data)))
    sm.set_array([])
    plt.colorbar(sm, ax=ax, label='活动强度')
    
    ax.set_xlim(-1.2, 1.2)
    ax.set_ylim(-1.2, 1.2)
    ax.set_title('脑电活动拓扑图')
    plt.show()

# 使用示例
electrodes = ['Fp1', 'Fp2', 'F3', 'F4', 'C3', 'C4', 'P3', 'P4', 'O1', 'O2', 'Fz', 'Cz', 'Pz']
activity_data = np.random.rand(len(electrodes))
plot_head_topography(activity_data, electrodes)

九、常见问题解决

9.1 串口权限问题

bash 复制代码
# Linux系统下添加用户到dialout组
sudo usermod -aG dialout $USER

# 重启后生效

9.2 实时性能优化

python 复制代码
# 优化建议:
# 1. 使用较小的缓冲区
# 2. 减少不必要的计算
# 3. 使用多线程/多进程
# 4. 降低采样率(如果允许)

# 示例:使用多线程
import threading

class DataProcessor(threading.Thread):
    def __init__(self, board):
        super().__init__()
        self.board = board
        self.running = True
        
    def run(self):
        while self.running:
            data = self.board.get_current_board_data(100)
            # 处理数据...
            time.sleep(0.05)
    
    def stop(self):
        self.running = False

9.3 数据同步问题

python 复制代码
# 使用时间戳同步
timestamps = data[BoardShim.get_timestamp_channel(board_id)]

# 计算实际采样率
actual_fs = len(timestamps) / (timestamps[-1] - timestamps[0])
print(f"实际采样率: {actual_fs:.2f} Hz")

十、总结

Python为BCI开发提供了强大的工具链,从数据采集到实时处理再到可视化,都有成熟的解决方案。

10.1 核心技能总结

  1. ✅ BrainFlow数据采集
  2. ✅ 实时数据处理
  3. ✅ 信号滤波与伪迹去除
  4. ✅ 特征提取(频域和时域)
  5. ✅ 数据可视化
  6. ✅ 状态评估

10.2 下一步计划

在下一篇文章中,我们将深入学习信号预处理技术,包括滤波、去噪和伪迹去除的高级方法。


**继续探索Python在BCI开发中的应用!**下一篇我们将学习信号预处理技术。

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

关键字:Python、OpenBCI、脑电采集、实时处理、信号分析


相关推荐
AI行业学习1 小时前
CC-Switch 下载、安装与使用配置指南【2026.5.29】
java·开发语言·vscode·python·eclipse·laravel
JustNow_Man1 小时前
“失败后自动拉起修复 Agent”的闭环流水线
前端·人工智能·chrome·python
许彰午1 小时前
03_Java流程控制详解
java·开发语言·python
SoftLipaRZC1 小时前
C语言内存函数完全指南:memcpy/memmove/memset/memcmp
c语言·开发语言
2201_761199041 小时前
python运维1
运维·开发语言·python
盼小辉丶2 小时前
PyTorch深度学习实战(55)——在Android上部署PyTorch模型
android·pytorch·python·模型部署
Cx330❀2 小时前
【Qt 核心机制篇】深度解析 Qt 信号与槽(Signals & Slots)机制:从底层原理、实战演练到 Lambda 进阶
linux·开发语言·c++·人工智能·qt·ubuntu
SunnyDays10112 小时前
使用 Python 加密、保护和签名 PowerPoint 演示文稿 (PPT)
python·powerpoint·加密 ppt·保护 ppt·给ppt添加数字签名