眨眼模式分析:疲劳检测的时序特征

眨眼模式分析:疲劳检测的时序特征

引言

眨眼是人类最常见的生理活动之一,平均每天眨眼15,000-20,000次。在驾驶场景中,眨眼模式的变化是疲劳最敏感的早期指标之一。相比单一指标,眨眼的时序特征分析能够提供更丰富、更可靠的疲劳评估信息。本文将深入探讨眨眼特征提取方法、PERCLOS及其替代方案。

眨眼特征提取

1. 基础特征

特征 正常范围 疲劳表现 计算方法
眨眼频率 15-20次/分钟 降低或升高 单位时间眨眼计数
眨眼持续时间 100-400ms 延长(>300ms警惕) 眼睑闭合到张开时间
眨眼幅度 完全闭合 幅度减小 眼睑开合距离比
眨眼间隔 2-4秒 变异增大 相邻眨眼时间差

2. 时序特征

疲劳状态下眨眼的时序模式发生显著变化:

1
2
3
4
5
6
7
正常状态眨眼模式:
|▔▔|__|▔▔▔▔▔▔|__|▔▔▔▔▔▔|__|▔▔▔▔▔▔|__|▔▔|
规律间隔,短暂闭合

疲劳状态眨眼模式:
|▔▔|___|▔▔▔|_____||________|▔▔|_______||
间隔不规律,闭合延长,出现微睡眠

3. PERCLOS原理

PERCLOS(Percentage of Eyelid Closure)是最经典的疲劳检测指标:

计算公式:

1
PERCLOS = (闭眼帧数 / 总帧数) × 100%

判断标准:

  • PERCLOS < 15%:正常
  • 15% ≤ PERCLOS < 25%:轻度疲劳
  • PERCLOS ≥ 25%:重度疲劳(需警告)

PERCLOS替代方案

1. PERCLOS的局限性

局限性 描述 影响
阈值敏感 依赖精确的睁闭眼阈值 不同个体差异大
时滞问题 需要足够时间窗口 检测延迟
单一维度 仅考虑闭合时间 忽略时序模式
假阳性 正常眨眼被误判 干扰驾驶体验

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
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
import numpy as np
from collections import deque
from scipy import stats

class BlinkPatternAnalyzer:
"""眨眼模式分析器"""

def __init__(self,
fps=30,
window_size=900, # 30秒窗口
closure_threshold=0.2): # 眼睑闭合阈值(EAR<0.2)
"""初始化

Args:
fps: 帧率
window_size: 分析窗口大小
closure_threshold: 闭合阈值
"""
self.fps = fps
self.window_size = window_size
self.closure_threshold = closure_threshold

# 历史数据
self.ear_history = deque(maxlen=window_size)
self.blink_events = deque(maxlen=100) # 最近100次眨眼

# 状态追踪
self.is_closed = False
self.closure_start = None
self.last_blink_time = 0

def update(self, ear, frame_idx):
"""更新EAR值并检测眨眼

Args:
ear: 当前帧的眼睛纵横比(EAR)
frame_idx: 当前帧索引

Returns:
blink_detected: 是否检测到眨眼
"""
self.ear_history.append(ear)

blink_detected = False

# 检测眼睑状态变化
if ear < self.closure_threshold and not self.is_closed:
# 开始闭合
self.is_closed = True
self.closure_start = frame_idx

elif ear >= self.closure_threshold and self.is_closed:
# 闭合结束
self.is_closed = False
duration = (frame_idx - self.closure_start) / self.fps * 1000 # ms

# 过滤过短/过长事件
if 50 < duration < 1000: # 合理的眨眼持续时间
interval = (frame_idx - self.last_blink_time) / self.fps

blink_event = {
'frame': frame_idx,
'duration': duration,
'interval': interval if self.last_blink_time > 0 else 0
}
self.blink_events.append(blink_event)
self.last_blink_time = frame_idx
blink_detected = True

return blink_detected

