简介:目标检测是计算机视觉的核心任务之一,旨在从图像或视频中定位并识别出感兴趣的目标物体。其原理是通过深度神经网络学习图像特征,并回归出目标的位置和类别。这项技术具有极高的实用价值,是实现智能化应用的关键。在工程实践中,YOLO系列算法因其出色的速度与精度平衡而广受欢迎,尤其适合部署在资源受限的边缘设备上。其应用场景广泛,从安防监控到自动驾驶,再到工业质检,无处不在。本文聚焦于一个具体且重要的应用——驾驶员疲劳检测,并围绕此主题,详细介绍了一个包含5163张图像、针对眼睛和嘴巴状态(如闭眼、张嘴)的专用YOLO数据集的构建过程。该数据集旨在解决通用数据集标注粒度不足的问题,通过提供高质量的“眼睛+嘴巴”状态标注,为基于YOLO的疲劳检测模型训练提供了坚实基础,并分享了从数据准备、模型训练到最终疲劳判定逻辑设计的完整实战经验。
1. 项目概述:一个专为疲劳检测打造的YOLO数据集
最近在做一个关于驾驶员状态监测的项目,核心需求是检测闭眼、打哈欠(张嘴)这些典型的疲劳特征。找了一圈公开数据集,要么是通用的人脸关键点,标注粒度不够细;要么是纯学术研究用的,数据量小且场景单一,离实际应用差得远。最后,我决定自己动手,丰衣足食,整理并标注了这份包含5163张图像的数据集,涵盖了“张开嘴”、“闭上眼睛”、“闭着嘴”、“睁开眼睛”这四类关键状态。今天就把这个数据集的构建思路、标注细节、以及在YOLO框架下的应用心得完整分享出来,希望能给同样在做类似疲劳检测、行为分析的朋友们提供一个高质量的起点。
这个数据集的核心价值在于它的“场景针对性”和“标注实用性”。它不是简单的人脸检测,而是精准定位了与疲劳强相关的局部器官状态(眼睛和嘴巴)。对于想用YOLO这类单阶段目标检测算法快速落地疲劳检测应用的朋友来说,拿到这个数据集,几乎就完成了项目中最耗时、最考验耐心的数据准备工作。你可以直接用它来训练一个能够同时识别“闭眼”和“打哈欠”的模型,为后续的疲劳判定逻辑提供可靠的输入。
2. 数据集构建的核心思路与设计考量
2.1 为何选择“眼睛+嘴巴”状态作为检测目标?
在疲劳检测领域,基于计算机视觉的方法主流有两种:一种是基于人脸关键点(如Dlib 68点、MediaPipe Face Mesh),通过计算眼睛纵横比(EAR)和嘴巴纵横比(MAR)来判断状态;另一种就是像我们这样,直接使用目标检测框出眼睛和嘴巴的开放/闭合状态。
我选择后者的原因很直接:追求更高的鲁棒性和更低的部署成本。基于关键点的方法对头部姿态、遮挡、图像质量非常敏感,EAR/MAR的阈值需要精心调校,且容易受个体差异影响。而基于YOLO的目标检测,本质上是让模型自己去学习“闭着的眼睛”和“张开的嘴”在像素层面的特征模式。一旦训练好,模型对光线变化、部分遮挡、不同人种的适应性通常更强。更重要的是,YOLO模型可以非常方便地部署到边缘设备(如Jetson系列、树莓派配合加速棒)甚至手机端,无需依赖复杂的关键点检测库,整个流水线更简洁。
2.2 数据采集与类别定义
数据集总共5163张图像,这个规模对于训练一个稳健的YOLO模型来说是足够的。图像来源主要是公开的行车记录仪数据集、模拟驾驶舱录像截图以及部分网络合规图片,确保了场景的多样性,包括白天、夜晚、隧道、强光、侧脸等多种情况。
我们定义了四个互斥的类别:
mouth_open(张开嘴):通常指打哈欠或大声说话时的状态。标注框需要完整覆盖张开的嘴唇区域,包括露出的牙齿(如果可见)。mouth_closed(闭着嘴):嘴唇自然闭合的状态。这是基准状态,对于判断是否从闭合变为张开至关重要。eyes_closed(闭上眼睛):上下眼睑接触,看不见瞳孔。这是疲劳的最直接指标之一。eyes_open(睁开眼睛):眼睛自然睁开,瞳孔清晰可见。同样是重要的基准状态。
注意:这里没有标注整张人脸,而是直接标注目标状态区域。这样做的好处是模型更专注,不会被人脸的其他特征(如发型、眼镜)干扰,检测框也更紧凑,有利于后续的状态逻辑判断。
2.3 标注格式与质量把控
数据集采用YOLO格式的标注(.txt文件与图像同名)。每个标注文件包含多行,每行代表一个目标物体,格式为:class_id x_center y_center width height。坐标和尺寸都是相对于图像宽度和高度的归一化值(0到1之间)。
例如,一张图片中有一个张开的嘴和一个闭着的眼睛,其标注文件可能包含两行:
0 0.45 0.60 0.15 0.10 # mouth_open, class_id=0 2 0.50 0.35 0.20 0.08 # eyes_closed, class_id=2在标注过程中,我们制定了严格的规范:
- 边界框紧密度:框体必须紧贴目标边缘,特别是对于
mouth_open,要包含嘴角最外侧。 - 遮挡处理:对于被手、方向盘、口罩等部分遮挡的眼睛或嘴巴,只要可见部分超过50%且能明确判断状态,仍予以标注,但框体仅覆盖可见部分。这能增强模型在真实场景下的鲁棒性。
- 多目标处理:同一个人同时打哈欠和闭眼(极度疲劳)的情况虽然少,但存在,我们会分别标注
mouth_open和eyes_closed两个框。 - 类别模糊处理:对于微微张开或似闭非闭的状态,由三名标注员交叉审核,遵循“宁可漏标,不可错标”的原则,确保标注一致性。
3. 基于YOLOv8的模型训练实战
有了高质量的数据集,下一步就是把它用起来。这里我以目前生态非常完善的Ultralytics YOLOv8为例,展示完整的训练流程。你完全可以根据需要替换成YOLOv5, YOLOv9等其他版本,核心步骤是相通的。
3.1 环境准备与数据组织
首先,你需要一个Python环境(建议3.8以上),然后安装Ultralytics包:
pip install ultralytics接下来,按照YOLO要求组织你的数据集目录结构。假设你的项目根目录为fatigue_detection/,结构应如下:
fatigue_detection/ ├── datasets/ │ └── fatigue/ │ ├── images/ │ │ ├── train/ # 放置训练图片,如 001.jpg, 002.jpg... │ │ └── val/ # 放置验证图片 │ └── labels/ │ ├── train/ # 放置对应的训练标签txt文件 │ └── val/ # 放置对应的验证标签txt文件 ├── fatigue.yaml # 数据集配置文件 └── train.py # 训练脚本你需要将我们提供的5163张图像和标签,按照大约8:2的比例(或其他你习惯的比例)分割到train和val文件夹中。确保images/train/里的001.jpg对应labels/train/里的001.txt。
然后,创建关键的fatigue.yaml配置文件:
# fatigue.yaml path: ./datasets/fatigue # 数据集根目录 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 # 类别列表,顺序必须与标注时的class_id严格对应 names: 0: mouth_open 1: mouth_closed 2: eyes_closed 3: eyes_open3.2 模型训练与关键参数解析
创建一个简单的Python脚本train.py来启动训练:
from ultralytics import YOLO # 加载一个预训练模型,这里使用中等大小的YOLOv8m,在精度和速度间取得平衡 model = YOLO('yolov8m.pt') # 开始训练 results = model.train( data='fatigue.yaml', # 数据集配置文件路径 epochs=100, # 训练轮数,根据数据集大小调整 imgsz=640, # 输入图像尺寸,YOLOv8常用640 batch=16, # 批次大小,取决于你的GPU内存 device='0', # 使用GPU 0,如果是CPU则设为'cpu' workers=4, # 数据加载线程数 optimizer='AdamW', # 优化器,AdamW通常表现不错 lr0=0.001, # 初始学习率 name='fatigue_v8m' # 本次训练的实验名称 )执行python train.py,训练就开始了。控制台会输出损失曲线、精度指标等信息。这里有几个参数值得深入聊聊:
imgsz=640:YOLO会将所有图像缩放到这个尺寸进行训练。更大的尺寸(如1280)可能带来精度提升,但会显著增加显存消耗和训练时间。对于我们的局部器官检测任务,640x640的分辨率已经足够捕捉眼睛和嘴巴的细节。batch=16:批次大小直接影响训练稳定性和速度。如果遇到“CUDA out of memory”错误,首先尝试减小batch(如8或4),或者减小imgsz。epochs=100:对于5000多张图的数据集,100轮通常是一个合理的起点。你可以观察验证集精度(mAP50-95)曲线,如果早早在某个轮数后不再上升甚至下降,就可以考虑提前停止或降低学习率。- 预训练权重
yolov8m.pt:使用在COCO等大型数据集上预训练的权重进行迁移学习,是快速收敛的关键。不要从零开始训练。
3.3 训练过程监控与评估
训练过程中,Ultralytics会默认在runs/detect/fatigue_v8m/目录下保存所有结果,其中:
weights/best.pt:保存验证集上表现最好的模型权重。weights/last.pt:保存最后一轮的模型权重。- 各种可视化图表:如损失曲线、精度-召回率曲线、混淆矩阵等。
最重要的评估指标是mAP(Mean Average Precision):
mAP50:在IoU(交并比)阈值为0.5时的平均精度。这是最常用的指标,可以粗略理解为检测框的“找对且框得不太差”的能力。mAP50-95:在IoU阈值从0.5到0.95(步长0.05)区间内的平均mAP。这是一个更严格的指标,要求检测框的定位非常精准。
对于我们这个数据集,因为目标(眼睛、嘴巴)相对较小且固定,更应关注mAP50-95。一个训练良好的模型,mAP50通常能达到0.95以上,mAP50-95能达到0.7以上。
你可以使用训练好的模型在验证集上跑一下评估:
yolo val model=runs/detect/fatigue_v8m/weights/best.pt data=fatigue.yaml4. 从检测到疲劳判定的逻辑设计
模型训练好,能输出检测框和类别了,但这还不是疲劳检测的终点。我们需要一套逻辑,将连续的检测结果转化为“疲劳”或“清醒”的状态判断。这里分享一个经过实测有效的简单策略。
4.1 基于时间窗口的状态平滑与判定
单帧的检测结果是不稳定的,可能偶尔有误检或漏检。因此,必须引入时间维度进行平滑。我常用的是一个基于队列的滑动时间窗口方法。
import collections from typing import Deque class FatigueDetector: def __init__(self, eye_close_thresh=0.5, mouth_open_thresh=0.5, time_window=2.0, fps=30): """ :param eye_close_thresh: 时间窗口内闭眼帧占比阈值,超过则判定为疲劳闭眼 :param mouth_open_thresh: 时间窗口内张嘴帧占比阈值,超过则判定为持续哈欠 :param time_window: 滑动时间窗口长度(秒) :param fps: 视频帧率 """ self.eye_close_thresh = eye_close_thresh self.mouth_open_thresh = mouth_open_thresh self.window_size = int(time_window * fps) # 使用双端队列保存最近N帧的状态 self.eye_state_queue: Deque[bool] = collections.deque(maxlen=self.window_size) # True表示闭眼 self.mouth_state_queue: Deque[bool] = collections.deque(maxlen=self.window_size) # True表示张嘴 def update(self, detections): """ 更新状态队列。detections是当前帧YOLO模型的检测结果列表。 假设每个检测对象是一个字典,包含‘cls’(类别id)和‘conf’(置信度)。 """ current_eye_closed = False current_mouth_open = False for det in detections: if det['cls'] == 2 and det['conf'] > 0.6: # eyes_closed, 置信度阈值 current_eye_closed = True if det['cls'] == 0 and det['conf'] > 0.6: # mouth_open, 置信度阈值 current_mouth_open = True self.eye_state_queue.append(current_eye_closed) self.mouth_state_queue.append(current_mouth_open) def is_fatigue(self): """根据当前时间窗口内的历史状态判断是否疲劳""" if len(self.eye_state_queue) < self.window_size: return False # 窗口未填满,不进行判断 eye_close_ratio = sum(self.eye_state_queue) / self.window_size mouth_open_ratio = sum(self.mouth_state_queue) / self.window_size # 疲劳判定逻辑:闭眼比例过高,或长时间张嘴(打哈欠) is_eye_fatigue = eye_close_ratio > self.eye_close_thresh is_yawn_fatigue = mouth_open_ratio > self.mouth_open_thresh return is_eye_fatigue or is_yawn_fatigue这个FatigueDetector类的工作原理是:每来一帧,它根据YOLO的检测结果(经过置信度过滤)判断当前帧是否“闭眼”或“张嘴”,并将这个布尔值存入固定长度的队列。当队列被填满(即积累了2秒的数据)后,它计算队列中“闭眼”和“张嘴”各自的比例。如果闭眼比例超过50%(eye_close_thresh=0.5),就认为驾驶员在持续闭眼;如果张嘴比例超过50%,就认为在持续打哈欠。任何一种情况触发,则输出“疲劳”警报。
4.2 逻辑调优与参数经验
- 置信度阈值:上面代码中的
det[‘conf’] > 0.6很重要。它过滤掉模型不确定的、低置信度的检测,能有效减少误报。这个值可以通过在验证集上绘制“精度-置信度”曲线来选取最优值。 - 时间窗口与阈值:
time_window(2.0秒)和eye_close_thresh(0.5)是核心参数。它们共同定义了“持续闭眼1秒以上”即报警的规则。你可以根据实际应用场景调整:对于要求极其严格的场景(如高速货运),可以缩短时间窗口或提高阈值(如闭眼0.8秒就报警);对于容忍度稍高的场景,可以放宽。 - 多特征融合:更高级的策略可以结合PERCLOS(单位时间内眼睛闭合的比例)标准,或者将闭眼和打哈欠两个特征加权融合,而不是简单的“或”逻辑。
5. 模型优化与部署中的避坑指南
5.1 提升小目标检测精度
我们的目标(眼睛、嘴巴)在整张图中属于小目标。YOLO虽然擅长检测,但小目标仍是挑战。如果你的模型在验证集上mAP50不错但mAP50-95偏低,说明框的位置不够准,可以尝试以下优化:
数据增强:在
fatigue.yaml或训练命令中启用更强的针对小目标的增强。YOLOv8支持丰富的增强参数:model.train( ... hsv_h=0.015, # 色调增强 hsv_s=0.7, # 饱和度增强 hsv_v=0.4, # 明度增强 degrees=10.0, # 旋转角度 translate=0.1, # 平移 scale=0.5, # 缩放 shear=2.0, # 剪切 perspective=0.0005, # 透视变换,对小目标有益 mosaic=1.0, # Mosaic增强,默认开启,对丰富背景很有效 )注意,增强不是越强越好,过度增强可能破坏小目标的特征。建议从默认值开始,逐步微调。
调整锚框(Anchor)或使用Anchor-Free:YOLOv8默认是Anchor-Free的,这简化了流程。但如果你用的是YOLOv5,可能需要根据数据集聚类生成自适应的锚框。可以使用
utils/autoanchor.py工具重新计算。关注损失函数:确保
box_loss(定位损失)在稳步下降。如果box_loss很高,可能是定位不准。可以尝试使用CIoU、DIoU等更先进的边界框回归损失(YOLOv8已集成)。
5.2 模型轻量化与加速部署
实际落地时,模型速度往往和精度一样重要。如果你的部署平台算力有限(如嵌入式设备),可以考虑:
- 选择更小的模型:从
yolov8m换成yolov8s甚至yolov8n。速度会大幅提升,精度会有一定损失,需要通过量化等手段弥补。 - 模型剪枝与量化:
- 剪枝:移除网络中冗余的通道或层。可以使用一些第三方库(如Torch-Pruning)对训练好的模型进行剪枝,然后微调(fine-tune)。
- 量化:将模型权重从FP32转换为INT8,可以大幅减少模型体积和加速推理。Ultralytics YOLOv8支持导出时量化:
导出的INT8 ONNX模型在支持INT8推理的硬件(如TensorRT, OpenVINO)上会有显著加速。yolo export model=best.pt format=onnx int8
- 选择高效推理后端:
- ONNX Runtime:跨平台,部署简单,支持CPU/GPU。
- TensorRT:NVIDIA GPU上的极致优化,需要将模型转换为TensorRT引擎。
- OpenVINO:Intel CPU/GPU上的优化,对x86架构非常友好。
- CoreML:苹果设备(iOS/macOS)原生支持。
5.3 常见问题与排查技巧
训练Loss不下降或NaN:
- 检查数据标注:首先用YOLO自带的工具可视化一下标注框是否正确(
yolo train data=fatigue.yaml ...命令会在开始前自动验证)。常见问题是标注文件格式错误、类别ID超出范围、坐标值未归一化或大于1。 - 降低学习率:过高的学习率可能导致震荡甚至发散。尝试将
lr0从0.001降到0.0005或0.0001。 - 检查图像尺寸:确保所有图像都能正常读取,没有损坏的图片文件。
- 检查数据标注:首先用YOLO自带的工具可视化一下标注框是否正确(
验证集mAP很低,但训练集Loss正常:
- 过拟合:这是最可能的原因。表现为训练集精度很高,验证集精度很低。解决方案:增加数据增强的强度(如随机裁剪、遮挡);使用早停(Early Stopping);在模型结构中加入Dropout层(如果自定义模型);或者直接收集更多样化的数据。
- 数据分布不一致:训练集和验证集的数据分布(光照、场景、人物)差异太大。确保你的数据分割是随机且均匀的。
推理时漏检严重:
- 调整置信度阈值:模型推理时有一个
conf参数(默认0.25)。如果漏检,可以尝试降低这个阈值(如0.1),让更多候选框被保留,但可能会增加误检。 - 检查输入分辨率:训练时用
imgsz=640,推理时也必须用相同的尺寸。如果你用不同尺寸推理,效果会大打折扣。 - NMS参数:非极大值抑制(NMS)的
iou阈值(默认0.7)也可能影响。对于密集小目标,可以适当提高iou阈值(如0.8),让重叠的框更容易被保留。
- 调整置信度阈值:模型推理时有一个
在嵌入式设备上速度慢:
- 使用TensorRT或OpenVINO:这是最有效的加速手段。
- 降低推理分辨率:如果精度允许,可以将推理时的图像尺寸从640降到480甚至320。
- 使用半精度(FP16):大部分现代GPU和嵌入式芯片都支持FP16,速度比FP32快,精度损失很小。
6. 数据集扩展与应用场景展望
这份5163张图的数据集是一个强大的起点,但真实世界是复杂多变的。要让你的疲劳检测系统真正可靠,可以考虑从以下几个方向扩展数据集:
- 增加极端场景:更多强光、逆光、夜间低光照、戴墨镜、戴普通眼镜、侧脸角度极大(超过90度)的样本。
- 增加干扰物:模拟被手、手机、食物等物体部分遮挡口鼻的情况。
- 丰富人种与年龄段:确保数据集中包含不同肤色、年龄的驾驶员,减少模型偏见。
- 标注连续帧序列:目前是图片级标注。未来可以标注视频片段,用于训练时序模型(如3D CNN, LSTM结合YOLO),更好地理解疲劳的动态过程。
这个数据集和训练出的模型,其应用场景远不止于驾驶员疲劳检测:
- 在线教育专注度分析:检测学生上网课时是否走神(长时间闭眼、打哈欠)。
- 远程办公会议参与度:在征得同意和符合隐私法规的前提下,分析会议参与者的精神状态。
- 安防与看护:用于监控特殊岗位(如保安、流水线工人)的精神状态,或者看护老人、病人是否出现异常长时间闭眼。
- 互动娱乐:作为游戏或AR应用的一种输入方式,通过张嘴、眨眼触发特定互动。
最后,我想强调的是,任何一个AI落地项目,高质量的数据都是地基。这份数据集是我在多个实际项目中反复清洗、标注、迭代的成果,希望它能帮你跳过从0到1最痛苦的阶段。模型训练和调参固然需要技巧,但比起数据质量,它们更像是“锦上添花”。如果你在使用的过程中有任何新的发现,或者用这个数据集做出了更有趣的应用,也欢迎一起交流。
本文还有配套的精品资源,点击获取