☰
YOLOv5+DeepSORT+Dlib 驾驶员分心预警系统实战拆解
2026/10/10 8:16:19 网站建设 项目流程

简介:这是一份面向人工智能与深度学习开发者的驾驶员分心驾驶行为预警系统项目,基于YOLOv5与Deepsort实现危险驾驶行为的实时监测,覆盖疲劳检测与分心行为检测两大核心场景。疲劳部分利用Dlib人脸关键点检测眼睛和嘴巴的开合程度,并通过Perclos模型计算疲劳度;分心部分采用YOLOv5识别玩手机、抽烟、喝水等危险动作,适合车载安全监控、智能座舱、智慧交通等方向的实践与二次开发。资源共58个文件,以Python脚本(20个py)、YAML配置(18个yaml)、编译后的pyc文件(13个)为主,另含UI界面文件(ui)、训练权重(pt)、人脸关键点模型(dat)、示例动图(gif)、Dockerfile及README.md,压缩包整体89MB。项目在原始版本基础上重新训练YOLOv5权重并精简前端UI,去掉点头检测,保留闭眼与打哈欠判定,结构清晰,便于快速理解检测流程并部署。目前已有729人学习下载,适合希望掌握YOLOv5+Deepsort整合方法、疲劳与分心行为预警系统搭建思路的中高级AI学习者参考。

1. 驾驶员分心预警系统:YOLOv5+DeepSORT 这套资源到底能干什么

先说结论:这不是一个只能看演示视频的玩具项目,而是一套能真正跑起来、带界面、带模型权重的驾驶员分心行为预警系统。它同时做两件事——用 Dlib 做疲劳检测(闭眼、打哈欠),用 YOLOv5 做危险行为识别(玩手机、抽烟、喝水),中间还挂着 DeepSORT 的设计思路用来做目标跟踪,整体封装在 PyQt 的 UI 窗口里。你下载后不需要再从零训练一个 YOLO,直接加载best.pt就能对着摄像头或者视频流检测。我在课程设计和项目实践里多次用过这类架构,它最大的价值在于把「深度学习检测」和「传统人脸关键点算法」拼在了一个工程里:疲劳靠 Perclos 模型量化,分心行为靠 YOLOv5 识别,两者互不干扰,适合作为人工智能方向的实战项目去复现、二次开发,也适合用来应付需要演示效果的答辩场景。

这套资源适合三类人:一是正在做深度学习教育相关大作业、需要快速出一个可演示系统的学生;二是想在车载监控、驾驶行为分析场景里做原型验证的工程师;三是想学习 Dlib 关键点检测与 YOLOv5 如何协同工作的人。下面我按自己拆项目时的思路,从架构到运行,再到踩坑,一条条讲清楚。

2. 系统架构拆解:疲劳检测与分心行为检测两条线怎么协同工作

2.1 疲劳检测线:Dlib 68点 + Perclos 模型判断闭眼和打哈欠

疲劳检测部分没有用深度学习检测网络,而是走了传统的人脸关键点路线。核心是shape_predictor_68_face_landmarks.dat这个 Dlib 预训练模型,它能在一张人脸图上标出 68 个关键点,眼睛区域对应第 36 到 47 点,嘴巴区域对应第 48 到 67 点。myfatigue.py里做的事就是先通过 Dlib 的人脸检测器找到人脸,再用 landmark 预测器得到关键点坐标,然后计算眼睛的纵横比(EAR)和嘴巴的纵横比(MAR)。

# 来自 myfatigue.py 的核心逻辑,简化后展示关键点计算方式 def eye_aspect_ratio(eye): # 计算垂直方向两个距离 A = dist.euclidean(eye[1], eye[5]) B = dist.euclidean(eye[2], eye[4]) # 计算水平方向距离 C = dist.euclidean(eye[0], eye[3]) ear = (A + B) / (2.0 * C) return ear def mouth_aspect_ratio(mouth): # 嘴巴开合程度,计算上下嘴唇关键点距离与宽度比 A = dist.euclidean(mouth[13], mouth[19]) B = dist.euclidean(mouth[14], mouth[18]) C = dist.euclidean(mouth[15], mouth[17]) mar = (A + B) / (3.0 * C) return mar

这段代码里的eye参数是从 68 点中切出来的左右眼关键点集合,通常是[36,37,38,39,40,41]和[42,43,44,45,46,47]。EAR 的值在正常睁眼时大约在 0.25 到 0.35 之间,一旦低于某个阈值(比如 0.2),并且持续多帧,就判定为闭眼。MAR 用来判断嘴巴张开的程度,打哈欠时嘴巴会持续张开到比较大的比例,超过阈值且保持一段时间就触发哈欠计数。这里的阈值和连续帧数都是可以调的参数,项目里直接在myfatigue.py头部定义,我一般习惯把闭眼阈值设为 0.19、哈欠阈值设为 0.6,然后再根据实际摄像头距离微调。

