主体无关的驾驶员疲劳检测:NTHU数据集严格评估框架(MDPI Sensors 2026)

主体无关的驾驶员疲劳检测:严格评估框架

论文信息

  • 标题:A Subject-Independent Temporal Framework for Behavioural Eye-Based Driver Drowsiness Detection
  • 期刊:MDPI Sensors, 2026, 26(19), 6193
  • 链接:MDPI
  • 发表:2026-09-29

核心问题

现有疲劳检测方法的泛化性被严重高估。

问题 描述 影响
数据泄露 同一驾驶员的数据出现在训练集和测试集 泛化性虚高 10-20%
主体依赖 模型学到特定驾驶员的个人特征 换驾驶员后性能骤降
评估不一致 各论文使用不同数据划分 方法间无法公平比较

一句话总结: 本文在 NTHU 数据集上进行严格的主体无关(leave-one-driver-out)评估,证明现有方法在新驾驶员上的泛化性能显著下降,并提出时序框架改善这一问题。

方法详解

1. 严格评估协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
"""
Leave-One-Driver-Out (LODO) 交叉验证

传统方法:
- 随机划分 → 同一驾驶员的帧出现在训练集和测试集
- 结果:准确率 95%+,但无法泛化

严格方法:
- 每次留出一个驾驶员的全部数据作为测试集
- 用其余驾驶员训练
- 循环 N 次(N = 驾驶员数)
- 结果:真实泛化性能
"""

import numpy as np
from sklearn.model_selection import KFold

class LeaveOneDriverOut:
"""
主体无关交叉验证框架
"""

def __init__(self, driver_ids: np.ndarray):
"""
Args:
driver_ids: 每个样本对应的驾驶员ID (n_samples,)
"""
self.drivers = np.unique(driver_ids)
self.n_drivers = len(self.drivers)

def split(self, X, y, driver_ids):
"""生成 LODO 划分"""
for test_driver in self.drivers:
test_mask = driver_ids == test_driver
train_mask = ~test_mask

yield train_mask, test_mask

def evaluate(self, model, X, y, driver_ids):
"""
严格评估

Returns:
per_driver_accuracy: 每个驾驶员作为测试集时的准确率
mean_accuracy: 平均准确率
std_accuracy: 标准差(泛化稳定性)
"""
per_driver = []

for test_driver in self.drivers:
test_mask = driver_ids == test_driver
train_mask = ~test_mask

model.fit(X[train_mask], y[train_mask])
acc = model.score(X[test_mask], y[test_mask])

per_driver.append(acc)
print(f" 驾驶员 {test_driver}: 准确率 {acc:.2%}")

return {
'per_driver': per_driver,
'mean': np.mean(per_driver),
'std': np.std(per_driver),
'min': np.min(per_driver),
'max': np.max(per_driver)
}

2. NTHU 数据集分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""
NTHU Driver Drowsiness Detection Dataset 统计
"""

nthu_stats = {
'总帧数': 65929,
'驾驶员数': 4,
'类别': ['非疲劳(0)', '轻度疲劳(1)', '严重疲劳(2)'],
'采集场景': [
'白天/夜晚', '戴眼镜/不戴眼镜',
'正常光照/昏暗光照'
],
'标注': '人工逐帧标注'
}

# 传统 vs 严格评估
evaluation_comparison = {
'随机划分': {
'准确率': '95%+',
'问题': '同一驾驶员跨集',
'泛化评估': '虚高'
},
'LODO划分': {
'准确率': '70-80%',
'问题': '主体无关泛化',
'泛化评估': '真实'
}
}

3. 时序框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""
主体无关的时序疲劳检测框架
"""

import torch
import torch.nn as nn

class SubjectIndependentDrowsinessModel(nn.Module):
"""
设计目标:在新驾驶员上泛化

核心策略:
1. 使用主体无关特征(眨眼频率、PERCLOS、头部运动模式)
2. 时序建模捕捉疲劳趋势(而非瞬时状态)
3. 对抗训练消除个体特征
"""

def __init__(self, feature_dim: int = 64,
hidden_dim: int = 128,
num_classes: int = 3):
super().__init__()

# 时序特征提取(LSTM)
self.temporal_encoder = nn.LSTM(
input_size=feature_dim,
hidden_size=hidden_dim,
num_layers=2,
batch_first=True,
bidirectional=False, # 因果性:只用过去
dropout=0.3
)

# 主体无关特征提取器
# 只提取与个人身份无关的特征
self.subject_invariant_features = SubjectInvariantExtractor(
input_dim=feature_dim,
output_dim=32
)

# 分类头
self.classifier = nn.Sequential(
nn.Linear(hidden_dim + 32, 64),
nn.GELU(),
nn.Dropout(0.3),
nn.Linear(64, num_classes)
)

# 对抗判别器(用于对抗训练)
self.subject_discriminator = nn.Sequential(
nn.Linear(hidden_dim, 32),
nn.ReLU(),
nn.Linear(32, 4) # 4个驾驶员身份
)

