厨师帽检测数据集VOC转YOLOv5实战:2090张标注数据训练与避坑指南

发布时间:2026/10/11 5:54:59
厨师帽检测数据集VOC转YOLOv5实战:2090张标注数据训练与避坑指南 简介本资源为面向计算机视觉开发者与目标检测学习者的厨师帽检测数据集采用Pascal VOC标注格式适用于餐饮后厨场景下的厨师帽佩戴识别、人员头部定位等任务可支撑YOLO系列等检测模型的训练与验证。压缩包共约2000个文件包含2090张jpg图片与2090个对应xml标注文件另附一份使用说明txt整体约174.79MB图片与标注一一对应便于直接接入主流训练框架。标注由labelImg完成采用矩形框方式涵盖chef hat与head两类其中chef hat标注框4174个、head标注框1553个类别分布清晰适合作为二分类检测任务的训练素材。目前已有1534人学习下载读者可据此快速搭建数据加载流程、验证标注质量并参考作者基于yolov5的演示视频理解实际检测效果为后厨合规检测等应用提供可复用的数据基础。1. 厨师帽检测数据集2090 张 VOC 标注能不能直接喂给 YOLOv5后厨监控里要判断厨师有没有按规定戴帽子靠人盯屏幕不现实落地时第一步往往不是选模型而是找一份标注干净、类别定义明确的厨师帽检测数据集。这次拆的是一份 Pascal VOC 格式的厨师帽检测数据集2090 张 jpg 图片配 2090 个同名 xml 标注文件类别只有两个chef hat 和 head。chef hat 一共 4174 个框head 1553 个框标注工具是 labelImg规则就是矩形框。它适合做后厨合规检测、明厨亮灶、食堂卫生巡检这类目标检测任务也适合刚接触 VOC 数据集、想拿真实标注练手 YOLOv5 或 YOLOv8 训练流程的人。数据集本身不承诺任何模型精度只保证标注合理这点在选型时反而要看清它给的是原料不是成品。2. VOC 格式拆解2090 张图怎么读、怎么核对、怎么转 YOLO2.1 先看清 VOC 的目录结构和 xml 字段Pascal VOC 格式不是单一文件而是一套约定图片放 JPEGImages标注放 Annotations两者文件名主干必须一致。这份数据集给的是 firc_chefhat_1819.xml 这类命名对应图片就是 firc_chefhat_1819.jpg。xml 里真正影响训练的是几个节点filename 记录图片名size 记录宽高和通道object 下面有 name、pose、truncated、difficult、bndbox。name 就是类别名这份数据里只会出现 chef hat 或 headbndbox 的 xmin、ymin、xmax、ymax 是左上角和右下角坐标单位是像素。常见做法是先写个脚本统计一遍确认图片数、xml 数、类别分布和框的合法性。不要一上来就转格式先核对能省掉后面很多玄学问题。import os import xml.etree.ElementTree as ET from collections import Counter ann_dir Annotations img_dir JPEGImages xml_files [f for f in os.listdir(ann_dir) if f.endswith(.xml)] jpg_files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] print(xml 数量:, len(xml_files)) print(jpg 数量:, len(jpg_files)) cls_counter Counter() bad_boxes [] for xf in xml_files: tree ET.parse(os.path.join(ann_dir, xf)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text.strip() cls_counter[name] 1 box obj.find(bndbox) xmin int(float(box.find(xmin).text)) ymin int(float(box.find(ymin).text)) xmax int(float(box.find(xmax).text)) ymax int(float(box.find(ymax).text)) # 越界或宽高为 0 的框要单独记下来 if xmin 0 or ymin 0 or xmax w or ymax h or xmax xmin or ymax ymin: bad_boxes.append((xf, name, xmin, ymin, xmax, ymax)) print(类别分布:, cls_counter) print(异常框数量:, len(bad_boxes))这段脚本做三件事统计 xml 和 jpg 是否一一对应统计两个类别的框数是否和简介里的 4174、1553 对得上找出越界或退化的框。参数上只需要改 ann_dir 和 img_dir 两个路径。如果类别分布对不上说明有 xml 缺失或重复如果异常框很多转 YOLO 之前要先决定是修正还是丢弃别直接带着脏框训练。2.2 转成 YOLO txt归一化坐标和类别映射YOLOv5、YOLOv8 吃的是每张图一个 txt每行是 class_id x_center y_center width height全部归一化到 0 到 1。VOC 给的是绝对像素坐标转换时要用图片宽高做除法。类别映射必须固定顺序比如 chef hat 映射成 0head 映射成 1写进 data.yaml 时顺序不能乱否则模型学出来的类别会整体错位。import os import xml.etree.ElementTree as ET classes [chef hat, head] # 顺序必须和 data.yaml 的 names 一致 cls_to_id {c: i for i, c in enumerate(classes)} ann_dir Annotations img_dir JPEGImages out_dir labels os.makedirs(out_dir, exist_okTrue) for xf in os.listdir(ann_dir): if not xf.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, xf)) root tree.getroot() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in cls_to_id: continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 归一化中心点和宽高 xc (xmin xmax) / 2.0 / w yc (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_to_id[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) txt_name os.path.splitext(xf)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines))转换逻辑里有两个容易翻车的点一是 xmax 和 xmin 的顺序labelImg 正常导出不会反但人工改过的 xml 可能反转换前最好加一层 min/max 保护二是归一化用的宽高必须来自 xml 的 size而不是想当然用固定尺寸这份数据图片尺寸未必统一。转换完成后labels 目录里的 txt 数量应该也是 2090少一个都说明有 xml 没被处理。2.3 划分训练集和验证集别让同一场景泄漏2090 张图不算大划分时最怕的是同一段视频或同一场景的连续帧被同时分进训练和验证导致验证指标虚高。常见做法是按 8:2 或 9:1 划分并且先按文件名排序再打乱避免原始顺序里隐含的采集批次。如果数据来自连续视频抽帧更稳妥的是按场景或时间段切分而不是纯随机。import os import random import shutil random.seed(42) img_dir JPEGImages lab_dir labels out_root dataset for split in [train, val]: os.makedirs(os.path.join(out_root, images, split), exist_okTrue) os.makedirs(os.path.join(out_root, labels, split), exist_okTrue) names [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)] random.shuffle(names) split_idx int(len(names) * 0.8) train_names names[:split_idx] val_names names[split_idx:] for split, subset in [(train, train_names), (val, val_names)]: for n in subset: shutil.copy(os.path.join(img_dir, n .jpg), os.path.join(out_root, images, split, n .jpg)) shutil.copy(os.path.join(lab_dir, n .txt), os.path.join(out_root, labels, split, n .txt))seed 固定成 42 是为了可复现实际项目里换 seed 多跑几次看指标波动也很有必要。划分完要检查 train 和 val 的类别分布是否接近如果 val 里 head 特别少验证指标会抖得厉害。data.yaml 里 train、val 指向这两个目录nc 写 2names 顺序和转换脚本保持一致。3. 拿这份数据训练 YOLOv5配置、参数和验证口径3.1 data.yaml 与模型选型YOLOv5 的目录约定是 images 和 labels 平行且路径里用 images 替换成 labels 就能找到标注。data.yaml 写清楚三个路径和类别即可。模型选型上2090 张图属于小数据集直接上 yolov5x 容易过拟合yolov5s 或 yolov5m 更稳先跑通再考虑换大模型。path: ./dataset train: images/train val: images/val nc: 2 names: 0: chef hat 1: head这里 nc 必须是 2names 的键值顺序要和前面 cls_to_id 完全一致。很多人转完格式发现模型把帽子识别成头八成就是这里顺序反了。path 用相对路径时训练命令要在 dataset 的上一级目录执行否则会报找不到图片。3.2 训练命令与关键参数python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data/chefhat.yaml \ --weights yolov5s.pt \ --project runs/train \ --name chefhat_exp1img 640 是常见起点如果原图分辨率远大于 640小目标 head 可能被缩得看不清可以试 960 但显存要够。batch 16 在 8G 显存上比较稳爆显存就降到 8。epochs 100 对小数据集够用重点看验证集 mAP 是否在后期还在涨如果 30 轮就平了加轮数意义不大。weights 用预训练权重比从头训收敛快得多这是小数据集的常规操作。训练过程中重点盯三个指标box_loss 是否稳定下降mAP0.5 是否在验证集上同步上升以及 chef hat 和 head 两个类别的 AP 是否差距过大。如果 head 的 AP 明显低通常是 head 框更小、遮挡更多可以考虑提高输入分辨率或做针对性增强。3.3 推理验证拿没参与训练的图看效果训练完不要只看指标要拿真实后厨图跑一遍。用 detect.py 指定权重和图片目录看输出的框和置信度。简介里提到有基于 yolov5 训练模型的演示视频且视频数据未参与训练这种留出视频做验证的思路是对的能看出模型在连续帧上的稳定性而不是只在验证集上好看。python detect.py \ --weights runs/train/chefhat_exp1/weights/best.pt \ --source test_images \ --img 640 \ --conf-thres 0.4 \ --save-txtconf-thres 0.4 是起步值漏检多就降到 0.25误检多就升到 0.5。save-txt 会把检测结果存成 txt方便后续统计。重点看两类错误把没戴帽子的人头误判成 chef hat以及把远处的帽子漏掉。前者影响合规判断后者影响召回业务上哪个更不能接受决定了阈值往哪调。4. 避坑与排查这份 VOC 数据集最容易翻车的五件事4.1 现象训练时提示找不到标签loss 一直是 nan原因通常是 images 和 labels 的目录结构不符合 YOLOv5 约定或者 txt 文件名和图片名主干不一致。VOC 转 YOLO 时如果 xml 里有 filename 和实际 jpg 名不同的情况按 xml 名生成 txt 就会对不上。解决方法是转换脚本里统一用 xml 文件名主干生成 txt并在划分后抽查几个样本确认 images/train 下的每张图在 labels/train 下都有同名 txt。4.2 现象模型把 head 识别成 chef hat或者两个类别混在一起原因基本是 data.yaml 的 names 顺序和转换时的类别映射不一致。VOC 里类别名是字符串YOLO 里是整数 id中间这层映射一旦错位模型学到的就是错的对应关系。解决方法是把 classes 列表和 data.yaml 的 names 放在同一处维护转换和训练都引用它改的时候一起改别两边各写一份。4.3 现象验证集 mAP 很高实际后厨图效果很差原因是训练集和验证集划分时同一场景泄漏或者验证集图片和训练集过于相似。2090 张图如果来自少数几个摄像头随机划分很容易让验证集变成训练集的近邻。解决方法是按摄像头或时间段分组划分保证验证集里的场景在训练集里没出现过这样指标才接近真实泛化能力。4.4 现象小目标 head 大量漏检原因是 head 的框普遍偏小缩放到 640 后像素太少。解决方法是提高训练和推理的 img 尺寸到 960或者在数据增强里减少缩放幅度也可以针对小目标单独统计 AP。不要指望只调置信度阈值能救回来漏检是特征层面的事阈值只能微调。4.5 现象xml 里有框但转出来的 txt 是空的原因是 bndbox 坐标越界或宽高为 0被转换脚本过滤掉了或者类别名带了多余空格导致匹配不上。解决方法是转换前先跑一遍统计脚本把异常框和未知类别名打印出来人工确认是修正坐标还是丢弃样本。带空格的类别名用 strip 处理别直接拿原始文本去查字典。5. 进阶技巧用这份数据做类别不平衡处理和阈值标定这份数据里 chef hat 有 4174 个框head 只有 1553 个比例接近 2.7:1属于轻度不平衡。直接训练时 head 的召回往往吃亏因为模型见到的 head 样本更少。我一般会先看两个类别的 AP 差距如果 head 明显低再考虑在损失里给 head 更高权重或者对含 head 的图片做重复采样。YOLOv5 本身没有直接的类别权重参数常见做法是复制含 head 的样本进训练集但要注意别把验证集里的图复制进去否则指标就假了。另一个实用技巧是阈值标定。业务上判断“有没有戴帽子”最终往往要落成一个二分类决策而不是看两个类别的框。可以拿验证集跑一遍推理统计不同 conf-thres 下 chef hat 的召回和误报画一条简单的权衡曲线选一个业务能接受的阈值。比如后厨巡检宁可多报也不能漏报就把阈值压低如果是自动抓拍处罚就要抬高阈值减少误报。这个标定过程比单纯看 mAP 更贴近落地。import os from collections import defaultdict # 假设 detect.py --save-txt 输出的每行是 cls xc yc w h conf pred_dir runs/detect/exp/labels gt_dir dataset/labels/val def load_gt(path): boxes defaultdict(list) for f in os.listdir(path): with open(os.path.join(path, f)) as fp: for line in fp: c, xc, yc, w, h line.split() boxes[f].append((int(c), float(xc), float(yc), float(w), float(h))) return boxes gt load_gt(gt_dir) # 统计每个阈值下 chef hat 的预测框数量和 gt 对比做召回估计 for thres in [0.25, 0.35, 0.45, 0.55]: total_pred 0 for f in os.listdir(pred_dir): with open(os.path.join(pred_dir, f)) as fp: for line in fp: parts line.split() if float(parts[5]) thres and int(parts[0]) 0: total_pred 1 print(f阈值 {thres}: chef hat 预测框数 {total_pred})这段脚本只是做数量级对比真正算召回还要做 IoU 匹配但先用它看趋势已经够用。参数上改 pred_dir 和 gt_dir 两个路径阈值列表按业务需要增减。跑完你会发现阈值从 0.25 提到 0.55预测框数量掉得很快这就是为什么不能拍脑袋定阈值。从那以后我每次拿到 VOC 数据集都强制先跑一遍统计脚本核对类别分布和异常框再转格式、再划分、再训练顺序一步都不省。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询