ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

监控视角头盔佩戴检测:2000张YOLO/VOC双格式数据集实战指南

监控视角头盔佩戴检测:2000张YOLO/VOC双格式数据集实战指南 简介这份资源是面向计算机视觉方向的科研人员、毕业设计学生及算法工程师的非机动车头盔佩戴检测数据集聚焦监控视角下电动车骑行场景的真实抓拍图像可用于电动车头盔佩戴检测、骑行人员识别等任务帮助解决实际落地项目中样本获取难、标注质量参差的问题。压缩包共2000个文件包含1000个xml格式的VOC标注文件与1000个txt格式的YOLO标注文件整体约918.81MB两种标签可分别适配不同检测框架的训练流程。数据集划分四类目标头盔、未佩戴头盔的骑行者、佩戴头盔的骑行者以及被骑行的电动车全部经labelimg精准标注使用YOLOv8训练精度可达96.7%。目前已有671人学习下载适合需要快速搭建检测基线、验证模型效果或完成课程作业与毕设实验的读者直接使用。1. 监控视角下头盔佩戴检测2000 张双格式数据集到底能干什么路口摄像头拍到的画面里电动车、摩托车、自行车混行骑行者头部只占几十个像素还经常被雨棚、遮阳伞、后车遮挡。想用 YOLO 做「戴没戴头盔」的二分类或三分类检测第一个卡点从来不是模型结构而是数据。这个标题指向的就是一份面向该场景的成品数据集2000 张监控视角图像同时提供 YOLO 格式和 VOC 格式的标签文件。它解决的是「从零标注成本高、格式不统一、场景不贴脸」这三件事适合两类人一类是想快速跑通 yolov8训练自己的数据集 流程的算法新手另一类是手里有路口视频、需要先验证头盔检测可行性的工程人员。2000 张不算大但双格式省掉了格式转换的重复劳动监控视角又比网上大量摆拍图更接近真实部署条件值不值得投入取决于你怎么用它。2. 先看清数据2000 张监控图与双格式标签的匹配逻辑2.1 为什么监控视角的头盔检测比通用检测难通用目标检测数据集里人骑车戴头盔往往是近景、正面、光照均匀。监控视角完全不是这回事。摄像头装在 4 到 6 米高的杆上俯角大骑行者从画面远端进入头部在 1080P 画面里可能只有 30×30 像素。更麻烦的是三类目标天然不平衡戴头盔的占多数没戴的少头盔拿在手里或挂在车把上的更少。如果直接拿 COCO 预训练模型微调模型会倾向于把「头部区域」整体判成戴头盔因为背景里裸露头部的样本太少。这份数据集的价值就在场景一致性。2000 张全部来自监控视角意味着训练时的域和部署时的域接近mAP 不会出现「测试集漂亮、上路就崩」的落差。但要注意2000 张里如果按 8:1:1 切分验证集只有 200 张指标波动会很大尤其是「未佩戴」这一类。我的习惯是先把类别分布统计出来再决定要不要做重采样。2.2 YOLO 格式与 VOC 格式的标签差异两种格式描述的是同一批标注但组织方式不同。VOC 用 XML一个图一个文件坐标是左上角和右下角的绝对像素值YOLO 用 txt一个图一个文件每行是类别 中心x 中心y 宽 高全部归一化到 0 到 1。很多人拿到双格式后随手用 VOC 训练结果发现框全错位原因就是没做坐标转换。对比项VOC (XML)YOLO (txt)坐标形式xmin,ymin,xmax,ymax 绝对像素cx,cy,w,h 归一化 0~1类别表示字符串标签名从 0 开始的整数索引文件组织Annotations JPEGImagesimages labels 同名对应常用读取xml.etree直接按行 split如果你打算用 YOLOv5 或 YOLOv8 训练直接用 YOLO 格式最省事如果要用 MMDetection 或做 VOC 评测就用 XML。两条路都留着是这份数据集比较实用的地方。2.3 用脚本核对图像与标签是否一一对应拿到数据集第一件事不是训练是核对。图像和标签数量对不上、文件名对不上是血泪经验里最常见的翻车点。下面这段脚本同时检查 YOLO 和 VOC 两套标签的完整性。import os from pathlib import Path img_dir Path(dataset/images) # 图像目录 yolo_dir Path(dataset/labels) # YOLO 标签目录 voc_dir Path(dataset/annotations) # VOC 标签目录 imgs {p.stem for p in img_dir.glob(*.jpg)} yolo {p.stem for p in yolo_dir.glob(*.txt)} voc {p.stem for p in voc_dir.glob(*.xml)} print(图像数量:, len(imgs)) print(YOLO 标签数量:, len(yolo)) print(VOC 标签数量:, len(voc)) print(有图无 YOLO 标签:, sorted(imgs - yolo)[:10]) print(有图无 VOC 标签:, sorted(imgs - voc)[:10]) print(多余 YOLO 标签:, sorted(yolo - imgs)[:10])逻辑说明用Path.glob收集三类文件的 stem不含扩展名的文件名做集合差集。参数上img_dir、yolo_dir、voc_dir要按你解压后的真实目录改。如果差集非空先别急着训练把缺标签的图挑出来单独看往往是标注时漏存或者文件名大小写不一致。这一步花五分钟能省掉后面训练半天发现 loss 不降的后悔药。3. 把数据集喂给 YOLOv8从目录结构到第一次训练3.1 目录结构怎么摆才不报错YOLOv8 对目录结构有固定预期摆错了会直接抛No labels found。常见做法是建一个datasets/helmet根目录下面分images和labels各自再分train、val、test。datasets/helmet/ ├── images/ │ ├── train/ # 约 1600 张 │ ├── val/ # 约 200 张 │ └── test/ # 约 200 张 └── labels/ ├── train/ ├── val/ └── test/注意 images 和 labels 下的子目录名必须完全一致YOLO 是靠路径替换找标签的把images换成labels、.jpg换成.txt。如果你把标签全平铺在一个目录里训练时就会找不到。3.2 划分训练验证集并生成 data.yaml划分脚本要保证图像和标签同步移动不能只移图像。import random, shutil from pathlib import Path random.seed(42) # 固定种子保证可复现 src_img Path(dataset/images) src_lbl Path(dataset/labels) root Path(datasets/helmet) files sorted(p.stem for p in src_img.glob(*.jpg)) random.shuffle(files) n len(files) train, val files[:int(n*0.8)], files[int(n*0.8):int(n*0.9)] test files[int(n*0.9):] for split, names in [(train, train), (val, val), (test, test)]: (root/images/split).mkdir(parentsTrue, exist_okTrue) (root/labels/split).mkdir(parentsTrue, exist_okTrue) for name in names: shutil.copy(src_img/f{name}.jpg, root/images/split/f{name}.jpg) shutil.copy(src_lbl/f{name}.txt, root/labels/split/f{name}.txt) print(train/val/test:, len(train), len(val), len(test))参数说明seed42是为了每次划分结果一致方便复现比例 8:1:1 是 2000 张量级下的常用切法验证集和测试集各 200 张。如果「未佩戴」样本很少建议改成按类别分层抽样否则验证集里可能一个未佩戴都没有。接着写data.yamlpath: datasets/helmet train: images/train val: images/val test: images/test names: 0: helmet # 佩戴头盔 1: no_helmet # 未佩戴头盔 2: head # 裸露头部可选类别名和索引必须和标签文件里的整数严格对应。如果你拿到的标签只有两类就把head删掉别留着空类否则训练时该类永远学不到混淆矩阵里会出现一整行零。3.3 用 yolov8n 跑通第一轮训练先别上大模型。2000 张的量级yolov8n或yolov8s足够验证流程跑得快、显存占用低。yolo detect train \ modelyolov8n.pt \ datadatasets/helmet/data.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/helmet \ namebaseline参数说明modelyolov8n.pt是官方预训练权重做迁移学习比从零训收敛快得多imgsz640是默认输入尺寸监控小目标如果漏检严重可以提到 960 或 1280但显存和耗时同步上涨batch16在 8G 显存上比较稳爆显存就降到 8epochs100配合早停通常 60 到 80 轮就趋于平稳。训练日志里重点看mAP50和每一类的Precision/Recall不要只盯总 loss。3.4 训练完先看混淆矩阵别急着调参第一轮跑完runs/helmet/baseline下会生成confusion_matrix.png。头盔检测最典型的错误是把no_helmet判成helmet因为两者头部区域高度重叠模型容易偷懒。如果混淆矩阵显示no_helmet大量漏到helmet说明未佩戴样本太少或特征不够优先补数据而不是调学习率。另一个要看的是val_batch预测图直接肉眼确认小目标有没有被框住。这一步是判断数据集质量的关键比盲目加 epoch 有用得多。4. 从 VOC 标签转 YOLO转换脚本与四个边界坑4.1 转换的核心是坐标归一化VOC 的xmin,ymin,xmax,ymax是绝对像素YOLO 要的是相对整图宽高的中心点和尺寸。公式很直接cx(xminxmax)/2/Wcy(yminymax)/2/Hw(xmax-xmin)/Wh(ymax-ymin)/H。坑不在公式在边界处理。import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image classes [helmet, no_helmet, head] # 顺序决定类别索引 voc_dir Path(dataset/annotations) img_dir Path(dataset/images) out_dir Path(dataset/labels_from_voc) out_dir.mkdir(exist_okTrue) for xml_path in voc_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() img_file root.find(filename).text W, H Image.open(img_dir/img_file).size # 用真实图像尺寸别信 XML 里的 size lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue b obj.find(bndbox) xmin max(0, float(b.find(xmin).text)) ymin max(0, float(b.find(ymin).text)) xmax min(W, float(b.find(xmax).text)) ymax min(H, float(b.find(ymax).text)) cx (xmin xmax) / 2 / W cy (ymin ymax) / 2 / H w (xmax - xmin) / W h (ymax - ymin) / H lines.append(f{classes.index(name)} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) (out_dir/f{xml_path.stem}.txt).write_text(\n.join(lines))逻辑说明classes列表的顺序就是最终标签里的整数索引必须和data.yaml的names一致。用PIL读真实图像尺寸而不是 XML 里的size字段是因为部分标注工具写进去的宽高和实际图不符这是踩过的坑。max(0,...)和min(W,...)做裁剪防止标注框越界导致归一化后出现负数或大于 1 的值。4.2 四个必须检查的边界情况第一标注框越界。有些框的xmax超过图像宽度归一化后w会大于 1YOLO 训练时直接报错或产生异常梯度。上面的裁剪能挡住大部分但转换后仍要抽查。第二宽高为零。xminxmax的退化框会让w0训练时算 IoU 出 NaN。转换后加一句过滤if w 0 or h 0: continue。第三类别名大小写和空格。VOC 里写Helmet、helmet带尾空格的情况都有classes.index会直接抛ValueError。稳妥做法是先name.strip().lower()再匹配。第四图像尺寸读取失败。如果img_dir里找不到对应图Image.open会报错中断整个循环。建议用try/except包住把失败的文件名记到日志里转换完统一处理而不是让脚本中途挂掉。4.3 转换后做一次可视化抽检转换完不要直接开训。随机抽 20 张把 YOLO 标签画回图上确认框的位置和类别都对。import random from PIL import Image, ImageDraw from pathlib import Path img_dir Path(dataset/images) lbl_dir Path(dataset/labels_from_voc) classes [helmet, no_helmet, head] for name in random.sample([p.stem for p in lbl_dir.glob(*.txt)], 20): img Image.open(img_dir/f{name}.jpg).convert(RGB) W, H img.size draw ImageDraw.Draw(img) for line in (lbl_dir/f{name}.txt).read_text().splitlines(): c, cx, cy, w, h line.split() cx, cy, w, h map(float, (cx, cy, w, h)) x1, y1 (cx-w/2)*W, (cy-h/2)*H x2, y2 (cxw/2)*W, (cyh/2)*H draw.rectangle([x1, y1, x2, y2], outlinered, width2) draw.text((x1, y1), classes[int(c)], fillred) img.save(fcheck_{name}.jpg)这一步是黑匣子打开的关键。如果框整体偏移多半是宽高用错如果框大小对但位置反了检查cx/cy有没有写反。抽检通过再进训练能避免白跑几小时。5. 训练与部署中的避坑排查清单5.1 现象loss 一直不降mAP 卡在 0.1 以下原因通常是标签路径没对上模型实际在学空标签。YOLO 找不到标签时不会报错而是把该图当负样本。解决训练前用第 2.3 节的脚本核对一遍再确认data.yaml里的train路径是相对path的不是绝对路径写错。5.2 现象验证集 mAP 很高实际视频里漏检严重原因是训练集和验证集同分布但和真实视频的域不一致。监控视频有运动模糊、夜间红外、雨雾静态图数据集覆盖不到。解决从真实视频里抽帧人工标 100 到 200 张加入训练集做一轮微调。这一步比调任何超参都有效。5.3 现象小目标头盔大量漏检原因imgsz640下30 像素的头部缩到更小特征在深层几乎消失。解决把imgsz提到 960 或 1280或者在data.yaml同级配置里开启多尺度训练。代价是显存和推理耗时上升部署端要同步评估。5.4 现象混淆矩阵里 no_helmet 几乎全被判成 helmet原因类别不平衡未佩戴样本占比过低模型倾向多数类。解决对no_helmet做过采样或在损失里给该类更高权重。YOLOv8 不直接暴露类别权重简单做法是把未佩戴样本复制多份进训练集配合mosaic增强。5.5 现象训练中途 BN 崩溃或 loss 变 NaN原因某个 batch 里出现了退化框宽或高为 0或越界坐标导致梯度爆炸。解决回到第 4.2 节在转换脚本里过滤掉w0 or h0的框并确认所有坐标都在 0 到 1 之间。数据干净了BN 自然稳。6. 让 2000 张数据发挥更大价值分层采样与难例回流2000 张的规模想再往上提点靠的不是换更大的模型而是把数据用得更细。我一般会做两件事。第一件是分层采样。先统计每类目标的实例数如果no_helmet只有几百个就在划分训练集时对它做 2 到 3 倍过采样让每个 epoch 里三类大致均衡。实现上不用改 YOLO 源码直接在生成train.txt时按类别重复写入路径即可。第二件是难例回流。模型上线后把置信度在 0.3 到 0.6 之间的预测框对应的图挑出来人工复核后加入训练集。这些是模型「拿不准」的样本信息量远大于随机新增的图。下面这段脚本从预测结果里筛难例import json from pathlib import Path pred_file Path(runs/helmet/predictions.json) # 推理输出 hard [] for line in pred_file.read_text().splitlines(): r json.loads(line) for det in r[detections]: if 0.3 det[confidence] 0.6: hard.append(r[image_id]) break Path(hard_samples.txt).write_text(\n.join(sorted(set(hard)))) print(难例数量:, len(set(hard)))参数说明置信度区间 0.3 到 0.6 是经验值太低说明模型完全没把握可能是标注噪声太高说明已经学会没必要重复。筛出来的图优先人工复核确认是漏标还是真难例再决定是否回流。一个具体技巧训练时把close_mosaic设成最后 10 个 epoch 关闭马赛克增强。马赛克会把四张图拼一起对小目标学习有帮助但训练末期会让框的位置不够准。关掉之后 mAP 通常还能再涨一两个点这个习惯我一直在用。最后说个教训。我早期做头盔检测时拿到数据直接开训跑了两天才发现验证集里「未佩戴」只有 8 个样本指标全是虚高。后来养成习惯任何数据集到手先统计类别分布、核对标签完整性、抽检可视化这三步走完再碰模型。数据上的十分钟抵得上调参的一整天。希望帮到你。本文还有配套的精品资源点击获取
返回列表