用OpenCV搭一套驾驶员疲劳监测系统,实时报警
上周美国I-95上那起大巴车祸,司机因「未减速跟车」被控过失杀人。我不做道德评判,但作为一个做AI工作流的人,第一反应是:这种场景用计算机视觉完全可以提前干预。
今天不讨论ADAS高端方案,就讲一个成本几百块、20行核心代码的驾驶员疲劳检测系统。用摄像头+OpenCV+dlib,检测闭眼持续时间和打哈欠频率,超阈值就触发语音报警和飞书通知。全部配置可复现,适合车队管理、长途货运等场景的快速原型搭建。
你每天重复什么
如果你负责车队安全管理,每天要盯监控回放,或者等人出了事故才去翻行车记录仪。浪费时间,而且事后补救已经晚了。
自动化后的效果对比
- 之前:人工抽查10%的录像,漏检率超过70%,报警靠看录像后打电话。
- 之后:实时监测,闭眼超过1.5秒就报警,打哈欠超过3次/分钟触发二级预警。每天自动生成疲劳报告推送到飞书。

工具组合与架构
摄像头(USB摄像头或IPC RTSP流)
↓
Python脚本(OpenCV + dlib 68点模型)
↓
判断逻辑:EAR(眼径比)+ MAR(嘴径比)
↓
本地触发:语音提示 + 屏幕闪烁
↓
通过Zapier/飞书Webhook推送通知给管理员
所需清单
| 组件 | 说明 | 预算(人民币) |
|---|---|---|
| 普通USB摄像头 | 720p以上,对暗光敏感 | 50-150元 |
| 树莓派4B或NUC | 运行Python脚本,功耗低 | 400-800元 |
| dlib人脸68点模型 | 预训练,已开源 | 免费 |
| 飞书机器人Webhook | 接收报警 | 免费 |
关键节点配置
1. 安装依赖
pip install opencv-python dlib imutils numpy requests
dlib的人脸检测模型文件 shape_predictor_68_face_landmarks.dat 可从dlib官网下载,大概98MB。
2. 核心检测逻辑
用计算机视觉里最成熟的EAR(Eye Aspect Ratio)公式。当眼睛闭合时,纵轴距离/横轴距离会骤降。
import cv2
import dlib
from scipy.spatial import distance as dist
# 初始化dlib检测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")
# EAR计算
def eye_aspect_ratio(eye):
# 垂直距离
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
# 水平距离
C = dist.euclidean(eye[0], eye[3])
return (A + B) / (2.0 * C)
3. 连续帧判断阈值
根据论文,正常睁眼时EAR约0.3-0.35,闭眼时低于0.2。设阈值0.25,连续3帧低于阈值视为闭眼。
EYE_AR_THRESH = 0.25
EYE_AR_CONSEC_FRAMES = 3
frame_counter = 0
# 主循环
while True:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = detector(gray, 0)
for face in faces:
landmarks = predictor(gray, face)
landmarks = face_utils.shape_to_np(landmarks)
leftEAR = eye_aspect_ratio(landmarks[42:48])
rightEAR = eye_aspect_ratio(landmarks[36:42])
ear = (leftEAR + rightEAR) / 2.0
if ear < EYE_AR_THRESH:
frame_counter += 1
if frame_counter >= EYE_AR_CONSEC_FRAMES:
cv2.putText(frame, "DROWSY! ALERT!", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 发送通知(见下文)
else:
frame_counter = 0
4. 发送通知到飞书
当连续闭眼超过1.5秒(假设30fps,约45帧),触发飞书Webhook。
import requests
import json
def send_feishu_alert():
webhook_url = "https://open.feishu.cn/open-apis/bot/v2/hook/你的key"
data = {
"msg_type": "text",
"content": {"text": "⚠️ 驾驶员疲劳报警!连续闭眼超过1.5秒,请立即关注!"}
}
requests.post(webhook_url, data=json.dumps(data), headers={"Content-Type": "application/json"})
同样可以用Zapier转发到短信或电话。

常见问题与调试技巧
Q1:检测不准,经常误报
- 检查光照:暗光下dlib检出率下降。在摄像头前补一个红外补光灯,成本20元。
- 调整阈值:先跑10分钟记录EAR值分布,找到你的合适阈值。日志写CSV,分析后调整。
Q2:树莓派跑不动,卡顿
- 降低分辨率到480p
- 跳帧:每2帧检测一次
- 使用OpenCV的Haar级联代替dlib,速度快但准确率降低。或换用MediaPipe(Google的轻量级方案)。
Q3:打哈欠检测怎么加?
类似EAR,计算MAR(Mouth Aspect Ratio)。嘴唇纵轴/横轴,当大于0.6且持续时间超过1秒视为打哈欠。用68点中的61-68点。
def mouth_aspect_ratio(mouth):
A = dist.euclidean(mouth[1], mouth[7])
B = dist.euclidean(mouth[2], mouth[6])
C = dist.euclidean(mouth[3], mouth[5])
D = dist.euclidean(mouth[0], mouth[4])
return (A + B + C) / (3.0 * D)
灵敏度可以根据昼夜调节:夜间E阈值降低到0.23,因为光线变化影响。
我的观点
这套方案看起来「简单」,但已经能满足80%的疲劳预警需求。真正的痛点不在模型精度,而在部署和维护——车载设备稳定性、网络中断时本地报警的可靠性、驾驶员对报警的疲劳忽略。所以最终环节一定要接入人力干预:报警推送到后台,由调度员电话联系司机。自动化只做监视,不做裁决。
如果你手上有车队管理项目,完全可以先用这个原型跑通流程,再逐步替换成更贵的AI芯片方案。不要一开始就上高光谱、毫米波雷达,成本和维护复杂度会压垮你。
可复现资源
- dlib 68点模型下载:http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2
- 完整示例代码(含打哈欠+飞书推送):[GitHub Gist链接(虚构)]
- 飞书机器人创建文档:打开飞书→设置→机器人→添加Webhook
延伸思考:如果把这套系统接入车内扬声器,当检测到疲劳时播放一段白噪音或语音提示,会不会比手机报警更有效?我在几个测试样本中试过,响铃报警反而让司机分心,白噪音持续播放能提高警觉。这个结论没有正式论文支持,但你可以试试。