简介本资源提供一套已训练完成的YOLOv8摩托车佩戴头盔与驾驶员检测模型面向计算机视觉学习者、交通安全智能分析开发者及需要快速落地检测功能的工程人员可直接加载权重进行推理或二次微调省去从零标注与训练的成本。压缩包共901个文件约101.23MB以508个md文档、134个py脚本、91个pyc缓存、48个yaml配置、30张jpg与15张png示例图为主另含6个pt权重、6个rs源码、4个cpp推理示例及dockerfile系列部署文件覆盖训练、推理、导出与容器化运行环节。目前已有828人学习下载。资源包含训练日志、模型权重、推理脚本与多平台部署配置读者可据此完成头盔佩戴状态与驾驶员目标的识别验证并参考可视化案例理解检测效果与调优方向。1. 摩托车头盔与驾驶员检测为什么双任务同框比单做头盔更值得投入去年帮一个做智慧工地和园区出入口的团队看模型他们原本只做“戴没戴头盔”的二分类上线两周就被现场打脸画面里出现后座乘客没戴、驾驶员单手扶把、甚至一辆车两个人只戴一顶的情况单任务模型全判成“合规”。问题不在数据量而在任务定义——yolov8摩托车佩戴头盔和驾驶员检测本质是一个多目标、多类别、强关联的检测问题头盔状态必须绑定到“人”这个主体上才有意义。这个方向能解决三件事一是把驾驶员和乘客区分开二是把每个人的头盔状态单独判定三是给后续的违规抓拍、语音提醒、统计报表提供结构化输出。适合谁做做交通/园区安防的算法工程师、拿它当毕业设计的学生、以及要在 RK3588、Orin 这类边缘盒子上跑实时推理的嵌入式同学。它不追求 SOTA追求的是“类别定义清楚、数据能标、模型能落地”。2. 类别体系与数据标注把“人-头盔-车”拆成可训练的标签2.1 为什么类别设计决定了这个项目能不能收敛很多人一上来就标helmet和no_helmet结果训练完发现模型把路边的安全帽、工地上的帽子全检出来了。原因很简单你只告诉它“什么是头盔”没告诉它“头盔长在谁头上”。我一般会把类别拆成四类这是经过多个现场验证后比较稳的方案类别名含义典型误检来源rider摩托车驾驶员含电动摩托自行车骑行者、行人passenger后座乘客被遮挡的驾驶员helmet戴在头上的头盔车筐里的头盔、手持头盔no_helmet未戴头盔的头部戴帽子、光头反光注意helmet和no_helmet都只标“头部区域”不标整个人。这样模型学到的是头部与头盔的空间关系而不是“车上有头盔就算戴了”。rider和passenger标整个人体框用于后续把头盔状态归属到具体的人。2.2 用 labelme 标注并转成 YOLO 格式的完整脚本热词里labelme标注用于yolov8和处理数据集用于yolov8训练出现频率很高说明卡在格式转换的人不少。labelme 默认输出 JSONYOLO 要的是class_id x_center y_center w h归一化坐标。下面这个脚本我用了两年处理过几万张图直接抄import json import os from pathlib import Path # 类别映射顺序必须和 data.yaml 里的 names 一致 CLASS_MAP {rider: 0, passenger: 1, helmet: 2, no_helmet: 3} def labelme_to_yolo(json_path, img_w, img_h, out_txt): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue # 跳过未定义类别避免训练时报错 pts shape[points] xs [p[0] for p in pts] ys [p[1] for p in pts] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 归一化并限制在 0~1防止标注越界导致 loss 爆炸 x_c max(0, min(1, (x_min x_max) / 2 / img_w)) y_c max(0, min(1, (y_min y_max) / 2 / img_h)) w max(0, min(1, (x_max - x_min) / img_w)) h max(0, min(1, (y_max - y_min) / img_h)) lines.append(f{CLASS_MAP[label]} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) # 批量转换图片尺寸从原图读取这里假设你已经知道尺寸 # 实际使用时建议用 PIL 打开图片获取 img_w, img_h逻辑说明CLASS_MAP的顺序就是data.yaml里names的顺序错一位整个训练就废了。归一化时做max(0, min(1, ...))裁剪是因为 labelme 手标时偶尔会把框拖出图像边界不裁剪的话 YOLO 训练会出现 NaN loss这个坑我踩过。参数上x_c、y_c是框中心点w、h是宽高全部除以图像宽高不是除以 640。2.3 data.yaml 与训练集划分的三个硬性要求转换完还要写data.yaml这是 YOLOv8 训练的入口配置path: /home/user/datasets/helmet train: images/train val: images/val nc: 4 names: [rider, passenger, helmet, no_helmet]三个硬性要求第一train和val路径下图片和标签必须同名同目录结构images/train/001.jpg对应labels/train/001.txt第二验证集不能和训练集有同一段视频的相邻帧否则 mAP 虚高我一般按视频源划分而不是按帧随机划分第三nc必须等于names长度多一个少一个都会在训练启动时直接报错。按 8:1:1 划分时如果某个类别样本少于 200 张优先补数据而不是调参。3. 用 YOLOv8 训练双任务模型参数怎么设、曲线怎么看3.1 环境搭建与预训练权重选择热词里ubuntu20.04搭建yolov8环境cpu版本和yolov8环境配置说明很多人在环境这步就卡住。我的建议是训练阶段别用 CPUgtx1660ti跑yolov8这种 6G 显存的卡足够跑yolov8sbatch 设 8 或 16。安装就三行conda create -n yolov8 python3.10 -y conda activate yolov8 pip install ultralytics opencv-python labelmeultralytics包自带 YOLOv8 全部代码和预训练权重下载逻辑不需要单独去下yolov8n.pt。首次训练会自动从官方源拉权重如果网络不稳可以手动下载后放到项目根目录训练时用modelyolov8s.pt指定本地文件。CPU 版本只适合推理验证训练一个 epoch 要几十分钟不划算。3.2 训练命令与关键参数含义yolo detect train \ data/home/user/datasets/helmet/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ patience30 \ device0 \ projecthelmet_run \ nameexp1参数逐个说imgsz640是输入分辨率摩托车场景里头盔目标偏小如果显存够可以上 960但推理速度会掉lr00.01是初始学习率YOLOv8 默认 SGD 下这个值比较稳用 AdamW 的话降到 0.001lrf0.01是最终学习率系数余弦退火到lr0*lrfpatience30表示 30 个 epoch 验证指标不升就早停防止过拟合。batch16在 6G 显存上跑yolov8s加 640 分辨率基本是上限报 OOM 就降到 8。3.3 损失曲线与 mAP 曲线的判读方法热词yolov8画损失函数曲线图背后是真需求训练完不知道模型好坏。YOLOv8 训练结束后runs/detect/exp1/下会有results.csv用下面这段画图import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/exp1/results.csv) df.columns df.columns.str.strip() # 列名可能带空格必须清理 fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].plot(df[epoch], df[train/box_loss], labeltrain_box) axes[0].plot(df[epoch], df[val/box_loss], labelval_box) axes[0].set_title(Box Loss) axes[0].legend() axes[1].plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) axes[1].plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) axes[1].set_title(mAP) axes[1].legend() plt.savefig(curve.png, dpi150)判读逻辑train/box_loss持续下降但val/box_loss开始上升就是过拟合加数据或加增强两条都平在高位是欠拟合或学习率太小mAP50到 0.85 以上、mAP50-95到 0.55 以上这个双任务场景基本可用。注意helmet和no_helmet的 AP 要单独看如果no_helmet的 AP 明显低说明负样本不够去补夜间和逆光场景。4. 推理部署与后处理把检测框变成“谁没戴头盔”的结论4.1 推理脚本与头盔归属逻辑训练完导出best.pt推理不是简单画框核心是把helmet/no_helmet归属到rider/passenger。逻辑是对每个头部框找与它 IoU 最大的人体框如果 IoU 大于阈值就归属否则丢弃。下面是核心代码from ultralytics import YOLO import cv2 model YOLO(runs/detect/exp1/weights/best.pt) CLASSES {0: rider, 1: passenger, 2: helmet, 3: no_helmet} def iou(box1, box2): x1 max(box1[0], box2[0]); y1 max(box1[1], box2[1]) x2 min(box1[2], box2[2]); y2 min(box1[3], box2[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area1 (box1[2]-box1[0]) * (box1[3]-box1[1]) area2 (box2[2]-box2[0]) * (box2[3]-box2[1]) return inter / (area1 area2 - inter 1e-6) img cv2.imread(test.jpg) results model(img, conf0.4, iou0.5)[0] persons, heads [], [] for box in results.boxes: cls int(box.cls[0]) xyxy box.xyxy[0].tolist() if cls in (0, 1): persons.append((xyxy, cls)) else: heads.append((xyxy, cls)) for h_box, h_cls in heads: best_iou, owner 0, None for p_box, p_cls in persons: v iou(h_box, p_box) if v best_iou: best_iou, owner v, p_cls if best_iou 0.1: # 阈值别设太高头部框通常只占人体上部 status 戴头盔 if h_cls 2 else 未戴头盔 role 驾驶员 if owner 0 else 乘客 print(f{role}: {status}, IoU{best_iou:.2f})参数说明conf0.4是置信度阈值现场误检多就提到 0.5iou0.5是 NMS 阈值人和车重叠时别调太低归属阈值0.1是经验值因为头部框和人体框的 IoU 天然偏小设 0.3 会漏掉大量归属。这个逻辑跑通后输出就是“驾驶员未戴头盔”这种可直接触发告警的结论。4.2 边缘部署时模型格式与分辨率怎么选热词里rk3588部署yolov8、hi3516cv610 yolov8模型转换与部署实战、orin部署yolov8分割都指向边缘落地。RK3588 上一般走 ONNX 转 RKNNOrin 走 TensorRT。我的经验是边缘端优先用yolov8n或yolov8s输入 640INT8 量化后 RK3588 单核能到 30 FPS 以上。量化时校准集必须包含夜间和逆光样本否则no_helmet的召回会掉得很厉害。如果板端算力实在不够把imgsz降到 416但头盔小目标会丢需要权衡。5. 避坑与排查双任务检测里最容易翻车的五件事5.1 现象训练 loss 正常但 mAP 一直是 0原因data.yaml里names顺序和标注转换时的CLASS_MAP不一致模型学的是错位标签。解决打印一张图的标签文件对照names逐行核对顺序错一位就全错。5.2 现象helmet 框把车筐里的头盔也检出来原因标注时把非佩戴状态的头盔也标成了helmet。解决重新定义标注规则只标“戴在头上”的头盔车筐、手持的一律不标或者单独加一类helmet_off。5.3 现象夜间画面 no_helmet 几乎检不到原因训练集夜间样本太少且夜间头部与背景对比度低。解决补 500 张以上夜间图训练时开hsv_v0.5、hsv_s0.7做色彩增强推理时对暗光图先做 CLAHE 增强再送入模型。5.4 现象后座乘客被漏检只检出驾驶员原因passenger样本少且被驾驶员遮挡严重。解决标注时对遮挡乘客也标完整框训练时开mosaic1.0和copy_paste0.3增加遮挡场景的泛化。5.5 现象RK3588 上量化后精度暴跌原因校准集只用了白天清晰图量化参数偏乐观。解决校准集按 7:3 混入夜间和模糊图量化后用 200 张验证集对比 FP16 和 INT8 的 mAP掉超过 5 个点就回退 FP16。6. 进阶技巧用跟踪 ID 把单帧检测变成连续违规判定单帧检测有个玄学问题同一辆车在连续帧里可能一帧判“未戴”一帧判“戴”告警就会抖动。我一般会接一个轻量跟踪器用rider框做跟踪目标把头盔状态按跟踪 ID 做多数投票。下面是用supervision库的简化写法import supervision as sv from ultralytics import YOLO model YOLO(best.pt) tracker sv.ByteTrack() box_annotator sv.BoxAnnotator() # 假设 frames 是视频帧列表 for frame in frames: results model(frame, conf0.4)[0] detections sv.Detections.from_ultralytics(results) detections tracker.update_with_detections(detections) # detections.tracker_id 就是跟踪 ID # 按 tracker_id 聚合 helmet/no_helmet 的投票结果 for tid in set(detections.tracker_id): mask detections.tracker_id tid # 统计该 ID 下 helmet 和 no_helmet 的帧数超过 70% 才判定违规 ...参数上ByteTrack 的track_activation_threshold默认 0.25摩托车场景建议提到 0.4减少碎片化 ID。投票窗口我一般取 15 帧超过 70% 帧判no_helmet才触发告警这样能压掉大部分抖动。验证方法是拿一段 5 分钟的视频跑一遍统计告警次数和人工核对次数差 10% 以内就算稳。最后说个血泪教训这个项目里最花时间的从来不是调模型而是把“戴没戴头盔”这个业务定义翻译成标注规则。规则没定清楚标 5000 张也是白标。我现在的习惯是先用 200 张图跑一轮把误检案例打印出来和业务方逐条对齐规则再批量标。希望帮到你。本文还有配套的精品资源点击获取