Euro NCAP 认知分心检测研究进展:眼动规律性与注视分散算法完整实现

一、认知分心检测的挑战

1.1 什么是认知分心

认知分心(Cognitive Distraction) 是指驾驶员”人在心不在”——眼睛看着道路,但注意力被分散到其他认知任务。与视觉分心不同,认知分心更难检测。

分心类型 定义 检测方法 检测难度
视觉分心 眼睛离开道路 视线落点追踪
手动分心 手离开方向盘 手部检测
认知分心 注意力离开驾驶任务 眼动规律性/瞳孔测量

1.2 认知分心的影响

根据 NHTSA 研究,认知分心会导致:

影响类型 程度 说明
反应时间延长 20-40% 刹车/转向反应变慢
隧道效应 视野收窄 外围信息处理能力下降
情境意识降低 显著 对周围环境感知减弱
“看而不见”事故 增加 Looked-but-failed-to-see

1.3 Euro NCAP 路线图

Euro NCAP Vision 2030 将认知分心检测纳入未来路线图:

时间节点 要求
2026 长分心/短分心检测(视觉分心)
2028 认知分心检测研究阶段
2030+ 认知分心检测纳入评分(预计)

二、眼动指标研究进展(2024-2025)

2.1 关键研究:Gaze-Based Indicators(ArXiv 2508.10624)

2025年8月发表的一项驾驶模拟器研究(N=29)发现:

实验设计:

  • 3种交通复杂度
  • 2种认知状态(心算任务 vs 正常)
  • 自由使用 ACC

关键发现:

指标 认知分心时变化 说明
Percent Road Center (PRC) ↓ 减少 注视道路中心比例下降
垂直注视分散 ↑ 增加 上下扫视范围扩大
水平注视分散 ↓ 减少 左右扫视范围收窄
隧道效应 ↑ 增强 心算期间暂时集中

重要发现: 认知分心的眼动模式具有阶段性

  • 心算期间:注视集中(隧道效应)
  • 心算间隙:注视分散(恢复性扫视)

2.2 检测指标综述

指标 计算方法 认知分心特征 成熟度
Percent Road Center (PRC) 道路中心区域注视比例 双向变化(心算↑,间隙↓)
水平注视分散 (HGD) 水平角度标准差 ↓ 减少
垂直注视分散 (VGD) 垂直角度标准差 ↑ 增加
瞳孔直径 平均瞳孔直径 ↑ 增加(认知负荷)
瞳孔变异性 瞳孔直径标准差 ↑ 增加
眨眼频率 单位时间眨眼次数 ↑ 增加
扫视频率 单位时间扫视次数 ↓ 减少

三、眼动指标计算完整实现

3.1 Percent Road Center (PRC) 计算

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
import numpy as np
from typing import Tuple, List
from dataclasses import dataclass
from enum import Enum

class GazeLocation(Enum):
"""注视位置分类"""
ROAD_CENTER = "road_center"
ROAD_LEFT = "road_left"
ROAD_RIGHT = "road_right"
MIRROR_LEFT = "mirror_left"
MIRROR_RIGHT = "mirror_right"
INSTRUMENT_CLUSTER = "instrument_cluster"
INFOTAINMENT = "infotainment"
UNKNOWN = "unknown"


@dataclass
class GazeData:
"""注视数据"""
timestamp: float # 时间戳(秒)
h_angle: float # 水平角度(度),正值=右,负值=左
v_angle: float # 垂直角度(度),正值=上,负值=下
pupil_diameter: float # 瞳孔直径(mm)
confidence: float # 置信度


class PercentRoadCenterCalculator:
"""
Percent Road Center (PRC) 计算器

PRC = (道路中心区域注视点数 / 总注视点数) × 100%

道路中心区域定义:
- 水平角度:前方 ±10°(可调)
- 垂直角度:前方 ±5°(可调)

参考:Victor et al. (2005), Harbluk et al. (2007)
"""

def __init__(self,
h_center_range: Tuple[float, float] = (-10.0, 10.0),
v_center_range: Tuple[float, float] = (-5.0, 5.0),
window_sec: float = 60.0):
"""
初始化 PRC 计算器

Args:
h_center_range: 水平角度范围(度),默认 ±10°
v_center_range: 垂直角度范围(度),默认 ±5°
window_sec: 计算窗口(秒),默认 60 秒
"""
self.h_center_range = h_center_range
self.v_center_range = v_center_range
self.window_sec = window_sec

