# BDF 预处理脚本改进对比说明
本文对比原始预处理脚本 `Step1_0Preprocess.m` 和改进版 `Step1_0Preprocess_improved.m`,说明具体做了哪些改动,以及这些改动对后续分类模型的意义。
---
## 1. 总体思路变化
- **原始脚本 `Step1_0Preprocess.m`**
- 目标:从原始 BDF 中快速提取每段 2 s 的 PSD 特征并打上 4 类情绪标签。
- 预处理流程相对简单:一次滤波、一次全局基线校正 + 平均参考、非重叠分段、绝对功率特征。
- **改进版 `Step1_0Preprocess_improved.m`**
- 目标:提高预处理质量,增强特征稳定性,统一输出结构,方便后续 SVM/LDA/CNN/CapsNet 直接使用。
- 核心改动:
- 更合理的带通 + 工频陷波;
- 按 trial 的局部基线校正与平均参考;
- 带重叠的分段;
- 绝对功率改为相对功率;
- 输出中显式保存频带信息。
---
## 2. 滤波与陷波
### 原始脚本
```matlab
EEG = pop_eegfiltnew(EEG,3,50);
```
- 只做了 3–50 Hz 带通,未显式对 50 Hz 工频做陷波处理。
### 改进版
```matlab
EEG = pop_eegfiltnew(EEG,1,50); % 1–50 Hz 带通
EEG = pop_eegfiltnew(EEG,49,51,[],1);% 49–51 Hz 工频陷波
```
**改进点:**
- 下限从 3 Hz 放宽到 1 Hz,保留更多情绪相关的慢波信息;
- 显式对 50 Hz 工频加入陷波,降低电源噪声对 PSD 的影响。
---
## 3. 基线和平均参考策略
### 原始脚本(简化描述)
```matlab
baseline = data(:, EEG.event(typeorder-1).latency : EEG.event(typeorder).latency);
m_baseline = mean(baseline,2);
for i = 1:length(data)
data(:,i) = data(:,i) - m_baseline; % 基线一次性作用于全程
end
m_data = mean(data,1); % 全程平均参考
for i = 1:size(data,1)
data(i,:) = data(i,:) - m_data;
end
```
- 基线段来自事件 3→4,但对 **整个记录 `data`** 一次性做校正;
- 平均参考也是对整个时间段做一次;
- 不同 trial 之间会互相影响。
### 改进版
```matlab
baseIdx = round(EEG.event(typeorder-1).latency) : round(EEG.event(typeorder).latency);
taskIdx = round(EEG.event(typeorder).latency) : round(EEG.event(typeorder+1).latency);
% 边界保护
baseIdx(baseIdx<1) = 1; baseIdx(baseIdx>size(data,2)) = size(data,2);
taskIdx(taskIdx<1) = 1; taskIdx(taskIdx>size(data,2)) = size(data,2);
trialData = data(:, taskIdx); % 只取当前 trial 的任务段
baseData = data(:, baseIdx); % 当前 trial 的基线段
m_base = mean(baseData,2);
trialData = trialData - m_base; % 基线只作用于本 trial
m_ref = mean(trialData,1);
trialData = trialData - m_ref; % 平均参考也只在本 trial 内做
```
**改进点:**
- 基线校正与平均参考都 **局限在当前 trial 内**,不同 trial 不会互相“牵连”;
- 对 `baseIdx` / `taskIdx` 做边界保护,避免索引越界;
- 与 3‑4‑5 事件段的真实实验结构更加一致。
---
## 4. 3‑4‑5 事件结构与任务段
两版脚本都通过事件 type = 3, 4, 5 来确定一个 trial:
```matlab
for typeorder = 2:length(EEG.event)-1
if EEG.event(1,typeorder).type == 4 && ...
EEG.event(1,typeorder-1).type == 3 && ...
EEG.event(1,typeorder+1).type == 5
% 认为这里是一个完整的 trial
end
end
```
- **type=3**:基线段开始(刺激前安静期);
- **type=4**:任务段开始(视频/刺激开始);
- **type=5**:任务段结束。
**改进版的变化:**
- 明确使用 3→4 作为基线区间,4→5 作为任务区间;
- 基线和参考都只应用在这个任务段上,而不是全程。
---
## 5. 分段方式:非重叠 vs 重叠
### 原始脚本
- 采样率 512 Hz,`trialLength = 1024`,相当于每段 2 s;
- 任务段 `TaskData` 被整齐切成若干 **不重叠** 的 2 s 段:
```matlab
pointNumber = fix(segTotalLength / trialLength); % 不重叠
sampleInput = TaskData(:, 1+(pointOrder-1)*trialLength : pointOrder*trialLength);
```
### 改进版
- 保持 2 s 作为窗口长度,但允许 **1 s 步长**,即 50% 重叠:
```matlab
winLength_s = 2; step_s = 1;
trialLength = winLength_s * fs;
stepLength = step_s * fs;
pointNumber = floor((segTotalLength - trialLength)/stepLength) + 1;
idxStart = 1 + (pointOrder-1)*stepLength;
idxEnd = idxStart + trialLength - 1;
segData = trialData(:, idxStart:idxEnd);
```
**改进点:**
- 使用滑动窗口 + 重叠,能从同一 trial 中提取更多段落样本;
- 时间覆盖更连续,有利于后续模型在时间维度上“看到”更多信息;
- 对小 trial(时长接近 2 s)仍有保护:`segTotalLength < trialLength` 直接跳过。
---
## 6. PSD 计算与特征归一化
### 原始脚本
```matlab
[Pxx,f] = pwelch(sampleInput(elecOrder,:),1024,[],1024,512,'linear');
ind1 = find(f>=bandLimit{bandOrder}(1),1,'first');
ind2 = find(f>=bandLimit{bandOrder}(2),1,'first');
Power(elecOrder,pointOrder) = mean(Pxx(ind1:ind2)); % 绝对功率
```
- 使用固定参数的 `pwelch`,输出每个频带的 **绝对功率**;
- 不考虑该段在 4–49 Hz 整体能量的变化。
### 改进版
```matlab
[Pxx,f] = pwelch(x,[],0.5,[],fs); % 更通用的调用
% 总功率(4–49 Hz),用于归一化
indAll1 = find(f>=4,1,'first');
indAll2 = find(f>=49,1,'first');
totalPower = sum(Pxx(indAll1:indAll2));
if totalPower == 0, totalPower = eps; end
for b = 1:nBand
ind1 = find(f>=bandLimit{b}(1),1,'first');
ind2 = find(f>=bandLimit{b}(2),1,'first');
bandP = mean(Pxx(ind1:ind2));
PowerBand{b}(elecOrder,pointOrder) = bandP / totalPower; % 相对功率
end
```
**改进点:**
- `pwelch` 调用更稳健,减少版本兼容问题;
- 引入 4–49 Hz 总功率归一化,将每个频带功率转成 **相对功率**;
- 减少被试间/试次数间整体能量差异对分类的干扰,特征更可比。
---
## 7. 标签划分与情绪象限
两版脚本在标签划分上保持一致:
```matlab
Valunce = SubVA(Trialnum,3);
Arousal = SubVA(Trialnum,4);
if Valunce < 5 && Arousal < 5
flag = 1; % LVLA
elseif Valunce > 5 && Arousal < 5
flag = 2; % HVLA
elseif Valunce < 5 && Arousal > 5
flag = 3; % LVHA
else
flag = 4; % HVHA
end
Flag{Trialnum,1} = repmat(flag, pointNumber, 1);
```
- 使用 V/A 评分 5 作为分界线,将情绪分为 4 个象限:
- 1:低效价 + 低唤醒 (LVLA)
- 2:高效价 + 低唤醒 (HVLA)
- 3:低效价 + 高唤醒 (LVHA)
- 4:高效价 + 高唤醒 (HVHA)
- `Flag{Trialnum,1}` 中为该 trial 所有段重复相同标签,方便后续段级分类。
改进版只是保持了同样的标签规则,未改变情绪定义。
---
## 8. 输出结构的统一
### 原始脚本
```matlab
Taskdata{Trialnum,bandOrder} = Power; % [通道 × 段数]
Flag{Trialnum,1} = repmat(flag,pointNumber,1);
save(...,'Taskdata','Flag');
```
- 只保存了 `Taskdata` 和 `Flag`,频带信息通过脚本中的 `bandLimit`、`bandName` 隐式给出。
### 改进版
```matlab
Taskdata{Trialnum,b} = PowerBand{b};
Flag{Trialnum,1} = repmat(flag,pointNumber,1);
...
save(...,'Taskdata','Flag','bandLimit','bandName');
```
**改进点:**
- 显式保存 `bandLimit` 和 `bandName`,下游脚本可直接读取,不再依赖“约定俗成”的频带顺序;
- BDF 和 CNT 预处理脚本都对齐成相同结构,方便 `Step1_1SVM_KFold_SubBand_improved.m`、`Step1_1LDA_KFold_SubBand.m`、`Step1_1CNN_TrialLevel.m` 等脚本统一处理。
---
## 9. 改进总结
相对于原始 `Step1_0Preprocess.m`,改进版 `Step1_0Preprocess_improved.m` 的主要收益可以概括为:
1. **频带处理更合理**:1–50 Hz + 50 Hz 陷波,抑制工频噪声,保留更多低频信息;
2. **trial 内局部基线与平均参考**:每个 trial 单独去基线、做平均参考,避免不同 trial 互相影响;
3. **重叠分段**:2 s 窗 + 1 s 步长,提高样本量与时间覆盖度;
4. **相对功率特征**:对 4–49 Hz 总功率归一化,特征更稳健、更可比;
5. **索引安全与版本兼容**:增加边界保护、调整 `pwelch` 调用方式,减少潜在报错;
6. **输出结构更完整统一**:统一输出 `Taskdata/Flag/bandLimit/bandName`,为 SVM/LDA/CNN/CapsNet 等模型提供统一输入接口。
整体来说,改进版在不改变实验标签和 3‑4‑5 事件结构的前提下,大幅提升了预处理质量和后续建模的便利性。