news 2026/9/23 3:26:04

YOLOv5+DeepSort实现驾驶员分心行为实时检测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5+DeepSort实现驾驶员分心行为实时检测

简介:本资源是一套基于YOLOv5与DeepSort融合实现的驾驶员分心驾驶行为智能监测系统,面向人工智能与计算机视觉方向的本科生、研究生及毕设开发者,聚焦疲劳驾驶(如闭眼、打哈欠)与危险行为(如玩手机、抽烟、未系安全带)的实时识别与预警。压缩包共60个文件,含20个核心Python源码(如mydetect.py、myfatigue.py、main.py)、18个配置与模型定义YAML文件(yolov5s/x/l/m.yaml等)、训练好的best.pt模型、人脸关键点检测dat文件、UI界面文件及演示视频MP4和GIF动图,整体110.69MB,结构清晰、模块解耦,便于学习调试与二次开发。已有120人下载学习,项目经助教审定、本地实测可运行,评审分高达98分,配套README.md说明文档、完整数据集与Dockerfile部署支持,覆盖从环境配置、模型推理到多目标跟踪与行为判据的全流程实践环节。

1. 为什么用 YOLOv5 + DeepSort 做驾驶员分心行为检测,不是“炫技”,而是工程上最稳的落地组合

你可能已经看过几十个“疲劳驾驶检测”的毕设项目——有的用 OpenCV 简单算眨眼频率,结果司机低头看手机也被判疲劳;有的直接套用 ResNet 分类模型,一帧一帧喂图,漏掉关键动作片段;还有的强行上 Transformer,训练三天跑不出一个可用权重,答辩前连夜换方案。而这个标题里的YOLOv5 + DeepSort + Python 实现驾驶员分心驾驶行为(疲劳+危险行为)预警监测,不是实验室玩具,是我在三款量产级车载DMS设备原型中反复验证过的最小可行技术栈:它不追求SOTA指标,但能稳定跑在 i5-8250U + GTX1050 的嵌入式工控机上,平均延迟 42ms/帧,漏检率低于 7.3%(实测 217 段真实行车视频),且所有代码可直接部署到 Jetson Nano 或树莓派 5(需量化)。核心逻辑很朴素:YOLOv5 负责“看见”——精准框出人脸、手、方向盘、手机等关键部件;DeepSort 负责“记住”——跨帧关联同一司机,避免因遮挡或短暂出画导致行为链断裂;Python 则是粘合剂——把 OpenCV 视频流、PyTorch 推理、规则引擎(如连续闭眼>1.5s+点头幅度>15°→判定疲劳)、报警触发(蜂鸣/LED/日志)全串起来。适合两类人:毕设党需要可演示、可答辩、可写进论文方法论的完整 pipeline;一线工程师想快速验证算法在真实驾驶舱光照、抖动、多角度下的鲁棒性。下面,我们从零开始搭起这个系统——不跳步,不假设你装过 CUDA,连pip install失败的报错都给你备好解法。


2. 用 YOLOv5 检测关键目标:人脸、手、手机、方向盘,不是靠调参,而是靠数据构造

YOLOv5 在这里不是拿来即用的黑匣子。它必须被“驯化”成专属于驾驶舱场景的检测器——普通 COCO 预训练权重对方向盘、握姿、手机屏幕几乎无感。我们不重头训练,而是用迁移学习 + 针对性数据增强,在 2 小时内完成适配。

2.1 数据集构造:为什么必须自己标注“驾驶舱四类目标”,而不是用公开疲劳数据集

公开数据集(如 NTHU-DDD、MRL)普遍存在三个致命缺陷:

  • 标注粒度粗:只标“人”,不区分“人脸区域”“左手”“右手”“手机”“方向盘”,而分心行为判断依赖部件级空间关系(例如:右手离开方向盘 + 左手持手机 → 危险操作);
  • 光照单一:多为实验室灯光,无法覆盖隧道进出、正午强光、夜间红外补光等真实车载场景;
  • 视角固定:90% 是正前方视角,但实际行车记录仪常有 15°~30° 俯角,导致模型对倾斜方向盘误检。

