基于YOLOv8与PERCLOS的驾驶者行为监测预警系统实战
2026/9/13 2:44:22 网站建设 项目流程

简介:基于深度学习的驾驶者行为监测预警系统毕业设计项目压缩包,由中南大学信息科学与工程学院与交通学院合作完成,面向计算机/人工智能方向毕业设计学生与研究人员,针对疲劳驾驶、分心行为实时监测预警问题,提供从数据预处理到模型训练、测试与推理的完整工程实现,覆盖语音识别、面部状态识别、肢体动作识别三条技术路线。压缩包共43个文件,约21.75MB,包含21个Python源码(如twoStream_CNN、VGGNet等模型实现)、4个Markdown说明文档、2个PDF技术文档以及配置文件等,既可看到深度学习模型定义与训练脚本,也能找到摄像头调用、语音转文本、数据集加载等辅助代码。目前已有172人学习浏览,适合正在准备毕业设计或想了解多模态行为分析系统的读者参考。从中可系统学习CNN、双流网络、VGGNet等经典模型的实际应用,理解多源数据(视觉、语音、肢体动作)的处理流程,并借鉴其模块划分快速搭建预警系统原型。

1. 深度学习驾驶者行为监测预警系统的两级问题

深度学习驾驶者行为监测预警系统这类题目,每年毕设季都扎堆。多数第一版方案从闭眼检测起步,但现场验证会发现:单帧检出闭眼只是第一级问题,真正决定系统能用与否的是第二级——闭眼持续多久、频率是否异常、要不要报警。两级不打通,模型再准也落不了地。

标题里的跨院合作不是锦上添花。信息类学院负责感知模型与推理管线,就是"看得到";交通类学院负责行为定义与风险等级,就是"看得懂"。系统能否通过评审,往往卡在后者。

下文按完整链路展开:任务拆解与选型,YOLOv8 训练单帧行为检测,PERCLOS 疲劳判定与分级告警,模型导出与现场验证。按顺序跑完,可得到能演示的最小系统。

2. 驾驶者行为监测的技术拆解与模型选型

2.1 三类检测任务:目标检测、关键点回归与时序动作识别

驾驶舱内部署深度学习方案,最容易犯的错误是把所有行为塞进同一个模型。实际上驾驶者行为可以拆成三类性质完全不同的任务。

第一类是"拿没拿、碰没碰"——手拿手机、递烟、喝水、伸手够东西,本质是目标检测问题,而不是通用图像识别问题,模型需要输出行为物品或手部的位置框和类别。第二类是"睁没睁、打没打"——闭眼、打哈欠、点头,需要回归眼睛轮廓、嘴部轮廓和头部姿态关键点,再用几何指标判断状态。第三类是"持续了多久"——连续转头离开前方超过 3 秒、分心事件累计时长,这是时序任务,可以用 TSM 这类动作识别模型,也可以用状态统计加阈值规则处理。

从工程落地角度看,三类任务的性价比差异非常大:

任务代表模型输出实时性实现成本
行为物品检测YOLOv8、RT-DETR类别 + 位置框30 FPS 以上
面部关键点MediaPipe FaceMesh、dlib 68 点468/68 个关键点单帧毫秒级
时序动作识别TSM、SlowFast片段动作类别需缓存 16~64 帧

常见做法是:单帧行为检测用 YOLO 系列,疲劳判定用关键点加规则,不做端到端动作识别。理由很直接——动作识别需要的数据量按视频片段计,标注成本远高于静态帧;车舱内多数行为靠单帧就能判别,时序信息交给 PERCLOS 这类统计指标处理,解释性强,答辩时也更容易讲清楚推理依据。也有方案直接对整帧分类,输出"安全驾驶/打电话/抽烟",演示视频上看着准确,但无法定位行为发生的区域,换场景后鲁棒性差,错误样本没法归因,调试阶段会很痛苦。理论基础不够的话,补《动手学深度学习》的目标检测和循环网络章节即可,不必整本啃完花书。

2.2 公开数据集与自建数据的差异

训练单帧行为检测,公开数据集选择不多。State Farm Distracted Driver Detection 是目前最常用的,约 2.2 万张驾驶舱视角图像,覆盖正常驾驶、打电话、发短信、喝水等类别;3MDAD 和 AUC 数据集侧重头部姿态与视觉注意力。但这些数据全部来自左侧驾驶、仪表台上方固定机位的采集,直接迁移到右侧驾驶或不同摄像头位置的场景,精度下降会非常明显。

