简介:本资源是一套基于YOLOv8的电动车佩戴头盔检测系统,面向计算机视觉学习者、安全监管研发人员及需要目标检测实战项目的开发者,可自动识别图像或视频中驾驶员、骑行者是否佩戴头盔,及时发现安全漏洞。压缩包共129个文件,约17.12MB,以105张jpg样本图片、6个xml标注、6张png图表、3个py源码及1个onnx模型为主,另含qrc资源与txt说明,覆盖数据、训练、推理与界面全流程。系统在Windows10、Anaconda3+Python3.8、PyTorch1.9.0+cu111及ultralytics8.2.70环境下运行,检测类别包括Bicyclist、driver、helmet、no-helmet,并附带评估指标曲线,便于量化分析准确率与召回率。GUI部分采用PyQt5构建,界面美观、操作直观。目前已有1012人学习下载,适合希望快速复现YOLOv8检测项目、理解模型评估与桌面端部署的读者参考。
1. 电动车头盔检测这套源码,拿到手先别急着点运行
电动车骑行不戴头盔这件事,说到底是个人安全习惯问题,但落到工程上就是一个标准的单类目标检测任务:从监控画面里把「戴头盔的骑手」和「没戴头盔的骑手」框出来。基于 YOLOv8 的电动车佩戴头盔检测系统,核心就是拿 YOLOv8 训练一个能区分这两类的模型,再配一套 Python 推理代码、一份 ONNX 模型、一组评估指标曲线,最后套一个能直接点按钮的 GUI 界面。这套东西适合谁?做毕业设计的学生、想快速验证检测方案的产品团队、以及需要把模型往 RK3588 或 Hi3516CV610 这类边缘板子上搬的嵌入式工程师。它解决的不是「从零研究检测算法」的问题,而是「我已经知道要用 YOLOv8,怎么把数据、训练、导出、界面这条链路一次性跑通」的问题。拿到压缩包之后,先别双击那个 GUI 的 exe 或者 py,因为一旦环境不对,报错信息会把你带偏,后面排查成本翻倍。
2. 从数据集到 YOLOv8 训练:头盔检测的完整链路
2.1 头盔检测数据集长什么样,Labelme 标注怎么转 YOLO 格式
电动车头盔检测的数据集,常见做法是两类:helmet 和 head(或者 no_helmet)。图片来源一般是路口监控截图、行车记录仪抽帧、或者公开的骑行数据集。标注工具用 Labelme 的居多,因为它画多边形方便,但 YOLOv8 训练要的是 YOLO 格式的 txt,每行是class_id x_center y_center width height,全部归一化到 0 到 1 之间。
Labelme 导出的 json 里,shapes 字段存的是多边形点集,转 YOLO 的时候要取外接矩形。下面这个脚本是我常用的转换逻辑,处理单张图对应的 json:
import json import os from PIL import Image def labelme_to_yolo(json_path, output_dir, class_map): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue cls_id = class_map[label] points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 归一化并转中心点加宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") base = os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(output_dir, base + '.txt'), 'w') as f: f.write('\n'.join(lines)) class_map = {'helmet': 0, 'head': 1}逻辑说明:class_map 把标签名映射成 0 和 1,顺序要和后面 data.yaml 里的 names 一致,否则训练出来的模型会把类别搞反。归一化用图像原始宽高,不是缩放后的尺寸,这点如果搞错,框会整体偏移。参数上,x_center 和 y_center 保留六位小数足够,YOLOv8 内部会再处理。
转换完之后,目录结构要整理成 YOLOv8 认的格式:
dataset/ images/ train/ val/ labels/ train/ val/然后写 data.yaml:
path: ./dataset train: images/train val: images/val nc: 2 names: ['helmet', 'head']注意:names 的顺序必须和 class_map 完全一致,很多「训练 loss 正常但预测框类别全反」的问题都出在这里。
2.2 YOLOv8 环境搭建:Ubuntu 20.04 CPU 版本和 GPU 版本怎么选
热词里有人搜「ubuntu20.04 搭建 yolov8 环境 cpu 版本」,也有人搜「gtx1660ti 跑 yolov8」。我的建议是:如果只是跑通推理和 GUI,CPU 版本足够;如果要训练自己的数据集,至少上一张 6G 显存的卡,1660Ti 跑 yolov8n 在 640 分辨率下 batch 设 8 是能跑的,再大就爆显存。
CPU 版本安装,Ubuntu 20.04 下我一般这么走:
conda create -n helmet python=3.9 -y conda activate helmet pip install ultralytics opencv-python pyqt5 onnxruntimeGPU 版本多一步,先确认 CUDA 版本,然后装对应 torch:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics参数说明:python 3.9 是兼容性最稳的版本,3.11 以上有些 pyqt5 轮子会出问题。onnxruntime 装 CPU 版就够跑 ONNX 推理,如果要 GPU 加速 ONNX,换 onnxruntime-gpu,但要注意 CUDA 版本匹配。装完之后用yolo checks验证,能看到环境信息就说明通了。
2.3 训练参数怎么设:yolov8n 在头盔数据集上的最小配置
头盔检测这种单场景两类任务,yolov8n 就够,没必要上 m 或 l。训练命令:
yolo detect train \ data=dataset/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/helmet \ name=exp1逻辑说明:epochs 设 100 是起步值,头盔数据集一般 3000 到 8000 张,100 轮足够收敛。imgsz 640 是 YOLOv8 默认,如果监控画面里目标很小,可以提到 960,但显存和速度要重新权衡。batch 16 在 8G 显存上跑 yolov8n 没问题,显存小就降到 8。lr0 0.01 是 SGD 的初始学习率,YOLOv8 默认会用 AdamW 自动调,但显式写出来方便复现。patience 20 表示 20 轮没提升就早停,省时间。
训练完之后,runs/helmet/exp1 下面会有 weights/best.pt 和 last.pt,还有 results.csv 和一堆曲线图。评估指标曲线就是从这里来的,后面单独讲。
3. 评估指标曲线怎么看:mAP、PR 曲线和损失函数的实际含义
3.1 mAP50 和 mAP50-95 差在哪,头盔检测该盯哪个
YOLOv8 训练完会在 runs 目录下生成 results.png,里面有 box_loss、cls_loss、mAP50、mAP50-95 这几条曲线。很多人只看 mAP50,觉得到 0.9 就万事大吉,但实际部署的时候发现漏检还是多。原因是 mAP50 只要求预测框和真实框 IoU 大于 0.5 就算对,这个门槛在头盔检测里偏松,因为头盔和头部的框本来就重叠大。
mAP50-95 是把 IoU 从 0.5 到 0.95 每隔 0.05 算一次再平均,更严格。头盔检测我一般盯 mAP50-95,能到 0.6 以上就算可用,0.7 以上算好。如果 mAP50 高但 mAP50-95 低,说明框的位置不够准,这时候要检查标注质量,尤其是那些头盔只露一半的图。
PR 曲线(Precision-Recall)在 results.png 里也有,它反映的是不同置信度阈值下精确率和召回率的权衡。头盔检测场景下,如果业务上更怕漏检(没戴头盔的没抓出来),就把置信度阈值调低,牺牲一点精确率换召回率。
3.2 用 Python 自己画损失函数曲线,方便写报告
训练日志在 results.csv 里,字段包括 epoch、train/box_loss、train/cls_loss、metrics/mAP50 等。写毕业设计或者技术报告的时候,用 matplotlib 重画一遍比直接截图好看:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/helmet/exp1/results.csv') df.columns = df.columns.str.strip() fig, ax1 = plt.subplots(figsize=(10, 5)) ax1.plot(df['epoch'], df['train/box_loss'], label='box_loss', color='tab:red') ax1.plot(df['epoch'], df['train/cls_loss'], label='cls_loss', color='tab:blue') ax1.set_xlabel('epoch') ax1.set_ylabel('loss') ax1.legend(loc='upper left') ax2 = ax1.twinx() ax2.plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP50-95', color='tab:green') ax2.set_ylabel('mAP50-95') ax2.legend(loc='upper right') plt.title('Helmet Detection Training Curve') plt.tight_layout() plt.savefig('training_curve.png', dpi=150)逻辑说明:双 y 轴,左边画 loss,右边画 mAP,这样一张图能同时看收敛和精度。参数上 dpi 150 够报告用,再高文件太大。注意 results.csv 的列名可能带空格,strip 一下避免 KeyError。
提示:如果 loss 曲线震荡厉害,先别急着调模型,检查 batch 是不是太小,或者学习率是不是偏高。
4. PyTorch 转 ONNX 再到 RK3588:模型导出与部署的实操路径
4.1 PyTorch 转 ONNX 的最小命令和三个必调参数
训练完的 best.pt 要部署到非 Python 环境,第一步就是转 ONNX。YOLOv8 自带导出命令:
yolo export model=runs/helmet/exp1/weights/best.pt format=onnx imgsz=640 opset=12 simplify=True参数说明:imgsz 要和训练时一致,否则检测框会偏。opset 12 是兼容性最好的版本,RK3588 的 rknn-toolkit2 对 opset 12 支持最稳,opset 17 有时候会报不支持的算子。simplify=True 会调用 onnx-simplifier 去掉冗余节点,减小模型体积,也方便后面转 rknn。
导出完之后用 onnxruntime 验证一下能不能跑:
import onnxruntime as ort import numpy as np sess = ort.InferenceSession('best.onnx') input_name = sess.get_inputs()[0].name # 构造一个假输入,NCHW,float32 dummy = np.random.randn(1, 3, 640, 640).astype(np.float32) outputs = sess.run(None, {input_name: dummy}) print([o.shape for o in outputs])逻辑说明:输出一般是 [1, 6, 8400],6 是 4 个框坐标加 2 个类别分数,8400 是候选框数量。如果输出形状不对,检查导出时的 imgsz 和类别数。
4.2 ONNX 转 RKNN 部署到 RK3588 的关键步骤
RK3588 部署 YOLOv8,常见路径是 ONNX 转 RKNN。需要 rknn-toolkit2,在 x86 机器上转换,然后把 rknn 模型拷到板子上跑。转换脚本核心部分:
from rknn.api import RKNN rknn = RKNN() rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') rknn.load_onnx(model='best.onnx') rknn.build(do_quantization=True, dataset='quant_dataset.txt') rknn.export_rknn('best.rknn')参数说明:mean 和 std 要和训练时的预处理一致,YOLOv8 默认是 0 到 1 归一化,所以 std 设 255。do_quantization=True 会做 int8 量化,需要准备一个 quant_dataset.txt,里面是几十张校准图片的路径。量化之后模型体积能降到四分之一左右,速度提升明显,但精度可能掉一两个点,头盔检测这种两类任务一般能接受。
注意:量化校准集要覆盖白天、夜间、逆光这些场景,否则量化后的模型在特定光照下会翻车。
4.3 ONNX 量化 int8 之后精度掉了怎么办
int8 量化掉精度是常见问题,尤其是小目标。如果量化后 mAP 掉超过 5 个点,先检查校准集是不是太少或者太单一。我一般会准备 200 张左右的校准图,按场景分层抽样。如果还是掉,可以只量化卷积层,保留检测头为 fp16,rknn-toolkit2 支持混合量化,但配置起来麻烦一些。
另一个办法是导出 ONNX 的时候不做 simplify,保留更多原始结构,量化时容错空间大一点。这个属于玄学范畴,但实测有时候有效。
5. GUI 界面怎么做:PyQt5 套 YOLOv8 推理的避坑记录
5.1 用 PyQt5 搭一个能选图片、能实时检测的界面
GUI 部分用 PyQt5 是最常见的做法,因为和 Python 结合紧,打包也方便。核心逻辑是:一个按钮选图片,一个 QLabel 显示原图,一个 QLabel 显示检测结果,再加一个文本框输出检测到的类别和数量。
推理线程要和 UI 线程分开,否则检测的时候界面会卡死。用 QThread 包一层:
from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectThread(QThread): finished = pyqtSignal(object) def __init__(self, model_path, img): super().__init__() self.model = YOLO(model_path) self.img = img def run(self): results = self.model(self.img, conf=0.4) self.finished.emit(results)逻辑说明:conf 0.4 是置信度阈值,头盔检测场景下可以调到 0.3 提高召回。finished 信号把结果传回主线程更新界面。注意 YOLO 模型加载放在线程里,如果放主线程,第一次加载会卡几秒。
5.2 界面卡顿和模型重复加载的排查
现象:点一次检测卡一次,第二次更慢。原因:每次点按钮都重新 YOLO(model_path),模型反复加载。解决:把模型加载放到init里,或者用单例模式全局只加载一次。
现象:检测大图的时候界面无响应。原因:推理在主线程跑。解决:上面那个 QThread 就是干这个的,所有推理都丢线程里。
现象:打包成 exe 之后找不到模型文件。原因:PyInstaller 打包后路径变了。解决:用 sys._MEIPASS 处理资源路径,或者把模型文件放到 exe 同级目录用绝对路径读。
6. 从跑通到好用:置信度阈值和 NMS 的调参习惯
置信度阈值和 NMS 的 IoU 阈值,是头盔检测从「能跑」到「好用」之间最关键的两个参数。默认 conf 0.25、iou 0.45 在通用场景下没问题,但头盔检测有它的特殊性:骑手密集的时候,框和框重叠大,NMS 的 iou 设高了会保留一堆重复框,设低了会把相邻的骑手框误删。
我的习惯是分两步调。第一步,固定 iou 0.5,把 conf 从 0.5 往下扫到 0.2,看召回率和精确率的拐点。头盔检测如果业务上更怕漏检,拐点可以往召回率那边偏。第二步,固定 conf 在拐点值,把 iou 从 0.4 扫到 0.6,看密集场景下的框数量。一般 iou 0.5 到 0.55 之间比较稳。
验证方法很简单,准备一段路口视频,用不同参数跑一遍,把结果导出来人工数一下漏检和误检。别只看 mAP,mAP 是数据集上的平均值,实际场景的分布可能和数据集不一样。
results = model(frame, conf=0.35, iou=0.5, classes=[0, 1])这行代码里的 classes 参数可以只保留头盔和头部两类,如果模型还输出了其他类别,过滤掉能减少后处理时间。
最后一个习惯:每次调完参数,把 conf、iou、对应的漏检数和误检数记在一个表格里,下次换数据集或者换场景的时候,直接从历史值附近开始扫,不用从零试。这个习惯帮我省了很多重复劳动,也避免了一拍脑袋就改参数的翻车。希望帮到你。
本文还有配套的精品资源,点击获取