因此,我们采用“3+1”数据构造法

  • 3 类真实视频源
    • 自采行车记录仪视频(1080P@30fps,含早晚高峰、雨天、隧道);
    • 公开道路监控片段(经脱敏处理,仅保留驾驶座区域);
    • 合成数据(用 Blender 渲染方向盘+手部姿态,控制光照、模糊、运动抖动);
  • 1 套精细标注规范
    • 每帧标注 4 类 bounding box:face(仅可见人脸区域,非整个头部)、hand_left/hand_right(手掌中心点朝向方向盘)、phone(屏幕亮区,非手机外壳)、steering_wheel(方向盘外圆环,非整个仪表台);
    • 标注工具用 CVAT(开源),导出为 YOLO 格式(txt 文件,每行class_id center_x center_y width height,归一化到 0~1);
    • 最终数据集规模:3276 张图像,按 7:2:1 划分 train/val/test,平均每图 3.2 个目标。

提示:不要用 LabelImg 标注!它不支持多类别同框(如手+手机重叠时需分别框出),CVAT 可打点+插值生成连续帧标注,省 60% 时间。

2.2 模型选型与微调:为什么选 yolov5s.pt 而不是 yolov5x,以及超参数怎么设

YOLOv5 官方提供 s/m/l/x 四个尺寸。在车载边缘设备上,yolov5s 是唯一兼顾速度与精度的选项

  • 在 GTX1050 上,yolov5s 推理速度 48 FPS(输入 640×640),yolov5m 仅 22 FPS,而 yolov5x 会卡在 11 FPS,无法满足实时预警需求;
  • 精度损失可控:在自建测试集上,yolov5s mAP@0.5 达 0.832,yolov5x 为 0.851,但后者体积 273MB vs 前者 14.2MB,Flash 存储受限时不可接受。

微调命令如下(基于 ultralytics/yolov5 v6.1):

python train.py \ --img 640 \ --batch 16 \ --epochs 50 \ --data ./data/driving.yaml \ --weights ./weights/yolov5s.pt \ --name driving_yolov5s_finetune \ --cache ram \ --workers 4 \ --optimizer AdamW \ --lr0 0.001 \ --lrf 0.1 \ --cos-lr \ --iou-thres 0.5 \ --conf-thres 0.001 \ --save-period 5

关键参数说明:

  • --cache ram:将训练集图像缓存到内存,避免 IO 瓶颈(实测提速 3.2×);
  • --optimizer AdamW:比默认 SGD 更稳定,尤其小批量训练时不易震荡;
  • --lr0 0.001+--cos-lr:余弦退火学习率,避免后期过拟合;
  • --conf-thres 0.001:极低置信度阈值——因为分心行为常由微小动作触发(如手指微动),宁可多检勿漏;
  • --save-period 5:每 5 个 epoch 保存一次权重,方便回滚(曾有次第 37 epoch 突然 mAP 下跌,靠此找回最佳权重)。

训练完成后,runs/train/driving_yolov5s_finetune/weights/best.pt即为可用检测模型。


3. 用 DeepSort 追踪同一驾驶员:解决遮挡、出画、相似外观导致的身份漂移

YOLOv5 检测是“帧级快照”,但分心行为是“时序事件”——比如“闭眼→点头→再睁眼”需连续 3 帧以上才判定疲劳。若每帧独立检测,司机转头瞬间 ID 就重置,行为链直接断裂。DeepSort 是目前最轻量、最稳定的在线追踪方案,它用卡尔曼滤波预测位置 + 外观特征(ReID)校验身份,比 SORT 准确率高 22%,比 ByteTrack 内存占用低 65%。

3.1 DeepSort 配置要点:为什么必须替换原版 ReID 模型,以及 tracker 参数怎么调

官方 DeepSort 使用 Market1501 训练的 ReID 模型,对驾驶舱场景完全失效:

  • Market1501 是行人全身照,而我们只有脸部+手部局部;
  • 特征向量维度 512,但车载端显存仅 2GB,无法加载。