自建数据最需要控制的不是数量而是标注一致性。以"手机"类别为例,人手举着手机放在方向盘上方、耳边、大腿上时,边界框大小差异极大,标注员在"是否框入手掌"上的分歧会直接污染模型。常见做法是先定标注规范,例如:框需要包含手机完整轮廓,手掌可以不完整;打电话场景下手机与手部合并为一个框;双手离开方向盘但没有任何物品时不标注。规范确定后再用 labelme 或 X-AnyLabeling 标注,统一转成 YOLO 格式。

# labelme 多边形标注批量转 YOLO txt 的核心逻辑 import json import numpy as np from pathlib import Path import cv2 CLASS_ID = {"safe": 0, "phone": 1, "smoke": 2, "drink": 3, "look_away": 4} def convert(json_path: Path, img_dir: Path, out_dir: Path): ann = json.loads(json_path.read_text(encoding="utf-8")) img = cv2.imread(str(img_dir / ann["imagePath"])) h, w = img.shape[:2] lines = [] for shape in ann["shapes"]: label = shape["label"] if label not in CLASS_ID: continue # 未定义类别直接跳过 pts = np.array(shape["points"], dtype=np.float32) x_min, y_min = pts.min(axis=0) x_max, y_max = pts.max(axis=0) x_min, x_max = max(0, x_min), min(w, x_max) y_min, y_max = max(0, y_min), min(h, y_max) if (x_max - x_min) < 2 or (y_max - y_min) < 2: continue # 过滤退化成点或线的无效框 cx = (x_min + x_max) / 2 / w cy = (y_min + y_max) / 2 / h bw = (x_max - x_min) / w bh = (y_max - y_min) / h lines.append(f"{CLASS_ID[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_dir.mkdir(parents=True, exist_ok=True) (out_dir / (json_path.stem + ".txt")).write_text( "\n".join(lines), encoding="utf-8")

脚本把 labelme 的多边形坐标取外接矩形,归一化后写入 YOLO txt。两个细节要注意:类别 ID 必须与后续训练 yaml 的 names 顺序严格一致;多边形退化成点或线时外接矩形宽高接近零,训练阶段会出现空标注告警,转换时用面积阈值过滤掉。

2.3 为什么选 YOLOv8 作为主体模型

单帧检测的选型通常在 Ultralytics YOLOv8 和 RT-DETR 之间讨论。RT-DETR 精度上限更高,但 transformer 解码器在嵌入式设备上的算子支持不如 YOLO 成熟;YOLOv8 采用 anchor-free 头部,不存在预设锚框匹配问题,训练时的收敛过程更可控,模型体积从 nano 到 x 梯度完整,便于先在办公显卡上跑通,再压缩到 Jetson 或 RK3588 上部署。

网络层数在这个任务里的作用比想象中小。车舱内目标尺度和景深变化都不大,yolov8n 与 yolov8s 的精度差距通常在 3~5 个 mAP 点以内,但推理延迟差距接近一倍。如果演示机是普通办公笔记本,直接用 yolov8s;如果目标平台是边缘盒子,从 8n 开始,把省下的算力留给关键点检测和跟踪。

提示:选型只看 mAP 不够,务必拿自己摄像头实拍的帧做一次脏数据测试。公开数据集里几乎全是光线均匀、驾驶员居中的理想画面,真实车舱的逆光和抖动才是决定成败的地方。

3. 用 YOLOv8 训练驾驶者行为监测模型的完整流程

3.1 深度学习环境配置与依赖安装

深度学习环境配置是第一个卡点,推荐用 conda 建独立环境,避免污染系统 Python。

conda create -n driver_monitor python=3.9 -y conda activate driver_monitor pip install "ultralytics==8.2.100" "torch==2.1.2" "torchvision==0.16.2" pip install opencv-python onnxruntime

把 ultralytics 固定到 8.2 大版本是有意的:这一系列对 YOLOv8 的训练默认值做过收敛性调整,日志和报错在网络上可查的信息最全;后续版本改了不少默认行为,对不上教程时排查成本很高。torch 2.1.2 与 torchvision 0.16.2 是配套版本,CUDA 11.8 环境下即可正常训练,RTX 3060 及以上显卡够跑完整个流程。

3.2 数据目录与训练配置

YOLOv8 要求图片和标签目录分离:

data/ └── driver/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/

train 与 val 的分割建议按视频片段切,而不是按帧随机切。同一段视频的相邻帧高度相似,按帧随机切分会让验证集被训练集"剧透",评估指标虚高,换到新场景马上现原形。

训练配置文件 driver_detection.yaml:

path: /home/user/data/driver train: images/train val: images/val nc: 5 names: 0: safe 1: phone 2: smoke 3: drink 4: look_away

启动训练:

yolo detect train \ data=driver_detection.yaml \ model=yolov8s.pt \ epochs=60 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=15 \ workers=4 \ project=runs/driver_detection \ name=exp01

关键参数的调整方向:

参数本次取值何时修改
imgsz640目标占比大时可降到 544,训练和推理同步提速
batch168GB 显存用 8,24GB 显存可加到 32,batch 直接影响 BN 统计量的估计可靠性
epochs60自建数据量小时 50~80 足够,收敛后由 patience 提前截断
lr00.01使用预训练权重迁移时可尝试 0.001,避免破坏底层特征
patience15验证集 mAP 连续 15 个 epoch 不提升即停止,节省时间

除了 epochs 和 lr0,Ultralytics 训练脚本默认开启 hsv_h、hsv_s、hsv_v 色彩增强以及 translate、scale、fliplr 空间增强。车内场景不建议关掉色彩增强,因为车窗进光和不同时段的色温差异大;fliplr 要谨慎——如果类别与手部左右语义相关,开启水平翻转会产生语义矛盾的样本。mosaic 增强在车内目标尺度不大的场景下容易出现目标被拼接裁碎的问题,训练后期发现小目标召回率异常时,把 mosaic 从 1.0 降到 0.3~0.5 再续训即可。

3.3 从训练日志里看真实水平

训练结束后先看混淆矩阵,而不是只盯着 mAP。驾驶行为数据里类别不均衡几乎是必然的:normal 帧占一半以上,smoke、drink 可能只有几百框。整体 mAP 可以很高,逐类看 recall 却发现 smoke 不足 0.5,这种情况在答辩现场拿真实视频一跑就露馅。

另一个常见问题是损失函数被大目标主导。YOLOv8 默认用 CIoU 做边界框回归,一张图里手机框很小、肩部框很大时,大框的误差会主导梯度。缓解做法有两种:把 imgsz 提到 1280,让小目标占更多像素;或者直接按驾驶位区域裁剪后再训练。两种方案都有效,第二种对算力要求更低,适合自建数据量有限的场景。

4. 驾驶者疲劳预警:PERCLOS 时序判定与分级告警

4.1 用关键点计算 EAR 闭眼指标

行为检测解决"分心"类问题,疲劳需要另一条线。行业通用的疲劳指标是闭眼百分率 PERCLOS,它的前端是眼部关键点回归。使用 dlib 68 点模型时,左眼取索引 36~41 六个点,右眼取 42~47。

EAR(Eye Aspect Ratio)的计算公式为:

EAR = (||P2 − P6|| + ||P3 − P5||) / (2 × ||P1 − P4||)

P1、P4 是内外眼角,P2/P3 是上眼睑两点,P5/P6 是下眼睑两点。睁眼时 EAR 约 0.28~0.35,完全闭眼时降到 0.15 以下。

import numpy as np def eye_aspect_ratio(landmarks, eye_idx=(36, 37, 38, 39, 40, 41)): # dlib 68 点索引:左眼 36-41,右眼 42-47 p1, p2, p3, p4, p5, p6 = (landmarks[i] for i in eye_idx) vertical_a = np.linalg.norm(p2 - p6) # 左垂直距离 vertical_b = np.linalg.norm(p3 - p5) # 右垂直距离 horizontal = np.linalg.norm(p1 - p4) # 眼裂宽度 return (vertical_a + vertical_b) / (2.0 * horizontal + 1e-6)

加 1e-6 是为了防止水平距离为零时除零。EAR 是纯几何比例,不依赖关键点输出的绝对坐标值,所以对摄像头安装高度和不同脸型有一定鲁棒性;但它对面部光照变化敏感,侧光造成某侧眼睑关键点漂移时,EAR 瞬时值会异常跳变,需要在时序层做滤波。

若改用 MediaPipe FaceMesh,计算方法不变,把 68 点索引替换成 468 点中对应的眼角与上下眼睑索引即可。实测中 EAR 阈值取 0.21~0.23 比盲目取 0.2 更贴合实际,因为车规摄像头距人约半米时眼睑在像素上的波动明显;阈值必须在真机画面的录像上标定,不能照抄论文数值。

4.2 PERCLOS 滑动窗口与疲劳阈值

单帧 EAR 只说明当前是否闭眼,疲劳本身是统计概念。PERCLOS 定义为固定时间窗口内眼睛闭合时间所占比例。按 30 FPS 的视频流、60 秒窗口计算,PERCLOS 等于窗口内 EAR 低于阈值的帧数除以窗口总帧数再乘 100%。窗口长度是精度与响应速度的折中:30 秒窗口判定快,但受单次闭眼影响大;3 分钟窗口更平缓,演示等待太久;60 秒是论文和实车方案里最常见的取值。

