ARTICLE DETAIL

资讯详情

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

YOLOv8疲劳驾驶检测实战:面部特征与自适应阈值调优

YOLOv8疲劳驾驶检测实战:面部特征与自适应阈值调优 简介这份资源面向计算机视觉学习者、交通安全方向研究者及需要实战项目的开发者提供一套基于YOLOv8与面部特征检测的驾驶员疲劳瞌睡检测完整方案可用于长途与夜间驾驶场景下的实时疲劳监控。压缩包共16个文件约24.81MB包含8个Python脚本承担模型训练、推理与界面管理2个pt权重文件与2个png示意图另有wav音频、md说明、yaml配置和xml标注文件覆盖数据预处理、检测推理到结果可视化的完整链路。目前已有616人学习下载。项目将目标检测定位面部区域再结合眼睛开闭状态与眨眼频率判断疲劳程度源码结构清晰便于理解YOLOv8的工程落地方式也适合在此基础上扩展表情识别或分心驾驶检测等方向是兼顾理论与实战的优质参考。1. 疲劳驾驶检测YOLOv8加面部特征到底怎么落地凌晨两点跑高速眼皮开始打架的那几秒方向盘一歪就是几十米。疲劳驾驶检测要解决的就是这个场景——在驾驶员真正睡着之前系统先报警。标题里的方案是 YOLOv8 加面部特征检测说白了就是用目标检测模型先把人脸、眼睛、嘴巴框出来再根据眼睛闭合时长、嘴巴张合频率这些特征判断是不是瞌睡。这套组合的好处是 YOLOv8 推理快、精度够面部特征逻辑又足够轻量普通摄像头加一块中端显卡就能跑起来。适合谁做毕业设计的学生、想给车队加主动安全预警的嵌入式工程师、以及手里有行车记录仪视频想跑个 demo 的开发者。项目源码通常包含训练脚本、推理脚本和权重文件但真正决定成败的是后处理逻辑和阈值调参这部分我会在中间章节拆开讲。2. 从人脸框到疲劳判定YOLOv8 检测层与面部特征逻辑2.1 为什么选 YOLOv8 而不是人脸关键点专用模型常见的人脸关键点方案是 MTCNN 或 PFLD 这类专用网络它们输出 68 个或 5 个关键点精度高但推理链路长在 1080p 视频上单帧经常跑到 30ms 以上。YOLOv8 的优势在于一次前向就能输出人脸框如果训练时把眼睛和嘴巴也标成独立类别模型可以直接给出眼睛、嘴巴的位置省掉二次裁剪和关键点回归。我一般会把人脸、左眼、右眼、嘴巴四个类一起训这样后处理只需要拿框的中心点和宽高比做判断代码量少延迟也低。另一个理由是 YOLOv8 的生态成熟导出 ONNX、TensorRT 都方便后面要上 RK3588 或 Orin 这类边缘板子时迁移成本低。2.2 数据集标注用 Labelme 还是直接上 YOLO 格式标题里提到项目源码通常数据集已经标好了但如果你想用自己的行车视频标注这一步绕不开。Labelme 适合标多边形但 YOLOv8 训练要的是矩形框加类别索引的 txt 文件。我的习惯是先用 Labelme 标人脸和眼睛嘴巴的矩形框再用脚本转成 YOLO 格式。转换时注意坐标归一化x_center 和 y_center 要除以图像宽高宽高也要归一化。下面这个脚本处理单个 Labelme 的 json 文件批量跑的时候套一层 os.listdir 就行。import json import os def labelme_to_yolo(json_path, output_dir, class_map): # class_map 形如 {face: 0, eye: 1, mouth: 2} with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 归一化并转成 YOLO 的 center_x center_y w h x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{class_map[label]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) base os.path.splitext(os.path.basename(json_path))[0] out_path os.path.join(output_dir, base .txt) with open(out_path, w) as f: f.write(\n.join(lines))逻辑说明Labelme 的 points 是矩形框的左上和右下两个点取 min/max 就能还原边界。class_map 把文字标签映射成数字YOLOv8 训练时只认数字。参数上注意归一化必须用原图宽高如果图片被预处理过尺寸这里要同步改。转换完建议抽查几张用可视化脚本把框画回图上确认没有坐标翻转。2.3 训练参数epochs、imgsz 和 batch 怎么定YOLOv8 的训练命令很简洁但参数设错会导致 mAP 上不去或者显存爆掉。我一般用 yolov8n 或 yolov8s 做疲劳检测因为人脸区域占画面比例大不需要太深的网络。imgsz 设 640 足够如果摄像头是 1080p 且人脸较小可以提到 960但显存占用会翻倍。batch 根据显卡来GTX1660Ti 6G 跑 yolov8n 可以到 16跑 yolov8s 建议 8。epochs 先跑 100 看损失曲线如果验证集 mAP 在 80 轮后还在涨就加到 200。下面是一条我常用的训练命令。yolo detect train \ dataface_eye_mouth.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ device0 \ projectruns/fatigue \ nameexp1参数含义data 指向数据集配置文件里面写 train/val 路径和类别数。model 加载预训练权重yolov8n.pt 从官方仓库下就行。lr0 是初始学习率0.01 对小数据集偏大如果损失震荡就降到 0.001。patience 是早停轮数30 轮验证集不涨就停省时间。训练完在 runs/fatigue/exp1/weights/best.pt 拿到最优权重。2.4 疲劳判定的后处理EAR 和 MAR 的阈值怎么算检测出眼睛和嘴巴的框之后疲劳判定靠两个指标眼睛宽高比 EAR 和嘴巴宽高比 MAR。EAR 等于眼睛框的宽除以高正常睁眼时一般在 0.25 到 0.35 之间闭眼时会掉到 0.15 以下。MAR 是嘴巴宽除以高打哈欠时嘴巴张开MAR 会从 0.3 左右升到 0.6 以上。但这里有个坑YOLO 输出的框是矩形眼睛闭上的时候框会变扁宽高比反而可能变大所以不能直接用框的宽高比要用眼睛区域内的像素分布或者关键点。如果项目源码里用的是矩形框我建议加一个简单的二值化判断把眼睛框裁出来转灰度算暗像素占比闭眼时暗像素比例明显升高。下面这段代码演示了基于框宽高比和暗像素比例的混合判断。import cv2 import numpy as np def eye_aspect_ratio(box): # box: [x1, y1, x2, y2] w box[2] - box[0] h box[3] - box[1] return w / max(h, 1) def dark_pixel_ratio(frame, box, threshold60): x1, y1, x2, y2 map(int, box) roi frame[y1:y2, x1:x2] if roi.size 0: return 0.0 gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) dark np.sum(gray threshold) return dark / gray.size # 连续帧计数 closed_frames 0 CLOSED_THRESH 0.4 # 暗像素占比超过 0.4 视为闭眼 FRAME_LIMIT 15 # 连续 15 帧闭眼触发报警 def check_fatigue(frame, eye_boxes): global closed_frames for box in eye_boxes: ratio dark_pixel_ratio(frame, box) if ratio CLOSED_THRESH: closed_frames 1 break else: closed_frames max(0, closed_frames - 1) return closed_frames FRAME_LIMIT逻辑说明dark_pixel_ratio 统计眼睛区域里灰度值低于 60 的像素比例闭眼时睫毛和眼皮遮挡暗像素比例会升高。CLOSED_THRESH 设 0.4 是经验值不同光照下要微调。FRAME_LIMIT 对应时间30fps 下 15 帧是 0.5 秒超过这个时长就报警。注意这段代码只用了眼睛实际项目里还会加嘴巴的 MAR 判断打哈欠两个条件满足一个就触发。3. 把模型跑起来环境搭建、推理脚本与报警联动3.1 Ubuntu 20.04 上配 YOLOv8 的 CPU 和 GPU 两条路热词里有人问 ubuntu20.04 搭建 yolov8 环境 cpu 版本这条路适合没有独显的机器比如用笔记本做 demo。CPU 版安装很简单先建虚拟环境再装 ultralytics 和 opencv。GPU 版要多一步 CUDA 和 cuDNNGTX1660Ti 这类卡装 CUDA 11.8 比较稳。下面给出两条命令序列按需选一条。# CPU 版本 python3 -m venv yolo_env source yolo_env/bin/activate pip install ultralytics opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple # GPU 版本先确认 nvidia-smi 能看到显卡 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python参数说明-i 指定清华源加速下载。GPU 版先装 torch 再装 ultralytics避免 ultralytics 自动拉 CPU 版 torch。装完用 python -c import torch; print(torch.cuda.is_available()) 验证返回 True 才算成功。如果返回 False检查驱动版本和 CUDA 版本是否匹配。3.2 推理脚本从视频流到报警输出的完整链路训练完拿到 best.pt推理脚本要做三件事读视频帧、跑 YOLO 检测、根据检测结果做疲劳判断并触发报警。我一般用 OpenCV 的 VideoCapture 读摄像头或视频文件每帧送进模型拿到人脸、眼睛、嘴巴的框再调上面的疲劳判断函数。报警可以用蜂鸣器、弹窗或者写日志demo 阶段用 cv2.putText 在画面上打红字最省事。下面是一个最小可运行脚本。import cv2 from ultralytics import YOLO model YOLO(runs/fatigue/exp1/weights/best.pt) cap cv2.VideoCapture(0) # 0 是默认摄像头也可以换成视频路径 while True: ret, frame cap.read() if not ret: break results model(frame, imgsz640, conf0.5, verboseFalse) eye_boxes [] for r in results: for box in r.boxes: cls_id int(box.cls[0]) xyxy box.xyxy[0].tolist() if cls_id 1 or cls_id 2: # 假设 1 和 2 是左右眼 eye_boxes.append(xyxy) # 画框 cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (0, 255, 0), 2) if check_fatigue(frame, eye_boxes): cv2.putText(frame, FATIGUE ALERT, (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 255), 3) cv2.imshow(driver, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明conf0.5 是置信度阈值低于这个值的框丢掉减少误检。verboseFalse 关掉每帧的日志输出不然终端会刷屏。eye_boxes 收集左右眼的框传给 check_fatigue 做判断。报警这里只做了画面提示实际部署可以接 GPIO 控制蜂鸣器或者通过串口发给车载终端。3.3 模型导出与边缘部署ONNX 和 RK3588 的注意点如果要把模型放到 RK3588 或 Orin 上跑需要先导出 ONNX 再转 RKNN 或 TensorRT。YOLOv8 导出 ONNX 的命令很简单但要注意 opset 版本和动态轴。RK3588 的 RKNN 工具链对 opset 11 支持最好导出时指定 opset11。另外输入尺寸最好固定动态 batch 在边缘板上容易出问题。下面是对比表列出几种部署方式的差异。部署方式导出命令适用硬件注意点ONNXyolo export modelbest.pt formatonnx opset11通用动态轴可能导致 RKNN 转换失败TensorRTyolo export modelbest.pt formatengine halfTrueNVIDIA GPUhalf 精度在部分卡上掉点RKNN先导 ONNX 再用 rknn-toolkit2 转换RK3588需要量化校准集否则精度损失大导出 ONNX 后建议用 onnxruntime 跑一遍验证输出和 PyTorch 一致再上板子。RK3588 部署时量化校准集要覆盖白天、夜间、戴眼镜等场景不然量化后眼睛框容易漏检。4. 避坑与排查疲劳检测项目里最容易翻车的五件事4.1 夜间红外画面下眼睛框直接消失现象白天跑得好好的模型一到晚上或者进隧道眼睛框检测不到了报警频繁误触发。原因训练集里夜间样本太少YOLOv8 对红外成像的灰度分布不熟悉眼睛区域对比度低。解决采集夜间红外视频补标 500 到 1000 张训练时加 HSV 增强里的亮度扰动推理时对图像做直方图均衡化再送模型。4.2 戴眼镜反光导致嘴巴误判为打哈欠现象驾驶员戴眼镜时镜片反光在嘴巴区域形成高亮斑块MAR 计算异常系统频繁报疲劳。原因反光区域被检测成嘴巴框的一部分宽高比失真。解决训练时加入戴眼镜和反光样本后处理里加一个亮度均匀性检查如果嘴巴框内高亮像素占比超过 30% 就跳过这一帧的 MAR 判断。4.3 连续帧计数在丢帧时归零现象摄像头偶尔丢帧closed_frames 计数被重置明明闭眼 1 秒却没报警。原因丢帧时 check_fatigue 没被调用或者帧率不稳定导致计数逻辑错乱。解决用时间戳代替帧计数记录闭眼开始的时间当前时间减去开始时间超过 0.5 秒就报警这样丢几帧不影响。4.4 模型导出 ONNX 后输出对不上现象PyTorch 推理正常导出 ONNX 后用 onnxruntime 跑框的位置偏移或者类别错乱。原因导出时没指定动态轴或者后处理里的 anchor 解码方式和导出图不匹配。解决导出时加 dynamicFalse 固定输入尺寸用 ultralytics 自带的推理接口跑 ONNX不要自己手写解码。对比两边输出如果差得多检查 opset 和输入归一化是否一致。4.5 阈值调好后换个人就失效现象自己测试时 EAR 阈值 0.2 很准换个人开就频繁误报。原因不同人眼型差异大单阈值泛化差。解决用滑动窗口自适应统计最近 300 帧的 EAR 均值闭眼阈值设为均值的 0.6 倍这样能跟随不同驾驶员调整。或者直接上关键点模型用 68 点里的眼睛轮廓算 EAR比矩形框稳得多。5. 把误报压下去自适应阈值与多特征融合的实战技巧疲劳检测做到最后拼的不是模型精度而是误报率。我踩过最深的坑是高速上阳光穿过树叶在脸上闪眼睛框忽大忽小系统一路报警驾驶员直接关掉了。后来我改成多特征融合加自适应阈值误报从每小时十几次降到一两次。具体做法是EAR 用滑动窗口算均值窗口大小 300 帧闭眼阈值取均值的 0.55 倍MAR 单独算打哈欠阈值取均值的 1.8 倍再加一个头部姿态判断如果人脸框中心在画面里持续偏移超过 20% 宽度说明驾驶员在低头或转头这时候降低疲劳判定权重。三个条件里满足两个才报警单条件只记录不触发。验证方法上我一般用自己录的 10 段视频做回归测试每段 5 分钟覆盖白天、夜间、戴眼镜、打哈欠、正常驾驶五种场景。统计每段的误报次数和漏报次数误报优先压漏报可以通过降低阈值补。下面这个表格是我最近一次调参的记录供参考。场景原阈值误报自适应后误报漏报变化白天正常30无夜间红外81无戴眼镜51增加 1 次打哈欠20无还有一个技巧是模型热切换。白天用彩色模型夜间自动切到红外增强模型两个模型共享同一套后处理。切换依据是画面平均亮度低于 50 就切红外模型。这个逻辑用 OpenCV 的 cv2.mean 就能算开销可以忽略。最后说个血泪教训别在训练集里混入太多正常驾驶的负样本否则模型会把闭眼学成背景mAP 看着高但实际漏报严重。我一般正负样本比例控制在 1:2 左右负样本里要有闭眼但没疲劳的片段让模型学会区分闭眼和疲劳。希望帮到你。本文还有配套的精品资源点击获取
返回列表