简介:这份毕业设计资源面向计算机相关专业学生与Python初学者,提供一套基于卷积神经网络的人脸识别驾驶员疲劳检测与预警系统完整源码,可用于课程设计、毕设答辩或深度学习入门实践。压缩包共15个文件,约2.8MB,包含3个py主程序与界面脚本、1个ui界面文件、4个xml工程配置、2个txt依赖与说明、1个whl离线安装包及md文档、png需求图等,覆盖从环境搭建到界面运行的完整链路。资源中附带dlib离线安装包与requirements依赖清单,便于在Windows环境下快速复现;main.py与main_ui.py分工明确,配合ui文件可直观查看检测界面逻辑,README与需求图则帮助理解系统设计思路与功能边界。目前已有364人学习下载,适合希望以人脸疲劳检测为切入点,掌握CNN模型调用、界面交互与工程组织方式的读者参考借鉴。
1. 从一份能跑起来的毕设包说起:Python 卷积神经网络人脸识别疲劳检测到底交付了什么
如果你正在为毕业设计选题发愁,或者已经选了「基于深度学习的人脸识别疲劳检测」却卡在环境配置和模型跑不通上,这份 Python 卷积神经网络人脸识别驾驶员疲劳检测与预警系统压缩包,大概率能帮你省掉两周的摸索时间。它不是一篇论文,也不是一个在线演示,而是一个完整的、带 UI 界面的本地可运行工程:main.py 是入口,main_ui.py 和 main.ui 负责界面,test.py 用来做单点验证,requirements.txt 锁定了依赖版本,甚至贴心地附带了 dlib-19.8.1-cp36-cp36m-win_amd64.whl 这个在 Windows 上出了名难装的轮子。核心逻辑是:用摄像头或视频流抓取驾驶员面部,通过卷积神经网络判断眼睛闭合程度和嘴巴开合状态,当疲劳特征持续超过阈值时触发预警。适合谁?适合需要快速搭出可演示系统的本科生,也适合想拿一个完整 pipeline 做二次开发的初级算法工程师。但要注意,它不是一个工业级产品,而是一个教学级原型,理解这一点,后面的坑你才不会踩得莫名其妙。
2. 拆开压缩包:文件结构、依赖链与 CNN 疲劳判定的技术选型
2.1 目录里每个文件到底干什么用
拿到压缩包先别急着双击 main.py,花三分钟把文件清单过一遍,能避免后面 80% 的「ModuleNotFoundError」。我按实际运行时的调用关系给你捋一遍:
| 文件/目录 | 作用 | 是否必须 |
|---|---|---|
| main.py | 程序主入口,初始化摄像头、加载模型、启动 UI 事件循环 | 是 |
| main_ui.py | 由 main.ui 编译生成的 Python 界面代码,负责控件布局和信号槽 | 是 |
| main.ui | Qt Designer 源文件,改界面时编辑它再重新编译 | 否,但建议保留 |
| test.py | 单张图片或短片段测试脚本,用来验证模型是否正常加载 | 建议保留 |
| requirements.txt | 依赖清单,包含 opencv-python、dlib、keras、tensorflow 等 | 是 |
| dlib-19.8.1-cp36-cp36m-win_amd64.whl | Windows 下 Python 3.6 的 dlib 预编译轮子 | Windows 必用 |
| pycache | Python 字节码缓存,可删 | 否 |
| .idea / *.iml / workspace.xml | PyCharm 工程配置,换 IDE 可删 | 否 |
| README.md | 简要说明,通常写运行命令和注意事项 | 建议读 |
| 关于系统.txt / 需求.png | 设计说明和需求截图,写论文时可直接引用 | 写论文时有用 |
这里最关键的认知是:main.ui 和 main_ui.py 是「源文件」和「生成文件」的关系。如果你直接改 main_ui.py,下次用 pyuic 重新编译 main.ui 时改动会被覆盖。常见做法是改 main.ui,然后运行pyuic5 main.ui -o main_ui.py重新生成。很多同学改了半天界面没生效,就是栽在这个关系上。
2.2 为什么选 CNN 而不是传统特征点方法
疲劳检测的经典路线有两条:一条是用 dlib 的 68 个面部特征点算 EAR(眼睛纵横比)和 MAR(嘴巴纵横比),纯几何规则;另一条是直接把眼部或嘴部区域裁出来,送进卷积神经网络做二分类。这份毕设走的是第二条路,但保留了 dlib 做面部对齐和区域裁剪。
为什么这么选?几何规则在正面、光照均匀时很准,但驾驶员一歪头、戴眼镜、夜间红外补光不足,EAR 就会剧烈抖动,误报率飙升。CNN 的优势在于它学的是纹理和结构特征,对姿态和光照的鲁棒性明显更好。但纯 CNN 又需要大量标注数据,所以这份工程的做法是:dlib 负责「找到脸和关键区域」,CNN 负责「判断这个区域是不是疲劳状态」。这是一种典型的混合 pipeline,也是目前教学项目里最稳妥的方案。
代价是什么?dlib 的安装极其痛苦,尤其在 Windows + Python 3.6 环境下,源码编译需要 CMake 和 Visual Studio 构建工具,新手基本卡死。所以作者直接附了 whl 轮子,这是这份资源最实在的地方之一。
2.3 依赖安装:先装 whl 再装 requirements
不要一上来就pip install -r requirements.txt,dlib 会卡住甚至报错。正确顺序是先手动装 whl,再装其余依赖。下面是我验证过的命令序列:
# 第一步:确认 Python 版本是 3.6.x,且是 64 位 python --version # 输出应为 Python 3.6.x # 第二步:先装 dlib 的预编译轮子,注意路径要指向你解压后的实际位置 pip install dlib-19.8.1-cp36-cp36m-win_amd64.whl # 第三步:再装其余依赖 pip install -r requirements.txt # 第四步:验证 dlib 和 cv2 能否正常导入 python -c "import dlib; import cv2; print(dlib.__version__, cv2.__version__)"逻辑说明:dlib 的 whl 文件名里cp36表示 CPython 3.6,win_amd64表示 64 位 Windows。如果你的 Python 是 3.7 或 3.8,这个 whl 装不上,会提示「is not a supported wheel on this platform」。解决办法要么换 Python 3.6,要么自己找对应版本的 dlib 轮子。requirements.txt 里的 tensorflow 和 keras 版本也要和 Python 3.6 匹配,常见组合是 tensorflow 1.14 或 2.x 的早期版本,具体以文件内容为准。
提示:如果你用 Anaconda,建议单独建一个 Python 3.6 的虚拟环境,避免和 base 环境里的包冲突。命令是
conda create -n fatigue python=3.6,然后激活再执行上面的步骤。
3. 跑通主流程:从摄像头帧到疲劳预警的完整代码链路
3.1 main.py 的启动逻辑与参数入口
main.py 是整个系统的调度中心。它通常做四件事:初始化摄像头、加载 CNN 模型权重、启动 Qt 界面、把视频帧循环绑定到界面刷新。你不需要逐行读懂,但要知道改哪里能控制行为。下面是一个典型的启动段结构,我按常见写法还原并加了注释:
# main.py 典型结构还原 import sys import cv2 import dlib from PyQt5.QtWidgets import QApplication from main_ui import Ui_MainWindow # 由 main.ui 生成 # 初始化检测器和预测器 detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") model = load_model("fatigue_cnn.h5") # 模型权重文件,压缩包内通常有 # 疲劳判定阈值,这两个参数是调优的核心 EAR_THRESHOLD = 0.25 # 眼睛纵横比低于此值视为闭眼 CONSEC_FRAMES = 20 # 连续多少帧闭眼后触发预警 cap = cv2.VideoCapture(0) # 0 表示默认摄像头,换视频文件填路径 app = QApplication(sys.argv) window = Ui_MainWindow() window.show() # 主循环:读帧 -> 检测人脸 -> 裁剪眼部 -> CNN 推理 -> 更新界面 while True: ret, frame = cap.read() if not ret: break gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = detector(gray, 0) for face in faces: landmarks = predictor(gray, face) # 取左眼区域,送入 CNN 判断 left_eye = extract_eye_region(gray, landmarks) pred = model.predict(preprocess(left_eye)) if pred < EAR_THRESHOLD: counter += 1 else: counter = 0 if counter >= CONSEC_FRAMES: trigger_alarm() # 触发预警,可能是蜂鸣或界面弹窗 window.update_frame(frame) # 刷新界面显示 if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:这段代码的核心是「检测-裁剪-推理-计数-报警」五步。detector 负责找到人脸框,predictor 给出 68 个关键点,extract_eye_region 根据关键点索引裁出眼部小图,CNN 对这张小图输出一个疲劳概率或 EAR 回归值,counter 做时间累积,超过 CONSEC_FRAMES 才报警。参数说明:EAR_THRESHOLD 越低越不敏感,适合光线差但驾驶员确实睁眼的场景;CONSEC_FRAMES 越大越不容易误报,但响应会变慢。我一般会把 CONSEC_FRAMES 设在 15 到 25 之间,低于 10 基本没法用,眨眼都会触发。
3.2 用 test.py 做单点验证,别直接上摄像头
摄像头一开,变量太多:光线、角度、分辨率、驱动。一旦报错你根本不知道是模型问题还是硬件问题。test.py 的价值就是把这些变量固定下来。常见做法是让它读一张本地图片,走完整个 pipeline,把中间结果打印或保存出来。
# test.py 单图验证脚本示例 import cv2 import dlib import numpy as np from keras.models import load_model detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") model = load_model("fatigue_cnn.h5") img = cv2.imread("test_face.jpg") # 准备一张清晰的正面人脸图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = detector(gray, 0) print("检测到人脸数量:", len(faces)) for i, face in enumerate(faces): landmarks = predictor(gray, face) print(f"第{i}张人脸关键点数量:", landmarks.num_parts) # 裁剪眼部并推理 eye = extract_eye_region(gray, landmarks) cv2.imwrite(f"eye_{i}.jpg", eye) # 保存裁剪结果,肉眼确认裁对了没 pred = model.predict(preprocess(eye)) print(f"第{i}张人脸疲劳预测值:", pred)逻辑说明:先确认 detector 能检测到人脸,再确认 predictor 返回 68 个点,再确认裁剪出的眼部图是眼睛而不是眉毛或鼻子,最后看模型输出是否在合理范围。参数说明:test_face.jpg 要选正面、光照均匀、不戴墨镜的图。如果这一步就检测不到人脸,后面摄像头环节不用试了,先解决 dlib 模型文件路径问题。shape_predictor_68_face_landmarks.dat 这个文件压缩包里通常有,如果没有,需要单独下载,注意它大约 100MB,别下到损坏的版本。
3.3 预警触发与界面联动的实现要点
预警不是简单 print 一句话。这份工程里,预警通常要同时做三件事:界面状态栏变红、播放提示音、记录疲劳时间戳。界面联动靠的是 Qt 的信号槽机制。main_ui.py 里会定义好控件对象,你在主循环里通过window.label_status.setText("疲劳")这类调用更新界面。
# 预警触发与界面更新片段 from PyQt5.QtCore import QTimer import winsound # Windows 下播放提示音 def trigger_alarm(window): window.label_status.setText("疲劳预警") window.label_status.setStyleSheet("color: red; font-weight: bold;") winsound.Beep(1000, 500) # 频率 1000Hz,持续 500ms # 记录时间戳,方便写论文时做统计分析 with open("fatigue_log.txt", "a") as f: f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} 疲劳触发\n")逻辑说明:winsound 是 Windows 专用,跨平台要用 pygame 或 Qt 的 QSound。参数说明:Beep 的第一个参数是频率,太高刺耳,1000 到 2000 比较合适;第二个参数是毫秒数,500 足够引起注意又不会烦人。注意,预警触发后不要每帧都写日志,否则文件会爆炸,常见做法是加一个冷却时间,比如 5 秒内只记录一次。
注意:如果你在非 Windows 环境跑,winsound 会直接报 ImportError。把这段换成
print("\a")或者用 Qt 的 QApplication.beep() 更稳妥。
4. 避坑与排查:dlib 安装、模型加载和误报的血泪经验
4.1 dlib 装不上,提示 CMake 或 Visual Studio 缺失
现象:执行pip install dlib后卡在「Building wheel for dlib」然后报错,提示找不到 CMake 或 cl.exe。原因:dlib 包含 C++ 扩展,pip 默认从源码编译,而你的机器没有构建工具链。解决:直接用压缩包里的 whl 轮子,命令是pip install dlib-19.8.1-cp36-cp36m-win_amd64.whl。如果 Python 版本不匹配,去下载对应 cp37、cp38 的轮子,别硬编译。这是最省时间的做法,没有之一。
4.2 模型加载报错,提示 Unknown layer 或 h5py 版本冲突
现象:load_model("fatigue_cnn.h5")抛出「Unknown layer: Sequential」或 h5py 相关错误。原因:Keras 和 TensorFlow 版本不匹配,或者保存模型时的 Keras 版本和当前环境不一致。解决:先看 requirements.txt 里锁定的版本,严格按它装。如果文件没写清楚,常见组合是 tensorflow 1.14 + keras 2.2.4,或者 tensorflow 2.3 + keras 2.4。另一个办法是用model = tf.keras.models.load_model(..., compile=False)跳过编译状态加载,能绕过一部分自定义层问题。
4.3 摄像头能开但检测不到人脸,或框乱跳
现象:界面显示视频流,但没有人脸框,或者框在背景上乱跳。原因:光照不足、摄像头分辨率太低、或者 dlib 的 detector 对侧脸和小脸不敏感。解决:先换一张清晰正面图用 test.py 验证,确认模型本身没问题。然后调detector(gray, 1)把第二个参数从 0 改成 1,表示上采样一次,能检测更小的脸,代价是速度变慢。再不行就加一个简单的直方图均衡化gray = cv2.equalizeHist(gray),对夜间红外画面提升明显。
4.4 误报频繁,正常眨眼也触发预警
现象:驾驶员明明睁着眼,系统却不停报警。原因:CONSEC_FRAMES 设得太小,或者 EAR_THRESHOLD 设得太高。解决:先把 CONSEC_FRAMES 调到 20 以上,再把 EAR_THRESHOLD 降到 0.2 左右。更稳的做法是同时判断眼睛和嘴巴,只有两者都满足疲劳条件才报警,这叫「与逻辑」,能大幅降低误报。代价是漏报率会上升,需要根据实际场景权衡。
4.5 打包成 exe 后模型文件找不到
现象:PyCharm 里跑得好好的,用 PyInstaller 打包后提示模型文件不存在。原因:打包后的程序工作目录变了,相对路径失效。解决:用sys._MEIPASS获取临时资源目录,把模型文件和 dat 文件通过--add-data参数打进去。常见写法是:
import sys, os def resource_path(relative): if hasattr(sys, "_MEIPASS"): return os.path.join(sys._MEIPASS, relative) return os.path.join(os.path.abspath("."), relative) model = load_model(resource_path("fatigue_cnn.h5"))逻辑说明:sys._MEIPASS是 PyInstaller 解压临时文件的目录,打包时必须把模型文件声明为数据文件。参数说明:--add-data "fatigue_cnn.h5;."在 Windows 下用分号分隔源和目标,Linux 下用冒号。这个坑不提前处理,答辩现场打不开就尴尬了。
5. 进阶调优与验证:让疲劳判定从「能跑」到「敢演示」
5.1 用 ROC 曲线找最佳阈值,而不是拍脑袋
EAR_THRESHOLD 和 CONSEC_FRAMES 这两个参数,拍脑袋设也能跑,但答辩时老师问「为什么是 0.25 不是 0.3」你就答不上来。正确做法是录一段包含清醒和疲劳两种状态的视频,人工标注每一帧的真实标签,然后跑一遍模型输出,画 ROC 曲线找约登指数最大的点。下面是一个简化的验证脚本:
import numpy as np from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt # y_true: 人工标注,1 表示疲劳,0 表示清醒 # y_score: 模型输出的疲劳概率 y_true = np.load("labels.npy") y_score = np.load("preds.npy") fpr, tpr, thresholds = roc_curve(y_true, y_score) roc_auc = auc(fpr, tpr) print("AUC:", roc_auc) # 找约登指数最大的阈值 youden = tpr - fpr best_threshold = thresholds[np.argmax(youden)] print("最佳阈值:", best_threshold) plt.plot(fpr, tpr, label=f"AUC = {roc_auc:.2f}") plt.plot([0, 1], [0, 1], "k--") plt.xlabel("False Positive Rate") plt.ylabel("True Positive Rate") plt.legend() plt.savefig("roc_curve.png")逻辑说明:AUC 越接近 1 说明模型区分能力越强,低于 0.8 就要考虑换模型或补数据。约登指数最大的点就是「真正率减假正率」最大的阈值,兼顾了灵敏度和特异度。参数说明:labels.npy 和 preds.npy 需要你自己采集和生成,这是写论文时最有说服力的实验数据。如果 AUC 只有 0.6 左右,别急着调阈值,先检查标注是否准确、裁剪区域是否对齐。
5.2 把单帧判定改成时序平滑,减少玄学抖动
单帧 CNN 输出天然会抖,这一帧判疲劳下一帧判清醒,体验很差。常见做法是加一个滑动窗口,比如取最近 10 帧的平均值再和阈值比较。更进阶一点可以用 LSTM 对时序特征建模,但对毕设来说属于过度设计。滑动窗口的代码很简单:
from collections import deque window = deque(maxlen=10) # 保存最近 10 帧的预测值 def smooth_predict(pred): window.append(pred) return sum(window) / len(window) # 返回滑动平均 # 在主循环里用 smooth_predict 替代原始 pred smoothed = smooth_predict(pred) if smoothed > 0.6: counter += 1逻辑说明:deque 的 maxlen 参数自动丢弃旧数据,不用手动维护索引。参数说明:窗口长度 10 是经验值,太小平滑效果不够,太大响应延迟明显。如果摄像头帧率是 30fps,10 帧大约 0.33 秒,人感觉不到延迟。这个改动虽小,但演示时的观感提升非常明显,属于性价比最高的优化。
5.3 验证方法:用录屏回放代替真人测试
答辩前反复找同学坐摄像头前测试,效率低还不稳定。更靠谱的做法是提前录几段视频:一段正常驾驶、一段模拟打哈欠闭眼、一段低头看手机。然后用cv2.VideoCapture("test_video.mp4")替代摄像头索引,跑完整流程,统计报警次数和漏报次数。这样每次改参数都能快速回归,不用求人配合。我一般会准备三段各 30 秒的视频,覆盖白天、夜间和侧脸场景,跑一遍只要两分钟,比真人测试快十倍。
从那以后我每次拿到这类毕设包,都强制先跑 test.py 单图验证,再跑录屏回放,最后才开摄像头。这个顺序能帮你把环境问题、模型问题和硬件问题彻底分开,省下的时间够你把论文的实验章节多写两千字。希望帮到你。
本文还有配套的精品资源,点击获取