def calculate(self, gaze_data: List[GazeData]) -> float:
"""
计算 Percent Road Center

Args:
gaze_data: 注视数据列表

Returns:
PRC 百分比 (0-100)
"""
if not gaze_data:
return 0.0

total_count = len(gaze_data)
center_count = 0

for gd in gaze_data:
# 判断是否在道路中心区域
h_in_range = self.h_center_range[0] <= gd.h_angle <= self.h_center_range[1]
v_in_range = self.v_center_range[0] <= gd.v_angle <= self.v_center_range[1]

if h_in_range and v_in_range:
center_count += 1

prc = (center_count / total_count) * 100.0
return prc

def classify_gaze_location(self, gd: GazeData) -> GazeLocation:
"""
分类注视位置

Args:
gd: 注视数据

Returns:
GazeLocation 枚举值
"""
h, v = gd.h_angle, gd.v_angle

# 道路中心
if -10 <= h <= 10 and -5 <= v <= 5:
return GazeLocation.ROAD_CENTER

# 左侧道路
if -30 <= h < -10 and -10 <= v <= 10:
return GazeLocation.ROAD_LEFT

# 右侧道路
if 10 < h <= 30 and -10 <= v <= 10:
return GazeLocation.ROAD_RIGHT

# 左后视镜
if -50 <= h < -30 and 10 <= v <= 30:
return GazeLocation.MIRROR_LEFT

# 右后视镜
if 30 < h <= 50 and 10 <= v <= 30:
return GazeLocation.MIRROR_RIGHT

# 仪表盘
if -20 <= h <= 20 and -30 <= v < -5:
return GazeLocation.INSTRUMENT_CLUSTER

# 中控屏
if 20 < h <= 60 and -30 <= v <= 10:
return GazeLocation.INFOTAINMENT

return GazeLocation.UNKNOWN


# 测试代码
if __name__ == "__main__":
calculator = PercentRoadCenterCalculator()

# 模拟数据
import random
gaze_data = []
for i in range(1800): # 60秒,30fps
gd = GazeData(
timestamp=i / 30.0,
h_angle=random.gauss(0, 15), # 水平角度,均值0,标准差15
v_angle=random.gauss(0, 8), # 垂直角度
pupil_diameter=random.gauss(4.5, 0.5),
confidence=0.9
)
gaze_data.append(gd)

prc = calculator.calculate(gaze_data)
print(f"Percent Road Center: {prc:.1f}%")

3.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
import numpy as np
from typing import List, Tuple
from dataclasses import dataclass
from collections import deque

@dataclass
class DispersionMetrics:
"""注视分散指标"""
h_dispersion: float # 水平分散(度)
v_dispersion: float # 垂直分散(度)
combined_dispersion: float # 综合分散
timestamp: float


class GazeDispersionCalculator:
"""
注视分散计算器

计算方法:
- 水平分散 = std(水平角度)
- 垂直分散 = std(垂直角度)
- 综合分散 = sqrt(std(h)² + std(v)²)

参考:Victor et al. (2005), Wang et al. (2014)
"""

def __init__(self, window_sec: float = 30.0, fps: int = 30):
"""
初始化注视分散计算器

Args:
window_sec: 计算窗口(秒)
fps: 帧率
"""
self.window_size = int(window_sec * fps)
self.fps = fps

# 历史缓存
self.h_angles = deque(maxlen=self.window_size)
self.v_angles = deque(maxlen=self.window_size)
self.timestamps = deque(maxlen=self.window_size)

def update(self, gd: GazeData) -> DispersionMetrics:
"""
更新计算并返回当前分散指标

Args:
gd: 注视数据

Returns:
DispersionMetrics 对象
"""
self.h_angles.append(gd.h_angle)
self.v_angles.append(gd.v_angle)
self.timestamps.append(gd.timestamp)

# 计算分散
h_disp = np.std(list(self.h_angles)) if len(self.h_angles) > 1 else 0.0
v_disp = np.std(list(self.v_angles)) if len(self.v_angles) > 1 else 0.0
combined = np.sqrt(h_disp**2 + v_disp**2)

return DispersionMetrics(
h_dispersion=h_disp,
v_dispersion=v_disp,
combined_dispersion=combined,
timestamp=gd.timestamp
)

def get_statistics(self) -> dict:
"""获取统计信息"""
if len(self.h_angles) < 2:
return {
'h_mean': 0, 'h_std': 0,
'v_mean': 0, 'v_std': 0,
'sample_count': 0
}

