
简介面向计算机相关专业期末大作业与毕设场景的基于YOLOv5的人物专注性检测系统源码包主要解决学习场景下的疲劳检测与分心行为检测问题适合正在做课程设计、毕业设计或需要Python项目实战练习的学生。项目源自大三高分期末设计经导师指导并获99分代码完整可运行。资源包共64个文件以20个Python源码、18个YOLOv5模型配置文件和17个预编译pyc文件为核心另含训练好的模型权重、人脸关键点检测模型、UI界面文件、演示视频和使用说明压缩包约110.69MB。系统通过人脸关键点检测与YOLOv5目标检测可追踪面部状态和身体姿态进而判断人物专注度目录结构清晰检测模块、界面模块与主程序分离便于快速启动和二次开发并提供可视化界面方便操作。目前已吸引113人学习或下载学习者可通过演示视频直观了解效果并借助使用说明快速跑通流程也可基于现有代码进一步扩展疲劳判断与分心行为识别的逻辑是较完整的实战型项目资料。1. 用 YOLOv5 做专注性检测先搞清楚它到底在检测什么很多人拿到这个课题的第一反应是“用 YOLOv5 去识别专注状态”但这个思路其实从根上就偏了。YOLOv5 是目标检测器它的输出是“某个物体在画面的什么位置”而不是“这个人现在专注不专注”。真正可行的做法是用 YOLOv5 把人脸、眼睛、嘴巴、人手这些与专注状态强相关的目标先框出来再基于检测框的位置、尺寸和时序变化用规则或轻量分类器去推断专注性。这个课题里的“人物专注性检测”本质上是目标检测加行为判定的两段式系统。疲劳检测依赖的是眼睛开合度、打哈欠频率分心行为检测依赖的是头部朝向、手部位置、视线是否偏离屏幕或路面。这套方案的落地难点不在模型结构而在三个地方其一是数据集的组织方式这个课题的检测目标和 COCO 那 80 类完全不重合得自己标注或者合成数据其二是疲劳和分心这类状态没有统一的客观标准边界需要自己定义其三是检测结果必须做时序平滑单帧的漏检和误检如果不处理行为判定根本没法看。本文用 YOLOv5 作为检测主干把数据准备、模型训练、疲劳与分心判定、推理端工程化这条链路完整走一遍适合准备做期末项目或者入门目标检测加行为分析的同学参考。2. 检测目标分析与 YOLOv5 训练数据准备2.1 先定义检测目标哪些框是专注性判定的输入YOLOv5 需要检测的目标类别决定了后续所有行为判定的可能性。疲劳检测需要眼睛和嘴巴的状态所以检测目标至少包含closed_eye闭眼和open_eye睁眼两类或者退一步只检测eye和mouth把开闭状态交给后续分类网络。分心行为检测需要知道人的头部朝向和手部位置所以还需要face、hand这两个类别。如果场景是课堂或工位监控可以再加phone类别用于识别玩手机行为。我的建议是不要直接采用二分类fatigue/normal做端到端训练因为专注状态是一种时序行为单帧图像里“闭眼”和“正常”之间的视觉差异很大但“专注”和“走神”之间的差异非常模糊目标检测器学不好这种边界。把检测目标限定在眼睛、嘴巴、人脸、手部这些物理可见的对象上模型训练难度低后续判定逻辑的灵活性也高。2.2 自建数据集的标注策略与工具选型这个课题没有现成的公开数据集可以直接用来检测“闭眼”和“打哈欠”所以大概率要自建数据。采集数据时要注意覆盖不同角度、不同光照、不同人脸尺寸否则模型在真实场景下泛化能力会很差。标注工具用 LabelImg 或者 Labelme 都行LabelImg 输出的是 YOLO 格式的 txt 文件每行是类别id x_center y_center width height坐标都归一化到 01 之间和 YOLOv5 的训练格式直接对齐。标注类别建议用一个数字映射表管理类别 ID名称对应行为判定0face头部位置计算1open_eye疲劳判定的负样本2closed_eye疲劳判定的正样本3mouth_open打哈欠判定4mouth_closed正常口部状态5hand分心行为判定这里有一个细节需要注意open_eye和closed_eye如果作为两个独立类别标注标注时的工作量会增加一倍但训练效果比只标一个eye类别再接入分类网络更好。原因在于 YOLOv5 的多类别检测头本身就能学到睁眼和闭眼之间的细粒度差异省掉一个额外的分类模型。不过如果采集到的闭眼样本太少还是退回到只检测eye后面用 EAR 算法算开合度更稳妥。2.3 数据增强与类别不平衡处理YOLOv5 自带的 mosaic 增强、随机仿射变换、HSV 扰动对这个小数据集场景很有用。Mosaic 增强会把四张图拼成一张等于变相扩大了 batch 中的目标数量对小目标眼睛、嘴巴的检测提升很明显。训练配置里对应的三个参数是hsv_h、hsv_s、hsv_v默认值就是 0.015、0.7、0.4基本不用动。类别不平衡是这个课题最头疼的问题。睁眼和正常闭口的样本数量会远大于闭眼和打哈欠样本。有两种处理方式第一种是在数据集层面做重采样把闭眼和打哈欠的图片复制几份再训练第二种是在损失函数层面调cls_pw参数类别权重YOLOv5 的cls_pw默认全是 1.0可以改成cls_pw: 0.5或更大值来提升稀有类别的损失贡献。实操中我一般会先重采样再看验证集的 PR 曲线决定要不要动cls_pw。3. 用 YOLOv5 训练部署你自己的检测模型3.1 环境配置与项目结构YOLOv5 的环境配置在热词里是高频问题很多人卡在 CUDA 版本和 PyTorch 版本的匹配上。YOLOv5 官方仓库对 PyTorch 的版本要求不严1.8 到 2.x 都能跑但 CUDA 必须和 PyTorch 的编译版本一致。建议直接用conda create -n yolo python3.9创建干净环境然后执行pip install torch1.13.1cu117 torchvision0.14.1cu117 -f https://download.pytorch.org/whl/torch_stable.html git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这段命令的执行顺序很关键。-f参数指定了 PyTorch 的 CUDA 11.7 版本索引这样可以保证装到的 torchvision 和 torch 是配套构建的避免出现libcudart.so找不到之类的链接错误。装完后用python -c import torch; print(torch.cuda.is_available())验证 GPU 是否可用输出True再继续。3.2 数据集配置文件与训练命令YOLOv5 训练一个自定义数据集需要准备两个.yaml文件一个是数据集描述文件一个是模型超参数文件。数据集文件放在yolov5/data/目录下核心内容是路径和类别列表path: ../datasets/focus_detection # 数据集根目录 train: images/train # 训练集图片目录 val: images/val # 验证集图片目录 nc: 6 # 类别数量 names: [face, open_eye, closed_eye, mouth_open, mouth_closed, hand]path字段支持绝对路径和相对路径相对路径是相对于yolov5项目根目录计算的所以写../datasets/focus_detection表示数据集在 yolov5 的上层目录。train和val指向的是图片所在目录YOLOv5 会自动在同级目录下找对应的labels文件夹不需要在配置文件里单独写标注路径。训练命令是python train.py --data focus_detection.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 100 --device 0--weights参数可以选择yolov5s.pt或者不传传了会加载 COCO 预训练权重做迁移学习训练收敛速度会快很多。--img是训练时输入图像的尺寸640 是精度和速度的平衡点如果检测目标眼睛太小可以改成 960但训练时间和显存占用都会上升。--batch的大小取决于显存16GB 显存跑yolov5s默认结构用 16 没问题显存不够就减半。3.3 训练过程中的关键指标与断点恢复训练日志里第一优先看的是P精确率、R召回率和mAP50。如果mAP50在某个 epoch 突然掉点可能是学习率设置过高导致 loss 震荡。YOLOv5 默认使用余弦退火调度器前 3 个 epoch 是 warmup学习率从接近 0 逐渐爬升到设定值后面再慢慢衰减。训练中断时用--resume参数接续python train.py --resume runs/train/exp/weights/last.ptlast.pt保存的是最近一个 epoch 的完整权重包括优化器状态所以中断恢复后学习率调度也能接上。建议每训练完 10 个 epoch 就看一下runs/train/exp/results.png里的 loss 曲线如果val/box_loss和val/cls_loss还在持续下降就没有过拟合不用急着加早停。3.4 模型导出把 PyTorch 权重转成推理可用的格式训练好的best.pt可以直接用 PyTorch 推理但部署到摄像头实时检测场景时一般会转成 TensorRT 的 engine 格式或者 ONNX 格式推理速度能提升 2 到 5 倍。导出命令python export.py --weights runs/train/exp/weights/best.pt --include onnx --opset 12注意--opset参数设为 12 是兼容性最稳的选择opset 版本太高在旧版 ONNX Runtime 上会报不支持的算子错误。导出的best.onnx可以用onnxruntime直接跑推理代码里只需要加载 session 再喂入预处理后的图像张量。4. 疲劳检测核心基于开闭眼状态与 PERCLOS 的判定4.1 单帧特征用检测框宽度高度比代替 EAR网上常见的疲劳检测方案都基于 dlib 的 68 个关键点计算 EAR眼睛纵横比但这个方案需要一个独立的人脸关键点模型和 YOLOv5 的目标检测输出是两个系统。这里介绍一种更贴近本项目技术栈的方案直接用 YOLOv5 输出的open_eye和closed_eye检测框的置信度差来判定当前眼睛状态。具体的判定逻辑是当一帧图像中同时出现open_eye和closed_eye的检测框时比较两者的置信度高置信度的类别胜出。如果只有单一类别的检测框则直接采纳该类别。为了避免单帧闪烁可以取最近 5 帧的判定结果做投票3 票以上是同一个状态才确认。这个方案的好处是不需要引入额外的关键点模型坏处是当头部偏转角度过大导致眼睛检测不到时会产生大量空帧。另一种用检测框几何特征的方案是用face框的宽度和eye框的高度做比值比如eye_aspect eye_height / eye_width睁眼时该比值大于 0.25闭眼时明显小于 0.2。这个方案要求eye框定位准确实际体验下来不如置信度投票方案稳定原因在于 YOLOv5 的框不是为关键点设计的框的边界对轻微姿态变化太敏感。4.2 时序判定PERCLOS 与连续闭眼帧数单帧的眼睛状态没有意义疲劳是一个时序累积过程。工程上最常用的两个指标是 PERCLOS 和连续闭眼帧数。PERCLOS 的定义是在一个时间窗口内眼睛闭合帧数占总帧数的比例学术界公认的阈值为 0.4即在一分钟内闭眼时间超过 40% 判定为疲劳。但实际场景中这个阈值太高建议在调试时从 0.3 开始调参。连续闭眼帧数的判定逻辑是设置一个滑动窗口例如 30 帧如果这个窗口内闭眼帧数超过 15 帧就触发一次疲劳事件。这个指标对“短暂低头看桌面”和“真的闭眼”的区分能力比 PERCLOS 好因为低头时眼睛检测框会短暂消失而闭眼时closed_eye的检测框持续存在。下面给出一个用帧缓存做状态判定的伪代码框架class FatigueDetector: def __init__(self, eye_close_thresh0.5, window_size30): self.eye_close_thresh eye_close_thresh # 闭眼帧占比阈值 self.window_size window_size # 判定窗口大小单位帧 self.frames [] # 缓存最近 window_size 帧的眼睛状态 def update(self, eye_state): # eye_state: 1 表示闭眼0 表示睁眼 self.frames.append(eye_state) if len(self.frames) self.window_size: self.frames.pop(0) close_ratio sum(self.frames) / len(self.frames) fatigue_event close_ratio self.eye_close_thresh return fatigue_event, close_ratiowindow_size的设计依赖帧率。摄像头如果是 30 FPS30 帧就是 1 秒的窗口闭眼持续超过 0.5 秒就会触发事件。如果摄像头是 15 FPS同样的窗口只覆盖 2 秒要相应调大阈值。打哈欠检测可以用mouth_open框的持续帧数判断连续 10 帧以上检测到张嘴且张嘴面积变化超过 20%判定为一次打哈欠事件。4.3 疲劳检测的常见误判场景与处理戴眼镜是最大的误判来源。镜框遮挡会导致open_eye检测置信度下降closed_eye的置信度反而上升最终系统把睁眼误判为闭眼。处理方式是在标注阶段专门采集几组戴眼镜的数据加入训练集或者在后端逻辑里检测到face框内同时存在镜框特征时把闭眼置信度阈值从 0.5 上调到 0.7。第二个常见误判是手遮住眼睛。用户在揉眼睛或托腮时手部框和眼睛框会重叠YOLOv5 的 NMS 会把低置信度的open_eye框抑制掉导致该帧被判为闭眼。这种场景没法从单帧解决只能靠时序逻辑如果闭眼帧之后紧接着出现hand框大面积覆盖face框就把刚才的闭眼事件标记为“疑似遮挡”不参与疲劳统计。5. 分心行为检测头部姿态与手部位置联合判定5.1 分心行为的定义与场景建模分心行为没有统一的视觉定义必须先给场景划边界。如果检测场景是驾驶分心包括低头看手机、视线偏离前方、单手离开方向盘如果检测场景是课堂或工位分心包括低头、趴桌、玩手机、频繁转身。这个课题是“人物专注性检测”大概率面向学习或办公场景。无论哪种场景分心行为检测的技术路径都分三步头部姿态估计、手部位置定位、行为规则匹配。注意这里不重新训练姿态估计网络而是从 YOLOv5 的face框形状反推粗粒度头部姿态。5.2 基于人脸框几何的头部姿态估计YOLOv5 输出的face框是一个矩形包含框的左上角坐标 (x, y)、宽度 w、高度 h。当人脸正对摄像头时face框的宽高比接近 0.75当人脸向左右转动时宽度会变小宽高比降低当人脸低头或抬头时高度会变小且框的中心点相对画面中线的偏移会增大。所以可以定义一个简单的人脸偏转指数yaw_ratio face_width / (face_height * 0.75)当yaw_ratio 0.6时认为头部大幅度左偏或右偏低头检测则看face框在画面中的垂直位置是否持续下移同时eye框是否消失。这个方案的误差比较大不能精确定量角度但作为分心/专注的二分类输入是足够的。精度要求更高的场景可以改用face框的中心坐标和面积做卡尔曼滤波跟踪减少抖动。5.3 分心行为的规则判定在检测到hand框的前提下结合face框的相对位置做积分判定def classify_distraction(face_box, hand_boxes, frame_w, frame_h): face_cx (face_box[0] face_box[2]) / 2 face_cy (face_box[1] face_box[3]) / 2 face_area (face_box[2] - face_box[0]) * (face_box[3] - face_box[1]) hand_on_face False for hand_box in hand_boxes: hand_cx (hand_box[0] hand_box[2]) / 2 hand_cy (hand_box[1] hand_box[3]) / 2 dist ((face_cx - hand_cx) ** 2 (face_cy - hand_cy) ** 2) ** 0.5 if dist face_area ** 0.5: hand_on_face True break if hand_on_face: return hand_on_face # 托腮或遮挡面部 top_region_y frame_h * 0.3 if hand_boxes and all(b[1] face_box[1] for b in hand_boxes): return hands_raised # 举手或脱离正常操作区域 return normal这段代码的判定逻辑是先算出face框的中心点和面积然后遍历所有hand框计算手和脸的欧氏距离如果距离小于人脸框边长的平方根就判定为手在面部区域。hands_raised的判定条件是手部框的左上角 y 坐标全部小于脸部框的顶边坐标表示手部已经脱离工作区。两个规则都用的是相对位置关系不依赖绝对像素坐标对不同分辨率的摄像头有比较强的鲁棒性。分类结果同样需要滑动窗口做时间滤波不然手部偶尔划过画面会触发误报。5.4 分心检测的误报控制分心行为检测最大的坑是“打哈欠被当成低头”因为打哈欠时嘴巴张开、头部上仰、面部框高度变化和低头动作在单帧上非常相似。解决办法是引入mouth_open框如果mouth_open置信度高于 0.6优先进入疲劳检测分支而不是分心检测分支。另一个场景是“看手机”被误判为“托腮”这两者的手部位置都在面部附近但看手机时头部会明显下压face框中心点的 y 坐标会产生对值大于 30 像素的位移。在判定逻辑里加入这个位移条件误报率会下降很多。6. 推理端优化技巧检测帧率提升与稳定判定输出6.1 图像预处理与检测区域裁剪摄像头的输入分辨率通常是 1920x1080 或 1280x720YOLOv5 推理前会做 letterbox 缩放。如果全帧直接缩放跑推理yolov5s在 640x640 输入下大约能跑 45 到 60 FPS看起来够用但人物在画面中通常只占很小区域眼睛和手这些关键目标在缩放后已经小于 16x16 像素检测精度下降明显。更好的做法是第一帧先用全帧检测face后续帧只在face框周围扩大 1.5 倍的 ROI 区域做推理相当于把“整图检测”切成“局部放大检测”。代码上用 OpenCV 切出 ROI 再送入模型roi frame[max(0, y1-pad):y2pad, max(0, x1-pad):x2pad] results model(roi, size320) # 将检测结果坐标映射回原图 results.xyxy[0][:, :4] [x1-pad, y1-pad, x1-pad, y1-pad]ROI 检测的输入分辨率可以设成 320 而不是 640因为目标物在 ROI 中的相对尺寸变大了低分辨率也能保持检测精度但推理速度可以提升 50% 以上。6.2 检测结果平滑与事件延迟目标检测的逐帧输出带有随机性同一个姿势可能这一帧检测到closed_eye下一帧就丢了。需要用滑动窗口做平滑频率一般是每 5 帧输出一次稳定状态。事件延迟的最小值是窗口长度除以帧率比如窗口 5 帧、帧率 30延迟就是 167ms行为判定可以接受如果把窗口拉长到 15 帧延迟会到 500ms已经不适合做实时告警。再补一个小技巧把conf_thres从默认的 0.25 上调到 0.35 或 0.4能显著减少hand类别的误检但closed_eye的召回率也会下降。实际调试时按照“优先保证 closed_eye 检出再过滤 hand 误报”的顺序分两个检测分支分别设置不同的阈值。最后把检测框渲染时的颜色按类别区分闭眼用红色、手部用黄色、正常用绿色演示视频的输出会直观很多。6.3 演示视频录制的推荐做法项目交付时“演示视频”和“源码”是两个独立产物源码里要写一个run_demo.py脚本读取视频流、跑推理、叠加行为标签后输出带 UI 的视频文件。推荐的做法是把疲劳事件和分心事件分别显示在画面左上角和右上角底部用进度条展示连续闭眼帧占比。视频编码用 H.264 的 MP4 格式帧率 25 FPS分辨率 1280x720。编码参数直接用cv2.VideoWriter_fourcc(*mp4v)就能满足要求不用额外安装 FFmpeg。注意录制时不要开实时的验证集随机增强保持输出画面的稳定性。本文还有配套的精品资源点击获取