ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python+卷积神经网络的人脸识别疲劳检测系统:从YOLOv5到PERCLOS预警

Python+卷积神经网络的人脸识别疲劳检测系统:从YOLOv5到PERCLOS预警 简介一款基于Python卷积神经网络的人脸识别驾驶员疲劳检测与预警系统完整项目面向毕业设计、课程设计及项目开发场景适合具备Python基础的深度学习学习者直接参考与二次扩展。项目从人脸朝向、眼睛开合度、眨眼频率、瞳孔收缩率等特征入手实现打哈欠、眨眼、点头三类疲劳行为的实时检测与安全提示代码结构覆盖数据处理、模型训练、界面交互等环节。压缩包共包含20个文件以11个Python脚本为核心辅以TXT运行说明、XML人脸特征分类器、HDF5预训练模型及可直接运行的EXE程序整体大小78.33MB目录清晰易上手。目前已有723人学习下载资源内附完整源码、文档及模型文件便于理解卷积神经网络在疲劳检测中的实际应用流程也支持在此基础上进一步延伸与优化。1. 这套基于Python卷积神经网络的人脸识别疲劳检测系统到底解决什么问题疲劳驾驶是路上最隐蔽的事故源头司机自己往往意识不到眼睛已经闭上。把卷积神经网络CNN用在驾驶员疲劳检测上等于给车内装了一双不眨眼的眼睛摄像头对着人脸模型判断眼睛是睁开还是闭合再结合时间窗口判断“这人是不是困了”困了就触发预警。这套方案要解决的不是“识别这是谁”而是“识别这个人现在的状态”这也是它和人脸识别门禁的最大区别。适合毕业设计、课程设计拿来落地也适合做产品原型——你不需要从零发明算法但要能把模型、摄像头、预警逻辑串成一套能跑的完整系统。这篇文章会把从原理到踩坑的整个路径讲清楚按着做就能复现。2. 原理先行为什么疲劳检测要用卷积神经网络2.1 传统方案卡在哪CNN补上了什么在CNN方案之前最常见的疲劳检测做法是dlib人脸关键点 眼睛纵横比阈值判断。dlib能给出人脸68个关键点取眼睛周围6个点算EAR值睁眼时EAR大约0.25闭眼时降到0.1以下设个阈值就能判断。这套逻辑本身没错但真实驾驶场景会把它逼到墙角逆光时关键点定位偏移、戴墨镜时眼睛区域特征丢失、侧脸时关键点直接飞掉、车内昏暗时整张脸都检测不到。CNN的本质是学习“眼睛区域长什么样”的视觉特征而不是依赖手工设计的几何规则。它直接在像素上做卷积把图像里“睁眼”和“闭眼”的分布差异学出来对光照变化、姿态变化、遮挡的容忍度都明显高于手工特征方案。这也是为什么这一类系统普遍把卷积神经网络作为检测前端的原因。2.2 把问题拆成两步人脸检测 眼睛状态识别很多人第一次做会想直接用CNN端到端判断“困了没”。这个想法听着省事实际做起来非常糟糕。疲劳是一个时间维度上的状态单帧图像只能判断“眼睛开没开”要判断“疲劳”需要连续几十帧的上下文。所以工程上必须拆成两步第一步是人脸检测定位驾驶员脸部在画面里的位置。这里用CNN检测器比如YOLOv5的人脸检测模型输出人脸边框。第二步才是眼睛状态识别把面部区域或眼部区域裁剪出来送入一个二分类CNN网络输出“open”或“closed”的置信度。这个两步链路的好处是可控人脸检测不准时可以单独调检测模型眼睛状态误判时可以单独调分类模型不会牵一发动全身。2.3 三套常见方案对比自制小CNN、YOLOv5两分类、MediaPipe方案精度表现部署成本适合场景自制小CNN几层卷积全连接数据集质量决定上限最低CPU也能跑课程设计、快速原型YOLOv5两分类直接检测睁开/闭合眼睛稳定兼顾检测与分类需要GPU训练推理可CPU生产可用、毕业设计完整项目MediaPipe人脸网格 关键点规则对光照敏感最轻实时性最好前期验证、算法对比我一般推荐YOLOv5这个路线。它本身是CNN目标检测网络标题里的“卷积神经网络”落在它身上最有说服力同时它能在检测人脸的同时输出闭眼/睁眼两个类别的边界框训练脚本、预训练权重、数据集格式都是现成的学生项目做到“完整可交付”的难度最低。自制小CNN适合讲解原理但要从人脸区域里再裁眼睛、再训练链路长MediaPipe则更偏工程摆拍和CNN的关联弱一些。2.4 为什么YOLOv5两分类能同时承担“检测”和“状态识别”YOLOv5是anchor-based的目标检测网络它在Backbone里用卷积提取特征Neck层做多尺度融合Head层输出类别和边框。我们用它训练“open”和“closed”两个类别的眼睛检测输入一张驾驶画面模型会框出驾驶员的左眼和右眼并对每个框给出“打开的概率”和“闭合的概率”。和“先裁眼睛再分类”的老路相比这个方案省掉了眼睛区域裁剪的像素坐标换算——那一环节在真实摄像头画面中经常因为人脸偏转而出错。YOLOv5直接把“眼睛在哪里”和“眼睛什么状态”合并成一个卷积网络的前向推理问题逻辑简单端到端可训练推理时一次forward就拿到全部结果帧率也好看。3. 从零搭建可复现环境Python版本、依赖安装与数据集准备3.1 Python版本与依赖安装一套踩过坑的版本组合环境搭错是这类项目最常见的第一道坎。YOLOv5官方要求Python 3.8以上我试过Python 3.11配合旧版torch的坑也遇到过pip默认装到conda环境外面的问题。最稳的组合是Python 3.8或3.10 PyTorch 1.13 / 2.0 CUDA 11.7/11.8。如果你只是课程设计且电脑没有NVIDIA显卡纯CPU版torch也能跑只是训练时间会从几十分钟拉长到几个小时。创建虚拟环境后依赖安装顺序这样做conda create -n fatigue python3.10 conda activate fatigue pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python numpy pillow pandas逻辑说明先把torch和torchvision单独装避免和ultralytics的依赖冲突。--index-url指定CUDA 11.8对应的wheel源如果你没有NVIDIA GPU去掉这行改用pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu即可。ultralytics包已经内置了YOLOv5的接口opencv-python负责视频流读取pandas后面用来统计疲劳指标。参数说明Python版本不要选3.12部分老版本依赖会编译失败CUDA版本要和显卡驱动匹配训练前用python -c import torch; print(torch.cuda.is_available())验证GPU可用返回False时别急着换显卡先查驱动和CUDA版本是不是对不上。3.2 数据集来源与取舍公开闭眼数据集和自采数据怎么组合眼睛开闭检测在学术界有相对成熟的开源数据集比如CEWClosed Eyes in the Wild和MRL Eye Dataset两类样本加起来都够训练一个基础模型。但公开数据集有个共同问题以欧美人脸为主光照条件偏实验室环境放在真实行车记录仪画面上效果会打折扣。我一般建议“公开数据打底 自采数据纠偏”。先下载公开数据集训练出一版能用的模型再用摄像头对着自己录10分钟视频正常睁眼、故意闭眼、戴眼镜、戴墨镜、侧脸、低头各录一段把画面按帧截取出来筛选后补进训练集。这样模型才能在交付演示时扛住你的真实环境而不是只在别人的数据集上自嗨。3.3 VOC转YOLO标注格式转换脚本与四个边界坑如果你用的数据集是VOC格式的XML标注需要转成YOLO的txt格式否则ultralytics读不了。转换脚本的核心逻辑如下import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, out_path, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip().lower() if name not in class_map: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: out_path.write_text(\n.join(lines), encodingutf-8)逻辑说明VOC标注里的坐标是像素值YOLO格式需要归一化到0~1。脚本先取图片宽高再把xmin/ymin/xmax/ymax换算成中心点坐标和宽高最后写出类别id cx cy w h的文本行。class_map是{open: 0, closed: 1}这样的字典。参数说明把坐标从int转成float后要检查w和h是否接近0如果原标注里存在退化的框直接跳过而不是带病训练。另外YOLO要求图片和txt同名且在同一目录图片用.jpg、标注用.txt路径对了训练才能找得到。VOC转YOLO有四个高频坑一是XML里size标签缺失脚本直接抛异常处理老数据集时要给size加容错二是类别名大小写不一致Open和open会变成两个类三是坐标超出图片边界比如xmax大于图片宽度归一化后超过1训练时框会跑到画面外四是白底黑字的眼睛图片转灰度后对比度剧烈变化这类样本要么提前做直方图均衡化要么直接过滤。4. 训练与推理把卷积神经网络接入摄像头画面4.1 训练眼睛状态检测模型命令与关键参数说明数据集目录准备好后开始训练。用ultralytics提供的YOLOv5训练接口训练脚本如下python train.py \ --img 640 \ --batch 16 \ --epochs 80 \ --data ./datasets/eye.yaml \ --weights yolov5s.pt \ --project runs/eye_train \ --name exp1逻辑说明train.py会自动从eye.yaml读取训练集路径、验证集路径和类别数量。--weights指定预训练权重用yolov5s作为起点可以让模型更快收敛尤其是你的数据集不大的时候从头训练容易欠拟合。参数说明--img 640输入图像分辨率。眼睛是小目标分辨率太低会漏检但640在CPU推理时会明显掉帧如果需要兼顾速度训练用640推理时降到320或480。--batch 16单卡显存8G以下建议改成8否则会出现CUDA out of memory。批大小影响BatchNorm统计改小后要适当降低学习率。--epochs 80眼睛二分类任务属于简单任务一般40~50轮就收敛80轮是为了让曲线彻底稳下来。跑完看results.png里验证集mAP是否还在上升上升就再加20轮。--weights yolov5s.pt网络规模最小的预训练权重。项目演示用s足够如果检测框频繁漏掉闭眼状态再换yolov5m但推理速度会下降。训练完成后模型保存在runs/eye_train/exp1/weights/best.pt。记得用验证集里的闭眼图片单独测一次确认模型真的学到了“闭眼”而不是靠背景颜色偷懒。4.2 数据增强与类别不均衡让模型在夜间逆光下不翻车训练时最容易忽视的是数据增强配置。YOLOv5默认开启hsv变换和随机翻转但驾驶场景还需要额外关注两点在eye.yaml同级的hyp.yaml里把hsv_h、hsv_s、hsv_v分别调整到0.02、0.6、0.5让模型在训练时看到更多亮度偏移的样本这相当于免费扩充夜间样本。同时degrees设为5允许轻微旋转——司机头部会自然晃动完全不旋转会让模型对姿态变化过于敏感。类别不均衡是另一个坑如果你的数据集里睁眼样本是闭眼的两倍以上模型会倾向把所有框都判成open因为这样整体loss最小。解决办法是在eye.yaml里给闭眼类别加权重或者在采样阶段对闭眼图片做复制增强。4.3 单帧推理从YOLOv5输出到摄像头画面训练完模型写一段最简推理脚本验证模型能对摄像头画面实时输出眼睛状态import cv2 import torch model torch.hub.load(ultralytics/yolov5, custom, pathruns/eye_train/exp1/weights/best.pt) cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(摄像头打开失败检查驱动或换个USB口) while True: ret, frame cap.read() if not ret: continue results model(frame, size320, conf_thres0.35) for det in results.xyxy[0].tolist(): x1, y1, x2, y2, conf, cls_id det label model.names[int(cls_id)] cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255) if label closed else (0, 255, 0), 2) cv2.putText(frame, f{label} {conf:.2f}, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.imshow(fatigue, frame) if cv2.waitKey(1) ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明torch.hub.load直接加载训练好的权重首次运行会自动下载YOLOv5依赖组件。model(frame, size320)把每一帧缩放到320分辨率送进网络这里为了推理速度故意降低分辨率conf_thres0.35表示置信度低于0.35的检测结果会被过滤掉。results.xyxy[0]返回一个二维张量每行是x1, y1, x2, y2, 置信度, 类别id。参数说明conf_thres的高低直接影响误报率。设成0.2会看到很多置信度低的抖动框设成0.6又会漏掉一些闭眼状态。可以根据摄像头画面实测效果调整我一般会在0.3~0.4区间试一圈再定。推理时如果CPU占用过高把size再降到256代价是远距离眼睛检测会失准。4.4 从单帧检测到连续帧状态追踪摄像头每秒产生大约30帧画面逐帧独立检测的问题是某一帧检测框抖动会导致状态跳变闭眼检测偶尔漏一帧中间夹一个“睁眼”。疲劳预警必须基于连续帧的状态序列最简单的状态机做法是维护一个闭眼帧计数closed_frames 0 CLOSE_FRAME_THRESHOLD 12 # 约0.4秒30fps while True: ret, frame cap.read() results model(frame, size320) labels [model.names[int(d[5])] for d in results.xyxy[0].tolist()] if closed in labels and open not in labels: closed_frames 1 else: closed_frames 0 if closed_frames CLOSE_FRAME_THRESHOLD: trigger_alarm()逻辑说明这个状态机只做两件事——检测到闭眼就累加计数检测到睁眼就把计数清零。CLOSE_FRAME_THRESHOLD设为12表示连续12帧闭眼才报警目的是滤掉单帧的检测噪声。参数说明阈值12是经验值对应30fps下约0.4秒。实际调试时你可以在车内记录一段“驾驶员打瞌睡”的视频数一下闭眼到彻底入睡的帧数再反推阈值。闭眼持续时间短于0.3秒可能是正常眨眼不该报警超过2秒必须报警。中间的阈值看你想要系统更灵敏还是更保守。5. 疲劳预警逻辑EAR、PERCLOS与报警阈值怎么定5.1 EAR眼部纵横比把闭眼程度变成一个可计算数值虽然YOLOv5直接输出开闭类别但为了做到更精细的预警分档比如“微困”“嗜睡”需要引入EAREye Aspect Ratio作为连续指标。EAR的计算依赖眼睛6个关键点外眼角1个、内眼角1个、上下眼睑各2个。公式是垂直距离的平均值除以水平距离def eye_aspect_ratio(landmarks, eye_idxs): # eye_idxs 是dlib 68关键点中左右眼的索引集合 p1 landmarks[eye_idxs[0]] p2 landmarks[eye_idxs[1]] p3 landmarks[eye_idxs[2]] p4 landmarks[eye_idxs[3]] p5 landmarks[eye_idxs[4]] p6 landmarks[eye_idxs[5]] vertical abs(p2.y - p6.y) abs(p3.y - p5.y) horizontal abs(p1.x - p4.x) return vertical / (2.0 * horizontal)逻辑说明EAR的原理很直观眼睛睁开时上下眼睑距离远、内外眼角距离相对固定比值在0.25~0.35闭眼时上下眼睑几乎重合垂直距离趋近于0EAR会跌到0.1以下。把每组眼睛的EAR按时间轴画出来能清晰看到“眨眼”对应的V型低谷和“闭眼”对应的持续低谷。参数说明左右眼的eye_idxs不同——右眼是[36, 37, 38, 39, 40, 41]左眼是[42, 43, 44, 45, 46, 47]。这个编号对应dlib 68关键点标准排序搞反了算出来的EAR是乱值。EAR阈值一般取0.2但不同人眼睛大小差异明显正式使用前需要做一次10秒标定——睁眼正常看前方记录睁眼EAR平均值乘以0.7作为个人闭眼判定线。5.2 PERCLOS标准与连续闭眼帧报警触发条件该怎么设计行业内衡量疲劳有一个经典指标叫PERCLOSPerclos of Eye Closure核心定义是单位时间内眼睛闭合时间所占的百分比。实际落地时通常有两种触发模式第一种是“窗口比例模式”统计最近60秒内闭眼帧数占总帧数的比例超过15%触发一级预警。这种模式适合做长期疲劳趋势判断。第二种是“连续闭眼模式”统计连续闭眼帧数超过1.2秒触发报警。这是更紧迫的嗜睡信号适合做紧急预警。触发模式时间窗口判定阈值实际用途眨眼频率异常60秒每分钟眨眼次数低于5次疲劳潜伏期提示PERCLOS一级预警60秒闭眼时间占比≥15%建议休息连续闭眼紧急报警实时连续闭眼≥1.2秒立即提醒这套组合比单纯看某几帧更有说服力短暂闭眼可能是正常眨眼连续闭眼才是危险信号。把两种模式合并成一个综合判定状态机就能覆盖从“轻度疲劳”到“嗜睡”的全过程。5.3 预警触发与响应声音报警、界面提示与防抖预警响应端要做三件事声音报警、界面状态切换、报警日志记录。声音报警用playsound库或系统提示音界面画面上把状态文字从“正常”切换到“疲劳”并变成红色。日志记录每个报警时间点和当时的EAR均值方便答辩或验收时展示“系统确实在准确的时间点报警了”。import threading from playsound import playsound alarm_active False def play_alarm(): while True: if alarm_active: playsound(alarm.wav, False) time.sleep(1)逻辑说明报警播放放到独立线程里避免阻塞摄像头主循环导致帧率骤降。alarm_active是全局状态位由疲劳判定状态机置位和复位。参数说明这里避开了playsound的阻塞参数True因为它在Windows上偶尔会卡住主线程设为False后用睡眠循环控制重复频率。报警后要设置一个至少10秒的冷却窗口否则司机刚被提醒就恢复正常状态系统会反复报警造成烦扰。6. 避坑与验收光照、眼镜和低算力环境下的踩坑记录6.1 戴眼镜时闭眼被模型判成睁眼误报率翻倍现象戴眼镜的测试者闭眼时系统无响应日志显示闭眼帧占比很低。原因是镜片反光把眼睑轮廓照亮模型看到的是镜片上的环境光影误认为眼睑还有缝隙。解决在数据增强里加入高斯模糊和随机亮度抖动的闭眼样本让模型学会忽略镜片反光更直接的办法是在录制训练数据时就戴上眼镜录一遍闭眼视频强制模型见到“闭眼眼镜”的组合。6.2 夜间逆光下检测框抖动闭眼帧断续现象车载摄像头在夜间隧道出口或对向车灯照射下检测框每几帧跳一次坐标状态机里的闭眼计数被“伪睁眼”帧打断。原因是高光区域导致YOLOv5的IoU不稳定。解决推理前对帧做一次自适应直方图均衡化再用OpenCV的createCLAHE限制对比度同时在状态机里把“闭眼计数”改成允许在10帧中出现至多1帧睁眼避免被孤立噪声打断报警。6.3 误报频繁还是漏报频繁两类阈值谁先调现象系统在家里测试一切正常上车实测后每分钟误报一次。原因是眼部区域光照不均模型在低置信度区间摇摆。解决先调conf_thres从0.25提到0.4把低置信度的检测框过滤掉再看EAR阈值闭眼判定从0.2降到0.18。调参顺序很重要——永远先过滤检测噪声再调状态判定阈值反过来容易越调越乱。6.4 低端CPU跑不动降分辨率还是换轻量网络现象没有NVIDIA显卡的笔记本跑640分辨率推理只有5帧画面像是幻灯片。解决推理时把model(frame, size320)改成224分辨率帧率能翻倍代价是远距离人脸漏检增加。如果还需要更快把YOLOv5s替换成更轻量的方案比如用YOLOv5的nano版本或者把眼睛区域单独裁出来送一个极小的二分类CNN。做课程设计用降分辨率就够了做产品原型再考虑nano版。6.5 验收方法用标注视频量化系统的疲劳检出率最后验收不能靠“感觉好像能检测到”。我会录制三段60秒的测试视频一段正常驾驶、一段频繁眨眼、一段持续闭眼嗜睡人工标注闭眼的起止帧跑完系统后用Python脚本对比报警时间段和标注时间段的重合度。重合时间除以标注闭眼总时长就是检出率至少90%才算合格误报率控制在每5分钟一次以内。这套量化流程比一百句“系统运行稳定”都有说服力答辩时直接展示数据和曲线。做这套系统的过程中我踩过最多的坑就是拿主观感受代替量化指标后来习惯了每次调参都跑同一段标注视频前后对比清晰了问题也定位得快。希望帮到你。本文还有配套的精品资源点击获取
返回列表