h_array = np.array(list(self.h_angles))
v_array = np.array(list(self.v_angles))

return {
'h_mean': np.mean(h_array),
'h_std': np.std(h_array),
'v_mean': np.mean(v_array),
'v_std': np.std(v_array),
'sample_count': len(h_array)
}

3.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
import numpy as np
from typing import List, Tuple, Optional
from dataclasses import dataclass
from enum import Enum
from collections import deque

class CognitiveState(Enum):
"""认知状态"""
NORMAL = "normal" # 正常
LOW_DISTRACTION = "low" # 轻度分心
HIGH_DISTRACTION = "high" # 高度分心
TUNNEL_EFFECT = "tunnel" # 隧道效应(心算中)
UNKNOWN = "unknown"


@dataclass
class CognitiveEvent:
"""认知分心事件"""
timestamp: float
state: CognitiveState
prc: float
h_dispersion: float
v_dispersion: float
pupil_diameter: float
confidence: float


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

基于多指标融合的认知分心检测:
1. Percent Road Center (PRC)
2. 注视分散(水平/垂直)
3. 瞳孔直径
4. 时序模式分析

检测逻辑:
- 正常状态:PRC 稳定,分散度适中
- 隧道效应(心算中):PRC↑,分散度↓
- 恢复性扫视(心算间隙):PRC↓,分散度↑
- 认知分心:连续的异常模式

参考:
- Victor et al. (2005): PRC and dispersion
- Harbluk et al. (2007): Tunnel effect
- Halin et al. (2025): Phasic patterns
"""

def __init__(self,
fps: int = 30,
window_sec: float = 30.0,
prc_normal_range: Tuple[float, float] = (50, 80),
h_disp_normal_range: Tuple[float, float] = (8, 20),
v_disp_normal_range: Tuple[float, float] = (5, 12),
pupil_normal_range: Tuple[float, float] = (3.5, 5.5)):
"""
初始化认知分心检测器

Args:
fps: 帧率
window_sec: 计算窗口(秒)
prc_normal_range: PRC 正常范围(%)
h_disp_normal_range: 水平分散正常范围(度)
v_disp_normal_range: 垂直分散正常范围(度)
pupil_normal_range: 瞳孔直径正常范围(mm)
"""
self.fps = fps
self.window_size = int(window_sec * fps)

# 正常范围
self.prc_normal_range = prc_normal_range
self.h_disp_normal_range = h_disp_normal_range
self.v_disp_normal_range = v_disp_normal_range
self.pupil_normal_range = pupil_normal_range

# 初始化计算器
self.prc_calculator = PercentRoadCenterCalculator(window_sec=window_sec)
self.disp_calculator = GazeDispersionCalculator(window_sec=window_sec, fps=fps)

# 历史数据
self.gaze_history = deque(maxlen=self.window_size)
self.prc_history = deque(maxlen=self.window_size)
self.h_disp_history = deque(maxlen=self.window_size)
self.v_disp_history = deque(maxlen=self.window_size)
self.pupil_history = deque(maxlen=self.window_size)

# 基线
self.baseline_established = False
self.baseline_prc = None
self.baseline_h_disp = None
self.baseline_v_disp = None
self.baseline_pupil = None

# 当前状态
self.current_state = CognitiveState.UNKNOWN

# 事件记录
self.events: List[CognitiveEvent] = []

def update(self, gd: GazeData) -> Tuple[CognitiveState, Optional[CognitiveEvent]]:
"""
更新检测器,返回当前认知状态

Args:
gd: 注视数据