def calculate_perclos(self):
"""计算PERCLOS值

Returns:
perclos: PERCLOS百分比
"""
if len(self.ear_history) == 0:
return 0

closed_frames = sum(1 for ear in self.ear_history if ear < self.closure_threshold)
perclos = closed_frames / len(self.ear_history) * 100

return perclos

def extract_blink_features(self):
"""提取眨眼特征

Returns:
features: 特征字典
"""
if len(self.blink_events) < 3:
return None

durations = [e['duration'] for e in self.blink_events if e['duration'] > 0]
intervals = [e['interval'] for e in self.blink_events if e['interval'] > 0]

features = {
# 频率特征
'blink_rate': len(self.blink_events) / (self.window_size / self.fps) * 60, # 次/分钟

# 持续时间特征
'avg_duration': np.mean(durations) if durations else 0,
'std_duration': np.std(durations) if len(durations) > 1 else 0,
'max_duration': max(durations) if durations else 0,
'long_blink_ratio': sum(1 for d in durations if d > 300) / len(durations) if durations else 0,

# 间隔特征
'avg_interval': np.mean(intervals) if intervals else 0,
'std_interval': np.std(intervals) if len(intervals) > 1 else 0,
'cv_interval': np.std(intervals) / np.mean(intervals) if intervals and np.mean(intervals) > 0 else 0,

# PERCLOS
'perclos': self.calculate_perclos()
}

return features

def assess_fatigue(self):
"""综合疲劳评估

Returns:
fatigue_score: 疲劳分数(0-100)
indicators: 各指标评估
"""
features = self.extract_blink_features()

if features is None:
return 0, {}

indicators = {}
score = 0

# PERCLOS评估 (权重30%)
perclos_score = min(features['perclos'] / 25 * 30, 30)
indicators['perclos'] = {
'value': features['perclos'],
'score': perclos_score,
'status': 'danger' if features['perclos'] >= 25 else 'warning' if features['perclos'] >= 15 else 'normal'
}
score += perclos_score

# 眨眼持续时间评估 (权重25%)
duration_score = min(features['avg_duration'] / 400 * 25, 25)
indicators['duration'] = {
'value': features['avg_duration'],
'score': duration_score,
'status': 'danger' if features['avg_duration'] > 350 else 'warning' if features['avg_duration'] > 250 else 'normal'
}
score += duration_score

# 长眨眼比例评估 (权重20%)
long_blink_score = features['long_blink_ratio'] * 20
indicators['long_blink'] = {
'value': features['long_blink_ratio'],
'score': long_blink_score,
'status': 'danger' if features['long_blink_ratio'] > 0.3 else 'warning' if features['long_blink_ratio'] > 0.15 else 'normal'
}
score += long_blink_score

# 眨眼间隔变异性评估 (权重15%)
cv_score = min(features['cv_interval'] * 15, 15)
indicators['variability'] = {
'value': features['cv_interval'],
'score': cv_score,
'status': 'warning' if features['cv_interval'] > 0.5 else 'normal'
}
score += cv_score

# 眨眼频率评估 (权重10%)
freq_deviation = abs(features['blink_rate'] - 17) / 17 # 正常约17次/分钟
freq_score = min(freq_deviation * 10, 10)
indicators['frequency'] = {
'value': features['blink_rate'],
'score': freq_score,
'status': 'warning' if freq_deviation > 0.5 else 'normal'
}
score += freq_score

return score, indicators

# EAR计算辅助函数
def calculate_ear(eye_landmarks):
"""计算眼睛纵横比(Eye Aspect Ratio)

Args:
eye_landmarks: 6个眼部关键点 [(x1,y1), (x2,y2), ..., (x6,y6)]

Returns:
ear: 眼睛纵横比
"""
# 垂直距离
v1 = np.linalg.norm(np.array(eye_landmarks[1]) - np.array(eye_landmarks[5]))
v2 = np.linalg.norm(np.array(eye_landmarks[2]) - np.array(eye_landmarks[4]))

# 水平距离
h = np.linalg.norm(np.array(eye_landmarks[0]) - np.array(eye_landmarks[3]))

