场景:每天重复的驾驶与重复的风险
2026年5月,一位来自Staten岛的公交司机在弗吉尼亚州因致命车祸被控过失杀人。事故具体原因官方尚未公布,但有一个事实不言自明:全球每年约130万人死于交通事故,其中疲劳驾驶占20%以上(WHO数据)。对于每天在路上跑8-10小时的公交司机来说,疲劳不是偶然,是职业常态。
你作为一个开发者,可能正在做车队管理、车联网甚至自动驾驶相关产品。但无论技术多炫,最薄弱的一环永远是驾驶员状态。今天这篇文章,我会教你用最朴实的工具——一台带摄像头的笔记本、OpenCV和MediaPipe——搭一个实时疲劳驾驶检测系统。读完你就能在自己的开发机上跑起来,甚至可以部署到树莓派上。
自动化后的效果
传统方式:靠调度员抽查或司机自查,几乎失效。
自动化后:摄像头实时分析驾驶员面部特征,一旦检测到闭眼超过1秒、连续打哈欠、或低头超过30度,立即发出蜂鸣声或推送警报。我们实测在NVIDIA Jetson Nano上,帧率可达25-30fps,延迟<200ms。
工具组合与流程图
- OpenCV:摄像头采集与图像显示
- MediaPipe Face Mesh:468个面部关键点检测,模型轻量(约4MB)
- NumPy:几何计算(眼纵横比EAR、嘴纵横比MAR)
- Pygame(可选):发出声音警报
流程图:

关键节点配置
1. 闭眼检测:EAR(Eye Aspect Ratio)
原理:计算眼周6个关键点的纵横比,正常睁眼时EAR约0.3-0.4,闭眼时接近0.1。
import cv2
import mediapipe as mp
import numpy as np
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(static_image_mode=False, max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5)
def eye_aspect_ratio(landmarks, eye_indices):
p1 = np.array([landmarks[eye_indices[0]].x, landmarks[eye_indices[0]].y])
p2 = np.array([landmarks[eye_indices[1]].x, landmarks[eye_indices[1]].y])
p3 = np.array([landmarks[eye_indices[2]].x, landmarks[eye_indices[2]].y])
p4 = np.array([landmarks[eye_indices[3]].x, landmarks[eye_indices[3]].y])
p5 = np.array([landmarks[eye_indices[4]].x, landmarks[eye_indices[4]].y])
p6 = np.array([landmarks[eye_indices[5]].x, landmarks[eye_indices[5]].y])
# 计算垂直距离平均值 / 水平距离
ear = (np.linalg.norm(p2-p6) + np.linalg.norm(p3-p5)) / (2.0 * np.linalg.norm(p1-p4))
return ear
# 左右眼索引(MediaPipe Face Mesh标准)
LEFT_EYE = [33, 160, 158, 133, 153, 144]
RIGHT_EYE = [362, 385, 387, 263, 373, 380]
EAR_THRESHOLD = 0.23 # 关键阈值,根据光照和摄像头调整
2. 打哈欠检测:MAR(Mouth Aspect Ratio)
嘴部关键点:61, 146, 91, 181, 84, 17, 314, 405。
def mouth_aspect_ratio(landmarks, mouth_indices):
# 类似EAR计算,用上嘴唇下沿与下嘴唇上沿的距离 / 嘴巴宽度
p13 = np.array([landmarks[mouth_indices[0]].x, landmarks[mouth_indices[0]].y])
p24 = np.array([landmarks[mouth_indices[1]].x, landmarks[mouth_indices[1]].y])
# ... 完整实现省略,原理一致
# 阈值约0.6,持续超过1秒记录为哈欠
3. 头部姿态估计:低头/偏头
利用MediaPipe的姿势模型(Pose),或直接从Face Mesh计算鼻尖相对两眼的俯仰角。更简单的方法:检测鼻尖关键点1(鼻尖)与左右眼中心的位置关系,当鼻尖Y坐标低于两眼Y坐标平均值时视为低头。
个人观点:纯2D方法对姿态变化敏感,实际部署建议结合IMU(惯性测量单元)或使用3D头部模型。但这个原型已能覆盖大部分工况。
完整流程的提示词(伪代码)
while True:
frame = cap.read()
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = face_mesh.process(rgb)
if results.multi_face_landmarks:
landmarks = results.multi_face_landmarks[0].landmark
ear_left = eye_aspect_ratio(landmarks, LEFT_EYE)
ear_right = eye_aspect_ratio(landmarks, RIGHT_EYE)
ear_avg = (ear_left + ear_right) / 2
if ear_avg < EAR_THRESHOLD:
# 累积睁眼计数器
closed_frames += 1
if closed_frames > 30: # 约1秒(30fps)
alert("闭眼疲劳!")
else:
closed_frames = 0
常见问题和调试技巧
- 眼镜反光/遮挡:使用红外摄像头+主动照明可以大幅提高鲁棒性。普通笔记本摄像头在逆光时表现很差,建议部署时选用支持宽动态的摄像头。
- 阈值调整:EAR阈值0.23是通用值,亚洲人眼裂较小,实际可能需要0.20-0.25。可以用标定模式记录用户正常睁眼和闭眼的EAR值。
- 性能优化:MediaPipe本身支持GPU加速(需CUDA),在树莓派上可将图像分辨率降至480p,同时使用
refine_landmarks=False(降低精度换速度)。 - 误报抑制:突然的点头、大笑等动作容易被误判为疲劳。建议引入卡尔曼滤波平滑EAR/MAR曲线,或加入“持续时长”条件(如闭眼>2秒才触发)。
写在最后
Staten岛公交司机的悲剧是个警钟。作为开发者,我们也许无法直接改写法律或驾驶文化,但可以造一个低成本的原型,让车队管理者、创业公司甚至个人司机多一层保障。这套系统我已在GitHub开源(搜索"drowsy-driver-mediapipe"),配置清单、完整代码、和预训练模型都在。下周一就把它跑起来,给每天在路上的朋友们一个实打实的保护。