恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python疲劳检测系统源码:从面部特征到分级报警的毕设完整方案
首页
资讯中心
/
Python疲劳检测系统源码:从面部特征到分级报警的毕设完整方案
Python疲劳检测系统源码:从面部特征到分级报警的毕设完整方案
发布时间:2026/10/17 7:04:38
简介这份资源是一套基于Python开发的驾驶员面部特征疲劳检测系统源码面向计算机相关专业学生、毕业设计选题者以及需要人脸识别与状态监测方案的开发者。系统通过摄像头采集驾驶员面部图像识别其疲劳状态并判断是否需要休息可迁移至交警摄像头、高速收费站等场景用于排查疲劳驾驶隐患。压缩包共24个文件以10个xml配置、5个py核心脚本为主另含iml工程文件、md说明、docx文档、dat数据与mp3提示音等整体约68.33MB下载解压后即可运行。目前已有1359人学习下载说明该方案在同类毕业设计选题中具备一定参考价值。源码目录结构完整涵盖人脸检测、特征提取与疲劳判定等模块读者可据此理解从图像采集到状态输出的完整链路并在此基础上调整阈值、替换模型或扩展报警逻辑快速形成可演示、可答辩的项目成果。1. 从一次答辩被问到哑口无言的经历说起去年帮一个学弟看毕业设计答辩预演他做的正是基于驾驶员面部特征的疲劳检测系统。功能演示很流畅摄像头一开绿框标出人脸眼睛闭合超过阈值就弹报警。台下导师问了三个问题EAR 阈值怎么定的夜间红外图像怎么处理误报率测过没有他一个都答不上来。这个场景我印象太深了因为绝大多数做疲劳检测毕设的同学卡的不是代码能不能跑而是说不清设计依据在哪。这套 Python 疲劳检测系统源码要解决的就是让做毕业设计或课程设计的人拿到一份从特征提取到报警逻辑都能讲清楚来龙去脉的完整方案。它适合两类人一是需要交出一份能通过答辩的毕设作品的学生二是想快速搭一个驾驶员状态监测原型的开发者。核心链路是摄像头采集、人脸检测、关键点定位、疲劳特征计算、分级报警每一环都有可调参数和可解释的设计理由。2. 需求拆解与技术选型为什么是面部特征而不是别的方案2.1 疲劳检测的三条技术路线对比做疲劳检测摆在面前的路其实有三条基于车辆行为方向盘转角、车道偏移、基于生理信号脑电、心电、基于面部视觉特征。毕设场景下选哪条不是拍脑袋决定的。技术路线数据来源实现难度硬件成本毕设适配度车辆行为分析方向盘、车道线中需车辆或模拟器低难复现生理信号检测脑电/心电传感器高高需接触式设备低设备门槛高面部视觉特征普通摄像头中低一个摄像头即可高易演示面部视觉特征这条路最大的优势是数据获取门槛低。一个普通 USB 摄像头就能采集不需要接触式传感器也不需要真实车辆。对毕设来说能演示、能复现、能讲清楚原理比技术多先进重要得多。2.2 技术栈的取舍逻辑这套方案的技术栈是 Python OpenCV Dlib或 MediaPipe NumPy。为什么不用深度学习端到端的方案因为毕设答辩时端到端模型是个黑匣子导师问“为什么判定为疲劳”你只能回答“模型输出的”。而基于面部几何特征的方法每一步都有明确的数学定义EAR眼睛纵横比、MAR嘴巴纵横比、头部姿态角这些指标可以画曲线、可以设阈值、可以解释。# 面部关键点索引定义基于68点模型 # 左眼36-41右眼42-47嘴巴48-67 LEFT_EYE list(range(36, 42)) RIGHT_EYE list(range(42, 48)) MOUTH list(range(48, 68)) def eye_aspect_ratio(eye_points): 计算眼睛纵横比 EAR eye_points: 6个关键点的坐标列表 [(x1,y1), ..., (x6,y6)] 原理眼睛睁开时垂直距离大闭合时垂直距离趋近于0 # 垂直方向两个距离 A dist(eye_points[1], eye_points[5]) B dist(eye_points[2], eye_points[4]) # 水平方向距离 C dist(eye_points[0], eye_points[3]) ear (A B) / (2.0 * C) return ear这段代码是整个系统的核心。EAR 的计算逻辑是眼睛睁开时上下眼睑的垂直距离较大EAR 值通常在 0.25 到 0.35 之间眼睛闭合时垂直距离趋近于零EAR 值会降到 0.15 以下。这个阈值不是固定的不同人种、不同光照条件下会有偏移所以实际部署时通常要做一次基线校准。提示EAR 阈值建议在 0.18 到 0.22 之间做初始值然后根据实际测试微调。不要直接抄网上的 0.3那个值对亚洲人眼型偏大。2.3 疲劳判定的多指标融合策略单靠 EAR 判定疲劳翻车概率很高。打哈欠的时候眼睛是睁着的低头看仪表盘的时候眼睛也是睁着的但这些都可能是疲劳前兆。所以这套方案用了三个指标融合PERCLOS单位时间内眼睛闭合时间占比这是疲劳检测的黄金指标通常取 30 秒或 60 秒窗口MAR嘴巴纵横比用于检测打哈欠MAR 超过 0.6 且持续 2 秒以上判定为哈欠头部姿态通过 solvePnP 计算头部俯仰角和偏航角低头超过 15 度且持续 3 秒判定为分神三个指标各自有独立的计数器和阈值最终报警等级由三者加权决定。这样设计的好处是答辩时你可以画一张融合逻辑图每个分支都有依据。3. 系统总体设计从摄像头到报警的完整数据流3.1 四层架构与模块职责划分整个系统按数据流分成四层每层职责单一方便调试和替换。采集层负责从摄像头读取视频帧做灰度化和直方图均衡化。这里有个细节夜间或逆光环境下直接灰度化会导致人脸检测失败所以通常的做法是先做自适应直方图均衡化CLAHE再送入检测器。检测层用人脸检测器定位人脸区域再用关键点预测器提取 68 个面部关键点。Dlib 的检测器精度高但速度慢MediaPipe 速度快但关键点定义不同。毕设演示用 Dlib 足够如果追求实时性可以换 MediaPipe。分析层是核心计算 EAR、MAR、头部姿态角维护滑动窗口计数器输出疲劳状态标志位。报警层根据疲劳等级触发不同响应一级疲劳轻度在画面上叠加黄色警告文字二级疲劳中度播放提示音三级疲劳重度持续蜂鸣并记录日志。# 疲劳状态机核心逻辑 class FatigueStateMachine: def __init__(self, ear_thresh0.2, mar_thresh0.6, consec_frames20, yawn_frames15): self.ear_thresh ear_thresh # EAR阈值 self.mar_thresh mar_thresh # MAR阈值 self.consec_frames consec_frames # 闭眼持续帧数阈值 self.yawn_frames yawn_frames # 哈欠持续帧数阈值 self.eye_counter 0 # 闭眼帧计数器 self.yawn_counter 0 # 哈欠帧计数器 self.fatigue_level 0 # 0正常 1轻度 2中度 3重度 def update(self, ear, mar, pitch_angle): # 闭眼判定 if ear self.ear_thresh: self.eye_counter 1 else: self.eye_counter 0 # 哈欠判定 if mar self.mar_thresh: self.yawn_counter 1 else: self.yawn_counter 0 # 分级报警 if self.eye_counter self.consec_frames * 2: self.fatigue_level 3 elif self.eye_counter self.consec_frames: self.fatigue_level 2 elif self.yawn_counter self.yawn_frames or abs(pitch_angle) 15: self.fatigue_level 1 else: self.fatigue_level 0 return self.fatigue_level这段状态机代码是整个系统的决策中枢。consec_frames设为 20 意味着在 30fps 下约 0.67 秒的持续闭眼才触发报警这个值不能太小否则正常眨眼会误报也不能太大否则疲劳响应延迟。yawn_frames设为 15 对应约 0.5 秒的哈欠动作实际测试中打哈欠通常持续 1 到 3 秒所以这个阈值偏保守可以根据场景调整。3.2 关键参数配置与调优方法系统里有一组参数直接决定检测灵敏度和误报率我一般会把这些参数集中放在一个配置文件里方便答辩时展示调参过程。# config.py 参数配置表 CONFIG { camera_id: 0, # 摄像头编号 frame_width: 640, # 处理帧宽 frame_height: 480, # 处理帧高 ear_threshold: 0.20, # EAR闭眼阈值 mar_threshold: 0.60, # MAR哈欠阈值 consec_frames: 20, # 闭眼持续帧数 yawn_frames: 15, # 哈欠持续帧数 pitch_threshold: 15, # 低头角度阈值 perclos_window: 900, # PERCLOS窗口帧数(30秒30fps) alarm_sound: True, # 是否开启声音报警 log_path: fatigue_log.csv # 日志保存路径 }调参的顺序通常是先固定 MAR 和头部姿态参数单独调 EAR 阈值用录制好的睁眼/闭眼视频测试找到误报和漏报的平衡点然后调 PERCLOS 窗口大小窗口越大越稳定但响应越慢最后调报警分级阈值。这个过程在毕设论文里可以写成“参数敏感性分析”一节是加分项。4. 核心模块实现人脸检测、特征计算与报警触发4.1 人脸检测与关键点定位的工程细节人脸检测这一步很多人直接调库就完事了但实际跑起来会发现两个问题一是侧脸检测不到二是戴眼镜时关键点漂移。这两个问题不解决演示时摄像头稍微偏一点就翻车。处理侧脸问题的做法是先用 Dlib 的正脸检测器如果连续 N 帧检测不到人脸切换到基于 CNN 的检测器或者 MediaPipe 的 BlazeFace后者对侧脸鲁棒性更好。代价是速度慢一些但毕设演示对实时性要求没那么苛刻。import dlib import cv2 detector dlib.get_frontal_face_detector() predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def detect_landmarks(frame): 检测人脸并返回68个关键点坐标 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # CLAHE增强对比度改善逆光检测 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8,8)) gray clahe.apply(gray) faces detector(gray, 0) if len(faces) 0: return None, None # 取最大人脸假设驾驶员是画面中最大的人脸 face max(faces, keylambda r: r.width() * r.height()) landmarks predictor(gray, face) points [(landmarks.part(i).x, landmarks.part(i).y) for i in range(68)] return face, pointsCLAHE 的clipLimit设为 2.0 是经验值太大反而会放大噪声。tileGridSize设为 8x8 意味着把图像分成 8x8 的块分别做均衡化这个粒度对 640x480 的图像比较合适。如果摄像头分辨率更高可以适当增大。4.2 头部姿态估计的实现头部姿态用 solvePnP 求解需要 3D 人脸模型点和对应的 2D 图像点。3D 模型点可以用标准人脸模型的几个关键位置鼻尖、下巴、左右眼角、左右嘴角共 6 个点。import numpy as np # 3D人脸模型参考点单位毫米近似值 MODEL_POINTS np.array([ (0.0, 0.0, 0.0), # 鼻尖 (0.0, -63.6, -12.5), # 下巴 (-43.3, 32.7, -26.0), # 左眼角 (43.3, 32.7, -26.0), # 右眼角 (-28.9, -28.9, -24.1), # 左嘴角 (28.9, -28.9, -24.1) # 右嘴角 ], dtypenp.float64) # 对应的68点模型索引 LANDMARK_IDS [30, 8, 36, 45, 48, 54] def estimate_head_pose(landmarks, frame_shape): 估计头部姿态角返回pitch/yaw/roll h, w frame_shape[:2] focal_length w center (w / 2, h / 2) camera_matrix np.array([ [focal_length, 0, center[0]], [0, focal_length, center[1]], [0, 0, 1] ], dtypenp.float64) dist_coeffs np.zeros((4, 1)) image_points np.array([landmarks[i] for i in LANDMARK_IDS], dtypenp.float64) success, rotation_vec, translation_vec cv2.solvePnP( MODEL_POINTS, image_points, camera_matrix, dist_coeffs, flagscv2.SOLVEPNP_ITERATIVE ) if not success: return None rotation_mat, _ cv2.Rodrigues(rotation_vec) # 从旋转矩阵提取欧拉角 pitch np.arctan2(rotation_mat[2][1], rotation_mat[2][2]) * 180 / np.pi yaw np.arctan2(-rotation_mat[2][0], np.sqrt(rotation_mat[2][1]**2 rotation_mat[2][2]**2)) * 180 / np.pi roll np.arctan2(rotation_mat[1][0], rotation_mat[0][0]) * 180 / np.pi return pitch, yaw, rollfocal_length直接用图像宽度近似这在没有相机标定数据时是常用做法。SOLVEPNP_ITERATIVE适合点数较少的情况如果关键点更多可以用SOLVEPNP_EPNP。pitch 角为负值表示低头正值表示抬头实际判定疲劳时关注的是低头角度。4.3 报警触发与日志记录报警模块要处理两件事实时画面叠加和日志持久化。画面叠加用 OpenCV 的 putText 和 rectangle日志用 CSV 格式方便后续分析。import csv from datetime import datetime class AlarmLogger: def __init__(self, log_path): self.log_path log_path # 写入表头 with open(log_path, w, newline) as f: writer csv.writer(f) writer.writerow([timestamp, ear, mar, pitch, fatigue_level, event]) def log(self, ear, mar, pitch, level): event_map {0: normal, 1: mild, 2: moderate, 3: severe} with open(self.log_path, a, newline) as f: writer csv.writer(f) writer.writerow([ datetime.now().strftime(%Y-%m-%d %H:%M:%S.%f)[:-3], round(ear, 4), round(mar, 4), round(pitch, 2), level, event_map[level] ])日志里记录时间戳和原始指标值答辩时可以把 CSV 导入 Excel 画曲线图展示疲劳发生前后的 EAR 变化趋势。这比单纯说“系统能检测疲劳”有说服力得多。5. 避坑与常见问题排查那些演示时最容易翻车的点5.1 摄像头打不开或画面卡死现象程序启动后摄像头指示灯亮但画面全黑或者运行几秒后卡死。原因通常是摄像头被其他程序占用或者 OpenCV 的 VideoCapture 后端选择不对。Windows 上默认用 MSMF 后端有时会出问题Linux 上可能是权限问题。解决先确认没有其他程序在用摄像头。代码里显式指定后端Windows 用cv2.CAP_DSHOWLinux 用cv2.CAP_V4L2。如果还是卡死加一个帧读取超时判断连续 10 帧读取失败就释放重连。cap cv2.VideoCapture(0, cv2.CAP_DSHOW) # Windows # cap cv2.VideoCapture(0, cv2.CAP_V4L2) # Linux cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) fail_count 0 while True: ret, frame cap.read() if not ret: fail_count 1 if fail_count 10: cap.release() cap cv2.VideoCapture(0, cv2.CAP_DSHOW) fail_count 0 continue fail_count 0 # 后续处理...5.2 正常眨眼被误判为疲劳现象人正常眨眼时系统频繁触发一级报警演示时很尴尬。原因consec_frames设得太小或者 EAR 阈值偏高。正常眨眼持续时间约 0.1 到 0.4 秒30fps 下对应 3 到 12 帧。如果consec_frames设为 10刚好卡在眨眼和闭眼的边界上。解决把consec_frames提高到 20 以上确保只有持续闭眼才触发。同时检查 EAR 阈值如果正常睁眼时 EAR 就在 0.22 左右阈值设 0.25 会导致频繁误报。建议先跑一段睁眼视频统计 EAR 的均值和标准差阈值设为均值减去 2 倍标准差。5.3 戴眼镜时关键点漂移现象戴眼镜的测试者眼睛关键点定位到镜框上EAR 计算完全失准。原因Dlib 的 68 点模型训练数据中戴眼镜样本偏少镜框反光会干扰关键点回归。解决换用 MediaPipe Face Mesh它输出 468 个关键点对眼镜的鲁棒性更好。或者在做关键点定位前先做一个眼镜检测如果检测到眼镜对眼部区域做额外的对比度增强。毕设场景下最简单的做法是让测试者摘掉眼镜演示同时在论文里说明这个局限性。5.4 夜间或暗光环境下检测失效现象白天演示正常晚上或拉窗帘后摄像头画面噪点大人脸检测直接失败。原因普通 RGB 摄像头在低照度下信噪比急剧下降灰度化后人脸特征几乎不可见。解决软件层面加 CLAHE 增强同时降低检测器的阈值。如果条件允许用带红外补光的摄像头这是工程上最直接的方案。毕设论文里可以把“光照鲁棒性”作为一个讨论点分析不同光照条件下的检测成功率。5.5 多张人脸时误检现象画面里出现两个人时系统在两张脸之间跳来跳去报警逻辑混乱。原因没有做目标跟踪每帧独立检测取最大人脸策略在两人脸大小接近时会抖动。解决加一个简单的 IOU 跟踪器维护上一帧的人脸位置当前帧选择与上一帧 IOU 最大的人脸。如果 IOU 低于阈值再重新全局检测。这样保证始终跟踪同一个人。6. 验收与答辩怎么证明这套系统真的能跑6.1 功能验收清单答辩前自己先过一遍这份清单每项都能演示且能解释原理基本就稳了。验收项操作步骤预期结果对应论文章节人脸检测正对摄像头绿框标出人脸检测层设计关键点定位观察眼部区域68点覆盖五官特征提取闭眼报警闭眼3秒触发二级报警状态机逻辑哈欠报警张嘴打哈欠触发一级报警MAR计算低头报警低头看下方触发一级报警头部姿态日志记录触发报警后查看CSV有对应时间戳记录数据持久化参数可调修改config后重启灵敏度变化参数配置6.2 答辩时导师最可能问的五个问题第一个问题通常是“你的疲劳判定依据是什么”。回答时不要只说 EAR要把 PERCLOS、MAR、头部姿态三个指标都讲出来说明为什么单指标不够。第二个问题是“阈值怎么确定的”。这里要展示你的调参过程比如用录制视频统计 EAR 分布画直方图找分界点。有数据支撑的阈值比拍脑袋的阈值可信得多。第三个问题是“误报率多少”。毕设不要求你做大规模测试但至少要有 5 到 10 个测试样本的统计。可以自己录几段视频一段正常驾驶、一段模拟疲劳统计报警次数和实际疲劳次数的比值。第四个问题是“实时性怎么样”。用time.time()在代码里打点统计每帧处理耗时算出 FPS。Dlib 在普通笔记本上大概 10 到 15 FPSMediaPipe 能到 25 以上。如果 FPS 太低说明需要优化或者换轻量模型。第五个问题是“和现有方案比有什么改进”。这个问题比较开放可以从多指标融合、分级报警、参数可配置这几个角度回答。如果论文里做了对比实验直接拿数据说话。6.3 一个让答辩加分的小技巧答辩演示时不要只放实时摄像头画面。提前录一段模拟疲劳的视频用系统跑一遍把 EAR 曲线和报警时刻标注在同一张图上。这张图能直观展示“疲劳发生时 EAR 下降、系统在 N 帧后触发报警”的完整因果链。导师看到这张图基本不会再追问判定逻辑的问题。我自己的习惯是每次交付这类系统前都会用三段视频做回归测试一段正常睁眼、一段频繁眨眼、一段模拟疲劳。三段都跑通才敢拿去演示。从那以后我每次做视觉类项目都强制走一遍这个流程省了很多答辩现场翻车的机会。希望这套方案和源码能帮到你把毕设稳稳拿下。本文还有配套的精品资源点击获取