
简介这是一套获导师认可、98分的YOLOv5疲劳驾驶检测识别毕业设计完整源码项目适合计算机视觉方向的学生、开发者及毕业设计使用者围绕眨眼次数Blinks、眼睛闭合程度EAR、眨眼持续时间dura、打哈欠次数Yawning和嘴巴张开程度MAR等核心指标实现疲劳状态多维度判断。资源共93个文件核心包括Python源码31个py、YOLOv5模型配置24个yaml、预训练权重best.pt、面部关键点模型shape_predictor_68_face_landmarks.dat及说明文档压缩包整体约136.48MB。源码结构清晰主入口yolov5.py可直接运行model、utils目录保存YOLOv5相关模块逻辑并附带requirements.txt依赖清单目前已有362人学习使用。对需要快速搭建疲劳驾驶检测原型、理解YOLOv5工程化落地流程的读者来说这套含权重、源码与运行说明的资源能帮助节省模型训练与调参时间直接聚焦检测逻辑与应用集成。1. 疲劳驾驶检测为什么都选YOLOv5从数据集到报警的完整链路跑过几个疲劳驾驶检测项目之后我越来越倾向于一句话这类任务选YOLOv5不是因为它算法最先进而是因为“YOLOv5源码权重文件说明文档”这套组合恰好把从数据集标注、训练调参、权重管理到视频流部署的整条链路都铺好了。疲劳驾驶检测识别的本质是先定位出眼睛闭合、打哈欠、低头这些疲劳状态对应的目标再通过帧间统计把单帧检测结果变成“持续疲劳”的报警信号。这个系统能解决的实际问题是让普通工程师在没有算法团队支援的情况下用现成源码和预训练权重在一两周内训练出自己的检测模型并部署到实时视频流里。适合手里有摄像头、有几十段真实驾驶视频、想把原型快速跑起来的从业者和学生。2. 环境配置先行YOLOv5环境配置与源码目录拆解拿到压缩包最忌讳的就是直接双击train.py。YOLOv5的环境配置看似简单实际坑几乎都集中在版本组合上Python、PyTorch、CUDA三者的匹配关系决定了你是半小时跑通还是两天装环境。我先说结论优先用conda建独立环境Python选3.8到3.10之间PyTorch用官方安装命令自动匹配CUDA别手动拼版本。2.1 用conda创建独立环境PyTorch与CUDA的一次到位常见做法是先把项目解压然后用conda新建一个专用环境。YOLOv5在requirements.txt里列了所有依赖但直接pip install -r requirements.txt的前提是PyTorch已经装对版本。顺序不能反先装PyTorch再装其余依赖。# 解压项目包进入根目录 unzip yolov5_fatigue_detection.zip cd yolov5_fatigue_detection # 新建conda环境指定Python 3.9 conda create -n fatigue python3.9 -y conda activate fatigue # 安装CPU版PyTorch无NVIDIA显卡时用这个 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安装GPU版PyTorch先查一下自己的CUDA版本11.8是常见选择 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 最后安装项目依赖 pip install -r requirements.txt这段命令的逻辑是先把深度学习框架装稳再补YOLOv5需要的opencv-python、pyyaml、matplotlib这些周边库。参数上最容易翻车的是“CUDA版本猜测”如果你的显卡驱动是较新版本直接用cu118或cu121的wheel一般都能跑如果驱动很老反而要退回cu113。判断方法是执行nvidia-smi看右上角的CUDA Version那个数字只要大于等于你装的PyTorch对应版本就行不是要完全相等。提示requirements.txt里没有锁死pandas和matplotlib的具体版本如果后面训练时遇到numpy版本冲突把numpy降到1.23.5再试这是YOLOv5环境配置里最常见的老坑。2.2 源码目录拆解训练、检测、导出三个入口分别在哪项目根目录下的关键文件其实就三个层级train.py是训练入口detect.py是推理入口models/下面yolo.py定义了网络结构。说明文档里一般会告诉你要下载哪个权重文件放到weights文件夹但很多新手拿到的是已经训练好的best.pt直接就能跑检测。# 用自带的权重文件跑一次推理验证环境是否正常 python detect.py --weights weights/best.pt --source data/images/test_driver.jpg --conf-thres 0.5这条命令做的事情是读取指定权重对一张驾驶员的测试图片做前向推理然后把画了边界框的结果输出到runs/detect/目录。--conf-thres参数控制置信度阈值疲劳驾驶场景我习惯设在0.5太高会漏检闭眼这种小目标太低会频繁误报。跑通这一条命令至少证明权重文件、依赖库和模型结构三者兼容后面去动训练脚本才有意义。2.3 说明文档先别扔先核对权重文件是谁训练的YOLOv5项目里的说明文档往往是整个压缩包里被读得最少、却最值钱的文件。我会先看文档里有没有提训练数据集的规模、标注类别数和验证集精度。如果有“mAP0.5: 0.92”这类数字说明这个权重在某个数据集上达到过不错的水平如果文档里只写了“训练了200轮”没写精度那这个权重能不能直接用就得自己验证。我一般建议用5到10张完全没出现过的司机照片先测一遍把边界框画出来的位置和真实疲劳特征对一下比自己信论文里的指标靠谱得多。3. 数据集与标注疲劳驾驶的4类目标与YOLO格式转换环境通了、权重能跑接下来要训练自己的模型就要面对数据集的问题。疲劳驾驶检测识别系统里最关键的决策不是选哪个算法而是“你到底要检测哪几类目标”。这个决策直接决定标注工作量、模型复杂度和最终报警逻辑的写法。3.1 标注目标怎么定闭眼、打哈欠、低头还是更多我在实际项目中见过分类过细的清单左眼闭、右眼闭、打哈欠、低头、抽烟、玩手机一共6类。分类越细标注成本越高且类别间容易混淆——左眼闭和右眼闭在外观上几乎一样模型训练时损失函数会被这种细分类别拖累。我一般推荐的疲劳驾驶标注目标就三类eye_closed闭眼、mouth_open张嘴打哈欠、head_down低头。# fatigue.yaml训练数据集的配置文件 train: dataset/fatigue/train/images val: dataset/fatigue/val/images nc: 3 names: [eye_closed, mouth_open, head_down]这个配置文件的逻辑是告诉YOLOv5去哪里找训练和验证图片以及类别数量nc和类别名称列表names。names的顺序就是标注txt文件里每行第一个数字的含义不能乱改顺序。如果后续要加一个smoking类别必须同步改这里和标注文件。数据集的图片目录下会自动生成对应txt标签每一行格式是“类别id 中心点x 中心点y 宽度 高度”后四个值除以图片宽高做了归一化。3.2 LabelImg标注到YOLO格式txt文件检查脚本标注工具我常用LabelImg标注完成后默认保存为Pascal VOC的xml。YOLOv5需要的是yolo格式txt虽然LabelImg可以设置成自动输出yolo格式但保险起见我还是会在训练前跑一遍检查脚本防止漏标、错标和格式错误。# check_labels.py逐行解析YOLO格式标注检查越界和格式错误 import os def check_label_file(txt_path, img_w, img_h): errors [] with open(txt_path, r) as f: lines f.readlines() for i, line in enumerate(lines): parts line.strip().split() if len(parts) ! 5: errors.append(fLine {i1}: expected 5 values, got {len(parts)}) continue cls_id, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) if cls_id 0 or cls_id 3: # 3对应fatigue.yaml里的类别数 errors.append(fLine {i1}: class id {cls_id} out of range) if cx 0 or cy 0 or bw 0 or bh 0: errors.append(fLine {i1}: negative or zero box value) if cx bw / 2 1 or cy bh / 2 1: errors.append(fLine {i1}: box goes beyond image boundary) return errors这段脚本的核心逻辑是把标注txt的每一行拆成5个字段先检查字段数量再检查类别id是否在0到2之间最后检查归一化后的中心点和宽高是否越界。参数里的img_w和img_h在实际使用时要通过图片文件读取也可以用PIL直接打开图片拿尺寸。疲劳驾驶标注里最常犯的错是把闭眼的小目标框标得过大把眉毛和部分额头框进去这在归一化坐标里不容易看出问题但会直接拉低检测精度。3.3 数据集划分别把所有图片塞进一个目录很多初学者把2000张标注好的图片全部放到train目录然后训练时发现loss一直在降但验证指标很差。原因是模型把训练集“背”下来了验证时没见过这些特定角度就不认识。正确做法是划分训练集、验证集和测试集比例一般取8:1:1。# 按8:1:1比例随机分配图片和对应txt find dataset/fatigue/images -name *.jpg | shuf all_images.txt total$(wc -l all_images.txt) train_count$((total * 8 / 10)) val_count$((total * 9 / 10)) head -n $train_count all_images.txt | while read img; do mv $img dataset/fatigue/train/images/ mv ${img%.jpg}.txt dataset/fatigue/train/labels/ done sed -n $((train_count1)),${val_count}p all_images.txt | while read img; do mv $img dataset/fatigue/val/images/ mv ${img%.jpg}.txt dataset/fatigue/val/labels/ done这段脚本的逻辑是先用shuf随机打乱所有图片路径然后计算训练集和验证集的截止行数用head和sed取不同区间的行最后同时移动图片和同名txt标签。注意labels目录和images目录要同级且子目录名必须严格对应train/val/test。关键参数是8:1:1的比例如果数据量少于1000张我建议改成7:2:1让验证集有更多样本来支撑调参判断。4. 权重文件与训练参数从预训练模型到自己的疲劳检测权重标题里的“权重文件”其实是整个压缩包里最有价值的东西。它可能是官方预训练权重也可能是别人训练好的疲劳检测权重。这两者的使用方式完全不同官方权重用来做迁移学习的基础训练好的权重直接拿去推理。4.1 官方权重怎么选yolov5s还是yolov5xYOLOv5官方提供了n/s/m/l/x五个规格后缀越小速度越快精度越低。疲劳驾驶检测部署在车内嵌入式设备上一般选yolov5s边界场景追求精度可以上yolov5m。选型理由不只是模型大小还有显存占用s模型在batch-size 16、640分辨率下约需要6G显存m模型就要翻倍。车载工控机如果是GTX 1060这类6G卡s模型是最稳妥的选择。# 下载官方s权重作为迁移学习起点在项目根目录执行 wget -P weights/ https://github.com/ultralytics/yolov5/releases/download/v6.0/yolov5s.pt # 训练自己的疲劳检测模型在预训练基础上继续训练 python train.py --data fatigue.yaml --weights weights/yolov5s.pt --img 640 --epochs 150 --batch-size 16 --device 0这条训练命令的--weights参数指向官方预训练权重YOLOv5会自动加载COCO数据集上训练好的特征提取层然后替换掉最后的分类头来适配你的3类疲劳目标。逻辑上这叫迁移学习前面的卷积层学会的是通用的“怎么识别眼睛、嘴巴、头部轮廓”的特征最后一层只需要重新学习类别映射。--epochs设150是经验值疲劳数据集通常在100到200轮之间收敛--batch-size 16是看显存定的显存不够就先降到8同时观察loss是否还能下降。4.2 训练日志和权重输出best.pt与last.pt的使用区别训练过程中项目会在runs/train/expN/目录下生成两个权重文件best.pt是验证集精度最高的那一轮权重last.pt是最后一轮训练完的权重。部署时永远用best.pt。很多人图省事直接拿last.pt推理结果精度比best差一截还反过来骂模型没训练好。# 推理时指定best.pt这是验证精度最优的权重 python detect.py --weights runs/train/exp2/weights/best.pt --source 0 --conf-thres 0.4这段命令里的--source参数如果设成0表示打开本地摄像头设成视频文件路径则逐帧读取视频。--conf-thres降到0.4是因为车内场景光照复杂置信度阈值太高会漏掉遮挡下的闭眼目标。权重文件之间的差异我一般这么判断best.pt在验证集上mAP0.5达到0.85以上就值得花时间继续调到0.9如果只有0.6到0.7先看训练日志确认loss有没有真的收敛。4.3 YOLOv5超参数文件值得动的几个数值YOLOv5把学习率、动量、损失权重这些超参数集中放在data/hyps/hyp.scratch.yaml里这个文件直接决定了疲劳数据集的训练效果。很多人拿默认值直接训结果闭眼这种小目标一直学不好。我整理过一组针对疲劳驾驶场景的调整建议懒人可以直接抄超参数默认值疲劳场景建议调整理由lr00.010.005疲劳数据量通常只有几千张学习率太大容易过拟合box0.050.1边界框损失权重加大让闭眼小目标框得更准cls0.50.7闭眼和正常睁眼样本数量悬殊加强类别损失mosaic1.00.5关闭部分mosaic增强避免闭眼小目标被裁掉hsv_h0.0150.02夜间和红外图像色偏大加强色相扰动超参数文件修改后训练命令里加上--hyp参数指定自定义文件。重点说mosaicyolov5的mosaic增强是把四张图拼成一张对小目标检测提升明显但疲劳驾驶数据里闭眼区域本来就小拼接过程中很容易被边缘裁掉一半。把mosaic降到0.5等于一半概率不走mosaic增强模型能更稳定地看到完整闭眼目标。5. 疲劳驾驶训练与部署避坑5条真实踩坑记录闭眼检测看起来只是一个小目标识别问题真正训练起来才发现坑都在细节里。我把项目里遇到的踩坑记录按“现象→原因→解决”整理出来每一条都配了排查思路照着查能省下大量试错时间。5.1 训练了50轮loss不降mAP一直是0现象训练日志里box_loss和cls_loss几乎没有波动验证集mAP0.5一直为0。原因大多是数据集路径配错fatigue.yaml里train路径指到了空目录模型实际没有读到任何标注数据。另一个常见原因是标注txt是空的用LabelImg标注时保存格式选成了VOC xml但没有转换成YOLO txt。解决训练前先跑一个数据加载检查。在train.py里加一行打印或直接用工具脚本统计标签数量确认labels目录下txt文件非空且每行格式正确。我习惯写个两行检查# 统计标签文件里的有效标注数应该大于0 cat dataset/fatigue/train/labels/*.txt | wc -l # 检查是否有空文件 find dataset/fatigue/train/labels -name *.txt -size 0 | head这两条命令的逻辑是第一个命令把所有txt的行数加起来等于所有标注框总量第二个命令找出大小为0的空文件。如果第一个命令输出是0说明标签压根没生成或路径错了如果第二个命令列出了一堆空文件说明标注转换脚本出了问题。排查完这两个点loss基本就能正常下降。5.2 闭眼目标总是漏检睁眼正常但闭眼框不出来现象白天正常光照下睁眼大部分能检到一旦闭眼就漏检precision高recall低。原因闭眼目标太小默认640分辨率下闭眼区域可能只占十几个像素特征不明显。更深层的原因是训练集里闭眼样本太少类别不平衡让模型倾向于把模糊目标归为“睁眼”背景。解决我给这个问题开两个药。第一是数据层面把闭眼图片单独抽出来做离线增强复制出更多闭眼样本让正负样本比例控制在2:1以内。第二是算法层面把训练分辨率从640提到768增加小目标像素占比训练命令里加--img 768。代价是显存占用变大且推理变慢但对闭眼这种极小的目标分辨率提升比换模型更有效。5.3 模型在训练集上mAP很高换了车载摄像头就翻车现象训练集和验证集都来自公开数据集mAP0.5到了0.9但在实际车内摄像头画面里误报率暴增。原因数据集偏置。公开疲劳驾驶数据集大多用正面视角拍脸车内摄像头通常是斜上方45度俯拍角度差异导致模型把光线好的正面样本学得太死。解决采集真实场景数据补充训练。这不是调参能解决的建议花时间录一段真实驾驶视频抽出关键帧重新标注和公开数据集混合训练。混合比例我一般让真实场景数据占比不低于30%。如果实在拿不到真实数据部署时把--conf-thres提到0.7用置信度卡掉一部分误报但这是缓兵之计治标不治本。5.4 加载best.pt报错shape mismatch模型完全跑不起来现象用别人训练好的权重部署到新环境加载权重打印出RuntimeError: size mismatch for model.24.m.0.weight。原因权重文件是在某个特定类别数和输入尺寸下训练的新环境的模型定义与它不一致。最常见的是类别数不同比如对方训练时用了5类而你本地fatigue.yaml里nc写了3。解决检查加载权重前模型的类别数与权重训练时的类别数是否一致。如果是自己的权重确认使用的项目版本和训练时一致如果是别人的权重最简单的方法是不用自己的模型结构去加载而是直接跑对方项目里的detect.py。如果确实要迁移需要手动把权重文件里最后一层卷积的参数清空重训做法是加载时加一行python train.py --data fatigue.yaml --weights weights/best.pt --epochs 50 --freeze 10这里--freeze 10的意思是冻结模型前10层参数不动后面的层重新训练。逻辑是前10层学到的是通用边缘和纹理特征可以直接复用后面层针对新类别重新学习。这个参数能同时解决shape mismatch和微调场景下的过拟合问题。5.5 夜间红外图像上模型几乎失效现象白天测试效果不错傍晚或夜间光线不足时闭眼检测完全不工作边界框乱画。原因训练数据几乎全是白天光照模型没有见过低照度下的色彩分布。红外摄像头画面是灰度风格颜色通道分布和白天的RGB差异很大等于模型面对的是另一个域。解决补数据的思路不变但有个技巧是在训练时加大hsv增强参数。yolov5超参数里的hsv_h、hsv_s、hsv_v分别控制色相、饱和度和明度的扰动范围把hsv_v从默认的0.4调到0.7等于在训练时人为制造更多暗光样本。另一个狠招是先把所有训练图做灰度化加上一组灰度增强样本混合训练这能让网络不过度依赖颜色信息。实测对红外画面灰度化混合训练比单纯调超参数效果更稳。6. 把检测结果变成报警PERCLOS计算与实时视频流接入模型能框出闭眼哈欠之后系统的最后一步是把单帧检测结果变成“这人是不是疲劳了”的报警信号。工程上常用的判定指标是PERCLOS——单位时间内闭眼帧数占总帧数的比例阈值一般取0.4也就是在一分钟内闭眼时间超过24秒就触发报警。6.1 用PERCLOS做疲劳判定的简单实现# fatigue_monitor.py基于检测结果统计PERCLOS并触发报警 import cv2 from collections import deque model load_model(weights/best.pt) perclos_window deque(maxlen300) # 300帧约为10秒按30fps计 def detect_fatigue(frame): detections model(frame) eye_closed any(det.name eye_closed and det.conf 0.5 for det in detections) perclos_window.append(1 if eye_closed else 0) perclos sum(perclos_window) / len(perclos_window) if perclos 0.4: trigger_alarm() # 语音或串口输出 return frame这段代码的核心逻辑是维护一个300帧长度的滑动窗口每当检测到闭眼就往窗口里追加1没检测到就追加0然后计算窗口内闭眼帧占比。代码里maxlen300是关键参数对应10秒的统计窗口我建议实际部署时设成600到900帧20到30秒太短会把瞬间低头误判成疲劳太长则会漏掉短暂但频繁的闭眼。--conf-thres在加载模型时设置0.5确保只有可靠的闭眼检测才计入统计。6.2 视频流处理别把检测和IO放在一个循环里直接用单线程循环做“读取摄像头—检测—写帧”会在高分辨率下卡顿原因是检测耗时和摄像头读取互相阻塞。我一般用生产者-消费者模式一个线程只负责抓帧放进队列另一个线程只负责检测和报警判断。# 用队列解耦视频抓取和检测降低卡顿 import threading, queue frame_queue queue.Queue(maxsize10) # 最多缓存10帧防止内存暴涨 def capture_loop(cap): while True: ret, frame cap.read() if not ret: break frame_queue.put(frame) def detect_loop(): while True: frame frame_queue.get() result detect_fatigue(frame) # 显示和报警判断 threading.Thread(targetcapture_loop, args(cv2.VideoCapture(0),)).start() threading.Thread(targetdetect_loop).start()这段代码的逻辑是用两个线程把“取帧”和“检测”拆开队列的maxsize10限制了积压帧数避免检测速度跟不上时内存无限增长。这里有一个部署经验如果检测速度只有15帧每秒而摄像头出流是30帧每秒可以把队列大小设为30配合跳帧策略——每两帧检测一帧另一个线程直接丢弃中间帧。疲劳驾驶判定的核心是统计闭眼比例不是连续性跳帧不影响准确度。6.3 报警输出从帧标记到串口和语音报警逻辑有两种落地方式轻量方案是直接在检测结果帧上画红框加文字提示适合做演示原型工程方案是检测到PERCLOS超阈值后通过串口发送指令给外部报警器或调用本地TTS合成语音提醒。串口发送在Linux下用pyserial写两行就行关键是触发后要加一个复位机制——比如持续报警30秒后自动复位否则驾驶员一闭眼报警就会一直响无法重新触发。我吃过这个亏最初报警触发后没有复位逻辑测试时报警器响了一路后来加了计数器累计报警次数和复位时间间隔整个系统才具备可用性。疲劳检测的落地模型精度只占一半统计逻辑和报警机制的稳定性占另一半。希望这些踩出来的经验能帮到你让YOLOv5这套方案少走弯路。本文还有配套的精品资源点击获取