from collections import deque class PerclosCounter: def __init__(self, window_seconds=60, fps=30, ear_threshold=0.22): self.events = deque(maxlen=window_seconds * fps) self.threshold = ear_threshold def update(self, ear_value) -> float: self.events.append(int(ear_value < self.threshold)) return sum(self.events) / len(self.events) * 100.0

deque 的 maxlen 天然实现滑动窗口淘汰,不需要手工管理队列长度。窗口未填满时 len(self.events) 是实际帧数,前几秒的 PERCLOS 数值会自然过渡,不会出现初始化跳变。

阈值配置参考:

指标阈值含义
单帧 EAR0.22低于该值视为闭眼
PERCLOS / 60s大于 20%判定为疲劳状态
单次闭合时长大于 1.5 秒判定为微睡眠前兆
哈欠 MAR大于 0.6 持续 1 秒与闭眼联合确认疲劳

满足单次闭合 1.5 秒或 PERCLOS 超过 20% 时触发一级预警;两者同时满足时升级为二级,触发声光告警并截存前 30 秒视频片段。

4.3 分级告警的状态机设计

预警系统最忌讳抖动:EAR 在阈值附近波动会让告警灯闪烁不停。常见做法是引入滞后比较和持续帧数确认。

class FatigueAlarm: def __init__(self, fps=30): self.closed_frames = 0 self.fps = fps def evaluate(self, ear_value, perclos) -> int: # 返回 0 无告警,1 一级提示,2 二级强告警 if ear_value < 0.22: self.closed_frames += 1 else: self.closed_frames = max(0, self.closed_frames - 2) if self.closed_frames > int(1.5 * self.fps): return 2 if perclos > 20.0: return 1 return 0

闭眼帧计数在连续闭眼时累加,睁眼后按每帧减 2 的速率衰减,形成一个轻微的滞后带,避免状态在告警边界来回跳动。如果现场误报仍多,把衰减速率改为减 1,让告警状态维持更久。

一级提示是仪表台闪烁指示灯;二级告警要求蜂鸣加语音播报加事件片段落盘。落盘需回溯 30 秒,最简单的实现是在内存里维护一个 30 秒的环形帧缓冲,触发时才写入磁盘,避免全程录像撑爆存储。

5. 监测预警系统的工程落地:模型导出、推理提速与三个现场验证技巧

5.1 用 ONNX 导出摆脱 PyTorch 运行时依赖

演示机装完整 PyTorch 没问题,但换机器部署时 ONNX 是更可靠的载体。

from ultralytics import YOLO model = YOLO("runs/driver_detection/exp01/weights/best.pt") model.export(format="onnx", dynamic=True, simplify=True, imgsz=640)

dynamic=True 让 batch 和宽高维度保持动态;simplify=True 会调用 onnx-simplifier 做算子折叠与融合。导出后必须对比 ONNX 与 PyTorch 的输出差异,容差低于 1e-3 才算有效;超出说明算子替换引入了精度损失,需要考虑降低 simplify 等级。

5.2 双线程管线与跳帧策略

单线程里摄像头采集和模型推理串行,笔记本上通常只有 15 FPS。拆成采集、推理两个线程并用队列解耦后,推理线程能跑满模型自身的速度。这个改造代码量很小,是性价比最高的第一步优化。

跳帧策略留给算力受限的边缘板卡。行为检测模型每 3~5 帧跑一次,约 9~15 FPS 的事件采样率对驾驶行为完全够用;中间帧只做 EAR 计算,因为关键点几何量在 CPU 上单帧也就一两毫秒。

5.3 三个现场验证技巧

第一,固定机位再标定阈值。摄像头固定后录制 3 分钟包含遮挡面部、戴墨镜、侧头打电话的视频,把每帧的 EAR 和模型置信度导出成 CSV 后离线回放,所有阈值调整都基于 CSV 而不是现场肉眼判断。

第二,防误报只信时序不信单帧。把单帧告警改成"连续 3 帧同一类别且置信度超过阈值才计入事件",误报能降一半以上。这一点在答辩视频演示里尤其重要,现场观众不会记得模型识别对了几次,但会记得警报乱响了几次。

第三,留一条人工兜底通道。演示机上保留一个快捷键手动触发告警和落盘,用来验证告警链路后端没有断。真正开车测试时,语音播报和视频落盘是否同步发生,比盯着推理画面更容易判断系统状态。

注意:现场演示跑通只是第一步,EAR 阈值标定流程和 PERCLOS 统计窗口的设计依据要写进毕业设计说明书,评审老师大概率会针对这两处追问。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询