Returns:
cognitive_state: 当前认知状态
event: 新事件(如果有状态变化)
"""
# 添加到历史
self.gaze_history.append(gd)

# 计算指标
disp_metrics = self.disp_calculator.update(gd)

# 计算 PRC(使用历史数据)
prc = self.prc_calculator.calculate(list(self.gaze_history))

# 记录历史
self.prc_history.append(prc)
self.h_disp_history.append(disp_metrics.h_dispersion)
self.v_disp_history.append(disp_metrics.v_dispersion)
self.pupil_history.append(gd.pupil_diameter)

# 建立基线
if not self.baseline_established and len(self.gaze_history) >= self.window_size:
self._establish_baseline()

# 检测认知状态
prev_state = self.current_state
self.current_state = self._detect_state(prc, disp_metrics, gd.pupil_diameter)

# 记录事件
event = None
if self.current_state != prev_state and self.current_state != CognitiveState.NORMAL:
event = CognitiveEvent(
timestamp=gd.timestamp,
state=self.current_state,
prc=prc,
h_dispersion=disp_metrics.h_dispersion,
v_dispersion=disp_metrics.v_dispersion,
pupil_diameter=gd.pupil_diameter,
confidence=self._calculate_confidence()
)
self.events.append(event)

return self.current_state, event

def _establish_baseline(self):
"""建立基线"""
self.baseline_prc = np.mean(list(self.prc_history))
self.baseline_h_disp = np.mean(list(self.h_disp_history))
self.baseline_v_disp = np.mean(list(self.v_disp_history))
self.baseline_pupil = np.mean(list(self.pupil_history))
self.baseline_established = True

def _detect_state(self, prc: float, disp_metrics: DispersionMetrics,
pupil: float) -> CognitiveState:
"""
检测认知状态

Args:
prc: Percent Road Center
disp_metrics: 注视分散指标
pupil: 瞳孔直径

Returns:
CognitiveState 枚举值
"""
if not self.baseline_established:
return CognitiveState.UNKNOWN

h_disp = disp_metrics.h_dispersion
v_disp = disp_metrics.v_dispersion

# 计算相对变化
prc_ratio = prc / self.baseline_prc if self.baseline_prc > 0 else 1.0
h_disp_ratio = h_disp / self.baseline_h_disp if self.baseline_h_disp > 0 else 1.0
v_disp_ratio = v_disp / self.baseline_v_disp if self.baseline_v_disp > 0 else 1.0
pupil_ratio = pupil / self.baseline_pupil if self.baseline_pupil > 0 else 1.0

# 检测隧道效应(心算中)
# 特征:PRC↑,分散度↓
if prc_ratio > 1.3 and h_disp_ratio < 0.7 and v_disp_ratio < 0.8:
return CognitiveState.TUNNEL_EFFECT

# 检测认知分心
# 特征:PRC↓,分散度↑(恢复性扫视)
if prc_ratio < 0.7 and v_disp_ratio > 1.3:
if pupil_ratio > 1.2: # 瞳孔放大 = 认知负荷
return CognitiveState.HIGH_DISTRACTION
else:
return CognitiveState.LOW_DISTRACTION

# 综合判断
abnormal_count = 0
if not (self.prc_normal_range[0] <= prc <= self.prc_normal_range[1]):
abnormal_count += 1
if not (self.h_disp_normal_range[0] <= h_disp <= self.h_disp_normal_range[1]):
abnormal_count += 1
if not (self.v_disp_normal_range[0] <= v_disp <= self.v_disp_normal_range[1]):
abnormal_count += 1
if not (self.pupil_normal_range[0] <= pupil <= self.pupil_normal_range[1]):
abnormal_count += 1

if abnormal_count >= 3:
return CognitiveState.HIGH_DISTRACTION
elif abnormal_count >= 2:
return CognitiveState.LOW_DISTRACTION

return CognitiveState.NORMAL

def _calculate_confidence(self) -> float:
"""计算置信度"""
# 基于数据量和一致性
data_score = min(len(self.gaze_history) / self.window_size, 1.0)

# 基于历史一致性
if len(self.prc_history) > 10:
prc_std = np.std(list(self.prc_history)[-100:])
consistency_score = max(0, 1 - prc_std / 20)
else:
consistency_score = 0.5

return (data_score * 0.5 + consistency_score * 0.5)

def get_statistics(self) -> dict:
"""获取统计信息"""
return {
'current_state': self.current_state.value,
'baseline_established': self.baseline_established,
'baseline_prc': self.baseline_prc,
'baseline_h_disp': self.baseline_h_disp,
'baseline_v_disp': self.baseline_v_disp,
'baseline_pupil': self.baseline_pupil,
'current_prc': list(self.prc_history)[-1] if self.prc_history else 0,
'current_h_disp': list(self.h_disp_history)[-1] if self.h_disp_history else 0,
'current_v_disp': list(self.v_disp_history)[-1] if self.v_disp_history else 0,
'event_count': len(self.events)
}


# 完整测试
if __name__ == "__main__":
import random

detector = CognitiveDistractionDetector(fps=30, window_sec=30.0)

print("=" * 60)
print("认知分心检测测试")
print("=" * 60)

# 模拟 90 秒数据
for i in range(2700): # 90秒
timestamp = i / 30.0

# 前30秒:正常驾驶
if i < 900:
h_angle = random.gauss(0, 12)
v_angle = random.gauss(0, 8)
pupil = random.gauss(4.5, 0.3)

# 30-60秒:心算任务(隧道效应)
elif i < 1800:
h_angle = random.gauss(0, 6) # 分散度降低
v_angle = random.gauss(0, 4)
pupil = random.gauss(5.5, 0.4) # 瞳孔放大

# 60-90秒:恢复
else:
h_angle = random.gauss(0, 15) # 分散度增加
v_angle = random.gauss(0, 10)
pupil = random.gauss(4.8, 0.3)

gd = GazeData(
timestamp=timestamp,
h_angle=h_angle,
v_angle=v_angle,
pupil_diameter=pupil,
confidence=0.9
)

state, event = detector.update(gd)

# 打印状态变化
if event:
print(f"[{timestamp:.1f}s] 状态变化: {event.state.value}, "
f"PRC={event.prc:.1f}%, "
f"H_disp={event.h_dispersion:.1f}°, "
f"V_disp={event.v_dispersion:.1f}°, "
f"Pupil={event.pupil_diameter:.2f}mm")

# 每30秒打印统计
if i > 0 and i % 900 == 0:
stats = detector.get_statistics()
print(f"\n--- {int(timestamp)}秒统计 ---")
print(f"当前状态: {stats['current_state']}")
print(f"基线 PRC: {stats['baseline_prc']:.1f}%")
print(f"当前 PRC: {stats['current_prc']:.1f}%")
print(f"事件数: {stats['event_count']}\n")

print("\n" + "=" * 60)
print("最终统计:")
stats = detector.get_statistics()
for k, v in stats.items():
print(f" {k}: {v}")
print("=" * 60)

四、瞳孔测量方法

4.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
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
import numpy as np
import cv2
from typing import Tuple, Optional
from dataclasses import dataclass

@dataclass
class PupilMeasurement:
"""瞳孔测量结果"""
diameter_mm: float # 直径(mm)
diameter_px: float # 直径(像素)
center: Tuple[int, int] # 中心坐标
confidence: float # 置信度
illumination: float # 照度(lux)


class PupilDiameterEstimator:
"""
瞳孔直径估计器

