ARTICLE DETAIL

资讯详情

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

俯视安全帽数据集详解:从VOC标注到YOLOv8小目标检测实战

俯视安全帽数据集详解:从VOC标注到YOLOv8小目标检测实战 简介面向计算机视觉与深度学习开发者这份数据集提供806张俯视视角视频抽帧图像聚焦工地安全帽佩戴检测与行人定位场景。图像由监控视频每30帧抽取1帧生成有效减少重复帧同时保留场景多样性贴合实际监控视角下的目标形态。标注对象覆盖安全帽与人两类目标并同时提供PASCAL VOC格式xml与YOLO格式txt两种标注文件既便于导入YOLO系列模型直接训练也能用于Faster R-CNN、SSD等经典检测框架省去格式转换与预处理成本。压缩包共2000个文件含806张jpg原图、806个xml标注、806个txt标注整体大小约600.42MB文件命名关联原图帧号方便按需划分训练集与验证集。已有1169人学习下载适合从事工地安全监控、行为识别、边缘端巡检等方向的研究者或工程师使用可用于算法评测、模型调优与工程项目原型验证。1. 俯视安全帽数据集先想清楚 806 张图能教给模型什么同样是安全帽检测平视摄像头下帽子是一个清晰的圆形正面而俯视监控里安全帽往往只有 30×30 到 60×60 像素的顶部平面人的外形则被压缩成肩膀和头顶的剪影。所以这个「安全帽图像标注数据集-俯视角度-视频抽帧-806」真正值得动手拆的不是 806 这个数量而是俯视视角带来的小目标标注与训练问题——它直接决定你用 YOLO 系模型时该怎么配增强、怎么设 imgsz、怎么评估 mAP。数据集由视频每 30 帧抽 1 帧得到避免相邻帧高度相似标注对象两类helmet 和 person同时给出 PASCAL VOC 的 XML 与 YOLO 的 txt 两种格式基本是开箱即用。适合做工地施工安全数据集、巡检视频分析、以及想跑通小目标检测完整流程的人。2. 数据集拆包VOC 与 YOLO 标注的坐标系对齐拿到数据集先别急着训练第一步是确认文件组织方式和标注格式。这个数据集官方说明里写的是「PASCAL VOC YOLO 双格式」但不同发布者给出的目录习惯差异很大。常见做法是一个根目录下分 images、annotations、labels 三个文件夹图片名与标注名完全同名只有后缀不同。先按这个结构做一次体检确认每一张 jpg 都能找到对应的 xml 和 txt。2.1 目录结构与 30 帧抽帧带来的数据特征典型目录结构如下helmet_dataset/ ├── images/ │ ├── 10101198-main_38.jpg │ ├── 10101198-main_96.jpg │ └── ... ├── annotations/ │ ├── 10101198-main_38.xml │ ├── 10101198-main_96.xml │ └── ... └── labels/ ├── 10101198-main_38.txt ├── 10101198-main_96.txt └── ...先解释「视频抽帧」这个来源对数据分布的影响。原始视频如果是 25 fps 或 30 fps连续两帧之间场景位移极小直接全部取帧会导致训练集和验证集高度相似模型学到的其实是帧间记忆而非目标泛化特征。每 30 帧取 1 帧约等于每秒只保留 1 张画面这类抽帧策略的好处是同一目标的位移、遮挡、光照变化都被拉开到足够大的间隔数据集的多样性主要靠「时间跨度」而不是「相邻帧数量」来保证。视频怎么抽帧不影响画面质量的关键也在这里抽帧之前先按场景切换做镜头切分再在单镜头内均匀采样而不是从视频开头连续数帧。如果你拿到的版本里 annotations 和 labels 都存在先用一个命令核对两边文件数是否一致ls annotations | wc -l ls labels | wc -l ls images | wc -l输出结果如果三个数都是 806说明双格式标注是完整的。如果 labels 少于 annotations说明部分 XML 转 YOLO 时被过滤掉了需要检查是空标注文件还是类别映射问题。这一步能省掉后面训练时报AssertionError: Label not found的排错时间。2.2 VOC XML 里真正影响训练的四个字段打开任意一个 XML典型的 PASCAL VOC 标注结构是这样的annotation folderimages/folder filename10101198-main_38.jpg/filename size width1920/width height1080/height depth3/depth /size object namehelmet/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin512/xmin ymin300/ymin xmax560/xmax ymax340/ymax /bndbox /object /annotation在训练中真正参与计算的字段只有四个filename、size、object/name、bndbox。truncated和difficult在 YOLO 系训练流程里默认不读取但俯视场景下它们反映了一个实际问题目标在画面边缘被截断或者被遮挡超过一半时标注员是否仍然画框。常见做法是截断目标也保留标注只在truncated1标记如果后续训练发现边缘位置的假阳率偏高再考虑把它过滤掉。与 COCO 数据集结构对比这是一个容易被忽视的差异。COCO 是单文件 JSONcategory_id是连续的整数编号读取时要先解析categories字段再做 id 映射而 VOC 的name直接是字符串读取成本更低代价是转换到 YOLO 时必须自己维护一套「字符串到整数」的映射表。如果数据发布者在 XML 里写了helmet和person而 labels 目录下 txt 第一列是 0 和 1那么类别顺序大概率就是0: helmet, 1: person。但不排除有人用0: person, 1: helmet的顺序导出所以训练前一定要抽查几个 txt 文件确认类别索引真实含义。2.3 把 XML 转成 YOLO txt 的换算与脚本YOLO 格式的 txt 每行五个数类别 id、归一化中心点 x、归一化中心点 y、归一化宽度 w、归一化高度 h。换算关系如下cx ((xmin xmax) / 2) / image_width cy ((ymin ymax) / 2) / image_height w (xmax - xmin) / image_width h (ymax - ymin) / image_height所有值都在 01 之间。写一个 XML 转 YOLO 的脚本是这类数据集的必备工具核心逻辑如下import xml.etree.ElementTree as ET import os CLASS_MAPPING {helmet: 0, person: 1} IMG_W 1920 IMG_H 1080 def xml_to_yolo_line(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAPPING: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{CLASS_MAPPING[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return \n.join(lines)逻辑说明先用size拿实际图宽高保证归一化基数正确遍历object节点时跳过类别字典外的名称对边界框做裁剪防止标注越界导致的负数宽高最后按CLASS_MAPPING写成 YOLO 行。参数上的注意点是CLASS_MAPPING和IMG_W/IMG_H不能写死。如果数据集的图片尺寸不一致必须逐张读取 XML 里的size字段而不是用一个全局常量否则不同分辨率的图片归一化坐标会整体偏移。能跑通这个脚本之后建议立刻做一次回绘校验把生成的 txt 框画回原图肉眼扫 20 张确认标注框没有出现位移或者缩放错误。3. 组织数据把 806 张图拼成可训练的 YOLOv8 数据集拿到双格式标注后最常见的需求是用 YOLOv5 或 YOLOv8 训练自己的数据集。这个章节把 806 张俯视图片组织成标准训练集从目录约定到 data.yaml再到 train/val 划分和增强参数取舍一步到位。3.1 目录约定与 data.yaml 配置Ultralytics 系列框架默认读取images/train、images/val和labels/train、labels/val四个目录。推荐按如下结构组织目录作用说明images/train训练图片约 80% 的样本放这里images/val验证图片约 20% 的样本放这里labels/train训练标注 txt与 images/train 文件名一一对应labels/val验证标注 txt与 images/val 文件名一一对应helmet.yaml数据集描述文件路径、类别数、类别名data.yaml 是最容易被忽略但又最关键的文件内容如下path: ./helmet_dataset train: images/train val: images/val names: 0: helmet 1: person参数说明path是数据集根目录的相对或绝对路径建议使用绝对路径避免在不同工作目录下启动训练时路径解析失败train和val是相对path的子目录不需要加斜杠names必须与 labels 里 txt 的类别索引严格一致。如果训练时出现unable to find images/train先检查 path 基准是否正确而不是直接改数据目录。3.2 train/val 划分与同步移动视频抽帧数据集有个隐患同一段视频的相邻抽帧场景相似如果直接sklearn train_test_split随机切分验证集里可能出现与训练集高度相近的帧导致 mAP 虚高。稳妥做法是按文件名的视频来源前缀分组后切分。这里以单段视频前缀为例import os import random import shutil random.seed(42) image_files [f for f in os.listdir(images) if f.endswith(.jpg)] grouped {} for img in image_files: prefix img.split(_)[0] grouped.setdefault(prefix, []).append(img) train_files [] val_files [] for prefix, imgs in grouped.items(): random.shuffle(imgs) split int(len(imgs) * 0.8) train_files imgs[:split] val_files imgs[split:] os.makedirs(images/train, exist_okTrue) os.makedirs(images/val, exist_okTrue) os.makedirs(labels/train, exist_okTrue) os.makedirs(labels/val, exist_okTrue) for img in train_files: shutil.move(fimages/{img}, images/train/) shutil.move(flabels/{img.replace(.jpg, .txt)}, labels/train/)逻辑说明按文件名前缀分组是为了避免同一视频段的连续帧跨 train/val 两界组内打乱后按 8:2 切分保留视频间差异。random.seed(42)固定随机种子保证两次运行划分结果一致模型对比才有参照意义。注意这里故意没有用train_test_split直接对文件列表切分因为它在「同源视频帧」这个问题上不做分组结果容易造成信息泄漏。如果数据发布时文件名前缀不是视频来源而是统一的main那就按文件名的连续编号做间隔采样奇数编号进 train偶数编号进 val。全体移动完成后检查ls images/train | wc -l与ls labels/train | wc -l是否一致这一步能把后面训练时的Label not found错误提前消灭掉。3.3 针对俯视小目标的增强参数取舍YOLOv8 的训练参数里增强项对俯视数据集的影响比平视更大。核心原因是俯视视角下安全帽属于典型的小目标而小目标对几何形变和数据增强特别敏感。下面这份参数表以 YOLOv8 默认 hyp 为基准给出调整方向参数默认范围俯视数据集的调整建议hsv_h / hsv_s / hsv_v各 0.015 / 0.7 / 0.4保持默认工地光照变化较多颜色抖动有助于泛化degrees0.0建议设 515。监控摄像头角度略有旋转但固定机位不建议超过 30translate0.1保持 0.1模拟目标在画面不同位置的分布scale0.5建议 0.30.5过大会把小目标缩到不可见fliplr0.5保持 0.5俯视安全帽左右翻转语义不变mosaic1.0保持 1.0 但 epoch 数少时可关掉mosaic 对密集小目标有增益参数说明degrees是旋转增强的上限俯视画面里安全帽顶部形状是圆形小角度旋转不会改变语义但超过 30 度后人的肩部剪影会变形失真scale控制缩放比例俯视目标本身只有几十个像素缩放太大目标会被缩成噪点mosaic把 4 张图拼成 1 张对小目标检测有明显提升但如果你只有 806 张图且已经做了 8:2 切分训练集只有 640 张左右mosaic 在训练后期建议关闭降低过度拟合风险。4. 训练与验收mAP 怎么读漏检先查哪一步数据组织和标注格式都确认无误后可以正式进入训练环节。这个章节给出可直接复制的训练命令以及从训练产物中判断「是标注问题还是模型问题」的方法。4.1 一条可直接落地的 YOLOv8 训练命令用 YOLOv8 训练自己的数据集命令行是最快的路径yolo detect train \ datahelmet.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ projectruns/helmet \ nameexp01参数说明data指向第 3 章的 yaml 文件model推荐从yolov8s.pt预训练权重开始806 张图不足以从头训练一个大模型迁移学习是常规选择imgsz设为 640 是速度和精度的折中如果显存允许可以提到 1280 再看小目标 AP 的变化batch按显存调整16 大约占 8GB 显存workers是数据加载进程数Windows 上设 0 或 2Linux 设 4 以上。这个命令会读取数据集目录下的图片和 txt自动完成增强、训练、验证。输出目录runs/helmet/exp01/下会出现weights/best.pt、weights/last.pt、results.csv和confusion_matrix.png。best.pt是在验证集上表现最好的权重后面做推理都用它而不是用最后一个 epoch 的last.pt。 这里的modelyolov8s.pt会自动下载 COCO 预训练权重网络允许时会从官方源拉取。下载失败则手动把权重放到项目根目录再指定model./yolov8s.pt。4.2 从 results.csv 判断标注质量训练完成后验证集指标可以用一条命令重新跑yolo detect val \ modelruns/helmet/exp01/weights/best.pt \ datahelmet.yaml \ imgsz640输出里最需要关注四个值mAP50、mAP50-95、precision、recall。mAP50是 IoU 阈值 0.5 下的平均精度对这个数据集来说如果标注框画得比较宽松、边界不贴合安全帽边缘mAP50-95会明显低于mAP50这是正常的真正要警惕的是mAP50低于 0.8说明训练集和验证集分布差异过大或者标注类别索引错位。如果recall偏低优先检查标注里是否漏标了大量安全帽。俯视视角下安全帽在画面远处很小标注员容易漏掉这是这类数据集最常见的质量问题。可视化验证方法是把best.pt跑一遍验证集推理并保存预测结果yolo detect predict \ modelruns/helmet/exp01/weights/best.pt \ sourceimages/val \ saveTrue \ conf0.25逐张看输出的预测框和真实标注重点观察漏检目标是「极小尺寸被忽略」还是「与背景颜色相近没被识别」两者对应的调参方向完全不同。极小尺寸漏检优先提高imgsz或在增强里关闭过强的scale颜色相近漏检则要增加颜色抖动幅度或补充更多样本。4.3 两个最容易踩的坑及其解法第一个坑是空标注文件。视频抽帧中部分画面确实没有任何目标如果标注工具导出的 txt 是空文件YOLO 训练时不会报错但会有效降低背景负样本的区分度导致误检率上升。处理办法是把空 txt 对应的图片从数据集中剔除或者标记为背景图单独做负样本训练。第二个坑是类别索引错位。前面提到过 VOC 的name是字符串YOLO 的 txt 是整数如果发布者的导出脚本里helmet对应 1 而person对应 0而你在data.yaml里写反了模型会拿安全帽的框去学人的特征最终 mAP 直接崩掉。检查方法很简单head -5 labels/train/10101198-main_38.txt看到第一列是 0 还是 1对照同文件 XML 里的name值确认映射。这一步 30 秒能完成但能避免浪费数小时训练时间。5. 进阶技巧旋转框迁移与小目标标注自检前面章节已经把 806 张图片跑通了完整的训练流程。最后一章给两个再往深走的操作一是水平框模型在俯视场景的天然局限二是用脚本量化标注质量反向指导训练参数调整。5.1 水平框不够时能否迁到旋转框检测俯视视角下安全帽在图像中是一个近似圆形的顶部平面但人的肩部剪影是有方向性的。水平框表示法对圆形目标没有浪费但在目标倾斜 45 度时水平框会框入大量背景像素。如果后续要做行为识别或轨迹跟踪这种背景噪声会影响特征提取。类似 mmrotate 训练 DOTA 数据集那样做旋转框检测流程上需要把 VOC 的bndbox转换为旋转框的(cx, cy, w, h, angle)结构再用 mmrotate 的 DOTA 格式重新标注或转换。需要注意这个数据集本身是水平框标注不具备旋转框真值迁移前要评估是否值得为几十个倾斜目标重标数据多数场景下水平框配合合适的 NMS 阈值已经够用。5.2 标注自检脚本量化尺度分布与类别失衡与其等到训练完再靠 mAP 反推标注问题不如训练前就做一次量化自检。下面这个脚本遍历 labels 目录统计每个类别的边界框数量、面积分布和极小目标占比import os import numpy as np label_dir labels/train class_names {0: helmet, 1: person} stats {name: {count: 0, areas: [], ratios: []} for name in class_names.values()} for fn in os.listdir(label_dir): if not fn.endswith(.txt): continue with open(os.path.join(label_dir, fn)) as f: for line in f: parts line.strip().split() cls class_names.get(int(parts[0]), unknown) _, cx, cy, w, h parts w, h float(w), float(h) area w * h stats[cls][count] 1 stats[cls][areas].append(area) stats[cls][ratios].append(h / (w 1e-6)) for cls, s in stats.items(): areas np.array(s[areas]) print(f{cls}: 框数{s[count]}, 面积中位数{np.median(areas):.5f}, f面积0.01的占比{(areas 0.01).mean():.2%})参数说明cx, cy, w, h是归一化坐标面积直接用w * h衡量真实占图比例面积小于 0.01 的目标在 640 输入下大约只有 64×64 像素以下属于小目标。ratios是宽高比俯视安全帽接近 1.0如果它的中位数偏离 1.0 太多说明部分标注框倾斜严重回绘检查一下是标注问题还是目标确实被截断。5.3 把自检结果用回训练配置自检脚本的输出会直接告诉你两件事。如果类别统计中person的框数远多于helmet说明类别不平衡训练时考虑给 helmet 提高 loss 权重或者用复制粘贴增强把安全帽小目标多复制几次如果面积小于 0.01 的占比超过 30%就把imgsz调到 960 或 1280让模型在更高分辨率下看到这些小目标的纹理。反过来如果面积中位数在 0.05 以上说明目标并不小此时 mAP 低就不是尺度问题而是标注一致性问题优先检查边界框是否贴边、类别是否混淆。这一套流程走完再回头训练每一步都有数据支撑而不是盲目调参。本文还有配套的精品资源点击获取
返回列表