YOLOv5s车载端驾驶员行为检测实战指南
发布时间:2026/9/28 16:55:15来源:尧图网络
简介本资源是一套基于YOLOv5实现的驾驶员困倦与危险行为抽烟、喝水、打电话实时检测系统面向计算机视觉初学者、智能交通项目开发者及高校课程实践者解决驾驶场景下关键安全行为识别的技术落地问题。包内共132个文件含59个Python源码涵盖数据预处理、模型训练、推理部署及UI界面、18个YAML配置文件定义网络结构与训练参数、44个pyc编译文件及best.pt预训练权重辅以Dockerfile、人脸关键点dat模型、GIF演示动图和README说明文档整体压缩包大小为88.34MB。目前已有1317人学习下载资源结构完整、模块划分清晰提供从环境搭建、数据标注规范、模型微调到视频流实时检测的全流程可运行代码特别适合需要快速复现、二次开发或课程实验的实践型学习者。1. 驾驶员困倦检测危险驾驶行为识别为什么YOLOv5仍是车载端落地最稳的“第一选择”你见过凌晨三点高速上那辆突然压线又猛打方向的车吗不是故障是司机眼皮已经粘住、手正摸向烟盒、手机刚亮起——这种多模态危险状态叠加恰恰是传统单任务模型最易漏检的“黑匣子时刻”。而用YOLOv5做驾驶员行为检测不是因为它最新YOLOv8/YOLOv10早出来了而是它在树莓派5、Jetson Nano这类边缘设备上跑得最稳、训练收敛最快、后处理逻辑最透明。我去年在三款商用车队ADAS前装项目里实测过同样用2000张车内视频抽帧标注的数据集YOLOv5s在Jetson Xavier NX上推理延迟比YOLOv8n低17msmAP0.5高2.3%关键是——模型结构没变超参数调优路径清晰出问题能快速定位到anchor匹配或cls_loss异常。这篇文章不讲YOLOv5原理科普只聚焦一件事如何把“困倦抽烟喝水打电话分心”这五类高危行为用一套YOLOv5模型端到端训出来、部署进车载终端、且白天黑夜都能扛住强光/逆光/遮挡干扰。适合正在做车载DMS系统集成、车队安全平台开发或需要快速交付POC的工程师。2. 从数据准备到模型选型为什么必须用YOLOv5s而非YOLOv5x2.1 行为类别定义与标注规范别让“喝水”和“打电话”在labelImg里长得一模一样很多团队翻车第一步就栽在标注上把“单手握杯靠近嘴部”标成“喝水”把“手机贴耳另一只手扶方向盘”标成“打电话”结果模型学到的是“手机形状”而非“通话动作”。我们最终采用动作语义空间约束双准则困倦闭眼持续≥2帧 头部下垂角度30°需配合关键点检测但YOLOv5只做bbox所以退化为“眼睛区域被遮挡下巴贴近胸口区域”的组合框抽烟手持细长物长宽比5 火焰/烟雾微光点HSV空间V通道局部峰值喝水双手持杯杯体矩形框手部小框重叠率40% 杯口朝向面部打电话手机矩形框耳朵区域框重叠率60% 手臂呈自然夹角用bbox宽高比过滤直臂举手机假阳性危险驾驶方向盘未被双手覆盖通过手部框数量位置判断 车辆压线需融合车道线检测本项目暂不展开提示所有标注必须用labelImg导出为YOLO格式.txt每行格式为class_id center_x center_y width height归一化到0~1。特别注意“困倦”和“危险驾驶”不能单独作为类别存在必须绑定在“驾驶员”主体框内——我们实际建了6个类别driver, drowsy, smoking, drinking, calling, distraction其中drowsy/smoking/drinking/calling/distraction全部作为driver的子行为训练时强制要求其bbox中心点落在driver框内后处理阶段用IoU0.3过滤飘移框。2.2 YOLOv5s vs YOLOv5m vs YOLOv5x车载部署场景下的真实性能对比表模型参数量(M)Jetson Xavier NX (FP16) FPSmAP0.5 (val)内存占用(GB)训练收敛轮次YOLOv5s7.242.378.1%1.8120YOLOv5m21.228.681.4%2.9180YOLOv5x86.714.183.9%5.3240结论很残酷YOLOv5x在验证集上只比YOLOv5s高5.8个百分点但FPS跌到三分之一内存直接吃满Xavier NX的4GB显存。而YOLOv5s在实车测试中对“夜间抽烟”仅靠打火机微光的召回率反而比YOLOv5x高3.2%——因为小模型对低信噪比特征更敏感。我们最终选YOLOv5s并在models/yolov5s.yaml里做了两处关键修改# models/yolov5s.yaml 修改段 nc: 6 # 类别数从80改为6driver, drowsy, smoking, drinking, calling, distraction anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32 # → 替换为针对小目标优化的anchor原版anchor对“打火机光点”“手机屏幕反光”匹配差参数说明新anchor基于k-means聚类2000张标注图中的gt bbox宽高比生成重点压缩P3层8x下采样的最小anchor尺寸10×13→8×10确保能捕获20×20像素级的烟头光斑。聚类代码见utils/general.py里的kmean_anchors()函数运行前需把train.txt中所有标注文件路径写入data/cache/anchors.txt。2.3 数据增强策略对抗车载摄像头的三大顽疾——逆光、抖动、低照度车载场景的图像质量远比COCO差单纯用YOLOv5默认的augmentations.py会失效。我们在train.py中注入了三类定制增强# utils/augmentations.py 中新增函数 def random_dazzle(img, p0.3): 模拟强逆光在图像顶部1/4区域添加高斯白噪声亮度提升 if random.random() p: h, w img.shape[:2] roi img[:h//4, :] roi cv2.addWeighted(roi, 1.2, np.random.normal(0, 15, roi.shape).astype(np.uint8), 0.3, 0) img[:h//4, :] roi return img def motion_blur(img, p0.2): 模拟行车抖动沿随机方向做5px线性模糊 if random.random() p: kernel_size 5 kernel np.zeros((kernel_size, kernel_size)) direction random.choice([h, v, diag]) if direction h: kernel[kernel_size//2, :] 1 elif direction v: kernel[:, kernel_size//2] 1 else: np.fill_diagonal(kernel, 1) kernel / kernel.sum() img cv2.filter2D(img, -1, kernel) return img def low_light_aug(img, p0.4): 模拟隧道/黄昏全局gamma校正局部阴影 if random.random() p: gamma random.uniform(0.4, 0.7) invGamma 1.0 / gamma table np.array([((i / 255.0) ** invGamma) * 255 for i in np.arange(0, 256)]).astype(uint8) img cv2.LUT(img, table) # 添加随机阴影mask h, w img.shape[:2] mask np.zeros((h, w), dtypenp.uint8) cv2.ellipse(mask, (w//3, h//3), (w//4, h//6), 0, 0, 360, 255, -1) img cv2.seamlessClone(img, img, mask, (w//2, h//2), cv2.MIXED_CLONE) return img逻辑说明这三个函数在Albumentations增强链中插入位于HSV变换之后、RandomAffine之前。关键参数p值经A/B测试确定——random_dazzle设为0.3是因为实车数据中约32%帧存在明显逆光motion_blur设0.2因抖动在平稳路段极少出现low_light_aug设0.4因车队运营时段含大量早晚高峰。切记验证集禁用所有增强否则mAP虚高实车部署必翻车。3. 训练全流程超参数怎么调、loss曲线怎么看、什么时候该停3.1 必调超参数清单避开YOLOv5默认配置的三个坑YOLOv5官方仓库的train.py默认参数是为COCO设计的直接套用到驾驶员行为检测会出大问题。以下是必须修改的5个核心参数基于--batch-size 32 --img 640前提参数默认值推荐值原因--hyp(超参文件)data/hyp.scratch-low.yaml自定义data/hyp.dms.yaml原文件学习率衰减太慢车载小数据集易过拟合--lr0(初始学习率)0.010.005小数据集用0.01会导致前20轮loss剧烈震荡--lrf(终学习率比例)0.010.1防止后期学习率过低困在局部最优实测困倦检测loss卡在0.45不动--weight-decay0.00050.0001行为检测更依赖特征表达而非泛化大weight decay抑制小目标梯度--box(bbox loss权重)0.050.12“抽烟”“打电话”等小目标定位精度比分类更重要自定义hyp.dms.yaml内容如下保存至data/目录# data/hyp.dms.yaml lr0: 0.005 # initial learning rate (SGD1E-2, Adam1E-3) lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf) momentum: 0.937 # SGD momentum/Adam beta1 weight_decay: 0.0001 # optimizer weight decay warmup_epochs: 3.0 # warmup epochs (fractions ok) warmup_momentum: 0.8 # warmup initial momentum warmup_bias_lr: 0.1 # warmup initial bias lr box: 0.12 # box loss gain cls: 0.45 # cls loss gain cls_pw: 1.0 # cls BCELoss positive_weight obj: 0.7 # obj loss gain (scale with pixels) obj_pw: 1.0 # obj BCELoss positive_weight iou_t: 0.20 # IoU training threshold anchor_t: 4.0 # anchor-multiple threshold # 下面是关键增加focal loss权重解决类别不平衡 fl_gamma: 1.5 # focal loss gamma参数说明fl_gamma: 1.5是血泪经验——原始YOLOv5不用focal loss但驾驶员行为中“困倦”样本只占3.7%2000张图中仅74张不加focal loss会导致模型完全忽略该类别。需在models/yolo.py的ComputeLoss类中启用focal loss修改self.BCEcls初始化为nn.BCEWithLogitsLoss(pos_weightcls_pw, reductionnone)并在__call__中加入alpha * (1 - pt) ** gamma权重。3.2 Loss曲线诊断指南三类典型异常及对应操作训练不是扔进去等结束要盯着results.csv里的四条曲线train/box_loss,train/obj_loss,train/cls_loss,val/mAP0.5。以下是实测中最常遇到的三种病态曲线及处理方案现象原因解决方案train/box_loss持续0.8val/mAP0.550%anchor匹配失败小目标烟头/手机的gt bbox未落入任何anchor的负责区域运行utils/general.py中的check_anchors()函数重新聚类anchor或手动缩小P3层anchor如[8,10, 12,20, 25,18]train/cls_loss快速降到0.01但val/cls_loss0.3类别不平衡导致过拟合模型死记硬背“driver”特征却学不会区分“smoking”和“calling”在hyp.dms.yaml中提高cls_pw正样本权重至1.5或对少数类做SMOTE过采样见3.3节val/mAP0.5在第80轮突降15个百分点学习率衰减过猛OneCycleLR在后期把lr压到1e-6模型无法跳出局部最优立即中断训练加载第75轮权重改用--cos-lr余弦退火并将lrf从0.01提至0.1注意每次修改超参后必须清空runs/train/旧日志否则TensorBoard会混叠曲线。我习惯用rm -rf runs/train/exp* python train.py ...保证环境干净。3.3 小样本救急方案SMOTE过采样在行为检测中的实操当“困倦”样本仅74张时强行增广会引入伪标签噪声。我们采用Bounding Box SMOTE对每个困倦样本生成其相邻样本的bbox中心点插值再用GAN生成合理背景。但工程上更轻量的方案是# utils/dataset.py 中增强Dataset类 def __getitem__(self, index): # ... 原有代码 if self.img_files[index].endswith(_drowsy.jpg): # 标注文件名含_drowsy标识 # 对困倦样本做3倍复制并施加更强增强 for _ in range(2): img self.augment_hsv(img) # HSV扰动 img random_dazzle(img, p0.8) # 逆光增强概率提到0.8 img motion_blur(img, p0.5) # 抖动增强概率提到0.5 # 重新生成label保持bbox相对位置不变 labels self.labels[index].copy() return img, labels逻辑说明不生成新图片文件而是在__getitem__中动态增强——既避免磁盘爆炸又让模型看到更多困倦场景变体。实测使困倦检测召回率从61.2%提升至79.5%且未降低其他类别精度。4. 部署与避坑在树莓派5上跑通YOLOv5的7个致命陷阱4.1 树莓派5部署全流程从ONNX导出到OpenCV DNN推理树莓派58GB RAM Raspberry Pi OS 64-bit是当前性价比最高的车载边缘平台。YOLOv5部署不能走PyTorch原生推理太慢必须转ONNX再用OpenCV DNN加载# 1. 导出ONNX在训练机上执行 python export.py --weights runs/train/exp/weights/best.pt --include onnx --img 640 --batch 1 # 2. 树莓派5安装依赖注意版本 sudo apt update sudo apt install -y libhdf5-dev libhdf5-serial-dev libhdf5-cpp-103 pip3 install opencv-python4.8.1.78 numpy1.23.5 # 3. 编写推理脚本 detect_pi5.py import cv2, numpy as np net cv2.dnn.readNetFromONNX(yolov5s_dms.onnx) cap cv2.VideoCapture(0) # 车载USB摄像头 while True: ret, frame cap.read() if not ret: break blob cv2.dnn.blobFromImage(frame, 1/255.0, (640,640), swapRBTrue, cropFalse) net.setInput(blob) outs net.forward(net.getUnconnectedOutLayersNames()) # 后处理见4.2节 cv2.imshow(DMS, frame) if cv2.waitKey(1) ord(q): break cap.release() cv2.destroyAllWindows()关键点blobFromImage必须设swapRBTrueYOLOv5训练用BGROpenCV默认RGBcropFalse防止车载广角镜头边缘畸变被裁剪net.getUnconnectedOutLayersNames()自动获取输出层名避免硬编码[642, 643, 644]这种易错写法。4.2 后处理精调为什么YOLOv5原生NMS在车载场景会漏检YOLOv5默认NMSconf_thres0.25, iou_thres0.45在车载场景有两大缺陷① 单帧内多个“打电话”行为司机副驾会被NMS合并为一个框② “抽烟”和“困倦”常共存闭眼手持烟但IoU0.45导致只保留置信度高的一个。我们改用分层NMS先按类别分组再对同类别框做NMS最后跨类别保留def non_max_suppression_custom(prediction, conf_thres0.3, iou_thres0.3): 分层NMS对每个class_id独立做NMS再合并结果 prediction: [batch, num_boxes, 5nc] nc prediction.shape[2] - 5 xc prediction[..., 4] conf_thres # candidates output [np.zeros((0, 6))] * prediction.shape[0] for xi, x in enumerate(prediction): # image index, image inference x x[xc[xi]] # confidence if not x.shape[0]: continue # 按class分组 for cls in range(nc): cls_mask x[:, 5 cls] conf_thres cls_boxes x[cls_mask] if len(cls_boxes) 0: continue # 提取该class的bbox和score boxes cls_boxes[:, :4] scores cls_boxes[:, 4] * cls_boxes[:, 5 cls] # obj_conf * cls_conf # OpenCV NMS indices cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), conf_thres, iou_thres) if len(indices) 0: for idx in indices.flatten(): output[xi] np.vstack([output[xi], np.hstack([boxes[idx], [scores[idx]], [cls]])]) return output参数说明conf_thres0.3比默认0.25更严格过滤掉低置信度的“疑似喝水”误检iou_thres0.3比默认0.45更低允许“抽烟”和“困倦”两个框共存只要中心点距离0.3*图像宽。实测使多行为并发检测率提升22.7%。4.3 避坑指南树莓派5部署YOLOv5的7个血泪教训常见问题以下7条均来自真实翻车现场按发生频率排序现象cv2.dnn.readNetFromONNX()报错Unsupported layer type: Resize原因ONNX导出时用了--dynamic参数导致模型含Resize算子树莓派OpenCV不支持解决导出时去掉--dynamic固定输入尺寸--img 640并在export.py中注释掉torch.onnx.export(..., dynamic_axes...)现象推理FPS仅3.2帧远低于标称42帧原因树莓派5默认用CPU推理未启用NEON加速解决编译OpenCV时加-D ENABLE_NEONON -D ENABLE_VFPV3ON或直接用预编译包pip3 install opencv-python-headless4.8.1.78现象检测框在画面边缘严重偏移如“打电话”框跑到车窗外原因车载摄像头有桶形畸变但blobFromImage未做矫正解决用cv2.undistort()先矫正需提前用calibrateCamera()获取相机内参矩阵现象“喝水”检测在强光下全军覆没原因训练时low_light_aug只加阴影未模拟强光过曝解决在low_light_aug()中增加cv2.convertScaleAbs(img, alpha1.2, beta-30)模拟过曝现象模型在连续视频中对同一行为反复触发告警如每秒报10次“困倦”原因未做时序滤波单帧检测抖动大解决实现滑动窗口投票——维护最近5帧的检测结果仅当同一类别在3帧以上出现才触发告警现象树莓派5运行10分钟后自动关机原因YOLOv5推理占满CPU温度80℃触发保护解决在detect_pi5.py中插入cv2.waitKey(33)强制30FPS上限并加散热风扇现象best.pt在树莓派上加载报RuntimeError: version_ kMaxSupportedFileFormatVersion原因训练机PyTorch版本2.0.1树莓派PyTorch1.12.1解决训练时用--device cpu导出或统一用PyTorch 1.12.1训练5. 实车验证与进阶技巧用时间戳对齐行为置信度衰减提升可用性5.1 时间戳对齐为什么GPS时间戳比系统时间更可靠车载DMS必须与车辆CAN总线数据对齐否则“检测到抽烟”和“车速80km/h”无法关联。树莓派系统时间漂移严重日均±2秒而GPS模块如u-blox NEO-6M提供PPS脉冲和UTC时间戳。我们采用硬件时间戳对齐# 在detect_pi5.py中插入 import serial gps_ser serial.Serial(/dev/ttyS0, 9600, timeout1) def get_gps_timestamp(): while True: line gps_ser.readline().decode(ascii, errorsignore) if line.startswith($GPRMC): parts line.split(,) if len(parts) 1 and parts[2] A: # 定位有效 time_utc parts[1] # HHMMSS.SS格式 return f{time_utc[:2]}:{time_utc[2:4]}:{time_utc[4:6]}逻辑说明每帧检测结果附加gps_timestamp字段后续与CAN日志按毫秒级对齐。实测时间误差50ms满足ADAS功能安全ASIL-B要求。5.2 行为置信度衰减模型让“困倦”告警更符合人类认知单纯用conf 0.5触发告警会误报——人眨眼本就是0.3秒模型可能把一次长眨眼判为困倦。我们引入指数衰减置信度class DMSAlert: def __init__(self): self.conf_history {cls: [] for cls in [drowsy,smoking,drinking,calling,distraction]} def update(self, cls, conf): self.conf_history[cls].append(conf) if len(self.conf_history[cls]) 10: # 保留最近10帧 self.conf_history[cls].pop(0) def get_alert_score(self, cls): # 加权平均新帧权重高旧帧权重按e^(-t/τ)衰减 scores self.conf_history[cls] weights np.exp(-np.arange(len(scores))[::-1] / 3.0) # τ3帧≈100ms return np.average(scores, weightsweights) # 使用示例 alert DMSAlert() for det in detections: cls_name [driver,drowsy,smoking,drinking,calling,distraction][int(det[5])] alert.update(cls_name, det[4]) if cls_name ! driver and alert.get_alert_score(cls_name) 0.65: trigger_warning(cls_name)参数说明τ3.0意味着3帧前的置信度衰减为原值的37%这与人类反应延迟200~300ms匹配。阈值0.65经200小时路测标定——低于此值误报率12%高于则漏报率8%。5.3 夜间模式自适应用图像亮度直方图动态切换检测阈值车载摄像头在隧道/夜间会自动提亮导致“抽烟”光斑被压制。我们用亮度直方图实时判断光照条件def get_brightness_level(frame): gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) hist cv2.calcHist([gray], [0], None, [256], [0,256]) # 计算亮度均值 mean_bright np.sum(hist * np.arange(256)) / np.sum(hist) if mean_bright 40: return night # 全黑 elif mean_bright 80: return tunnel # 隧道 elif mean_bright 140: return normal # 日常 else: return dazzle # 强光 # 在推理循环中 bright_mode get_brightness_level(frame) if bright_mode in [night, tunnel]: conf_thres 0.25 # 夜间降低置信度阈值 iou_thres 0.25 # 减少NMS压制 else: conf_thres 0.35 iou_thres 0.35实测效果隧道场景“抽烟”召回率从41.3%提升至76.8%且未增加白天误报。这个技巧让我在客户验收时少改了3版需求文档——他们原本要求“单独开发夜间模型”而用直方图自适应一行代码搞定。我干这行八年踩过的坑比写过的代码还多。现在每次接到DMS需求第一件事不是开IDE而是打开这个笔记把hyp.dms.yaml和detect_pi5.py模板拖进项目。YOLOv5或许不是最炫的但它像一辆老丰田卡罗拉——不惊艳但拉货、跑长途、修起来快关键时刻不趴窝。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网