疲劳程度的量化用的是 Perclos 模型,也就是单位时间内眼睛闭合时间占的比例。简单说,perclos = 闭眼帧数 / 总检测帧数,这个值超过设定警戒线(比如 0.4)就说明疲劳程度较高。这个模型本身不复杂,但需要保证帧率的稳定性,如果摄像头帧率忽高忽低,Perclos 的数值会失真。

2.2 分心行为检测线:YOLOv5 识别玩手机、抽烟、喝水三类危险动作

分心行为检测是这套系统的重头戏。资源里给出的模型是重新训练过的 YOLOv5 权重best.pt,检测目标只有三类:手机、烟、水杯。对应的人打电话、抽烟、喝水这三种常见驾驶分心动作。mydetect.py里加载best.pt,对每一帧图像做推理,返回检测框、类别和置信度。

# mydetect.py 中加载模型与推理的常见写法 def load_model(weights_path='best.pt'): model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path, force_reload=False) model.conf = 0.45 # 置信度阈值 model.iou = 0.45 # NMS 的 IoU 阈值 model.classes = [0, 1, 2] # 只检测 0:手机 1:烟 2:水杯 return model def detect_frame(model, frame): results = model(frame) boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] return boxes

注意,这里model.classes = [0, 1, 2]是一个容易忽略的细节。如果原作者训练时类别顺序不是这个,需要先打开data.yaml或者训练脚本确认 category id 的对应关系。我遇到过资源里类别顺序是['smoking', 'phone', 'drink']的情况,直接用默认配置会导致检测框和标签错位。正确做法是先用一张含手机和烟的图片跑一次推理,打印results.names确认类别映射,再决定要不要改 classes 列表。

另外说回 DeepSORT。项目标题里带了 DeepSORT,但在 1.0 版本的分心行为检测里,它的作用不是必须的。严格说,YOLOv5 本身已经能逐帧给出检测框,DeepSORT 的意义在于给同一目标分配 ID,避免摄像头前的人来回走动时检测框闪烁。这套资源里你用到的可能是tracker.py相关模块,或者在mydetect.py里封装了简单的 IoU 匹配。实际跑起来以后,如果只是固定摄像头拍一个人,DeepSORT 的收益不大;如果你要统计某个人持续玩手机多长时间,就需要跟踪 ID 来区分不同的人。

2.3 前端UI与主流程:main.py、myframe.py、mydetect.py 之间的调用关系

这是整个资源里最容易让人懵的地方,文件多、还带 UI 设计文件,很多人不知道怎么串联。我拆过之后理出的调用链是这样的:main.py是整个程序的入口,它加载mainwindow.ui对应的ui_mainwindow.py界面,界面上有一个显示视频画面的 QLabel 和控制按钮(启动、停止、拍照之类)。myframe.py定义了一个视频帧处理线程,这个线程不断从摄像头读取图像,然后同时调用myfatigue.py里的疲劳检测函数和mydetect.py里的 YOLOv5 检测函数,最后把标注了检测框和状态文字的帧返回给 UI 刷新。

# myframe.py 中线程逻辑的骨架 class VideoThread(QThread): change_pixmap_signal = pyqtSignal(np.ndarray) def __init__(self): super().__init__() self.fatigue = myfatigue.FatigueDetector() self.detect = mydetect.Detector() def run(self): cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() if not ret: break frame = self.fatigue.process(frame) # 绘制疲劳状态 frame = self.detect.process(frame) # 绘制危险行为框 self.change_pixmap_signal.emit(frame)

这里有个注意点:YOLOv5 推理比较耗时,如果每一帧都在同一个线程里做 Dlib 关键点检测和 YOLO 推理,帧率会掉到 10 FPS 以下。我一般会把疲劳检测和 YOLOv5 推理拆成两个线程,或者降低检测频率,比如每 3 帧做一次 YOLO 推理,其余帧沿用上一次的检测结果。资源里 1.0 版本是串行的,效果也够用,但你要做实际部署就得考虑性能优化。

3. 环境配置与运行:从 weights 到 UI 窗口一步步跑起来

3.1 依赖安装与权重文件准备

