ARTICLE DETAIL

资讯详情

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

安全帽检测数据集实战:YOLOv5格式转换与训练全流程

安全帽检测数据集实战:YOLOv5格式转换与训练全流程 简介这份资源是面向深度学习与计算机视觉方向的 YOLOv5 头盔目标检测数据集适合正在做安全帽佩戴识别、工地或厂区安全监控项目的开发者、学生与算法工程师使用可解决目标检测训练中缺少标注样本的问题。压缩包共 160 个文件以 80 个 jpg 图像与 80 个 xml 标注文件成对组成整体约 2.86MBxml 可直接转换为 YOLO 格式标签便于快速接入训练流程。目前已有 482 人学习下载具备一定参考热度。数据集覆盖多种头盔佩戴场景图像与标注一一对应读者可据此完成模型训练、验证与调优也可用于课程设计、毕业设计或算法对比实验帮助缩短数据采集与标注周期把精力集中在模型结构与参数调优上。1. 安全帽检测数据集从一堆散图到 YOLOv5 可训练样本的落地路径工地、电厂、矿区这类场景做视觉项目绕不开的第一道坎就是安全帽佩戴检测。算法本身不复杂YOLOv5 跑起来也就那几行命令真正卡住进度的是数据——要么没有要么标注格式不对要么类别定义混乱。这份「用于 yolo5 头盔目标检测的数据集.zip」解决的就是这个前置问题它把一批头盔/安全帽相关的图像素材打包好了省去从零采集和初筛的时间。适合两类人一是刚接触目标检测、想拿一个真实场景数据集跑通 YOLOv5 全流程的新手二是手头有检测需求、需要快速验证模型可行性的工程人员。但要注意拿到压缩包不等于能直接开训中间还有格式转换、类别映射、数据清洗几步硬活下面按实际落地顺序拆开讲。2. 先搞清楚手里有什么数据集结构解析与 YOLOv5 格式对齐2.1 压缩包解压后的典型目录形态拿到一个目标检测数据集压缩包第一件事不是急着写训练脚本而是把目录结构摸清楚。常见做法是解压后先tree或ls -R看一眼层级。这类头盔数据集通常有两种组织方式一种是已经切好images/和labels/的 YOLO 格式另一种是原始图片加一个annotations文件夹可能是 VOC XML 或 COCO JSON。从项目正文列出的文件名来看图片命名是uxxxx,xxxxfm253fmtautoapp138fPNG.jpg这种带查询参数的格式说明素材来源比较杂大概率是网络采集后统一重命名的标注文件需要单独确认。# 解压后先看目录层级别急着写代码 unzip 用于yolo5头盔目标检测的数据集.zip -d helmet_dataset cd helmet_dataset find . -maxdepth 2 -type d | sort # 统计图片数量心里有个底 find . -name *.jpg -o -name *.png | wc -l # 看看有没有标注文件 find . -name *.txt -o -name *.xml -o -name *.json | head -20这段命令的逻辑很直白先解压到独立目录避免污染工作区然后用find限制层级看目录骨架再分别统计图片和标注文件。参数上-maxdepth 2是防止目录太深输出刷屏head -20是先扫一眼标注文件类型不用一次性全列出来。如果发现只有图片没有标注那这个包可能只是素材集需要自己标注或另找标注文件如果标注是 XML 格式就得走后面的转换流程。2.2 YOLOv5 要求的标签格式与坐标归一化YOLOv5 的标签格式是每个图片对应一个同名.txt文件每行代表一个目标格式为class_id x_center y_center width height其中坐标全部是相对于图片宽高的归一化值范围 0 到 1。这一点和 VOC 的xmin, ymin, xmax, ymax绝对坐标完全不同也是新手最容易翻车的地方。归一化公式不复杂# 假设图片宽 w1920高 h1080 # VOC 格式的框xmin400, ymin300, xmax700, ymax600 w, h 1920, 1080 xmin, ymin, xmax, ymax 400, 300, 700, 600 x_center (xmin xmax) / 2.0 / w # (400700)/2/1920 0.2865 y_center (ymin ymax) / 2.0 / h # (300600)/2/1080 0.4167 width (xmax - xmin) / w # (700-400)/1920 0.1563 height (ymax - ymin) / h # (600-300)/1080 0.2778 print(f0 {x_center:.4f} {y_center:.4f} {width:.4f} {height:.4f}) # 输出0 0.2865 0.4167 0.1563 0.2778这里的关键参数是w和h必须用每张图片自己的实际尺寸不能统一用一个值。常见错误是批量转换时读了一张图的尺寸就套用到所有图上结果标签全部偏移。另外class_id从 0 开始编号如果数据集里头盔和安全帽分成两类需要提前定义好类别映射表比如{0: helmet, 1: head}这个映射关系要同时写进data.yaml和转换脚本里两边不一致训练时不会报错但精度会莫名其妙地低。2.3 类别定义与 data.yaml 配置YOLOv5 训练需要一个data.yaml文件来描述数据集路径和类别信息。头盔检测场景通常分两类戴头盔helmet和未戴头盔head也有只检测头盔一类的。具体几类要看标注文件里的class_id分布用脚本统计一下最稳妥import os from collections import Counter label_dir helmet_dataset/labels counter Counter() for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(label_dir, txt_file), r) as f: for line in f: cls_id int(line.strip().split()[0]) counter[cls_id] 1 print(类别分布, dict(counter)) # 典型输出{0: 3200, 1: 1800} 说明有两类统计完类别数后data.yaml按下面这样写# data.yaml path: ./helmet_dataset train: images/train val: images/val nc: 2 names: [helmet, head]nc是类别数量names的顺序必须和class_id一一对应。如果统计出来只有一类nc改成 1names只留一个。这里有个容易忽略的点path用相对路径时训练脚本的工作目录要对否则 YOLOv5 找不到图片。我一般直接用绝对路径省得后面排查半天发现是路径问题。3. 从原始标注到 YOLOv5 训练集转换脚本与数据划分实操3.1 VOC XML 转 YOLO txt 的完整脚本如果解压后发现标注是 XML 格式需要先转成 YOLO 的 txt。下面这个脚本处理了图片尺寸读取、坐标归一化和类别映射可以直接改路径用import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射根据实际标注文件里的 name 调整 CLASS_MAP {helmet: 0, head: 1, person: 0} def convert_annotation(xml_path, img_dir, out_dir): tree ET.parse(xml_path) root tree.getroot() # 从 XML 里拿图片文件名 filename root.find(filename).text img_path os.path.join(img_dir, filename) if not os.path.exists(img_path): print(f图片缺失{img_path}) return # 读取图片真实宽高 with Image.open(img_path) as img: w, h img.size lines [] for obj in root.iter(object): cls_name obj.find(name).text.strip() if cls_name not in CLASS_MAP: continue cls_id CLASS_MAP[cls_name] 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) # 归一化 x_center (xmin xmax) / 2.0 / w y_center (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) # 写同名 txt out_name os.path.splitext(filename)[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines)) # 批量执行 xml_dir helmet_dataset/annotations img_dir helmet_dataset/images out_dir helmet_dataset/labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): convert_annotation(os.path.join(xml_dir, xml_file), img_dir, out_dir)逻辑上分四步解析 XML 拿到图片名和框坐标用 PIL 读图片真实尺寸按公式归一化写同名 txt。参数上CLASS_MAP要根据实际标注里的name字段调整如果标注里写的是中文「头盔」映射表里就得写{头盔: 0}。:.6f保留六位小数是 YOLOv5 官方推荐的精度太少会有坐标误差太多没必要。跑完后抽查几个 txt 文件确认坐标都在 0 到 1 之间如果出现大于 1 的值说明图片尺寸读错了。3.2 训练集/验证集划分与目录组织YOLOv5 默认要求images/train、images/val、labels/train、labels/val这样的结构。划分比例一般 8:2 或 9:1数据量少的时候验证集至少留几十张否则评估指标波动很大。下面脚本按比例随机划分并复制文件import os import shutil import random random.seed(42) # 固定随机种子保证可复现 img_dir helmet_dataset/images label_dir helmet_dataset/labels base_out helmet_dataset # 收集所有有对应标签的图片 pairs [] for img_name in os.listdir(img_dir): if not img_name.lower().endswith((.jpg, .png, .jpeg)): continue stem os.path.splitext(img_name)[0] label_name stem .txt if os.path.exists(os.path.join(label_dir, label_name)): pairs.append((img_name, label_name)) random.shuffle(pairs) split_idx int(len(pairs) * 0.8) train_pairs pairs[:split_idx] val_pairs pairs[split_idx:] for subset, data in [(train, train_pairs), (val, val_pairs)]: img_out os.path.join(base_out, images, subset) lbl_out os.path.join(base_out, labels, subset) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for img_name, label_name in data: shutil.copy(os.path.join(img_dir, img_name), os.path.join(img_out, img_name)) shutil.copy(os.path.join(label_dir, label_name), os.path.join(lbl_out, label_name)) print(f训练集 {len(train_pairs)} 对验证集 {len(val_pairs)} 对)random.seed(42)是为了每次划分结果一致方便复现实验。split_idx按 0.8 切分如果数据量少于 500 张建议改成 0.9 留更多训练数据。复制而不是移动是为了保留原始文件后面如果发现划分有问题还能重新来。跑完后检查images/train和labels/train的文件数量是否一致不一致说明有图片没有对应标签训练时 YOLOv5 会跳过这些图但不会报错容易导致实际训练数据比预期少。3.3 用 YOLOv5 官方脚本做标签校验YOLOv5 仓库里自带一个utils/dataset.py或训练启动时的标签检查逻辑但更直接的方式是用官方提供的check_labels思路自己扫一遍。常见问题是标签文件里有空行、坐标越界、类别 id 超出nc范围。下面这段脚本快速排查import os label_dir helmet_dataset/labels/train nc 2 # 和 data.yaml 保持一致 issues [] for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue path os.path.join(label_dir, txt_file) with open(path, r) as f: for line_no, line in enumerate(f, 1): parts line.strip().split() if len(parts) ! 5: issues.append(f{txt_file}:{line_no} 字段数不对) continue cls_id int(parts[0]) coords [float(x) for x in parts[1:]] if cls_id nc: issues.append(f{txt_file}:{line_no} 类别id越界 {cls_id}) if any(c 0 or c 1 for c in coords): issues.append(f{txt_file}:{line_no} 坐标越界 {coords}) print(f发现 {len(issues)} 个问题) for i in issues[:10]: print(i)这段脚本检查三类高频问题字段数不是 5、类别 id 超过nc、坐标不在 0 到 1 之间。发现坐标越界通常是转换时图片尺寸读错或者标注本身有问题。类别 id 越界多半是CLASS_MAP和data.yaml的names对不上。跑完没问题再开始训练比训练到一半发现 loss 不降要省时间。4. 避坑与排查头盔数据集训练前必须过的五道关4.1 图片能打开但训练报「corrupt image」现象是训练启动后日志里出现corrupt JPEG或image cannot be decoded但手动双击图片能正常预览。原因是部分图片虽然扩展名是.jpg实际编码可能是 PNG 或 WebPPIL 和 OpenCV 读取时行为不一致。从项目正文的文件名看有fmtPNG和fmtJPEG混在一起这种情况概率不低。解决办法是用脚本统一转成标准 JPEGfrom PIL import Image import os img_dir helmet_dataset/images/train for name in os.listdir(img_dir): path os.path.join(img_dir, name) try: with Image.open(path) as img: rgb img.convert(RGB) rgb.save(path, JPEG, quality95) except Exception as e: print(f处理失败 {name}: {e})convert(RGB)是去掉 alpha 通道quality95是平衡质量和体积。转完后重新跑一遍标签校验因为图片尺寸可能因为重新编码有微小变化但通常不影响归一化坐标。4.2 标签文件和图片文件名不匹配现象是训练时提示No labels found或者某张图被跳过。原因是图片名里带u2855313472,3725098243fm253这种特殊字符转换脚本生成 txt 时可能因为编码或截断导致文件名不一致。解决方法是统一重命名图片和标签为纯数字或 UUIDimport os import uuid img_dir helmet_dataset/images/train lbl_dir helmet_dataset/labels/train for img_name in os.listdir(img_dir): stem, ext os.path.splitext(img_name) new_stem uuid.uuid4().hex[:12] # 重命名图片 os.rename(os.path.join(img_dir, img_name), os.path.join(img_dir, new_stem ext)) # 重命名对应标签 old_lbl os.path.join(lbl_dir, stem .txt) if os.path.exists(old_lbl): os.rename(old_lbl, os.path.join(lbl_dir, new_stem .txt))重命名后图片和标签仍然一一对应但去掉了特殊字符避免路径解析问题。注意这个操作不可逆建议先备份原始文件。4.3 类别不均衡导致模型只学一类现象是训练完模型对「戴头盔」检测很好但「未戴头盔」几乎检不出。原因是两类样本数量差距大比如 helmet 有 3000 个框head 只有 500 个。解决办法有两个方向一是对少数类做过采样在划分训练集时重复复制少数类图片二是用 YOLOv5 的--weights加载预训练权重让模型从小样本里也能学到特征。我一般先用预训练权重跑一版看效果如果少数类召回率还是低再做过采样。4.4 验证集 mAP 高但实际场景漏检现象是训练日志里 mAP0.5 到了 0.85但拿工地实拍图测试时漏检严重。原因是数据集里的图片可能场景单一比如都是白天、正面、清晰的头盔而实际场景有逆光、遮挡、小目标。解决办法是在验证集里刻意加入困难样本或者用--img-size增大输入分辨率让远处的小头盔有更多像素。YOLOv5 默认 640可以试 960 或 1280但显存占用会明显上升。4.5 训练中断后恢复时数据加载变慢现象是--resume恢复训练后每个 epoch 时间比之前长很多。原因是 YOLOv5 的缓存机制在恢复时可能重新扫描数据集如果图片数量多且磁盘慢就会卡在数据加载。解决办法是训练前加--cache ram或--cache disk把图片缓存起来。内存够就用ram不够用disk。这个参数在第一次训练时加上恢复时也能受益。5. 训练启动与效果验证从命令行到实际推理的闭环5.1 YOLOv5 训练命令与关键参数数据准备好之后训练本身反而简单。假设 YOLOv5 仓库已经克隆到本地data.yaml放在helmet_dataset目录下python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data helmet_dataset/data.yaml \ --weights yolov5s.pt \ --cache ram \ --name helmet_exp--img 640是输入分辨率头盔检测如果目标偏小可以调到 960。--batch 16看显存8G 显存跑 640 分辨率一般能到 16不够就降到 8。--epochs 100是上限实际看 mAP 曲线早停。--weights yolov5s.pt加载官方预训练权重小数据集上比从头训效果好很多。--cache ram把图片缓存到内存加快 epoch 速度。--name是实验名结果存在runs/train/helmet_exp下。5.2 用验证集跑推理看实际效果训练完拿验证集图片跑一遍推理直观看看漏检和误检python detect.py \ --weights runs/train/helmet_exp/weights/best.pt \ --source helmet_dataset/images/val \ --img 640 \ --conf 0.4 \ --save-txt \ --name helmet_val--conf 0.4是置信度阈值默认 0.25 会出很多低置信框头盔场景调到 0.4 到 0.5 比较合适。--save-txt会把检测结果存成 txt方便和标注对比算指标。结果图在runs/detect/helmet_val下重点看几类情况远处小头盔有没有检到、遮挡情况下有没有漏、背景里类似头盔的圆形物体有没有误检。如果误检多提高--conf如果漏检多降低--conf或者增大--img。5.3 一个容易忽略的验证习惯我每次训练完不会只看 mAP 数字而是随机抽 20 张验证集图片把检测结果和原图并排看一遍。这一步花不了几分钟但能发现指标看不出的问题比如框偏大偏小、类别标反、密集场景框重叠。从那以后我每次拿到新数据集训练完都强制走一遍这个抽检流程比单纯信 mAP 靠谱得多。希望这份数据集和上面的流程能帮你把安全帽检测项目快速跑起来。本文还有配套的精品资源点击获取
返回列表