丰田研究院IV 2025:可解释认知分心检测方案——眼动熵+瞳孔特征深度解析

🎯 核心亮点

维度 内容
论文标题 Cognitive Distraction Detection Using Gaze and Pupil with an Interpretable Approach
会议 IEEE IV 2025(第36届IEEE智能车辆研讨会)
作者 Kimimasa Tamura, Simon Stent, John Gideon, Kohei Shintani, Guy Rosman
机构 Toyota Research Institute
被试人数 52人驾驶模拟器实验
核心创新 可解释性 + 跨任务泛化 + CatBoost优于Transformer
IMS关联 🔴 高(认知分心是Euro NCAP 2026重点)

📊 研究背景

认知分心检测的挑战

挑战 描述 本文解决方案
特征微妙 认知分心不像疲劳那样有明显视觉特征 使用眼动熵等高级特征
个体差异 不同人的眼动模式差异大 52人大规模实验
可解释性 深度学习黑箱难以落地 CatBoost + SHAP分析
跨任务泛化 n-back任务训练的模型能否泛化到statement任务 交叉任务验证

与EyeCue对比

维度 EyeCue (arXiv 2605.07859) Toyota IV 2025(本文)
方法 视频+眼动融合 眼动+瞳孔+生理特征
模型 深度学习(Transformer) CatBoost(树模型)
可解释性 注意力可视化 SHAP + Sobol分析
被试 未公开 52人
任务 自定义 n-back + statement
泛化性 未验证 ✅ 交叉任务验证

🔬 方法详解

特征工程

本文的核心贡献是全面的特征工程,包含以下几类:

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
import numpy as np
from typing import Tuple

class GazeFeatureExtractor:
"""眼动特征提取器"""

def __init__(self, fps: int = 30):
self.fps = fps

def extract_basic_stats(self, gaze_data: np.ndarray) -> dict:
"""
提取基础统计特征

Args:
gaze_data: 眼动数据 (N, 4) - x, y, pupil_left, pupil_right

Returns:
features: 基础统计特征字典
"""
x, y = gaze_data[:, 0], gaze_data[:, 1]
pupil_l, pupil_r = gaze_data[:, 2], gaze_data[:, 3]
pupil = (pupil_l + pupil_r) / 2

features = {
# 注视点统计
'gaze_x_mean': np.mean(x),
'gaze_x_std': np.std(x),
'gaze_y_mean': np.mean(y),
'gaze_y_std': np.std(y),

# 瞳孔统计
'pupil_mean': np.mean(pupil),
'pupil_std': np.std(pupil),
'pupil_min': np.min(pupil),
'pupil_max': np.max(pupil),

# 注视-扫视比
'fixation_saccade_ratio': self._calc_fixation_saccade_ratio(x, y),
}

return features

def _calc_fixation_saccade_ratio(self, x: np.ndarray, y: np.ndarray) -> float:
"""
计算注视-扫视比

注视:速度<阈值,扫视:速度>阈值
"""
# 计算瞬时速度
dx = np.diff(x)
dy = np.diff(y)
velocity = np.sqrt(dx**2 + dy**2) * self.fps

# 分类
fixation_threshold = 100 # 像素/秒
fixation_count = np.sum(velocity < fixation_threshold)
saccade_count = np.sum(velocity >= fixation_threshold)

ratio = fixation_count / (saccade_count + 1e-6)
return ratio

2. 眼动熵特征

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
    def extract_entropy_features(self, gaze_data: np.ndarray) -> dict:
"""
提取眼动熵特征

眼动熵衡量注视点的空间分布随机性
"""
x, y = gaze_data[:, 0], gaze_data[:, 1]

features = {
# 静态熵(Spatial Entropy)
'gaze_entropy_static': self._spatial_entropy(x, y),

# 动态熵(Transition Entropy)
'gaze_entropy_transition': self._transition_entropy(x, y),

# 样本熵(Sample Entropy)
'gaze_sample_entropy': self._sample_entropy(x, y),

# 近似熵(Approximate Entropy)
'gaze_approximate_entropy': self._approximate_entropy(x, y),
}

return features

def _spatial_entropy(self, x: np.ndarray, y: np.ndarray,
grid_size: int = 8) -> float:
"""
计算空间熵

将注视点分到网格中,计算分布的熵
"""
# 网格化
x_bins = np.linspace(x.min(), x.max() + 1e-6, grid_size + 1)
y_bins = np.linspace(y.min(), y.max() + 1e-6, grid_size + 1)

# 计算每个网格的注视次数
hist, _, _ = np.histogram2d(x, y, bins=[x_bins, y_bins])

# 归一化为概率
prob = hist.flatten() / (hist.sum() + 1e-6)
prob = prob[prob > 0]

# 香农熵
entropy = -np.sum(prob * np.log2(prob))
max_entropy = np.log2(len(prob))

# 归一化到0-1
normalized_entropy = entropy / max_entropy if max_entropy > 0 else 0

return normalized_entropy