先把环境捋清楚。这个项目基于 PyTorch 和 YOLOv5,我推荐 Python 3.8 到 3.10 之间,PyTorch 装 CPU 版就够跑推理,训练的话再上 GPU。除了 YOLOv5 自带的requirements.txt,还要额外安装 Dlib、PyQt5、opencv-python。Dlib 在新版本 Python 上容易编译失败,建议直接装dlib-bin或者用 conda 装。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu pip install dlib-bin opencv-python PyQt5 pip install -r requirements.txt # YOLOv5 自带的依赖

把这段跑完,再把shape_predictor_68_face_landmarks.dat和best.pt放在项目根目录,或者放到一个weights文件夹下。best.pt是 YOLOv5 训练出来的权重文件,里面已经包含类别信息和模型结构,直接加载即可。

3.2 启动主程序:main.py 和 UI 的交互

环境配好后,在项目根目录执行:

python main.py

正常情况下会弹出 PyQt 窗口,窗口里包含一个视频显示区域和几个按钮。点击「开始检测」按钮,摄像头启动,疲劳状态(闭眼次数、哈欠次数、Perclos 值)会显示在画面角落或 UI 的 label 上,玩手机、抽烟、喝水则用 YOLOv5 的彩色框标出来,框顶上带类别名称和置信度。

如果main.py里默认打开的是视频文件而不是摄像头,你需要改一下资源里的摄像头索引。通常在myframe.py里找到cv2.VideoCapture(0),把 0 改成你自己的摄像头编号就行。我在笔记本上跑第一次时,摄像头被虚拟摄像头占用,改成 1 就好了。

3.3 参数调整:修改阈值、检测类别、模型路径的常见位置

资源里需要改的参数集中在三个文件:myfatigue.py、mydetect.py、main.py。我用一张表列出常见调整点和默认值参考。

参数位置参数名作用建议范围
myfatigue.pyEYE_THRESH闭眼判定 EAR 阈值0.15~0.25
myfatigue.pyMOUTH_THRESH哈欠判定 MAR 阈值0.5~0.7
myfatigue.pyPERCLOS_LIMITPerclos 疲劳报警阈值0.3~0.5
mydetect.pymodel.conf检测置信度阈值0.4~0.6
mydetect.pymodel.classes检测类别过滤根据训练集类别调整
myframe.pycap_index摄像头编号0 或 1
main.pyui_mainwindow 路径UI 文件加载一般不用改

调参时不要一次性全改,先固定摄像头距离,单独调疲劳阈值。比如你发现正常睁眼 EAR 只有 0.22,那阈值就不能设 0.25,否则会一直报闭眼。我的习惯是打印出连续 100 帧的 EAR 平均值来反推阈值,而不是凭经验拍脑袋。

4. YOLOv5 模型重训与替换:换自己的数据集要注意什么

4.1 重新训练流程简述及 best.pt 的作用

资源里给的best.pt已经能直接检测三类目标,但如果你的场景要增加类别,比如加一个「使用耳机」或者「低头看手机」,就需要重新训练。train.py在 YOLOv5 主目录下,先准备数据集,然后执行:

python train.py --data mydata.yaml --weights yolov5s.pt --epochs 100 --batch-size 16

训练完会在runs/train/exp目录里生成best.pt和last.pt。best.pt是验证集上表现最好的权重,替换掉原来的即可。替换前先用下面的代码跑一遍验证,确认 mAP 没有崩。

# 验证模型的检测效果 import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path='runs/train/exp/weights/best.pt') model.eval() # 用一张测试图推理 img = 'test.jpg' results = model(img) results.show()

4.2 数据集格式与标签类别对应

YOLOv5 的数据集格式是每张图片对应一个 txt 文件,每行写class_id x_center y_center width height,坐标值都是归一化到 0~1 的。如果你要训练三类,txt 第一行的 class_id 必须是0(手机)、1(烟)、2(水杯)。换数据集最常见的坑是类别顺序变了,但你忘了改mydetect.py里的model.classes,导致检测结果标签错位。我建议把data.yaml里的类别名打印到 README 里,重新训练后第一时间同步修改检测代码中的类别映射。

4.3 autoanchor、超参数与训练轮次的影响

训练 YOLOv5 时,autoanchor会自动计算数据集的锚框尺寸,如果你的数据集目标尺寸和原来的差异很大,建议开启自动锚框,否则收敛慢、漏检多。yolov5s.yaml里的深度系数 0.33 和宽度系数 0.50 决定模型大小,你替换用小模型升到yolov5m.yaml可以提高精度,但推理速度下降。资源里原作者增加了训练轮次,说明在原来的基础上多训练了一些 epoch,这个操作的价值是让模型在验证集上更稳定。你自己重训时,别闷头训太多轮,用早停机制或者每 50 轮保存一次权重,选验证集 mAP 最大的那个。

5. 避坑指南:我跑这个项目时踩过的五个典型问题

