简介基于PyQt5、YOLOv5与Dlib的驾驶员行为监控系统课程设计面向计算机视觉、深度学习及桌面应用开发学习者适合作为课程设计、毕业设计或项目实战的完整参照。系统可实时检测闭眼、张嘴、低头等危险驾驶行为并触发预警覆盖模型加载、视频流处理到界面展示的完整链路。资源共105个文件压缩包224.11MB主体包含66个Python源码、4个ui界面文件、2个YOLO配置yaml、1个PyTorch权重pt以及Dlib人脸关键点模型dat、6个mp3提示音、2个bat启动脚本和若干json/xml/csv配置便于直接运行与二次开发。目前已有272人学习。通过该项目可掌握PyQt5界面搭建、YOLOv5目标检测与Dlib人脸关键点联合推理、OpenCV实时视频处理、危险行为判定与警报触发等关键技术并对工程化目录组织方式形成直观认识。1. 驾驶员行为监控系统为什么非 PyQt YOLOv5 Dlib 不可做驾驶员行为监控最尴尬的不是模型精度不够而是你有一套能跑的检测逻辑却拿不出一个像样的界面给老师或甲方演示。课程设计卡到这一步的人我见过太多OpenCV 弹个窗口就算交差摄像头一关程序就崩疲劳检测的阈值调了一下午还是乱报。这套基于 PyQt YOLOv5 Dlib 的组合恰恰是把「能跑通的算法」和「能交付的系统」之间的缺口补上了——YOLOv5 负责认出驾驶员的手、脸和手机Dlib 负责在脸上精确定位眼睛嘴巴算出疲劳指标PyQt 负责把这一切变成一个人能操作的图形界面。它解决的是三类人的实际诉求做课程设计需要答辩演示的学生、想快速搭出预警原型的工程师、以及想验证「检测关键点规则判定」这套传统架构还能不能用的技术选型者。下面按我自己的落地顺序把整条链路拆开讲。2. 先理顺方案再写代码三驾马车的分工、选型与整体框架2.1 YOLOv5 干什么、Dlib 干什么别把活压给一个模型很多第一次做这个题目的人最容易犯的错是想让 YOLOv5 一个模型把所有事全干了——检测手、检测手机、检测眼睛睁开还是闭上、检测嘴巴是不是在打哈欠。YOLOv5 确实能检测「眼睛区域」和「嘴巴区域」但它给的是矩形框框里眼睛到底是睁是闭靠分类能勉强做但要做眨眼频率、PERCLOS眼睛闭合时间占比这种连续帧的时序指标纯靠检测框的类别置信度去猜稳定性很差。真实场景里驾驶员戴墨镜、光线从侧面打过来、手挡在脸前检测框会抖基于框的判定就跟着翻车。Dlib 在这个系统里的定位是补上「精细关键点」这块短板。它的人脸 68 点检测模型虽然老但在眼睛和嘴巴周围的关键点定位上非常稳而且算的是几何距离比值不依赖纹理和光照。我用 Dlib 对 YOLOv5 检出的脸部框做关键点提取再算眼睛纵横比EAR、嘴巴纵横比MAR这样疲劳判定就有连续的数值曲线可以设阈值而不是看单帧的分类概率。两套模型的负载边界要划清楚YOLOv5 只负责「找出人脸、人手、手机」这些语义物体Dlib 只负责「在脸上找眼睛嘴巴」各干各的互不抢活。否则你在 GPU 上能跑换到 CPU 机器直接卡成幻灯片。2.2 一条视频帧的完整流水线从摄像头到界面的数据流先看一条帧从采集到显示经过哪些环节再写代码就不会东一榔头西一棒子。摄像头采集到 640x480 的 BGR 帧后先送 YOLOv5 推理拿到人脸框、手机框、人手框然后把最大的人脸框裁出来缩放后送 Dlib 关键点检测Dlib 返回 68 个点的坐标后程序计算左右眼的 EAR 和嘴巴的 MAR最后把这些数值加进一个滑动窗口用规则判断当前是正常、疲劳还是分心。PyQt 界面在这个流程里扮演的是「终点站」。检测线程算完结果后通过信号槽把标注好的帧和判定状态发给主界面刷新显示。这里有个性能关键点检测和关键点计算必须放在后台线程主线程只负责把收到的帧转成 QImage 画到 QLabel 上以及渲染告警状态。如果让 PyQt 主线程去跑 YOLOv5 推理界面会卡死到系统弹「无响应」——这不是代码 bug是线程模型错了。2.3 技术选型对比为什么不是 MediaPipe / OpenPose / 纯 OpenCV拿这套组合去跟现如今的 MediaPipe 比MediaPipe 的人脸关键点有 468 个精度和鲁棒性都比 Dlib 好还自带眨眼检测和视线估计。但你如果做课程设计用 MediaPipe 意味着答辩时很难讲清楚底层原理因为它是封装好的黑匣子老师问「关键点怎么配准的」你就卡住了。Dlib 的 68 点检测是经典算法网上资料一抓一大把论文对应关系也清楚从讲解和答辩角度反而省心。跟 OpenPose 比OpenPose 适合做全身骨骼点用在驾驶员行为监控上属于杀鸡用牛刀模型体积大、推理慢、CPU 上跑不动。跟纯 OpenCV 传统图像处理比比如用肤色检测找手、用模板匹配找手机那种方案在实验室固定背景下勉强能跑换个光线就废了连课程演示都撑不过。我的建议很直接课程设计和中小型项目选 YOLOv5 Dlib 是「安全牌」不是因为它最好而是因为每个环节都有大量现成资料和可复现的调参经验踩坑了你知道去哪找答案。真要做工业级产品再考虑换更重的方案。3. 用 YOLOv5 训练驾驶员行为检测模型从 COCO 权重到自建数据3.1 准备驾驶员行为数据集类别定义与标注规范YOLOv5 官方权重是在 COCO 数据集上训的COCO 里有 person、cell phone 这些类别但它识别的是「图片里有没有手机」不是「驾驶员正在使用手机」。所以你必须用自己的数据微调。类别别贪多我建议从 4 类起步face人脸、hand手、phone手机、smoke抽烟。如果你只想先跑通甚至可以砍到 3 类把 smoke 去掉。数据来源用手机对着驾驶位拍视频或者找公开的驾驶行为数据集抽帧。抽帧间隔按 5 到 10 帧抽一张保证同一动作有多角度样本。标注工具用 labelimg存成 YOLO 格式的 txt——每行是「类别id 中心点x 中心点y 宽 高」坐标都归一化到 0 到 1。标注有个容易踩的坑手和手机重叠时两个框都要标而且手机框要完整包住手机不要因为被手挡住就只标一半。YOLOv5 的 mosaic 增强会随机裁剪拼接图片如果你的标注框边缘不干净增强后模型会学到错误边界。质量比数量重要2000 张精标数据比 8000 张粗标数据训出来的模型更可靠。3.2 用 conda 搭建 YOLOv5 环境并下载官方权重环境配置是劝退最多人的环节大部分报错不是代码问题是 PyTorch 版本和 CUDA 不匹配。我用的是 Python 3.8 PyTorch 1.12 CUDA 11.3 的组合稳定跑通 YOLOv5 v6.0 到 v7.0 的所有版本。conda create -n yolov5 python3.8 conda activate yolov5 # 先装 PyTorchcudatoolkit 版本和驱动要匹配 conda install pytorch1.12.0 torchvision0.13.0 cudatoolkit11.3 -c pytorch git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt # 下载 yolov5s 官方权重后面微调要用它做初始权重 python -c from utils.downloads import attempt_download; attempt_download(yolov5s.pt)这段命令里conda 单独建环境是为了隔离依赖不污染你机器上的其他 Python 项目。PyTorch 必须先于 requirements.txt 安装因为 requirements 里的 torch 版本是 CPU 版装错了后面训练慢到没法用。yolov5s 是速度和精度的平衡点显存 4G 以上的显卡都能跑没有独显就用 CPU 训练但要把模型换成 yolov5nepoch 减少到 50 个。3.3 组织数据集目录并修改 yaml 配置YOLOv5 训练要求数据集目录按固定结构组织你手搓一个不规范的目录训练时找文件要报一堆 FileNotFoundError。标准结构如下dataset/ images/ train/ # 训练集图片 val/ # 验证集图片 labels/ train/ # 与图片同名的 txt 标注文件 val/图片和标注文件的文件名要一一对应比如 img_001.jpg 对应 img_001.txt。划分比例按 8:2 或 9:1验证集不要混入训练集图片否则 mAP 虚高你看着指标挺好上真机就露馅。然后在 yolov5 目录下建一个 data.yaml 指向你的数据集# data.yaml train: /absolute/path/to/dataset/images/train val: /absolute/path/to/dataset/images/val nc: 4 names: [face, hand, phone, smoke]train 和 val 建议写绝对路径因为训练脚本里相对路径容易因为工作目录不同而错乱。nc 是类别数必须和 names 列表长度一致。这里写错不会报错但训练出来的模型预测类别会串位属于排查起来很隐蔽的低级错误。3.4 调参与训练yolov5n/s 选型与关键超参数模型选型上显存 6G 以下用 yolov5n6G 到 12G 用 yolov5s显存足够但想要更高精度就上 yolov5m。驾驶员监控是实时场景帧率优先我用 yolov5s 在 1080Ti 上推理大概 5ms 一帧完全够用。python train.py --img 640 --batch 16 --epochs 100 \ --data data.yaml --weights yolov5s.pt \ --hyp data/hyps/hyp.scratch-low.yaml --device 0--img 640 是输入分辨率不要随意调大图片越大显存占用越高而且小目标远处的手机提升有限。--hyp 参数文件里我一般把 mosaic 保持默认 1.0因为驾驶员行为数据里手和手机经常重叠mosaic 增强能提升遮挡场景的鲁棒性把 hsv_h、hsv_s 调高一点模拟不同光线环境。训练中盯着 loss 曲线train loss 下降val loss 也跟着下降说明模型在正常学习val loss 不降反升就是过拟合调大 --epochs 没用应该加数据增强或提前停止。3.5 导出与测试把 PT 权重在摄像头画面上跑起来训练完成后weights 目录下会生成 best.pt 和 last.pt。先用 detect.py 在图片和视频上验证效果再上摄像头python detect.py --weights runs/train/exp/weights/best.pt \ --source 0 --conf-thres 0.4 --iou-thres 0.45--source 0 是调用本机摄像头--conf-thres 0.4 表示置信度低于 40% 的框不显示。这个阈值很关键设太低会出大量误检设太高会漏检。实际测试时手机这类小目标置信度普遍偏低0.4 是个折中值。如果摄像头画面里手机经常检不出来把 conf-thres 降到 0.25 再看还不行就得补数据不要靠调阈值硬撑。4. 用 Dlib 算疲劳特征EAR、MAR 与 PERCLOS 的完整计算4.1 68 点人脸关键点模型下载与初始化Dlib 官方提供两个预训练模型人脸检测器和 68 点关键点检测器。人脸检测器是 HOG 线性分类器CPU 上跑得飞快但对手势遮挡和侧脸鲁棒性一般。我实际用的时候用的是 YOLOv5 检出的人脸框直接喂给关键点模型Dlib 的人脸检测器只在 YOLOv5 没检出人脸时的兜底。import dlib # 初始化关键点检测器dat 文件从 dlib 官网下载约 99MB predictor_path shape_predictor_68_face_landmarks.dat predictor dlib.shape_predictor(predictor_path) # 直接把 YOLOv5 的人脸框转成 dlib.rectangle face_rect dlib.rectangle(int(x1), int(y1), int(x2), int(y2)) shape predictor(frame, face_rect) # shape.part(i) 拿到第 i 个关键点的坐标代码里最关键的是 dlib.rectangle 的参数left、top、right、bottom。YOLOv5 输出的坐标是 float直接传给 dlib 会报类型错误必须转成 int。另外YOLOv5 检出的脸框有时会超出图像边界裁剪前要做 clamp不然 dlib 会抛异常。我一般都把脸框向外扩 10% 再裁剪这样能保证眼睛和嘴巴完整落在框内关键点定位更稳。4.2 EAR 眨眼检测公式与阈值选定EAREye Aspect Ratio是疲劳检测里最经典的几何指标原理是眼睛在睁开和闭合时纵向距离和横向距离的比值变化明显。68 点中左眼是 36 到 41 号点右眼是 42 到 47 号点用这些点的坐标就能算from scipy.spatial import distance as dist def eye_aspect_ratio(eye_points): # 计算眼睛的纵向距离 A 和 B A dist.euclidean(eye_points[1], eye_points[5]) B dist.euclidean(eye_points[2], eye_points[4]) # 计算眼睛的横向距离 C C dist.euclidean(eye_points[0], eye_points[3]) # EAR (A B) / (2 * C)眼睛闭合时 EAR 趋近于 0 ear (A B) / (2.0 * C) return ear公式本身不复杂但阈值和判定逻辑才是血泪经验所在。正常睁眼时 EAR 大概在 0.25 到 0.35 之间闭眼时低于 0.15。我一般设 0.2 作为临界值连续三帧都低于 0.2 才判定一次闭眼。单帧低于阈值不算因为眨眼过程中 EAR 本来就会瞬间掉下去你要区分「正常眨眼」和「闭眼」就得靠持续帧数。PERCLUS 的计算方式是在一个时间窗口内统计闭眼帧数占总帧数的比例比如 30 秒窗口内闭眼帧占比超过 40%就判定为疲劳。这个指标比单次眨眼更抗干扰但要注意检测帧率不稳定会影响 PERCLOS 的准确性。帧率 15fps 和 30fps 下同一段闭眼视频算出的 PERCLOS 差很多所以要把闭眼时间换算成秒数再算占比不要直接数帧。4.3 哈欠检测 MAR 与头部姿态估计哈欠检测和眨眼检测思路一样用的是嘴巴纵横比 MAR。68 点中嘴巴外轮廓是 48 到 59 号点我实际用 48 到 67 号全部嘴部点算因为只算外轮廓在张嘴大时容易受嘴唇纹理干扰def mouth_aspect_ratio(mouth_points): # 嘴部纵向距离上唇内点 51 到下唇内点 57以及 53 到 59 A dist.euclidean(mouth_points[2], mouth_points[10]) B dist.euclidean(mouth_points[4], mouth_points[8]) # 嘴部横向距离嘴角 48 到 54 C dist.euclidean(mouth_points[0], mouth_points[6]) mar (A B) / (2.0 * C) return mar正常闭嘴时 MAR 在 0.2 左右张大嘴打哈欠时会超过 0.5。我用 0.5 作为阈值持续 5 帧以上才算一次哈欠。哈欠和说话都会让 MAR 升高区别在于说话时 MAR 是快速波动的哈欠时 MAR 会保持在高位至少 1 秒。所以判定哈欠不能只看瞬时值要看持续时长。头部姿态估计是 Dlib 另一个值钱的功能。68 个关键点配合 OpenCV 的 solvePnP可以估算出驾驶员头部的俯仰角和偏转角用来判断驾驶员是否在长时间低头看手机或者左顾右盼。实现有点绕核心是把 68 个点中选几个稳定的点鼻尖 30 号、下巴 8 号、左眼外角 36 号、右眼外角 45 号和 3D 人脸模型上的对应点做配准解算。这个做出来答辩时很加分但如果你时间不够先把 EAR 和 MAR 做扎实就行。4.4 疲劳打分规则怎么组合阈值减少误报只靠单一指标做判定误报率会让你怀疑人生。比如驾驶员说话时会频繁触发哈欠判定戴墨镜时 EAR 基数整体偏低。我把多路指标组合成打分制事件类型判定条件单次评分闭眼EAR 0.2 持续 3 帧2哈欠MAR 0.5 持续 5 帧2低头头部俯仰角 -15 度持续 2 秒3使用手机YOLOv5 检出 phone 类且置信度 0.55手离开方向盘检出 hand 类但方向盘区域无手1每 30 秒统计一次总分超过 8 分触发一级告警超过 12 分触发二级告警。这个规则表和阈值不是一次写出来的我在实际测试中反复调了三轮才稳定。第一轮只有闭眼和哈欠漏报严重加了低头和手机检测后误报变多最后把「使用手机」权重提到 5 分同时要求手机框和脸框有重叠区域才判定误报才算压下来。组合规则的核心思想是单个指标可以错但多个独立指标同时指向疲劳状态时置信度就高了。这也是这套架构比端到端黑盒模型好调的地方——指标是白盒的阈值可以逐个调出了问题你能定位到是哪一个环节误报。5. PyQt5 整合别让 UI 卡成幻灯片这三个坑先填平5.1 QThread 做推理线程不要让检测和界面同线程PyQt 整合的最大坑就是把检测逻辑直接写进界面类里。YOLOv5 推理一次 5msDlib 关键点一次 10ms加起来还能接受但这只是理论值。Windows 上 OpenCV 的 VideoCapture 读摄像头本身就有 30ms 到 50ms 延迟再加上图像转换和绘制主线程被拖垮后界面直接无响应。我的做法是把整个检测链路塞进一个继承 QThread 的工作类里通过信号把结果抛给主界面from PyQt5.QtCore import QThread, pyqtSignal import cv2 import torch class DetectThread(QThread): # 定义两个信号一帧处理完成的画面 当前告警状态 frame_ready pyqtSignal(object, str) def __init__(self): super().__init__() self.running True self.model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadFalse) def run(self): cap cv2.VideoCapture(0) while self.running: ret, frame cap.read() if not ret: continue results self.model(frame) # 在这里调用 dlib 关键点检测和疲劳判定 alert self.check_fatigue(frame) self.frame_ready.emit(frame, alert) cap.release() def stop(self): self.running False self.wait()这段代码里最核心的设计是 self.running 标志位。线程退出时先置 False再调用 wait() 等待线程真正结束避免程序关闭时崩溃。frame_ready 信号携带 frame 和告警文本槽函数负责把 OpenCV 的 BGR 帧转成 RGB 的 QImage 再显示到 QLabel 上告警文本切换成红色显示。5.2 帧率上不去先查这三处系统做完帧率只有 10fps别急着怪模型慢先按顺序排查三个地方。第一是 OpenCV 读摄像头的分辨率很多摄像头默认 1280x720 甚至更高我固定设成 640x480减少读图时间和后续缩放开销。第二是 QImage 转换PyQt 的 QImage 构造需要拷贝数据不要在检测线程里反复构造大图转换一次就发一次信号发完就丢。第三是 YOLOv5 的推理尺寸如果 detect.py 里测试用的 640 实际被改成了 1280推理时间会翻三倍以上。一个典型的优化动作是把检测线程的帧间隔做成可配置的比如检测线程用 20ms 的延迟控制推理频率而摄像头线程始终满速抓帧并丢弃旧帧。这样模型推理跟不上采集速度时丢的是旧帧而不是阻塞新帧界面保持流畅。5.3 避坑Dlib 与 OpenCV 的坐标混用做完检测画框时经常遇到框的位置对不上画出来的人脸框偏了半个脸的位置。原因是 OpenCV 的坐标系是 (x, y)Dlib 的 rectangle 是 (left, top, right, bottom)两者混用时经常把 right 当成宽、bottom 当成高而 right 和 bottom 是绝对坐标不是偏移量。正确的裁剪方式是用 right - left 算出宽top - bottom 算出高或者直接用 OpenCV 的 slicing 语法裁原图再传 Dlib。另一个坐标坑是 YOLOv5 对图片做了 letterbox 缩放推理输出坐标是归一化到原图尺寸的不要直接拿这个坐标去索引 numpy 数组要先乘回原图宽高。5.4 避坑模型加载慢和显存占用torch.hub.load 每次启动都要去检查模型缓存网络不稳定时还会卡住。我一般把 best.pt 放在本地路径用 torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadFalse) 指定本地文件并用 force_reloadFalse 避免每次重新下载。如果机器显存只有 2Gyolov5s 跑 640 分辨率可能爆显存换 yolov5n 或者把推理分辨率降到 416。另外模型加载后要调用 model.conf 0.4 这样的属性设置而不是每次推理传参省掉重复配置的开销。5.5 避坑摄像头热插拔和程序退出崩溃程序运行中拔掉 USB 摄像头VideoCapture.read() 会持续返回 False 或者直接抛异常不做处理的后果是界面卡死。我在读帧循环里加了一个计数器连续 50 帧读不到数据就发一个摄像头断开信号主界面弹提示。程序退出时按「停止线程 → 释放摄像头 → 关闭窗口」的顺序走先关窗口再停线程会触发段错误这是 PyQt 多线程最常见的退出崩溃原因。6. 离线视频回放验证与课程设计加分技巧6.1 用一段事故视频离线回放验证告警逻辑系统写完不要直接上摄像头测我习惯先找一段驾驶室监控视频离线跑把告警时间点和 EAR/MAR 曲线打印出来手动确认每个告警的触发依据。你甚至可以给检测线程加一个「录像回放模式」把 VideoCapture 的入参从 0摄像头改成视频文件路径这样每次调阈值不用真人坐那里打哈欠。6.2 模型导出成 TorchScript 的推理提速取舍做课程设计可能用不到但如果后续想把系统往树莓派这类边缘设备上挪YOLOv5 支持把权重导出成 TorchScript 或 ONNX推理速度比原生 PyTorch 快一到两倍。导出命令是 python export.py --weights best.pt --include torchscript onnx。注意导出后 CPU 和 GPU 的推理结果会有微小差异部署前要在目标设备上重新验证一遍阈值。6.3 给课程设计加分的三个小功能一是告警日志。把每次告警的触发时间、告警类型、当时的 EAR/MAR 值和截图保存到本地答辩时展示一份真实的告警记录表说服力远超口头描述。二是统计图表。用 matplotlib 画 30 秒内的 EAR 曲线和 PERCLOS 累计值嵌入到 PyQt 窗口里评审老师能直观看到你「闭眼识别」的算法逻辑。三是分级告警。把「疲劳」和「分心」分开提示疲劳用黄色、分心用红色配合语音提示系统完整性立刻上一个台阶。我自己做这类系统的习惯是先离线验证再上实时先单模块联调再整机集成阈值永远基于统计而不是拍脑袋。这样一步步推下来不止课程设计能顺利收尾就算以后要改成工业级方案这套架构也能顺滑迁移。希望帮到你。本文还有配套的精品资源点击获取