# EAR = (v1 + v2) / (2 * h)
ear = (v1 + v2) / (2 * h) if h > 0 else 0

return ear

# 使用示例
if __name__ == "__main__":
analyzer = BlinkPatternAnalyzer()

# 模拟数据流
for i in range(900): # 30秒@30fps
# 模拟EAR值(正常眨眼 + 逐渐疲劳)
base_ear = 0.3
# 添加疲劳效应:眨眼时间延长
fatigue_factor = min(i / 900, 1.0)

# 每60帧模拟一次眨眼
if i % 60 < 3 + int(fatigue_factor * 5): # 疲劳时眨眼时间延长
ear = 0.1 # 闭合
else:
ear = base_ear + np.random.normal(0, 0.02)

analyzer.update(ear, i)

# 分析结果
score, indicators = analyzer.assess_fatigue()
print(f"疲劳分数: {score:.1f}/100")
for name, ind in indicators.items():
print(f" {name}: {ind['value']:.2f} ({ind['status']})")

EEG验证

眨眼模式与脑电图(EEG)的关联验证是提高检测可信度的重要手段:

EEG特征 眨眼特征 相关性
Alpha波功率增加 眨眼时间延长 强正相关
Theta波增加 PERCLOS升高 正相关
Beta波下降 眨眼频率下降 正相关

研究结论:

  • PERCLOS与主观疲劳评分相关性:r = 0.82
  • 多特征融合后相关性可达:r = 0.91

方法对比

方法 准确率 实时性 鲁棒性 计算开销
单一PERCLOS 75-82%
PERCLOS+眨眼频率 82-87%
多特征时序分析 88-93%
眨眼+EEG融合 92-96% 很高

IMS开发建议

1. 算法选择

场景 推荐方案
边缘计算 PERCLOS + 频率 + 持续时间
高安全需求 多特征时序分析 + EEG辅助
低成本方案 PERCLOS + 长眨眼比例

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
# 个体化阈值校准
class AdaptiveThreshold:
"""自适应阈值校准器"""

def __init__(self, calibration_time=60):
"""初始化

Args:
calibration_time: 校准时间(秒)
"""
self.calibration_time = calibration_time
self.samples = []

def add_sample(self, ear):
"""添加样本"""
self.samples.append(ear)

def get_thresholds(self):
"""获取个体化阈值

Returns:
thresholds: 阈值字典
"""
if len(self.samples) < 100:
return None

ear_array = np.array(self.samples)

return {
'closure_threshold': np.percentile(ear_array, 10), # 底部10%为闭合
'open_threshold': np.percentile(ear_array, 90),
'perclos_warning': 15, # 相对固定
'perclos_danger': 25
}

3. Euro NCAP合规要点

  1. 测试覆盖

    • 正常驾驶:PERCLOS < 15%
    • 模拟疲劳:系统需在疲劳发生后60秒内报警
    • 夜间场景:IR照明下保持检测能力
  2. 误报控制

    • 正常眨眼不触发警告
    • 揉眼、打哈欠单独判断
  3. 记录要求

    • 保存眨眼事件日志
    • 支持事后分析

总结

眨眼模式分析是疲劳检测的核心技术之一。从经典的PERCLOS到多维度时序特征,方法的演进显著提升了检测准确性和可靠性。在实际DMS开发中,建议采用多特征融合策略,并结合个体化校准,以适应不同驾驶员的生理特征。


参考文献:

  1. MDPI - Detection of Operator Fatigue Based on PERCLOS
  2. ResearchGate - Driver Fatigue Detection Based on Head Gesture and PERCLOS
  3. PLOS One - Eye tracking cognitive load using pupil diameter and microsaccades
  4. ScienceDirect - Blink detection using Adaboost and contour circle

眨眼模式分析:疲劳检测的时序特征
https://dapalm.com/2026/06/01/2026-06-01-眨眼模式分析疲劳检测的时序特征/
作者
Mars
发布于
2026年6月1日
许可协议