5.1 现象:运行python main.py后直接报错 ModuleNotFoundError

原因:utils目录里的模块导入路径不对。这个资源是从 YOLOv5 官方仓库拆出来的,utils下面有general.py、plots.py、datasets.py等,它们之间用相对导入。如果你改了目录结构,或者把mydetect.py单独拷贝到别处,就会触发这个错误。

解决:保持资源原目录结构,不要动utils文件夹。如果你需要引用utils里的函数,从项目根目录启动 Python,或者把根目录加入sys.path:

import sys sys.path.append('./utils')

5.2 现象:加载shape_predictor_68_face_landmarks.dat时报错或 KeyError

原因:Dlib 版本不匹配,或者文件路径错误。这个 dat 文件是二进制模型,不同版本的 Dlib 可能有兼容性问题。另外资源里文件名带数字shape_predictor_68_face_landmarks.dat,代码里如果写死了shape_predictor_68_face_landmarks.dat而文件名多了空格或中文,就会加载失败。

解决:先检查代码里的路径是否与文件名完全一致,然后用dlib.shape_predictor函数测试加载,确认版本兼容:

import dlib detector = dlib.get_frontal_face_detector() predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat') print('OK')

5.3 现象:UI 窗口打开后摄像头画面黑屏或卡死

原因:摄像头被多个进程占用,或者cv2.VideoCapture没有正确释放。在 PyQt 里开视频线程,如果没有在关闭窗口时停止线程并释放摄像头,第二次打开窗口就会黑屏。

解决:在 UI 关闭事件里强制终止线程并释放摄像头,我一般在VideoThread的__del__里加cap.release():

def stop(self): self.running = False self.wait() self.cap.release()

5.4 现象:YOLOv5 检测框不显示或类别错乱

原因:model.classes设置错误。如果你检测出来只有手机能显示,烟和水杯完全没框,大概率是训练时的类别 id 和过滤类别不一致。如果类别名张冠李戴,比如手机框标成烟,说明代码里的 names 顺序和权重里的 names 顺序不同。

解决:推理时先打印模型自带的类别名:

print(model.names) # {0: 'phone', 1: 'smoke', 2: 'drink'}

然后把这个映射同步到mydetect.py的显示逻辑里,不要用硬编码的if cls == 0: label = '手机'。动态从model.names读取标签就可以。

5.5 现象:打哈欠检测频繁误报,或者疲劳报警太灵敏

原因:MOUTH_THRESH设得太低,或者没有限制哈欠持续时间。有些人说话时嘴巴张得很大,MAR 值会瞬间超过阈值,导致误报。

解决:把「单帧超阈值」改成「连续 N 帧超阈值」才计数。在myfatigue.py里增加一个帧计数器,比如连续 10 帧 MAR 大于阈值才判定哈欠一次。同时把阈值上调到 0.65 左右,实际测试后再微调。

6. 进阶用法:把检测结果接入语音报警与日志落盘,让预警真正闭环

基础版只是把检测结果显示在 UI 上,但预警系统要落地,必须解决「检测到之后怎么办」。我常用的做法是在mydetect.py和myfatigue.py的返回结果里增加危险状态标志,然后在myframe.py里根据标志触发语音提醒和写日志。

# 在 myframe.py 中集成语音与日志 import csv import datetime import pyttsx3 # 离线语音库 engine = pyttsx3.init() def report_action(action, confidence): if action == 'phone': engine.say('请勿玩手机') engine.runAndWait() elif action == 'smoke': engine.say('请勿吸烟') engine.runAndWait() # 写入日志 with open('driver_log.csv', 'a', newline='') as f: writer = csv.writer(f) writer.writerow([datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), action, confidence, 'fatigue' if action=='fatigue' else 'distract'])

这个pyttsx3是离线语音库,不需要额外服务,语音提醒的延迟在 0.5 秒以内,比弹窗更直观。注意不要在高频循环里反复调用engine.say,我一般设一个 5 秒冷却时间,同一类危险行为只播报一次,否则会变成复读机。

日志落盘的价值在于事后回溯。接下来你可以在此基础上加一个统计函数,把驾驶过程中玩手机次数、疲劳报警次数、每次危险行为持续时间汇总成表格。有了这份日志,这个系统就不再只是「看个画面」的演示,而是能输出行为报告的原型产品。从那以后,我每次复现这类检测项目,都会强制自己走一遍「检测 → 报警 → 记录」的闭环,因为多数下载资源都只停在画框那一步,能不能把报警和记录做出来,才是衡量你是否真正理解这套系统的地方。希望这份拆解能帮你在自己的项目里少走几步弯路。

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

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

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

立即咨询