解决方案:用轻量 ReID 模型替代
我们采用torchreid库中的osnet_ain_x1_0(仅 1.2M 参数,推理耗时 8ms/帧),并用自建数据微调:

  • 数据:从标注集中截取 12,000 张人脸+手部 ROI 图像(尺寸 256×128),按司机 ID 分类;
  • 微调命令:
# train_reid.py from torchreid import models, data, engine datamanager = data.ImageDataManager( root='data/reid', sources=['driving'], targets=['driving'], height=128, width=256, batch_size_train=32, batch_size_test=100 ) model = models.build_model( name='osnet_ain_x1_0', num_classes=datamanager.num_train_pids, loss='softmax', pretrained=True ) engine = engine.ImageSoftmaxEngine( datamanager, model, optimizer='adam', lr=0.0003, max_epoch=20 ) engine.run( save_dir='log/osnet_driving', max_epoch=20, eval_freq=1, print_freq=10 )

训练后得到log/osnet_driving/model/model.pth,替换 DeepSort 中的deep_sort_pytorch/deep_sort/model_weights/mars-small128.pb(需转换为 PyTorch 格式)。

3.2 Tracker 初始化与参数调优:3 个必改参数让 ID 切换率下降 40%

DeepSort 默认配置在驾驶舱场景下 ID 切换频繁(平均 1.7 次/分钟)。我们通过以下调整优化:

参数默认值推荐值作用说明
max_age7030卡尔曼滤波预测最大帧数。驾驶舱中司机极少长时间出画,设太高会导致旧 ID 残留干扰新 ID
n_init35连续匹配成功才确认 ID。驾驶舱中目标易被遮挡(如方向盘遮脸),提高到 5 帧可过滤误匹配
nn_budget10050外观特征匹配候选框数量。降低后减少计算量,且驾驶舱中相似外观目标少(不像行人密集场景)

修改deep_sort_pytorch/deep_sort/deep.py中的__init__方法:

self.max_age = 30 self.n_init = 5 self.nn_budget = 50

注意:不要调threshold(余弦相似度阈值)!默认 0.4 已最优。我们实测过 0.2~0.6 区间,0.4 时 IDF1 分数最高(0.782),低于此值误关联增多,高于此值 ID 断裂加剧。


4. 行为规则引擎设计:把检测+追踪结果翻译成“疲劳”“危险操作”预警

检测和追踪只是输入,真正价值在于可解释、可调试、可审计的行为判定逻辑。我们不用端到端深度学习(黑盒难解释、难合规),而是构建基于时空约束的规则引擎。

4.1 疲劳判定:为什么用“PERCLOS + 点头幅度 + 闭眼持续时间”三因子融合,而非单一指标

  • PERCLOS(闭眼时间占比):经典指标,但易受眨眼干扰(正常人 0.3~0.4s/次);
  • 点头幅度:用鼻尖与下巴连线角度变化率,>15°/s 判定为点头;
  • 闭眼持续时间:单次闭眼 >1.5s 才计入疲劳事件。

三者必须同时满足才触发预警(AND 逻辑),避免误报:

  • 场景:司机揉眼睛(闭眼久但无点头)→ 不预警;
  • 场景:急刹车点头(有幅度但眼未闭)→ 不预警;
  • 场景:打哈欠(闭眼+点头但 <1.5s)→ 不预警。

实现代码(behavior_engine.py):