基于 MediaPipe Face Mesh 的瞳孔关键点检测
需要标定:像素到毫米的转换比例
"""

# MediaPipe 瞳孔关键点索引
LEFT_PUPIL = 468
RIGHT_PUPIL = 473

# 瞳孔边界关键点(用于估计直径)
LEFT_IRIS = [468, 469, 470, 471, 472] # 左眼虹膜
RIGHT_IRIS = [473, 474, 475, 476, 477] # 右眼虹膜

def __init__(self,
pixels_per_mm: float = 30.0,
eye_radius_mm: float = 12.0):
"""
初始化瞳孔直径估计器

Args:
pixels_per_mm: 每毫米像素数(需要标定)
eye_radius_mm: 眼球半径(mm),默认 12mm
"""
self.pixels_per_mm = pixels_per_mm
self.eye_radius_mm = eye_radius_mm

def estimate(self, landmarks: np.ndarray,
frame: np.ndarray,
illumination: float = 500.0) -> PupilMeasurement:
"""
估计瞳孔直径

Args:
landmarks: (468, 3) 关键点坐标
frame: 原始图像(用于计算置信度)
illumination: 照度(lux)

Returns:
PupilMeasurement 对象
"""
h, w = frame.shape[:2]

# 提取虹膜关键点
left_iris = np.array([landmarks[i, :2] for i in self.LEFT_IRIS])
right_iris = np.array([landmarks[i, :2] for i in self.RIGHT_IRIS])

# 计算虹膜直径(像素)
left_diameter_px = self._calculate_diameter(left_iris)
right_diameter_px = self._calculate_diameter(right_iris)

# 平均
avg_diameter_px = (left_diameter_px + right_diameter_px) / 2

# 转换为毫米
diameter_mm = avg_diameter_px / self.pixels_per_mm

# 瞳孔中心
left_center = np.mean(left_iris, axis=0)
right_center = np.mean(right_iris, axis=0)
avg_center = (left_center + right_center) / 2

# 置信度(基于关键点可见性)
confidence = 0.8 # 简化

return PupilMeasurement(
diameter_mm=diameter_mm,
diameter_px=avg_diameter_px,
center=(int(avg_center[0]), int(avg_center[1])),
confidence=confidence,
illumination=illumination
)

def _calculate_diameter(self, iris_points: np.ndarray) -> float:
"""
计算虹膜直径

