背景:为什么要 FBCCA?
SSVEP 解码的目标是回答一个问题:被试当前注视的是哪个频率的刺激?
最简单的做法是 FFT(快速傅里叶变换)——看 EEG 信号频谱中哪个频率能量最大。但这种方法在数据长度较短(< 3秒)或信噪比较低的情况下,准确率会急剧下降。
标准 CCA(典型相关分析)比 FFT 好得多——它将解码问题转化为”找到一组参考信号,使得它们与 EEG 信号的线性组合相关性最大”。但标准 CCA 有一个缺陷:它把原始 EEG 信号直接和参考信号做整体相关,没有先做滤波分解,噪声频段和无关频段一起参与计算,信噪比受限。
FBCCA(滤波器组-CCA)通过在多个子频带上分别做 CCA,然后将各频带的相关性加权融合,让每个子带只贡献它擅长的频段信息,克服了标准 CCA 的这个局限。
数学基础:CCA
CCA 的目标是:给定两组变量 和 ,找到两组线性变换 和 ,使得变换后的变量 和 相关性最大。
这是一个广义特征值问题,通过 SVD 可以快速求解。
FBCCA 的完整流程
第一步:滤波器组
将 EEG 信号通过多个带通滤波器,得到若干子频带信号:
import numpy as np
from scipy.signal import butter, filtfilt
def apply_filter_bank(eeg, fs, num_bands=5):
"""
eeg: (n_channels, n_samples)
滤波器组:将8Hz以上频带划分为num_bands个子带,低截止频率递增
"""
bands = []
for k in range(1, num_bands + 1):
low = 8 * k # 第k个子带的低截止频率
high = min(90, 8 * (k + 2))
b, a = butter(3, [low / (fs/2), high / (fs/2)], btype='band')
band_signal = filtfilt(b, a, eeg, axis=1)
bands.append(band_signal)
return bands
这里给出的是简化的等宽子带划分示意。实际的 FBCCA 设计通常让低频子带覆盖基频、高频子带覆盖高次谐波,各子带权重按信噪比递减配置。
第二步:构造参考信号
对于每个候选频率 ,构造其基频和多次谐波的参考信号:
\sin(2\pi \cdot 1 \cdot f_k \cdot t) & \cos(2\pi \cdot 1 \cdot f_k \cdot t) \\ \sin(2\pi \cdot 2 \cdot f_k \cdot t) & \cos(2\pi \cdot 2 \cdot f_k \cdot t) \\ \vdots & \vdots \\ \sin(2\pi \cdot N_h \cdot f_k \cdot t) & \cos(2\pi \cdot N_h \cdot f_k \cdot t) \end{bmatrix}^T $$ ```python def build_reference_signal(freq, t, num_harmonics=5): """构造包含基频和N_h次谐波的参考信号""" ref = [] for h in range(1, num_harmonics + 1): ref.append(np.sin(2 * np.pi * h * freq * t)) ref.append(np.cos(2 * np.pi * h * freq * t)) return np.array(ref).T # (n_samples, 2 * num_harmonics) ``` ### 第三步:多频带 CCA 计算 ```python from sklearn.cross_decomposition import CCA def fbcca_classify(eeg_bands, freqs, t, fs): """ eeg_bands: 滤波器组输出, list of (n_channels, n_samples) freqs: 候选频率列表 返回: 预测的类别索引 """ num_bands = len(eeg_bands) correlations = np.zeros((len(freqs), num_bands)) for i, freq in enumerate(freqs): ref = build_reference_signal(freq, t, num_harmonics=5) for j, band in enumerate(eeg_bands): cca = CCA(n_components=1) X = band.T # (n_samples, n_channels) Y = ref cca.fit(X, Y) U, V = cca.transform(X, Y) correlations[i, j] = np.corrcoef(U.T, V.T)[0, 1] # 加权融合 weights = np.array([k ** (-1.25) + 0.25 for k in range(1, num_bands + 1)]) weighted_corr = correlations @ weights return np.argmax(weighted_corr) ``` ### 第四步:加权融合 权重公式是 FBCCA 的关键设计: $$ w(n) = n^{-1.25} + 0.25 $$ 其中 $n$ 是子频带索引(1-based)。这种设计使得: - 低频子带(基频)权重较高:基频信号最强,最可靠 - 高频子带(谐波)权重递减但不为零:谐波提供了额外的判别信息 - 常数偏移 $+0.25$:确保高次谐波仍有最小权重 ## 实验效果 在公开数据集(清华大学 SSVEP Benchmark Dataset)上的对比: | 算法 | 数据长度 0.5s | 1.0s | 2.0s | |------|-------------|------|------| | FFT 峰值检测 | 42.3% | 58.7% | 72.1% | | 标准 CCA | 61.5% | 78.3% | 88.6% | | FBCCA (N_h=3) | 73.2% | 87.4% | 93.8% | | FBCCA (N_h=5) | 78.9% | 90.1% | 95.6% | FBCCA 在 1.0s 的短数据窗口下就能达到约 90% 的准确率,这对于实时 BCI 系统至关重要——更短的数据窗口意味着更快的指令响应速度。 ## 工程优化 在实际部署中,以下几个优化可以进一步提升性能: 1. **滤波器组的重叠设计**:相邻子带之间设置 2Hz 的重叠区域,避免边缘频率的信号丢失 2. **通道选择**:只选用枕叶(O1、O2、Oz、POz)电极,减少不相关通道的噪声 3. **数据缓存**:缓存最近 N 次识别结果,用投票机制提高稳定性 4. **自适应校准**:系统运行中不断更新参考模板,适应被试注意力状态的变化 ## 小结 FBCCA 之所以成为 SSVEP-BCI 的标准解码算法,是因为它在**速度、精度、鲁棒性**三者之间达到了最优平衡。理解 FBCCA 不仅意味着掌握一种算法,更是理解 BCI 信号处理核心思维的入口——如何在极低信噪比的环境中,通过精巧的数学设计提取出可靠的意图信号。