def _transition_entropy(self, x: np.ndarray, y: np.ndarray,
grid_size: int = 8) -> float:
"""
计算转移熵

衡量注视点在网格间转移的随机性
"""
# 网格化
x_bins = np.linspace(x.min(), x.max() + 1e-6, grid_size + 1)
y_bins = np.linspace(y.min(), y.max() + 1e-6, grid_size + 1)

# 计算每个点的网格索引
x_idx = np.digitize(x, x_bins) - 1
y_idx = np.digitize(y, y_bins) - 1
x_idx = np.clip(x_idx, 0, grid_size - 1)
y_idx = np.clip(y_idx, 0, grid_size - 1)

# 计算转移矩阵
cell_idx = x_idx * grid_size + y_idx
transitions = np.zeros((grid_size * grid_size, grid_size * grid_size))

for i in range(len(cell_idx) - 1):
transitions[cell_idx[i], cell_idx[i+1]] += 1

# 归一化
row_sums = transitions.sum(axis=1, keepdims=True)
trans_prob = transitions / (row_sums + 1e-6)

# 计算转移熵
entropy = 0
for i in range(len(transitions)):
for j in range(len(transitions)):
if trans_prob[i, j] > 0:
entropy -= trans_prob[i, j] * np.log2(trans_prob[i, j])

# 归一化
max_entropy = np.log2(grid_size * grid_size)
normalized = entropy / max_entropy if max_entropy > 0 else 0

return normalized

def _sample_entropy(self, signal: np.ndarray, m: int = 2,
r: float = 0.2) -> float:
"""
计算样本熵

衡量信号的不规则性
"""
N = len(signal)
r *= np.std(signal)

def _count_matches(template, data, tol):
count = 0
for i in range(len(data) - len(template) + 1):
if np.max(np.abs(data[i:i+len(template)] - template)) <= tol:
count += 1
return count

# 简化实现
A = 0 # m+1长度匹配数
B = 0 # m长度匹配数

for i in range(N - m):
template_m = signal[i:i+m]
template_m1 = signal[i:i+m+1]

for j in range(i+1, N - m):
if np.max(np.abs(signal[j:j+m] - template_m)) <= r:
B += 1
if j < N - m and np.max(np.abs(signal[j:j+m+1] - template_m1)) <= r:
A += 1

if B == 0:
return 0

return -np.log(A / B)

def _approximate_entropy(self, signal: np.ndarray, m: int = 2,
r: float = 0.2) -> float:
"""计算近似熵"""
N = len(signal)
r *= np.std(signal)

def _phi(m):
patterns = []
for i in range(N - m + 1):
patterns.append(signal[i:i+m])

counts = []
for p in patterns:
count = sum(1 for q in patterns if np.max(np.abs(q - p)) <= r)
counts.append(count)

return np.mean(np.log(np.array(counts) / (N - m + 1)))

return _phi(m) - _phi(m + 1)


# 测试
if __name__ == "__main__":
extractor = GazeFeatureExtractor(fps=30)

# 模拟正常驾驶眼动数据
np.random.seed(42)
normal_gaze = np.column_stack([
np.random.normal(0.5, 0.1, 900), # x
np.random.normal(0.5, 0.08, 900), # y
np.random.normal(4.0, 0.3, 900), # pupil_l
np.random.normal(4.0, 0.3, 900), # pupil_r
])

# 模拟认知分心眼动数据(更分散、瞳孔变化更大)
distracted_gaze = np.column_stack([
np.random.normal(0.5, 0.2, 900), # x - 更分散
np.random.normal(0.5, 0.15, 900), # y - 更分散
np.random.normal(4.2, 0.5, 900), # pupil_l - 更大变化
np.random.normal(4.2, 0.5, 900), # pupil_r
])

normal_features = extractor.extract_entropy_features(normal_gaze)
distracted_features = extractor.extract_entropy_features(distracted_gaze)

print("正常状态:")
for k, v in normal_features.items():
print(f" {k}: {v:.4f}")

print("\n认知分心状态:")
for k, v in distracted_features.items():
print(f" {k}: {v:.4f}")

3. CatBoost分类器

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
from catboost import CatBoostClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

class CognitiveDistractionDetector:
"""认知分心检测器"""

def __init__(self):
self.model = CatBoostClassifier(
iterations=500,
learning_rate=0.05,
depth=6,
l2_leaf_reg=3.0,
loss_function='Logloss',
eval_metric='AUC',
random_seed=42,
verbose=100
)
self.feature_extractor = GazeFeatureExtractor()

def prepare_features(self, gaze_data: np.ndarray) -> dict:
"""准备所有特征"""
basic = self.feature_extractor.extract_basic_stats(gaze_data)
entropy = self.feature_extractor.extract_entropy_features(gaze_data)
return {**basic, **entropy}

def train(self, X: np.ndarray, y: np.ndarray):
"""
训练模型

Args:
X: 特征矩阵 (N, feature_dim)
y: 标签 (N,) - 0=正常, 1=认知分心
"""
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