Args:
iris_points: 虹膜关键点坐标 (N, 2)

Returns:
直径(像素)
"""
# 计算中心
center = np.mean(iris_points, axis=0)

# 计算到中心的距离
distances = np.linalg.norm(iris_points - center, axis=1)

# 直径 = 2 × 平均半径
diameter = 2 * np.mean(distances)

return diameter

def correct_for_illumination(self, measurement: PupilMeasurement) -> float:
"""
校正照度影响

瞳孔直径受照度影响:
- 高照度:瞳孔缩小(2-4mm)
- 低照度:瞳孔放大(4-8mm)

Args:
measurement: 瞳孔测量结果

Returns:
校正后的直径(mm)
"""
# 标准照度:500 lux
standard_illumination = 500.0

# 简化的线性校正
# 实际需要更复杂的瞳孔光反射模型
correction_factor = np.sqrt(standard_illumination / max(measurement.illumination, 10))

corrected_diameter = measurement.diameter_mm * correction_factor

# 限制在合理范围
corrected_diameter = np.clip(corrected_diameter, 2.0, 8.0)

return corrected_diameter

五、技术挑战与解决方案

5.1 主要挑战

挑战 描述 影响
个体差异 不同驾驶员的眼动模式差异大 需要个性化基线
环境因素 光照、交通复杂度影响眼动 误报/漏报
多任务场景 驾驶本身需要认知资源 难以区分
实时性 需要快速检测状态变化 计算压力
侵入性 EEG 等方法不可接受 只能用非接触方法

5.2 解决方案

挑战1:个体差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 解决方案:自适应基线
class AdaptiveBaseline:
"""自适应基线管理"""

def __init__(self, adaptation_rate: float = 0.01):
self.adaptation_rate = adaptation_rate
self.baseline = None

def update(self, current_value: float):
"""更新基线"""
if self.baseline is None:
self.baseline = current_value
else:
# 指数平滑
self.baseline = (1 - self.adaptation_rate) * self.baseline + \
self.adaptation_rate * current_value
return self.baseline

挑战2:环境因素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 解决方案:多因素融合
class MultiFactorFusion:
"""多因素融合"""

def __init__(self):
self.weights = {
'prc': 0.3,
'h_dispersion': 0.2,
'v_dispersion': 0.2,
'pupil': 0.15,
'blink_rate': 0.15
}

def fuse(self, scores: dict) -> float:
"""融合多指标得分"""
total_score = 0.0
for key, weight in self.weights.items():
if key in scores:
total_score += weight * scores[key]
return total_score

5.3 IMS 开发建议

阶段 任务 优先级
Phase 1 PRC 计算 P0
Phase 1 注视分散计算 P0
Phase 2 瞳孔测量 P1
Phase 2 自适应基线 P1
Phase 3 多指标融合 P2
Phase 3 阶段性模式识别 P2

六、参考资料

6.1 论文

  1. Halin et al. (2025). “Gaze-Based Indicators of Driver Cognitive Distraction: Effects of Different Traffic Conditions and ACC Use” - AutomotiveUI Adjunct ‘25
  2. Victor et al. (2005). “Sensitivity and specificity of physiological and behavioral markers” - PRC与分散度原创研究
  3. Harbluk et al. (2007). “An on-road assessment of cognitive distraction” - 隧道效应研究
  4. Wang et al. (2014). “Gaze dispersion”
  5. ScienceDirect (2024). “Driver Cognitive Distraction Detection (DCDD) model”

6.2 标准

  • Euro NCAP Vision 2030
  • NHTSA Driver Distraction Guidelines

发布日期: 2026-04-16
标签: Euro NCAP, DMS, 认知分心, 眼动追踪, 注视分散, Percent Road Center, 瞳孔测量
更新记录: v1.0 - 基于2024-2025最新研究,完整实现眼动指标算法


Euro NCAP 认知分心检测研究进展:眼动规律性与注视分散算法完整实现
https://dapalm.com/2026/04/16/2026-04-16-cognitive-distraction-detection-gaze-dispersion-prc/
作者
Mars
发布于
2026年4月16日
许可协议