公司动态
基于YOLOv8的疲劳驾驶检测系统实战:人脸关键点与PERCLOS判定
简介计算机视觉技术正在重塑交通安全领域目标检测与面部特征分析是其中基石。疲劳驾驶检测的核心在于将“困倦”这一主观状态转化为可计算的生理指标——如基于眼睛闭合比例的PERCLOS标准。YOLOv8作为新一代实时检测框架在轻量化和精度间取得平衡可高效定位驾驶员面部区域结合dlib或MediaPipe提取人脸关键点计算EAR和MAR数值再通过时间窗口状态机综合判定正常、疲劳与瞌睡状态。该类技术价值显著可无缝嵌入车载驾驶员监控系统DMS、智能座舱及安全预警平台有效降低交通事故风险。本文围绕YOLOv8的完整工程落地涵盖数据准备、模型训练、疲劳判定算法及边缘部署为实际项目提供可复现的参考路径。 深夜开车眼皮打架的时候方向盘比任何时候都沉。疲劳驾驶检测这件事我一直觉得不是“锦上添花”的功能而是真正能救人的东西。这个项目用YOLOv8做人脸目标检测配合面部特征关键点分析实时判断驾驶员是否进入疲劳或瞌睡状态。如果你正在做毕业设计、参加竞赛或者想在车载嵌入式设备上落地一个检测原型这篇就是我从零搭完全流程、跑通并踩过坑之后的完整复盘。项目本身不复杂但真正做下来会发现坑全藏在细节里——比如关键点检测在暗光下的稳定性、眼镜反光导致的误判、帧率不够导致的卡顿感、疲劳判定阈值的“玄学”区间。这篇不会只给你看几个公式截图我会把整个项目的技术选型逻辑、训练数据准备、疲劳判定算法、实测表现和优化方向全部摊开讲保证你拿到思路之后能直接动手复现。1. 为什么用视觉方案做疲劳检测技术选型的真实考量1.1 疲劳驾驶到底怎么量化很多人第一次接触这个项目时会问困不困不是主观感受吗机器怎么判断这个问题是整个系统的核心也是疲劳驾驶检测区别于普通目标检测的地方。疲劳不是某个瞬间的“是/否”状态而是一个渐进的生理和行为变化过程。目前工程上成熟的做法是把疲劳映射为一系列可观测的面部行为特征比如眨眼频率变化、眼睛闭合持续时长、嘴巴打哈欠的开合程度、头部低垂角度以及这些行为在时间维度上的累积。这套逻辑的医学基础是PERCLOSPercentage of Eyelid Closure Over the Pupil over Time也就是单位时间内眼睛闭合时间所占的比例。研究表明当PERCLOS值超过一定阈值通常取P80标准即眼睛闭合面积超过80%的时间占比时驾驶员的警觉水平已经显著下降。把医学标准转成工程师能用的东西就成了代码里的核心判定逻辑。所以整个项目的本质就是要把“困了”这个抽象概念转换成能在每一帧图像上计算出来、并能在连续时间窗口内统计的数值指标。1.2 生理信号方案与视觉方案之争做驾驶员疲劳检测业内技术路线基本分两大派生理信号派和视觉派。生理信号派采集脑电EEG、心电ECG、肌电EMG或皮肤电导率等数据理论上准确率极高因为困意本身就是神经系统的状态变化。但这派方案在实际车载场景里非常难落地需要佩戴电极或接触式设备司机不会愿意每次开车前往头上贴一圈传感器长时间佩戴也会因出汗导致信号质量漂移。更麻烦的是设备成本和维护成本都很高基本停留在实验室或者特殊行业比如高铁司机、矿车司机的标配里。视觉派就灵活得多一个普通RGB摄像头装在仪表盘或后视镜上不需要接触人体算法自动定位人脸、提取关键点、计算疲劳指标。这条路线最大的优势是硬件成本极低、部署无感且随着深度学习目标检测技术的成熟在白天和光照良好的环境下准确率已经能满足工程需求。再加上汽车厂商本来就在车里装摄像头做其他功能比如驾驶员监控系统DMS视觉方案几乎是乘用车场景下唯一有量产可能的路线。这个项目选视觉方案也是基于同样的判断。1.3 为什么是YOLOv8不是YOLOv5、SSD或者OpenCV HOGYOLOv8是Ultralytics公司在2023年发布的YOLO系列迭代版本相比前代做了不少结构改进。但市面上的目标检测框架这么多为什么偏选它我在项目里认真对比过理由主要有三个。第一是检测精度与速度的平衡。疲劳检测对实时性要求很高普通摄像头拍到人脸的尺寸在640x640分辨率下大约只有几十像素见方属于典型的小目标检测。YOLOv8通过改进C2f模块和SPPF结构在保持轻量化的同时对小目标的特征提取能力比YOLOv5有明显提升。实测下来在GTX 1660Ti这种老显卡上YOLOv8s模型跑640输入分辨率能做到35-45 FPS完全满足实时处理要求。第二是工程生态成熟。Ultralytics的代码仓库把训练、验证、导出、部署的链路做得非常完整一条命令就能开始训练导出ONNX、TensorRT、TFLite、RKNN都有现成支持。对于一次实战项目来说不需要从零搭训练管道能把精力集中在核心的疲劳判定算法上。第三是模型可扩展性好。YOLOv8系列本身就包含目标检测、实例分割、姿态估计等衍生任务如果你想在这个项目后续加入手部姿态检测比如识别驾驶员手持手机打电话或者头部姿态估计YOLOv8-pose可以直接复用同一套基础设施不用重新搭框架。作为对比SSD和OpenCV自带的HOG人脸检测不是不能用但HOG在头部旋转、暗光、遮挡场景下漏检严重SSD在CPU上推理速度又不够理想。综合下来YOLOv8是这个场景下最省心的选择。2. 系统整体架构从摄像头画面到疲劳状态输出2.1 完整工作链路整个系统跑起来之后内部的处理流程大致是这样的摄像头采集到的视频流逐帧送入YOLOv8检测模型模型输出画面中所有人脸的边界框。系统选取面积最大、位置最靠近画面中央的框作为“驾驶员人脸”——这个策略是为了防止副驾或后排乘客干扰判定。拿到人脸框之后裁剪出人脸区域再进行关键点检测。关键点检测器返回一组面部特征点坐标如眼睛轮廓点、眉毛、嘴巴、鼻子、下巴轮廓等下一步就根据这些坐标点计算眼睛开合度EAR、嘴巴开合度MAR、头部姿态角等疲劳特征指标。这些指标再经过时间窗口状态机进行累积判断最终输出三个状态正常、疲劳、瞌睡并触发相应的声光报警提示。听起来环节很多但在代码实现里每帧处理时间需要控制在25毫秒左右才能保证流畅度。所以性能优化是贯穿整个项目的关键任务。我最初把YOLOv8和关键点检测串行跑一帧要80毫秒肉眼可见卡顿后来改成多线程并行处理检测模型和关键点模型各占一个线程用队列传递数据帧率直接翻倍。如果你的设备性能更弱还可以考虑每隔2-3帧才跑一次关键点检测中间帧沿用上一帧的结果对最终判定影响很小。2.2 人脸关键点获取的两种路径dlib 68点和MediaPipe疲劳特征计算依赖人脸关键点这一步主要两条成熟路线我分别实现对比过。第一条是dlib的68点关键点检测器。dlib是比较经典的传统机器学习方案使用ERTEnsemble of Regression Trees算法模型文件shape_predictor_68_face_landmarks.dat大概99MB在CPU上单张人脸关键点检测大约需要5-8毫秒。优点是精度稳定、文档齐全、离线运行缺点是不能直接复用GPU加速虽然有GPU版本支持但配置麻烦而且模型文件较大。第二条是Google的MediaPipe Face Mesh。它不是简单的68点而是输出468个密集面部关键点精度更高对姿态变化的鲁棒性更强能检测出侧脸、低头等复杂情况。MediaPipe底层用TensorFlow Lite推理在CPU上速度也很快而且在移动端优化得非常好。缺点是模型比较大约5MB虽远小于dlib但在某些条件下仍算有要求需要额外处理依赖。两者怎么选我的建议是如果你追求简单稳定、不太在意模型文件大小直接用dlib如果你后续想把手势识别、头部姿态、甚至表情识别都做进同一个系统MediaPipe是更划算的选择。项目源码里两个版本都给了接口切换只需要改一行配置。2.3 特征算子的数学定义EAR、MAR与头部姿态角这是整个项目中最核心的代码逻辑所在。先说眼睛开合度EAREye Aspect Ratio这也是PERCLOS算法的基础参数。EAR的计算方法是从68个关键点中提取眼睛轮廓的6个点左眼外角p1、左眼外眼皮p2、左眼内眼皮p3、左眼内眼角p4、左眼下眼皮p5、右眼内眼皮p6实际按dlib索引定位然后按以下公式计算EAR (||p2 - p6|| ||p3 - p5||) / (2 * ||p1 - p4||)通俗点解释就是眼睛的“宽度”除以“高度”的比值。当人睁开眼睛时垂直距离较大EAR值通常在0.25到0.35之间当人闭上眼睛时垂直距离趋近于零EAR值会骤降到0.1以下。这个比值的好处是光照变化、距离远近、脸部大小变化时都相对稳定不需要额外做尺度归一化。嘴巴开合度MARMouth Aspect Ratio用同样的思路计算取嘴巴轮廓的最上和最下、最左和最右的关键点坐标同样算纵横比。正常说话时MAR在0.2到0.5之间波动打哈欠时嘴巴大张MAR会超过0.6甚至0.7。头部姿态角则需要更复杂的计算。简单方案是使用solvePnP函数把2D面部关键点与标准3D人脸模型进行匹配求解相机的旋转向量再换算成欧拉角pitch俯仰角、yaw偏航角、roll翻滚角。当驾驶员的pitch角持续向下超过一定阈值比如低头超过20度说明他在频繁点头打瞌睡。这块我在项目里用MediaPipe的Face Mesh算法直接给出了头部姿态角省去了自己调solvePnP的麻烦。三段代码结合起来我就得到了一组可用于状态判断的基础数值。需要注意这里之所以费这么大劲算EAR而不是简单用“眼睛是否闭着”二值来判断是因为疲劳是一个渐变过程睁眼不完全、眼睛半闭状态本身就是一个重要信号。用连续数值表示就能捕捉到这个中间态。3. 数据准备与YOLOv8训练实操记录3.1 用自己的数据还是公开数据集疲劳驾驶检测项目的数据集分为两部分人脸检测模型的数据集和疲劳判定逻辑的数据集。人脸检测模型需要的是“方向盘后面的人脸”图片且最好包含不同光线条件、不同肤色、不同角度、戴眼镜/不戴眼镜等情况。公开数据集方面比较适合的有WIDER FACE、FDDB但它们大多不是驾驶场景。NTHU-DDD台湾清华大学驾驶数据集和YawDD加拿大魁北克大学驾驶数据集是专门针对驾驶员疲劳检测的视频数据集包含正常驾驶、聊天、打哈欠、昏昏欲睡等场景非常贴合需求。这两个都可以用作模型微调和疲劳阈值标定。如果找不到合适的公开数据完全可以自己采集。我见过不少人用手机摄像头对着电脑屏幕录自己打哈欠的视频来凑数据虽然规模小但如果配合数据增强用YOLOv8在迁移学习的基础上微调也能达到不错效果。这个项目里我混合使用了YawDD和一段自采数据自采数据主要补充了戴墨镜和夜间光线两个场景。3.2 数据标注的具体操作YOLO格式的数据标注是一个细活很多人第一次做常常在编码阶段花掉大量时间。这里我给出一套可以完全照搬的流程。标注工具推荐用labelimg轻量简单直接pip安装就能用输出格式选YOLO。每张图片标注后生成一个同名txt文件每一行代表一个目标格式为类别索引 归一化中心x 归一化中心y 归一化宽 归一化高。比如一张640x480的图中人脸框左上角在(100, 50)、右下角在(300, 350)那么转换后就是0 0.3125 0.4167 0.3125 0.625具体计算中心x ((100300)/2) / 640 0.3125中心y ((50350)/2) / 480 0.4167宽 (300-100)/640 0.3125高 (350-50)/480 0.625。标注时需要注意两个容易忽略的细节。一是类别索引必须从0开始连续编号没有第1类直接写0会报错二是标注框尽量贴紧人脸不要包含太多背景因为这会让模型学到不必要的背景特征导致后续对画面中远处的小头像敏感度下降。我做标注时还专门检查了一遍数据集的类别平衡确保正脸、侧脸、低头、戴帽子的样本数量都在合理比例避免模型对某一种姿态产生偏向。3.3 训练参数设置和Loss曲线观察用Ultralytics框架训练YOLOv8非常直接但参数不能盲抄默认值。我最终训练用的配置如下# dataset.yaml train: datasets/face_detection/train/images val: datasets/face_detection/val/images nc: 1 names: [face]训练命令yolo detect train datadataset.yaml modelyolov8s.pt epochs100 imgsz640 batch8 lr00.01 optimizerAdamW patience10 device0这里几个参数可以讲讲理由。模型选择yolov8s而不是nano或m是考虑到精度和速度的平衡nano在复杂驾驶场景下漏检率偏高m在CPU推理时帧率太低s是折中方案。imgsz设为640是标准分辨率如果你希望提升小目标检出率可以尝试用960训练再在640下推理即训练和推理分辨率解耦但显存需求会明显增加。batch大小视显卡而定GTX 1660Ti 6GB显存的条件下batch8比较稳妥调太大容易OOM。训练过程中一定要盯着Loss曲线和验证集指标看不要傻等100个epoch跑完。建议用TensorBoard或Ultralytics自带的日志可视化tensorboard --logdir runs/detect正常情况下训练集的box_loss和cls_loss应该稳步下降验证集mAP50在训练后期趋于平稳。如果你看到验证集mAP50在某个epoch后开始下降而训练集还在继续降那就是过拟合了可以提前停止训练如果Loss曲线出现明显震荡多半是学习率设置过大需要把lr0降到0.001再跑。还有一个非常实用的经验Mosaic数据增强在人脸小目标场景下要谨慎使用。YOLOv8默认开启Mosaic它会把四张图拼在一起训练对普通目标检测效果不错但对人脸这种小目标拼图后的人脸可能被缩得太小导致模型学到的是“模糊小方块”而不是清晰的人脸特征。我实测把mosaic设为0.5一半概率使用比默认的1.0效果好mAP50能提升约4个百分点。3.4 老显卡上怎么控显存很多同学用的还是GTX 1660Ti、RTX 2060甚至纯CPU环境跑YOLOv8训练时频繁OOM。这里分享几个实际可用的降低显存开销手段。第一是降低批次大小。batch从16降到8显存占用几乎减半代价是训练收敛速度变慢但最终精度通常不受影响。第二是使用梯度累积也就是batch4配合accumulate2效果等效于batch8但显存占用只需一半。Ultralytics框架里设置方法是# 在训练命令中加参数 yolo detect train ... batch4 accumulate2第三是降低输入分辨率把imgsz从640降到512显存占用直接下降约36%精度损失在5%以内。第四是开启混合精度训练AMP在PyTorch里只需要在训练命令里加ampTrue1660Ti的Tensor Core能自动加速半精度运算显存占用也能减少约20%。我实际测试过1660Ti上训练一个100轮的YOLOv8s人脸检测模型大约需要5-6小时。如果只是做项目演示用预训练权重微调30轮就能获得不错效果没必要跑满100轮。4. 疲劳判定算法阈值设置与连续帧策略4.1 PERCLOS标准在代码里怎么落地很多人把疲劳检测项目做成了“眼睛一闭就报警”的简单二值判断这是完全错误的做法。真实驾驶场景中眨眼本身是一个正常生理行为一次正常的眨眼也会让眼睛短暂闭合时长大约200-300毫秒。如果每眨一次眼就报警一次系统根本没法学。所以必须引入时间窗口的概念用一段连续时间内的闭眼行为特征来判断疲劳状态。PERCLOS标准落地时我采用的方法是在每帧计算EAR后判断该帧是否处于“闭眼状态”EAR低于闭眼阈值比如0.18。然后以60秒为一个统计窗口统计窗口中闭眼帧数占总帧数的比例。如果这个比例超过40%对应P80标准就判定为疲劳。代码示意如下class PERCLOSDetector: def __init__(self, ear_threshold0.18, window_size30*60, alarm_ratio0.4): self.ear_threshold ear_threshold self.window deque(maxlenwindow_size) # 30帧/秒 × 60秒 self.alarm_ratio alarm_ratio def process_frame(self, ear_value): is_closed int(ear_value self.ear_threshold) self.window.append(is_closed) if len(self.window) self.window.maxlen: close_ratio sum(self.window) / len(self.window) return close_ratio self.alarm_ratio return False这里窗口大小的选择要根据实际帧率调整。如果系统只有15 FPSwindow_size就设为15*60900如果帧率波动较大可以用时间戳而非帧数来维护窗口代码会更健壮。4.2 状态机与多特征融合判断只靠PERCLOS一个指标是不够的。因为有人天生眨眼频率高有人戴墨镜导致关键点检测不稳定单一指标容易误报。所以我在项目里设计了一个三状态状态机正常、疲劳、瞌睡每个状态由多特征投票决定。状态机逻辑是这样的正常状态EAR平均值处于正常范围0.25-0.35MAR在正常范围0.2-0.5头部姿态角无明显低头趋势。触发条件所有指标正常。疲劳状态PERCLOS超过35%或连续1分钟内多次出现MAR0.6即频繁打哈欠或头部频繁点头pitch角超过25度的次数增多。进入疲劳状态后系统播放语音提醒界面上显示黄色警示。瞌睡状态PERCLOS超过40%或发现持续3秒以上的闭眼事件休眠微睡眠系统触发蜂鸣器和红色闪烁警告。瞌睡状态是最高级别必须强制提醒驾驶员。状态之间的转换需要做滞回处理即进入疲劳状态后需要连续30秒指标正常才允许回到正常状态。否则司机只是抬头看了一眼路边标志系统就立刻从疲劳切回正常然后过几秒又变疲劳会把人烦死。滞回处理在工程上非常重要不做它再好的检测模型也像个神经病一样乱报警。4.3 光线、遮挡和眼镜问题真实驾驶场景里光线变化是最大的敌人。白天逆光、夜晚路灯闪烁、隧道出入口强光变化、阴天灰度偏低都会导致人脸检测和关键点定位质量下降。我处理这个问题的策略有三层第一层是图像预处理。在送入检测网络前先对原始帧做自适应直方图均衡化CLAHE增强局部对比度让脸部轮廓在暗光下更清晰。这个操作开销极低但对关键点检测的稳定度提升非常明显。第二层是置信度过滤。YOLOv8检测人脸时会输出置信度分数关键点检测也有置信度。我在代码里规定只有人脸置信度大于0.6且关键点置信度大于0.4的帧才被用于EAR计算。连续丢帧超过一定数量时系统降低检测频率并提示“检测质量下降”避免在关键点质量差时用垃圾数据判定疲劳。第三层是墨镜处理。戴墨镜时眼睛区域完全不可见EAR计算失效。我的方案是当检测到墨镜时放弃EAR特征只依赖MAR和头部姿态角进行判断。虽然没有眼睛信息会导致检测精度下降但总比完全失灵好。项目里我在预处理阶段加了一个简单的墨镜检测模块用颜色阈值判断眼睛区域是否大面积偏暗逻辑简单且实测有效。5. 实测效果、性能优化与后续扩展方向5.1 白天/夜晚/高速场景实测结果项目完整跑通之后我在多种场景下做了测试这里记录一下最真实的数据。白天正常光照下人脸检测的准确率非常高几乎不会漏检EAR计算也稳定正常眨眼时EAR在0.25-0.35之间波动模拟闭眼时稳定低于0.1区分度很清晰。实测中故意做轻微瞌睡动作眯眼、缓慢点头系统大约在3-5秒内能识别并从正常状态切到疲劳状态。夜晚低光照条件下摄像头自动增益导致画面噪点增多人脸检测准确率从白天的95%以上降到约80%关键点检测的抖动也明显增加。这时CLAHE预处理的功劳就体现出来了不开CLAHE的误报率大约在15%开了之后降到6%左右。高速公路上有个特殊问题车窗外的景物快速变化驾驶员会时不时瞟一眼后视镜或侧视镜这会导致头部姿态角出现瞬时大值如果不做时间平滑处理很容易误判为“东张西望”或疲劳状态。我给头部姿态角加了滑动平均滤波窗口为5帧效果立竿见影误报率显著下降。总的来说这个系统在白天和弱光环境下的疲劳检测准确率能达到85%以上深夜强噪点环境下约有10%的误差主要误差来源是漏检和关键点抖动。如果你对夜间精度有更高要求建议直接换红外摄像头或者用带红外补光的DMS专用摄像头在完全无可见光的情况下也能工作。5.2 我踩过的三个具体坑第一个坑是dlib安装问题。在Windows上通过pip安装dlib需要Visual Studio C Build Tools很多人在这一步卡住。其实不需要折腾visual studio直接用conda安装预编译好的conda-forge版本更省事conda install -c conda-forge dlib。或者干脆换MediaPipe它通过pip安装全程无障碍。第二个坑是UTF-8编码问题。项目里报警提示用的是中文Windows控制台默认GBK编码直接print中文会乱码甚至直接闪退。建议在代码开头加上import sys; sys.stdout.reconfigure(encodingutf-8)或者统一用日志模块输出。第三个坑是线程安全问题。我用多线程并行跑检测和关键点提取时OpenCV的VideoCapture对象不是线程安全的在子线程里反复读帧会导致画面撕裂或者直接崩溃。正确做法是主线程只负责读帧并放入队列工作线程从队列取帧处理确保VideoCapture对象只在主线程中被调用。这个坑排查了我一整天最后看OpenCV文档才发现原因。5.3 进一步优化注意力机制、姿态融合与边缘部署这个项目做完之后如果你想继续深挖有三个方面值得花时间。第一是模型结构的轻量改进。社区里已经有很多YOLOv8改进方案简单有效的比如在C2f模块中融入EMAEfficient Multi-scale Attention注意力机制可以在YOLOv8s的基础上进一步减小模型体积同时保持或小幅提升检测精度。EMA的好处是计算开销极低你不需要A100也能训练得很好只需要在yaml配置文件中替换backbone模块。我在项目里尝试过模型体积减小约6%mAP50几乎持平推理速度大约提升8%。第二是头部姿态估计的深度融合。目前实现里头部姿态角只是作为一个独立特征参与投票如果你想做更精细的疲劳检测可以在YOLOv8-pose的基础上把驾驶员骨架关键点也接入系统结合上半身姿态判断方向盘握持状态、身体倾斜程度等。这是一条很自然的扩展路径因为YOLOv8-pose和YOLOv8-detect的训练链路是同一个框架不需要额外引入其他模型库。第三是嵌入式部署。车载项目最终一定要跑在低成本边缘设备上。当前主流方案是RK3588等国产边缘计算平台YOLOv8要部署到RK3588需要经过“PyTorch → ONNX → RKNN”的转换流程转换过程中需要注意算子的兼容性问题个别算子如某些注意力模块的softmax在RKNN上支持不完整需要替换或手动实现。跑在RK3588上YOLOv8s经过RKNN优化后可以达到30 FPS以上完全满足实时检测需求。也有人尝试把YOLOv8通过NCNN/TFLite塞进手机或低功耗开发板那就要看模型量化INT8之后的精度损失是否能接受。最后说一个个人体会疲劳驾驶检测这个项目的价值不只是用YOLOv8做了一次目标检测而是把“目标检测结果”真正变成了一个可决策、可报警、可交互的产品逻辑。很多人做深度学习的实战项目往往止步于“模型输出几个框”但离“能用的产品”还差着十万八千里——数据清洗、疲劳判定的时序算法、状态机设计、多线程优化、边缘适配每一步都需要实打实的工程能力。你把这个项目完整跟下来获得的是一套“算法工程”的综合经验以后不管是做DMS、做安防监控还是做智能座舱底层逻辑都是相通的。本文还有配套的精品资源点击获取