import numpy as np from scipy.spatial.distance import euclidean class FatigueDetector: def __init__(self, window_size=30): # 30帧 ≈ 1秒(30fps) self.eye_closure_history = [] # 存储最近30帧闭眼状态(0/1) self.head_angle_history = [] # 存储最近30帧点头角度 self.window_size = window_size def update(self, eye_closed: bool, head_angle: float): self.eye_closure_history.append(1 if eye_closed else 0) self.head_angle_history.append(head_angle) if len(self.eye_closure_history) > self.window_size: self.eye_closure_history.pop(0) self.head_angle_history.pop(0) def is_fatigue(self) -> bool: if len(self.eye_closure_history) < self.window_size: return False # PERCLOS: 闭眼帧占比 > 0.3 perclos = sum(self.eye_closure_history) / len(self.eye_closure_history) # 点头幅度:最近5帧角度标准差 > 15° recent_angles = self.head_angle_history[-5:] head_std = np.std(recent_angles) if len(recent_angles) >= 3 else 0 # 闭眼持续时间:检查最长连续闭眼帧数 max_consecutive = 0 current = 0 for e in self.eye_closure_history: if e == 1: current += 1 max_consecutive = max(max_consecutive, current) else: current = 0 return (perclos > 0.3 and head_std > 15.0 and max_consecutive >= 45) # 45帧 = 1.5秒(30fps) # 使用示例 detector = FatigueDetector() for frame in video_stream: face_bbox = yolov5.detect(frame)['face'] eye_closed = is_eye_closed(face_bbox) # 用 facial landmarks 计算 EAR head_angle = calculate_head_angle(face_bbox) # 用 68-point landmarks 计算俯仰角 detector.update(eye_closed, head_angle) if detector.is_fatigue(): trigger_alert("FATIGUE_DETECTED")

4.2 危险操作判定:方向盘脱离 + 手持物体的时空耦合逻辑

危险操作核心是空间关系 + 时间持续性

  • 空间:左手/右手 bbox 与方向盘 bbox 的 IoU < 0.05(即手完全离开方向盘);
  • 时间:该状态持续 ≥ 3 秒(90 帧),且期间检测到phonecigarette(扩展类别)bbox 与手 bbox 重叠(IoU > 0.3)。

关键点:必须用 DeepSort 的 track_id 关联——确保是同一司机的手在脱离方向盘,而非副驾人员干扰。

def detect_dangerous_action(tracks, phone_dets, steering_wheel_dets): """ tracks: list of [track_id, x1,y1,x2,y2, ...] phone_dets: list of [x1,y1,x2,y2, conf] steering_wheel_dets: list of [x1,y1,x2,y2, conf] """ dangerous_actions = [] for track in tracks: tid, *bbox = track hand_iou_with_steering = 0 if steering_wheel_dets: hand_iou_with_steering = max([ calculate_iou(bbox, sw) for sw in steering_wheel_dets ]) # 手脱离方向盘 if hand_iou_with_steering < 0.05: # 检查是否手持物体 for phone in phone_dets: if calculate_iou(bbox, phone) > 0.3: dangerous_actions.append({ 'track_id': tid, 'action': 'holding_phone', 'frame_count': 1 }) # 维护状态字典,累计持续帧数 if not hasattr(detect_dangerous_action, 'state'): detect_dangerous_action.state = {} for act in dangerous_actions: tid = act['track_id'] if tid not in detect_dangerous_action.state: detect_dangerous_action.state[tid] = 0 detect_dangerous_action.state[tid] += 1 if detect_dangerous_action.state[tid] >= 90: # 3秒 trigger_alert(f"DANGEROUS_ACTION: {act['action']} by track {tid}") # 重置计数,避免重复报警 detect_dangerous_action.state[tid] = 0

5. 部署避坑指南:那些让毕设答辩翻车、让车载设备上线失败的 5 个血泪问题

这节不讲原理,只列真实踩过的坑——每个都来自我亲手 debug 过的 17 个失败案例。现象、原因、解法,一句废话没有。

5.1 现象:YOLOv5 检测框在视频中剧烈抖动,像“癫痫发作”

原因:OpenCV 读取视频时默认使用cv2.CAP_PROP_POS_FRAMES,但某些编码格式(H.264 High Profile)会导致帧定位不准,YOLO 输入图像实际是解码错误帧。
解决:强制用cv2.VideoCaptureCAP_PROP_BUFFERSIZE并关闭硬件加速:

cap = cv2.VideoCapture(video_path) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关闭缓冲 # 若仍抖动,加一行: cap.set(cv2.CAP_PROP_HW_ACCELERATION, cv2.VIDEO_ACCELERATION_NONE) # OpenCV 4.7+

5.2 现象:DeepSort 追踪 ID 在司机转头时频繁切换,1 分钟换 5 次 ID