self.model.fit(
X_train, y_train,
eval_set=(X_val, y_val),
early_stopping_rounds=50
)

# 评估
y_pred = self.model.predict(X_val)
print(classification_report(y_val, y_pred))

def explain(self, X: np.ndarray) -> dict:
"""
使用SHAP解释模型决策

Returns:
shap_values: 各特征对预测的贡献
"""
import shap
explainer = shap.TreeExplainer(self.model)
shap_values = explainer.shap_values(X)

# 特征重要性
feature_importance = self.model.get_feature_importance()

return {
'shap_values': shap_values,
'feature_importance': feature_importance,
}

📊 实验结果

检测性能

方法 准确率 F1-Score AUC
CatBoost(本文最优) 78.5% 77.8% 0.82
Transformer 76.2% 75.5% 0.79
Random Forest 74.8% 73.9% 0.77
Logistic Regression 68.3% 67.1% 0.71
SVM (RBF) 72.1% 71.0% 0.75

关键发现

  1. CatBoost优于Transformer

    • 更高准确率(78.5% vs 76.2%)
    • 更好的可解释性(SHAP分析)
    • 更快推理速度
  2. 眼动熵是关键特征

    • 空间熵排名前三
    • 转移熵贡献显著
    • 认知分心时熵值增大(注视更随机)
  3. 瞳孔基线重要

    • 基线瞳孔大小是Top-5特征
    • 认知负荷增加瞳孔直径
  4. 跨任务泛化成功

    • n-back训练 → statement测试:75.2%
    • statement训练 → n-back测试:73.8%
    • 证明模型学到通用认知分心特征

SHAP特征重要性

排名 特征 SHAP值 方向
1 眼动Y方向非线性 0.142 ↑分心
2 基线瞳孔大小 0.118 ↑分心
3 最小注视距离 0.095 ↑分心
4 空间熵 0.087 ↑分心
5 转移熵 0.073 ↑分心
6 注视-扫视比 0.068 ↓分心
7 瞳孔标准差 0.061 ↑分心
8 X方向标准差 0.052 ↑分心

🚗 IMS落地方案

系统架构

flowchart TD
    A[DMS摄像头] --> B[眼动追踪模块]
    B --> C[特征提取<br/>统计+熵]
    C --> D[CatBoost分类器<br/>INT8量化]
    D --> E{认知分心?}
    E -->|是| F[分级警报]
    E -->|否| G[持续监测]
    F --> H[一级: 视觉提示]
    F --> I[二级: 声音警告]
    F --> J[三级: 触觉反馈]

硬件选型

组件 推荐型号 参数
DMS摄像头 OV2311 2MP, RGB-IR, 全局快门
红外补光 SFH 4740 940nm, 120mW/sr
处理器 QCS8255 Hexagon NPU, 26 TOPS
模型大小 CatBoost INT8 ~200KB

Euro NCAP合规

Euro NCAP要求 本方案 合规性
检测认知分心 ✅ 支持 符合
检测时间 <60s ✅ 符合
误报率 <5% ⚠️ 78.5%准确率→误报~21%
可解释性 ✅ SHAP分析 优于深度学习

💡 IMS开发启示

优先级建议

优先级 任务 时间节点
🔴 P0 实现眼动熵特征提取 Q3 2026
🔴 P0 训练CatBoost分类器 Q3 2026
🟡 P1 SHAP可解释性集成 Q4 2026
🟡 P1 跨任务泛化测试 Q4 2026
🟢 P2 与疲劳检测融合 2027 Q1

技术路线

graph LR
    A[眼动数据采集] --> B[特征工程<br/>统计+熵+瞳孔]
    B --> C[CatBoost训练]
    C --> D[SHAP解释]
    D --> E[INT8量化部署]
    E --> F[实车测试]
    F --> G{精度达标?}
    G -->|是| H[量产集成]
    G -->|否| I[数据增强+迭代]
    I --> C

📚 参考资料

  1. 论文页面: https://toyotaresearchinstitute.github.io/IV25-cognitive-distraction/
  2. IEEE IV 2025: https://2025.ieee-iv.org/
  3. SHAP: https://github.com/shap/shap
  4. CatBoost: https://catboost.ai/
  5. EyeCue论文: https://arxiv.org/abs/2605.07859

📝 总结

丰田研究院的方案证明了可解释认知分心检测可行

  1. CatBoost优于Transformer:更高准确率+更好可解释性
  2. 眼动熵是关键:空间熵和转移熵有效区分认知分心
  3. 跨任务泛化成功:模型学到通用认知分心特征
  4. SHAP解释落地:OEM可理解模型决策逻辑

IMS落地建议: 优先实现眼动熵特征提取,使用CatBoost替代深度学习,通过SHAP满足可解释性要求。


本文最后更新:2026-08-19


丰田研究院IV 2025:可解释认知分心检测方案——眼动熵+瞳孔特征深度解析
https://dapalm.com/2026/08/19/2026-08-19-toyota-iv2025-cognitive-distraction-gaze-entropy/
作者
Mars
发布于
2026年8月19日
许可协议