def forward(self, x, return_features=False):
"""
Args:
x: (B, T, feature_dim) 时序特征序列
feature_dim 包含: [eye_aspect_ratio, blink_freq,
perclos, head_pose, gaze_entropy, ...]
"""
# 时序编码
temporal_out, (h_n, c_n) = self.temporal_encoder(x)
temporal_feat = temporal_out[:, -1, :] # 取最后时刻 (B, hidden_dim)

# 主体无关特征
invariant_feat = self.subject_invariant_features(x[:, -1, :])

# 融合
combined = torch.cat([temporal_feat, invariant_feat], dim=1)

# 分类
logits = self.classifier(combined)

if return_features:
return logits, temporal_feat, combined
return logits

def adversarial_loss(self, temporal_feat, driver_labels):
"""
对抗损失:让时序特征无法区分驾驶员身份

梯度反转层(GRL)实现
"""
# 逆转梯度
reversed_feat = GradientReversalLayer.apply(temporal_feat, 1.0)
driver_pred = self.subject_discriminator(reversed_feat)

# 如果判别器无法预测驾驶员身份
# 说明特征已主体无关
adv_loss = nn.CrossEntropyLoss()(driver_pred, driver_labels)
return adv_loss


class SubjectInvariantExtractor(nn.Module):
"""
提取主体无关特征

关键洞察:某些特征与疲劳程度相关但与个人身份无关
- PERCLOS:闭眼时间比例(所有人均适用)
- 眨眼频率变化趋势(频率上升=疲劳)
- 头部下垂角度变化率(而非绝对角度)

而以下特征是主体相关的,应消除:
- 眼睛绝对大小(因人而异)
- 头部绝对姿态(坐姿习惯不同)
- 眨眼绝对频率(基线不同)
"""

def __init__(self, input_dim: int, output_dim: int):
super().__init__()
self.extractor = nn.Sequential(
nn.Linear(input_dim, 64),
nn.BatchNorm1d(64), # 批归一化消除个体偏移
nn.GELU(),
nn.Linear(64, output_dim)
)

def forward(self, x):
return self.extractor(x)


class GradientReversalLayer:
"""梯度反转层"""
@staticmethod
def apply(x, lambda_):
x = x.clone()
x.requires_grad = True

class GRLFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, x):
return x.view_as(x)

@staticmethod
def backward(ctx, grad_output):
return -lambda_ * grad_output

return GRLFunction.apply(x)


# ============ 完整训练流程 ============

def train_subject_independent_model():
"""
主体无关疲劳检测模型训练流程
"""
model = SubjectIndependentDrowsinessModel()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

# LODO 交叉验证
for fold, (train_idx, test_idx) in enumerate(lodo_splits):
print(f"\n=== Fold {fold+1}: 测试驾驶员 = {test_drivers[fold]} ===")

for epoch in range(50):
model.train()
for batch_x, batch_y, batch_driver in train_loader:
# 1. 主任务:疲劳分类
logits, temporal_feat, _ = model(batch_x, return_features=True)
cls_loss = nn.CrossEntropyLoss()(logits, batch_y)

# 2. 对抗任务:驾驶员身份消除
adv_loss = model.adversarial_loss(temporal_feat, batch_driver)

# 3. 总损失
total_loss = cls_loss + 0.1 * adv_loss

optimizer.zero_grad()
total_loss.backward()
optimizer.step()

# 在测试集评估
model.eval()
with torch.no_grad():
test_acc = evaluate(model, test_loader)

if epoch % 10 == 0:
print(f" Epoch {epoch}: cls_loss={cls_loss:.4f}, "
f"adv_loss={adv_loss:.4f}, test_acc={test_acc:.2%}")


if __name__ == "__main__":
# 模拟特征数据
torch.manual_seed(42)

# 4个驾驶员,每人约16500帧
n_drivers = 4
n_per_driver = 16482 # 65929 / 4
seq_len = 30 # 30帧时序窗口
feature_dim = 8 # [EAR, blink_freq, perclos, head_pitch, head_yaw, gaze_x, gaze_y, pupil_size]

# 模拟数据
X = torch.randn(n_drivers * 500, seq_len, feature_dim)
y = torch.randint(0, 3, (n_drivers * 500,))
driver_ids = torch.repeat_interleave(torch.arange(n_drivers), 500)

model = SubjectIndependentDrowsinessModel(feature_dim=feature_dim)

# 前向测试
logits, temporal_feat, combined = model(X[:4], return_features=True)
print(f"输入形状: {X[:4].shape}")
print(f"输出形状: {logits.shape}")
print(f"时序特征形状: {temporal_feat.shape}")
print(f"融合特征形状: {combined.shape}")

# 对抗损失测试
adv_loss = model.adversarial_loss(temporal_feat, torch.tensor([0,1,2,3]))
print(f"对抗损失: {adv_loss.item():.4f}")

total_params = sum(p.numel() for p in model.parameters())
print(f"\n模型参数量: {total_params/1e6:.2f}M")

实验结果

传统 vs 严格评估对比

评估方法 准确率 标准差 泛化评估
随机划分(传统) 95.2% 1.2% ❌ 虚高
LODO(严格) 73.8% 8.5% 真实泛化
LODO + 对抗训练 78.3% 5.2% ✅ 改善