原因:ReID 模型未针对侧脸微调,转头时外观特征向量距离突变,卡尔曼滤波无法补偿。
解决:在 tracker 初始化时,对首帧检测到的 face bbox,额外提取左/右/正脸 3 个 ROI,拼接为 multi-view 特征向量(非简单平均),提升侧脸鲁棒性:

# 在 deep_sort/update.py 的 update() 方法中 if not track.is_confirmed(): # 首次匹配,提取多视角特征 face_img = crop_face(frame, bbox) left_profile = augment_face(face_img, 'left') # 仿射变换模拟左转 right_profile = augment_face(face_img, 'right') feat = np.concatenate([ reid_model(face_img), reid_model(left_profile), reid_model(right_profile) ], axis=0)

5.3 现象:Jetson Nano 上 CPU 占用 100%,GPU 利用率仅 12%,整体延迟飙升到 200ms

原因:PyTorch 默认使用多线程,但 Nano 的 4 核 Cortex-A57 对线程调度不友好,线程竞争导致锁死。
解决:在inference.py开头强制单线程:

import torch torch.set_num_threads(1) # 必加! # 并禁用 OpenMP import os os.environ['OMP_NUM_THREADS'] = '1' os.environ['OPENBLAS_NUM_THREADS'] = '1'

5.4 现象:报警日志里显示 “FATIGUE_DETECTED”,但回放视频发现司机全程清醒

原因:EAR(眼睛纵横比)计算用的是 68-point landmark,但 dlib 的shape_predictor_68_face_landmarks.dat在低光照下关键点漂移严重,导致闭眼误判。
解决:换用face_recognition库的 CNN 模型(更鲁棒),并加光照补偿:

import face_recognition # 预处理:CLAHE 增强对比度 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) enhanced = clahe.apply(gray) face_locations = face_recognition.face_locations(enhanced, model="cnn")

5.5 现象:树莓派 5 上运行报错Illegal instruction (core dumped)

原因:PyTorch wheel 编译时未启用 ARM NEON 指令集,而 Pi5 的 Cortex-A76 需要 NEON 加速。
解决:不用 pip install torch,改用官方预编译包:

wget https://github.com/pytorch/pytorch/releases/download/v2.0.1/torch-2.0.1-cp39-cp39-linux_armv7l.whl pip install torch-2.0.1-cp39-cp39-linux_armv7l.whl # 注意:Pi5 是 armv8,但需兼容 armv7l 轮子(官方未提供 armv8 轮子)

6. 实战技巧:用“行为热力图”替代阈值硬判决,让预警更符合人类认知

规则引擎虽可靠,但有个隐藏缺陷:它把连续行为切成离散事件(如“疲劳”/“不疲劳”),丢失了渐进性。司机从清醒到困倦是平滑过渡,而我们的报警却是“啪”一下触发。这在真实车载系统中会引发用户反感——就像空调突然狂吹冷风,而非缓缓降温。

我的解法是:把行为判定结果映射为 0~1 的“风险分数”,再用热力图可视化叠加在视频上。这样既保留规则可解释性,又提供渐进反馈。

6.1 风险分数计算:三因子加权,权重来自真实事故报告统计

我们分析了 213 份交警事故报告,统计各行为与事故的相关系数(Pearson),得出加权公式:

risk_score = 0.45 × PERCLOS + 0.30 × (head_std / 30.0) + 0.25 × (max_consecutive / 60.0)
  • PERCLOS归一化到 0~1(原始 0~1);
  • head_std除以 30°(最大合理点头幅度);
  • max_consecutive除以 60 帧(2 秒,疲劳临界值)。

分数 > 0.65 触发黄色预警(界面闪烁),> 0.85 触发红色报警(蜂鸣+语音)。

6.2 热力图渲染:用 OpenCV 的applyColorMap实现轻量级可视化

不依赖 matplotlib(太重),纯 OpenCV 实现:

def draw_risk_heatmap(frame, risk_score, position=(50, 50)): # 创建 200x30 的热力条 heatmap = np.zeros((30, 200, 3), dtype=np.uint8) # 根据 risk_score 插值颜色:蓝(0)→黄(0.5)→红(1) if risk_score <= 0.5: color = (int(255 * (1 - risk_score * 2)), int(255 * risk_score * 2), 0) # BGR: blue to yellow else: color = (0, int(255 * (1 - (risk_score - 0.5) * 2)), int(255 * (risk_score - 0.5) * 2)) # yellow to red cv2.rectangle(heatmap, (0, 0), (int(risk_score * 200), 30), color, -1) # 叠加到原图 frame[position[1]:position[1]+30, position[0]:position[0]+200] = heatmap # 添加文字 cv2.putText(frame, f"Risk: {risk_score:.2f}", (position[0], position[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 2) return frame # 主循环中调用 risk = fatigue_detector.get_risk_score() # 返回 0~1 frame = draw_risk_heatmap(frame, risk) cv2.imshow('DMS', frame)

效果:司机能直观看到风险在爬升,提前自我调整;售后人员回看视频时,热力图曲线比“报警日志”更能还原事件全貌。

最后说句实在话:这个方案不是为了发论文,而是为了“让车不撞人”。我见过太多毕设项目答辩时演示完美,一上路就失效——根源不在模型,而在没把光照、抖动、遮挡、设备温漂这些工程细节当回事。所以,别急着调参,先录一段自己开车的视频,用这套流程跑通,再看哪里卡住。那才是你真正该花时间的地方。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 3:26:05

2026最新解读:幻想与现实源码拆解,面试原理不再卡壳

2026最新解读:幻想与现实源码拆解,面试原理不再卡壳 面试被问“讲讲事件循环机制”时,你脑子里是空白还是清晰?很多开发者在2026最新的面试现场,对着“幻想与现实”的落差感到无力。你以为背了八股文就能过,现实是面试官一句“源码里怎么实现的”就把你问懵了。 这种 面试被问原理答不上来…

作者头像 李华
网站建设 2026/9/23 3:25:50

5个坑让你少熬3夜:druid连接池实战避坑指南

5个坑让你少熬3夜:druid连接池实战避坑指南 刚接手新项目,Spring Boot 配置里加个数据库连接,结果一跑起来就报错。改了半天 application.yml ,重启了十几次,日志里全是 CommunicationsException 和…

作者头像 李华
网站建设 2026/9/23 3:25:14

图解原理:本方最优价格委托的3个性能坑

图解原理:本方最优价格委托的3个性能坑 看到满屏红色的 StackTrace,报错信息像天书一样堆在控制台,你是不是也头疼过? 别慌,这不是代码写崩了,是 高频交易场景下的典型性能瓶颈 。 很多人以为“本方最优价格委托”只是换个参数,但底层逻辑完全不同。 今天用 图解原理…

作者头像 李华
网站建设 2026/9/23 3:25:06

3个td卡性能优化实战,新手避坑指南

3个td卡性能优化实战,新手避坑指南 面试被问“为什么你的接口响应慢”,你张口就说是数据库查询慢,结果面试官追问“具体是哪一步耗时?有做过Profiling吗?”,你瞬间大脑一片空白。这种场景,在Java后端或高并发场景的面试中太常见了。很多新手对性能优化的理解还停留在“加缓存”、“异步化”这些概念…

作者头像 李华
网站建设 2026/9/23 3:24:29

面试必问speedsoftware配置坑:3招解决页面边距报错

面试必问speedsoftware配置坑:3招解决页面边距报错 刚接了个活,用 SpeedSoftware 做报表导出,一跑代码就崩了。满屏的 java.lang.NullPointerException 和 com.speedsoftware.exception.LayoutException…

作者头像 李华
网站建设 2026/9/23 3:24:11

3个坑点搞定黑苹果,面试必问避坑指南

3个坑点搞定黑苹果,面试必问避坑指南 看了一堆教程还是不会写项目?这简直是无数开发者的噩梦。你跟着视频一步步敲代码,结果一到真实场景就报错,面试时面试官问起细节,你支支吾吾答不上来。其实, 黑苹果…

作者头像 李华