
简介整套驾驶员疲劳检测系统基于Python与Dlib人脸关键点模型实现围绕眨眼频率、嘴部开合度、瞌睡点头三类特征实时判断注意力状态并配有可视化交互界面。项目共17个文件压缩包约85.92MB包含6个Python源码、2个Jupyter Notebook、Dlib的68点人脸关键点模型dat文件、wxFormBuilder界面工程及演示视频、运行截图和README说明可直接运行或二次开发。适用于计算机、人工智能、电子信息等专业学生完成毕设、课设或初期项目演示也适合想学习人脸关键点与状态检测的入门者。已有232人浏览学习代码经测试可运行文档与演示素材齐全便于对照理解检测流程、界面搭建及工程组织方式。1. 驾驶员疲劳检测到底在检测什么从68点人脸关键点拆解三路疲劳信号如果你要在两天之内把一套“Python Dlib驾驶员疲劳检测”跑起来还要能在答辩现场不卡壳那这条技术路线是最稳的选择。这个系列做的核心事情是用Dlib的68点人脸关键点去捕捉三种典型疲劳信号眨眼变慢变长、连续打哈欠、头部不自觉下垂点头再把它们转换成EAR、MAR和头部俯仰角这三路数值最后通过可视化界面把驾驶员的疲劳等级实时显示出来。它不需要GPU普通笔记本CPU就能跑到每秒20帧以上适合做毕业设计、课程项目、辅助驾驶系统的预研验证也适合想快速上手人脸关键点应用场景的开发者。很多同学会误以为疲劳检测非得上深度学习不可实际做下来你会发现传统几何特征在这个场景里反而更好用解释性强、调试直观、出问题能定位。接下来的内容会按“原理选型 → 环境搭建 → 三路检测实现 → 部署踩坑 → 界面与验证”的顺序展开每一段都可以直接照着抄。2. 用Dlib做疲劳检测的核心原理EAR、MAR与头部姿态为什么能反映困倦2.1 为什么选Dlib而不是深度学习方案工程落地视角的选型对比拿到这个题目第一步不是写代码而是选型。方案大致有四条路Dlib 68点关键点、MediaPipe Face Mesh、OpenFace、自训练的YOLO关键点模型。从工程落地角度看差别非常明显。Dlib用的是HOG 线性SVM做人脸检测再用回归树做68点关键点定位模型文件大约100MB纯CPU跑单帧检测加关键点定位约30到50毫秒。MediaPipe的Face Mesh有468个关键点精度高且速度快但它输出的是三维网格坐标要做眼睛和嘴巴的开合度反而需要额外计算而且官方版本在不同平台上的行为差异较大调试起来有些“玄学”。OpenFace是学术界常用的头部姿态估计工具精度不错但安装依赖重Windows环境经常让人想摔键盘。自训练YOLO关键点模型效果最好但这明显超出“快捷交付、稳定演示”的目标训练数据清洗会占掉大量时间。我做这个主题时会直接选Dlib理由很简单68点刚好覆盖了眼部6点、嘴部6点、鼻尖下巴等姿态估计所需的关键点一个模型同时喂给三路检测算法不需要维护多个模型离线运行、无网络依赖答辩现场不会因为联网问题翻车OpenCV和Dlib的API配合非常成熟网上可参考的资料密度也最高。方案模型体积CPU单帧耗时关键点数量安装难度离线运行Dlib 68点约100MB30~50ms68中需编译支持MediaPipe Face Mesh约10MB20~40ms468低支持OpenFace较大50~100ms68高支持自训练YOLO关键点自定10~30ms自定高支持2.2 眨眼检测的几何基础眼睛纵横比EAR眨眼检测的核心不是“眼睛有没有闭上”而是“闭眼持续了多久”。Dlib给左右眼各6个关键点这四个特征点构成一个几何形状它的高度与宽度之比就是一个稳定的开度指标也就是EAREye Aspect Ratio。计算EAR的代码很简单但它是整个眨眼检测的地基import math def euclidean(p1, p2): return math.hypot(p1.x - p2.x, p1.y - p2.y) def eye_aspect_ratio(eye_points): # 垂直方向两个距离的平均值 A euclidean(eye_points[1], eye_points[5]) B euclidean(eye_points[2], eye_points[4]) # 水平方向的距离 C euclidean(eye_points[0], eye_points[3]) return (A B) / (2.0 * C)这段代码没有用scipy的distance函数而是自己用math.hypot算欧氏距离原因是很多Windows环境下scipy安装容易出幺蛾子而dlib的rectangle和point对象直接取坐标属性就够了。eye_points对应Dlib 68点中的左右眼各6个点左眼索引是36到41右眼是42到47传入前从shape.part(i)逐个取出来。人正常睁眼时EAR大约在0.25到0.35之间因人而异闭眼时会掉到0.15以下。之所以用纵横比而不是直接用像素高度是为了抵消人脸远近和摄像头分辨率变化带来的尺度影响。一个人从远处走近眼部的像素高度会变大但比值基本不变。2.3 哈欠检测的几何基础嘴巴纵横比MAR哈欠的几何特征和眨眼是对仗的。Dlib会为嘴巴区域输出内外两圈关键点取内唇的6个点计算嘴巴纵横比MARMouth Aspect Ratio数值越大说明嘴巴张得越开。def mouth_aspect_ratio(mouth_points): # 垂直方向上下嘴唇的距离 A euclidean(mouth_points[2], mouth_points[10]) B euclidean(mouth_points[4], mouth_points[8]) # 水平方向嘴角距离 C euclidean(mouth_points[0], mouth_points[6]) return (A B) / (2.0 * C)Dlib的68点中内唇的6个点分布在48到67之间具体取上下唇各两个垂直距离和一个水平距离。人在正常说话时MAR会在0.2到0.4之间快速波动嘴巴张开发出哈欠时会稳定在0.6以上并持续十几到几十帧。这里有个关键区别眨眼是“短而快”的变化哈欠是“长而稳”的变化。如果只按单帧MAR超过阈值就判定为哈欠那么说话、大笑、吃东西都会频繁误报。所以实际落地时一定会叠加一个“持续帧数”条件这也是后面第4章的实现重点。2.4 瞌睡点头用solvePnP估计头部俯仰角点头检测是这类项目里最容易被轻视、也最容易做砸的部分。常见错误做法是跟踪鼻尖点的y坐标发现往下移动就认为在点头结果驾驶员低头看导航、调整坐姿都会被误判。更可靠的做法是使用OpenCV的solvePnP做头部姿态估计拿鼻尖、下巴、左右眼外角、左右嘴角这6个2D关键点去匹配一个统一人脸模型的3D坐标解算出旋转向量再转换成欧拉角取俯仰角pitch作为点头的核心判据。import numpy as np def estimate_head_pitch(shape): # 2D 关键点从Dlib 68点中取6个稳定特征点 image_points np.array([ (shape.part(30).x, shape.part(30).y), # 鼻尖 (shape.part(8).x, shape.part(8).y), # 下巴 (shape.part(36).x, shape.part(36).y), # 左眼外角 (shape.part(45).x, shape.part(45).y), # 右眼外角 (shape.part(48).x, shape.part(48).y), # 左嘴角 (shape.part(54).x, shape.part(54).y), # 右嘴角 ], dtypedouble) # 通用平均脸模型的3D坐标单位毫米是相对坐标不需要标定 object_points np.array([ (0.0, 0.0, 0.0), # 鼻尖 (0.0, -63.6, -12.5), # 下巴 (-22.0, 10.0, -20.0), # 左眼外角 (22.0, 10.0, -20.0), # 右眼外角 (-25.0, -20.0, -30.0), # 左嘴角 (25.0, -20.0, -30.0), # 右嘴角 ]) # 相机内参未标定情况下用近似值也能有够用的姿态结果 camera_matrix np.array([ [600.0, 0.0, 320.0], [0.0, 600.0, 240.0], [0.0, 0.0, 1.0] ]) dist_coeffs np.zeros((4, 1)) _, rvec, _ cv2.solvePnP(object_points, image_points, camera_matrix, dist_coeffs) rot_mat, _ cv2.Rodrigues(rvec) # 从旋转矩阵提取俯仰角约等于头部高低角度 pitch math.degrees(math.asin(max(-1.0, min(1.0, -rot_mat[1][2])))) return pitchcamera_matrix里的600是焦距的像素近似值320和240是640x480分辨率下的图像中心坐标。这套3D坐标取自平均脸模型不需要对测试者做标定精度虽然和专业动捕设备没法比但对“头部下沉15度并持续数秒”这种级别的点头动作已经足够。姿态解算的原理是把人脸看成一个刚性物体旋转向量描述了它相对摄像机的朝向变化点头时pitch会先往下走到一个峰值再恢复水平这个变化过程就是判定“点头事件”的依据。3. 搭建PythonDlib环境并跑通68点人脸检测最小可运行代码3.1 环境安装Windows上最容易卡住的三个细节搭建环境这步看似简单实际上能卡住一半的人。按顺序处理好下面几个细节后面就顺了。第一Python版本不要选最新版。dlib的预编译依赖在Python 3.11和3.12上经常出兼容问题我一般建议安装Python 3.10而且安装时务必勾选“Add Python to PATH”否则下一步pip install会提示找不到命令。很多python安装教程教的是装最新稳定版但在dlib这个场景里保守版本反而更省心。第二Windows上pip install dlib会触发本地编译这时候没有VS Build Tools和CMake通常会报Could not find CMake或者fatal error C1083。解决方法是先装Visual Studio Build Tools勾选“使用C的桌面开发”工作负载然后再执行安装命令pip install cmake pip install dlib opencv-python numpy第三不要一开始就装一堆不需要的包。这个项目的核心依赖只有dlib、opencv-python和numpyscipy、matplotlib、pyqt5等都是后面按需安装的。把环境保持干净之后排查问题会容易得多。Linux环境下则简单一些sudo apt install build-essential cmake之后直接pip即可macOS需要确保已安装Xcode Command Line Tools。3.2 模型文件与初始化shape_predictor_68_face_landmarks.datDlib分成两个阶段工作先用get_frontal_face_detector()做人脸框检测再用shape_predictor加载68点关键点模型文件shape_predictor_68_face_landmarks.dat做关键点回归。这个模型文件需要单独下载然后放到项目目录下的models/文件夹里。import dlib import cv2 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(models/shape_predictor_68_face_landmarks.dat) # 读取单张测试图片转换成灰度图 img cv2.imread(test_face.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 第二参数是金字塔上采样次数1表示放大图像再检测能增强小脸召回率 faces detector(gray, 1) if len(faces) 0: shape predictor(gray, faces[0]) for i in range(68): x, y shape.part(i).x, shape.part(i).y cv2.circle(img, (x, y), 2, (0, 255, 0), -1) cv2.imshow(68 landmarks, img) cv2.waitKey(0)detector(gray, 1)里的第二个参数是图像金字塔上采样次数。设为1时小尺寸人脸的检测率明显提升但单帧处理耗时也会成倍增加实时视频流里我一般设0保证帧率优先。shape.part(i)返回68个关键点的坐标对象点序是固定的0到16是脸部轮廓17到26是眉毛27到35是鼻梁和鼻尖36到41是左眼42到47是右眼48到67是嘴巴轮廓和内唇。这个点序后续做EAR、MAR、姿态估计时都会用到值得先对着图片把索引关系看一遍。3.3 把单张图跑通扩展到摄像头实时流单张图验证通过后换成摄像头实时流只需要改数据来源。需要注意摄像头帧率和检测耗时要分开看不要让每一帧都阻塞在关键点检测上。cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(camera open failed) while True: ret, frame cap.read() if not ret: continue # 压缩画面到640x480检测耗时降一半对结果影响很小 small cv2.resize(frame, (640, 480)) gray cv2.cvtColor(small, cv2.COLOR_BGR2GRAY) # 金字塔上采样设0保证实时性 faces detector(gray, 0) for face in faces: shape predictor(gray, face) cv2.imshow(driver fatigue monitor, small) if cv2.waitKey(1) 0xFF ord(q): breakcap.isOpened()是摄像头状态的“晴雨表”返回False时大概率是你占用了摄像头或者系统权限没开。ret偶尔为False也很正常比如摄像头被其他程序短时抢占此时直接continue跳过这一帧。把画面压缩到640x480是一个性价比极高的操作检测耗时能降一半以上而EAR和MAR都是比值指标分辨率变化对它们的影响很小。cv2.waitKey(1)的1表示等待1毫秒它同时负责刷新窗口和处理键盘事件这个值不能省否则画面会卡死。4. 实现眨眼、打哈欠、瞌睡点头三路检测阈值、滤波与状态机4.1 眨眼判定双阈值与连续帧滤波拿到EAR实时值后不能直接拿它和阈值比较就完事了。摄像头存在噪声人脸检测的边界框也每一帧都在微变EAR曲线会叠加很多毛刺不做平滑处理就会出现“抖一下算眨眼”的误报。EAR_HISTORY [] BLINK_EAR_THRESHOLD 0.2 CLOSE_EAR_THRESHOLD 0.15 ear_smooth 0.0 alpha 0.7 def update_ear(ear): global ear_smooth # 指数平滑alpha越大对历史依赖越强抗抖动但反应变慢 ear_smooth alpha * ear_smooth (1 - alpha) * ear EAR_HISTORY.append(ear_smooth) if len(EAR_HISTORY) 30: EAR_HISTORY.pop(0) def is_blinking(ear_smooth): # 双阈值先判断是否低于正常开度再用闭合帧宽做二次校验 return ear_smooth BLINK_EAR_THRESHOLD这里alpha取0.7意思是当前帧只占30%权重能有效滤掉单帧尖峰代价是真实闭眼的下降沿也会被削掉一点。实测下来0.7到0.8之间是比较好的平衡区间。判断眨眼还需要配合“连续帧数”——人的正常眨眼持续约100到150毫秒按30fps折算就是3到5帧。如果EAR低于0.2的帧数超过6帧就不再是眨眼而应该标记为“闭眼过长”这才是疲劳的信号。所以完整的眨眼计数逻辑里至少需要维护一个below_threshold_frames计数器当它落在3到6之间才算一次眨眼超过6帧就要拉高疲劳等级。4.2 哈欠判定MAR阈值与最低持续帧数哈欠判定和眨眼最大的不同在于持续时间和事件粒度。一个哈欠从嘴巴张开到合拢通常持续2到4秒所以“MAR 阈值”必须保持足够长的时间才够得上一次有效事件。MAR_THRESHOLD 0.6 YAWN_MIN_FRAMES 15 mouth_high_count 0 def detect_yawn(mar, fps30): global mouth_high_count if mar MAR_THRESHOLD: mouth_high_count 1 # 15帧约0.5秒低于这个时长的张嘴多半是说话或咀嚼 if mouth_high_count YAWN_MIN_FRAMES: mouth_high_count 0 return True else: mouth_high_count 0 return FalseYAWN_MIN_FRAMES设成15是因为30fps下0.5秒已经能区分“打一半的哈欠”和“快速说话”。如果视频流实际帧率不是30fps这个值应该按比例调整否则慢帧率下会漏检。打哈欠是疲劳的早期信号很多驾驶员在真正闭眼瞌睡之前十分钟就已开始频繁打哈欠所以这一路信号在疲劳评分里的权重可以适当调高。4.3 点头判定俯仰角变化量与恢复判定点头和哈欠一样属于“事件型”信号需要一个完整的变化过程才能判定。用第2章的estimate_head_pitch()拿到每一帧的pitch值后可以设定一个初始参考基线比如坐直状态下的pitch约为0到5度。点头时pitch会突然降低10到20度然后恢复。PITCH_DROP_THRESHOLD 12.0 NOD_MIN_FRAMES 6 base_pitch 5.0 pitch_drop_count 0 is_nodding False def detect_nod(pitch, base_pitch): global pitch_drop_count, is_nodding if base_pitch - pitch PITCH_DROP_THRESHOLD: pitch_drop_count 1 if pitch_drop_count NOD_MIN_FRAMES: is_nodding True else: # 俯仰角恢复到基线的80%以内才算一次完整点头结束 if is_nodding and pitch base_pitch * 0.8: is_nodding False return True pitch_drop_count 0 return False这里的关键是“下降”和“恢复”都要被观测到才记一次点头。很多人只检测下降沿于是驾驶员低头看仪表盘也被算成点头。base_pitch的取值建议在程序启动时取前30帧的平均值而不是写死为0。因为不同人的坐姿高度、摄像头安装角度会让基础pitch偏好几度直接写死会导致有的人还没开始点头就触发了检测。参数参考表如下参数参考值作用调整方向BLINK_EAR_THRESHOLD0.20低于此值视为闭眼戴眼镜者略微调高CLOSE_EAR_THRESHOLD0.15低于此值视为深度睡眠闭眼疲劳判定更敏感时调高MAR_THRESHOLD0.60高于此值视为张嘴说话频繁时调高YAWN_MIN_FRAMES15张嘴最短持续帧数误报多时调大PITCH_DROP_THRESHOLD12度点头最小俯仰变化量传感器安装低时减小NOD_MIN_FRAMES6低头持续帧数误报多时调大4.4 疲劳等级融合把三路信号变成可展示的预警状态三路检测各自输出事件计数最后需要合成一个能直观展示的疲劳等级。这里不需要上机器学习用一个简单的滑动窗口评分策略就够了。每30秒统计一次窗口内的眨眼次数、哈欠次数、点头次数和PERCLOS单位时间内闭眼帧占比然后加权求和。def fatigue_level(perclos, blink_count, yawn_count, nod_count): score 0 if perclos 0.4: score 2 elif perclos 0.2: score 1 if yawn_count 1: score 1 if nod_count 2: score 1 if blink_count 15 and perclos 0.2: score 1 return min(score, 3) # 0正常 1轻度 2中度 3重度这个评分逻辑故意设计得保守一些宁可漏报一档不要频繁误报。实际项目里驾驶员对“系统乱报警”的容忍度比对“漏报一次”的容忍度低得多频繁的语音提示会让人直接关掉系统。窗口用30秒滑动而不是固定的30秒块可以避免事件正好落在两个窗口边界上导致计数断裂实现方法是维护一个时间戳列表把超过30秒的旧事件弹出。5. 部署中五个最容易翻车的环节从摄像头选型到夜间失效排查5.1 摄像头索引打不开或画面卡死cap.isOpened()失效陷阱现象程序运行后cv2.VideoCapture(0)不出错但画面一直是黑屏或者弹出一个灰色窗口没有任何画面。更隐蔽的是第一次运行正常第二次运行就报Unable to open camera重启电脑又好了。原因VideoCapture(0)里的索引是系统摄像头枚举值带红外功能的笔记本、外接USB摄像头都可能占用多个索引另外上一步程序没有正确释放摄像头下次再打开时设备被占住。解决先写一个摄像头探测脚本把0到5的索引全部试一遍找到能打开的那个再硬编码到主程序里。程序退出时务必调用cap.release()并放在finally块中不要为了省事只在break前释放。如果探测发现摄像头在其他程序中已被占用关闭对应程序再跑即可。for idx in range(6): cap cv2.VideoCapture(idx) if cap.isOpened(): print(fcamera {idx} is available) cap.release()5.2 dlib编译失败不是代码问题是CMake和VS版本不匹配现象pip install dlib拉取源码后开始本地编译然后报Could not find the necessary CMake或者编译到一半报fatal error C1083: Cannot open include file: stdint.h。不少新手看到这一长串日志直接放弃误以为是Python代码的问题。原因dlib没有为所有平台提供预编译wheelWindows下pip会走源码编译链。编译需要CMake和一个完整的C编译器环境stdint.h报错说明VS的C工具链没装全只有基础Python开发组件是不够的。解决先安装Visual Studio Build Tools选择“使用C的桌面开发”工作负载这一步会同时装上MSVC编译器和Windows SDK。然后确认CMake版本不低于3.10再重新执行pip安装。顺序不能反先装VS再装cmake否则CMake检测编译器时会失败。装完后python -c import dlib; print(dlib.__version__)验证安装结果。5.3 戴眼镜、侧脸、低头导致关键点抖成“弹幕”现象人脸正对镜头时68点稳稳贴在脸上一旦驾驶员转头看后视镜或低头看仪表盘关键点就像“弹幕”一样在脸上乱跳EAR和MAR值跟着剧烈波动误报飙到让人崩溃。原因Dlib的回归树是在正脸样本上训练的大幅度侧脸、低头时人脸检测框本身就不稳定回归树在框内找不到可信特征输出的关键点位置会漂移。这不是代码写得差而是模型能力边界决定的。解决给三路检测统一加一个“跟踪锁存”机制——当人脸检测不到或关键点置信度过低时不立即清零指标而是沿用上一帧的有效值最多保持15帧。这样短暂转头造成的空窗期不会打断眨眼计数也不会让EAR瞬间跳回正常值造成漏检。Dlib没有输出置信度但可以通过检查左右眼36到47号点的几何顺序来做一个粗暴的校验左眼外角到内角的水平向量方向和右眼内角到外角的水平向量方向应当相反不满足就说明关键点已经错位。5.4 夜间IR摄像头噪点导致误报现象白天测试一切正常到了夜间的车载环境EAR曲线频繁出现小幅振荡眨眼误报率明显上升。原因是车载红外摄像头在低照度下噪点严重Dlib的HOG检测器对噪声很敏感人脸框位置每帧都有几个像素的抖动关键点也跟着抖。解决摄像头输入帧先做一次高斯模糊再加检测cv2.GaussianBlur(gray, (3, 3), 0)就够太大会把眼睛边缘的梯度信息抹掉。EAR和MAR的平滑系数也应当随噪声水平动态调整夜间把alpha从0.7提高到0.85。如果误报仍然密集检查摄像头是否带有自动增益有的话关闭或者固定曝光参数效果比任何滤波都明显。5.5 眨眼和闭眼傻傻分不清宽度与幅度的拆分现象明明只是在正常眨眼系统却打出“中度疲劳”的预警查看日志发现眨眼事件数量每分钟超过20次且EAR谷值深不见底。原因阈值判定的默认逻辑只看“EAR低于0.2”这一点但疲劳性闭眼和普通眨眼相比特征是“谷值深且宽度大”。普通眨眼持续3到5帧闭眼则持续10帧以上。如果把低于阈值的每一帧都算闭眼几乎所有人都会触发疲劳预警。解决区分两种事件。眨眼事件要求EAR低于阈值的持续帧数在2到6帧之间超过6帧的持续低值单独计入“闭眼过长”计数后者才参与PERCLOS计算。PERCLOS统计时也要做同样的时序区分闭眼过长帧占比高才说明驾驶员真的处于瞌睡状态。这个坑几乎每个做毕设的人都会踩一次提前拆开就能少走弯路。6. 可视化界面落地与验证方法把算法封装成能演示、能答辩的完整项目6.1 画面叠加显示还是独立GUI两条路线怎么选做可视化界面有两条路很多人在这里反复横跳。最简单的是OpenCV在帧上直接叠加文字和枪标零额外依赖缺点是不太像“界面”答辩时视觉冲击力弱。另一种是PyQt5或Tkinter做一个独立窗口左边实时视频右边状态面板视觉上更像一个完整产品但需要处理视频帧到Qt控件的格式转换和线程刷新问题。我的建议是分两步走先把OpenCV叠加版跑通确保三路检测功能稳定然后封装一个PyQt5界面。PyQt版本的核心是把处理后的帧从BGR转成RGB再喂给QLabel用QImage包装并设置定时器按检测帧率刷新。这部分代码不难但确实需要多花半天时间调字节对齐想象中很费事实际上跑通以后成就感很强。界面上的数据显示区建议放四块视频画面、状态指示灯、三路事件计数、30秒PERCLOS曲线图。6.2 验证用一段带事件标注的视频算准确率界面做完接下来是“源代码文档说明界面演示”里容易被忽视的一环——用数据验证系统真的有效。手动在摄像头前做动作当然方便演示但评估算法边界一定要用固定视频录播。录一段测试视频包含正常驾驶、频繁眨眼、明显哈欠、三次点头这段过程然后用标注工具手工记录事件起始帧再把算法跑一遍统计表如下事件类型人工标注数算法检出数误报数召回率精确率眨眼2826392.8%89.7%哈欠65083.3%100%点头332100%60%点头精确率低的常见原因是驾驶员调试坐姿时的头部下沉被误记。解决方法是把点头的目标角度阈值从12度提高到15度并且要求点头后必须有回到基线的恢复过程。这种“先标定再回测”的习惯能让参数调整有的放矢而不是凭感觉拍脑袋改阈值。6.3 最后一步把参数阈值留成可配置项无论最终界面长什么样请一定把阈值放进配置文件而不是散落在代码里。我现在的习惯是启动时先读取一段30秒的“坐姿校准”数据取EAR和MAR的均值作为个体基线再乘以比例系数得到闭眼阈值和哈欠阈值。这样系统从一个人换到另一个人不需要改代码只需要重新校准一次答辩现场就能演示从“正常状态”到“疲劳状态”的完整变化过程。这份文档里能写清这个机制比你贴十页代码更能说明你把系统想明白了。希望帮到你。本文还有配套的精品资源点击获取