各驾驶员准确率

驾驶员 传统方法 LODO LODO+对抗
D1 96.5% 65.2% 74.1%
D2 94.8% 82.1% 85.3%
D3 95.7% 68.7% 76.9%
D4 95.8% 79.2% 77.0%

关键发现: D1 和 D3 在传统方法中表现最好,但在 LODO 中表现最差——说明模型学到了他们的个人特征而非疲劳通用特征。

混淆矩阵分析

1
2
3
4
5
6
7
8
9
10
11
12
# LODO 评估的混淆矩阵
confusion_matrix_lodo = {
'实际\预测': ['非疲劳', '轻度', '严重'],
'非疲劳': [78.5, 15.2, 6.3], # 误将正常判为轻度
'轻度疲劳': [22.1, 62.8, 15.1], # 轻度疲劳最难检测
'严重疲劳': [5.2, 18.7, 76.1], # 严重疲劳检测较好
}

# 关键问题:
# 1. 轻度疲劳检测率最低(62.8%)——这是最需要早期预警的阶段
# 2. 非疲劳→轻度疲劳的误报率 15.2%——影响用户体验
# 3. 严重疲劳→非疲劳的漏报率 5.2%——安全风险

IMS 开发启示

1. 评估协议必须严格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""
IMS 疲劳检测评估规范
"""

# ❌ 错误做法
def wrong_evaluation(model, dataset):
"""随机划分导致泛化虚高"""
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
dataset.X, dataset.y, test_size=0.2, random_state=42
)
model.fit(X_train, y_train)
return model.score(X_test, y_test) # 95%+ 但不可信

# ✅ 正确做法
def correct_evaluation(model, dataset):
"""LODO 划分确保泛化性"""
from sklearn.model_selection import LeaveOneGroupOut
logo = LeaveOneGroupOut()

scores = []
for train_idx, test_idx in logo.split(
dataset.X, dataset.y, groups=dataset.driver_ids
):
model.fit(dataset.X[train_idx], dataset.y[train_idx])
score = model.score(dataset.X[test_idx], dataset.y[test_idx])
scores.append(score)

return {
'mean': np.mean(scores),
'std': np.std(scores),
'min': np.min(scores), # 最差驾驶员的表现
'per_driver': scores
}

2. 主体无关特征选择

特征 主体无关性 疲劳相关性 建议
PERCLOS ✅ 高 ✅ 高 优先使用
眨眼频率变化率 ✅ 高 ✅ 中 推荐
头部下垂速度 ✅ 高 ✅ 中 推荐
眼睛绝对大小 ❌ 低 ⚠️ 中 需归一化
眨眼绝对频率 ❌ 低 ⚠️ 中 需基线校准
头部绝对姿态 ❌ 低 ⚠️ 低 不推荐

3. 实车部署校准流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
### IMS 疲劳检测实车部署校准

**前置条件:**
- 新驾驶员首次使用 IMS
- 正常驾驶状态(非疲劳)

**校准步骤:**
1. 采集前 5 分钟正常驾驶数据
2. 计算个人基线:
- 基线眨眼频率
- 基线 PERCLOS
- 基线头部姿态
3. 后续检测使用相对变化而非绝对阈值
4. 每次驾驶前 2 分钟刷新基线

**判定条件:**
| 指标 | 校准要求 |
|------|---------|
| 基线采集时长 | ≥ 5 分钟 |
| 基线刷新间隔 | 每次驾驶 |
| 相对变化阈值 | PERCLOS > 2× 基线 |
| 最少数据帧 | ≥ 3000 帧 |

**硬件配置:**
| 组件 | 型号 | 参数 |
|------|------|------|
| DMS 摄像头 | OV2311 | 2MP, 全局快门, 1600×1200 |
| 红外补光 | SFH 4740 | 940nm, 120mW/sr |
| 处理器 | QCS8255 | 8核, Hexagon NPU |

与其他工作的对比

方法 评估协议 准确率 泛化保证
传统 CNN 随机划分 95%+ ❌
GAN 增强 随机划分 96%+ ❌
本文(LODO 基线) LODO 73.8% ✅
本文(LODO + 对抗) LODO 78.3% ✅

总结

本文的核心贡献不在于算法创新,而在于评估视角的纠正。它揭示了:

  1. 95%+ 的疲劳检测准确率是假象 — 真实泛化性能约 73.8%
  2. 轻度疲劳检测是瓶颈 — 62.8% 准确率远低于严重疲劳
  3. 对抗训练有效但不充分 — 提升约 5%,仍需改进
  4. 个体基线校准是必须的 — 绝对阈值在不同驾驶员间不通用

对 IMS 的核心启示: 不要相信随机划分的疲劳检测准确率。IMS 必须采用 LODO 评估,并实车部署时进行个体基线校准。


论文:MDPI Sensors 2026, 26(19), 6193


主体无关的驾驶员疲劳检测:NTHU数据集严格评估框架(MDPI Sensors 2026)
https://dapalm.com/2026/10/05/2026-10-05-005-subject-independent-drowsiness-nthu-mdpi2026/
作者
Mars
发布于
2026年10月5日
许可协议