ARTICLE DETAIL

资讯详情

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

厨师帽检测数据集VOC-2090张:从格式转换到YOLO训练实战指南

厨师帽检测数据集VOC-2090张:从格式转换到YOLO训练实战指南 简介面向目标检测入门与实战的Pascal VOC格式数据集采集了2090张厨师帽佩戴场景图片每张均配有XML标注标注类别为chef hat与head两类框数分别为4174个和1553个适合用于安全帽/厨师帽检测、人员头部识别等模型训练与算法验证。数据由labelImg工具画矩形框生成标注信息完整Pascal VOC格式可直接被YOLO、SSD、Faster R-CNN等检测框架读取也方便自行划分训练集和验证集。资源包为ZIP格式共2090个jpg图片文件、2090个xml标注文件及1个说明txt不包含分割txt压缩包整体约174.79MB。作者额外提供了基于YOLOv5训练的演示视频数据未参与训练可辅助理解标注效果与实际检测流程。目前已有1534人学习下载适合需要标准VOC格式数据、进行目标检测练手或课程设计的中级开发者。1. 厨师帽检测数据集VOC-2090张先想清楚它能不能塞进你的项目做后厨安防或者餐饮明厨亮灶项目的人大概率都有过这种经历需求方第一条要求就是“检测员工有没有戴厨师帽”但翻遍公开数据集不是场景是欧美厨房就是只有几百张还带着水印。厨师帽检测数据集VOC-2090张解决的就是这个缺口——2090张jpg加2090个xmlPascal VOC格式标注了两个类别“chef hat”和“head”其中chef hat框数4174、head框数1553全部用labelImg画矩形框完成。它能用来直接训练YOLOv5、YOLOv8这类目标检测模型适合已经跑通YOLO基础流程、但缺一个“能交代得过去”的合规检测数据的开发者。有一点要先说清楚它提供的是标注数据不附带训练好的权重文件精度需要你自己训出来。2. 先把VOC格式读透xml字段决定你后面省不省事2.1 xml结构里真正影响训练的字段拿到手先别急着转格式。打开一个firc_chefhat_xxx.xml你看到的是一个标准Pascal VOC标注文件但真正决定后续训练效果的其实只有几个字段。annotation folderJPEGImages/folder filenamefirc_chefhat_1819.jpg/filename size width640/width height480/height depth3/depth /size object namechef hat/name bndbox xmin120/xmin ymin80/ymin xmax230/xmax ymax190/ymax /bndbox /object /annotation这里 就是类别名 是矩形框坐标。和COCO的JSON不同VOC坐标是绝对值像素不是归一化后的比例——这一点在转YOLO格式前务必记牢。另一点容易被忽略这套数据集的标注类别就两类“chef hat”是帽子本身“head”是头部范围。这两个框在空间上大量重叠后面训练时会影响NMS行为第4章会展开讲。真正影响训练上限的不是图片数量而是这两个类的定义边界是否稳定。我看了多份xml后发现帽子的框基本贴着帽子外沿而head框范围更随意有的框到下巴、有的到耳根这说明head类标注一致性不如chef hat类后续训练时它会是你主要的误差来源。2.2 数据完整性校验jpg与xml一一对应是底线2090张图片配2090个xml看起来数量对得上但实际用之前必须自己做一遍完整性校验。因为VOC的常见损坏方式就是文件缺配、xml里坐标越界、图片有EXIF旋转信息导致宽高读反。我用一个脚本把jpg和xml的对应关系、坐标范围、类别分布一次性拉出来。import os from xml.etree import ElementTree as ET img_dir JPEGImages xml_dir Annotations imgs {f.split(.)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)} xmls {f.split(.)[0] for f in os.listdir(xml_dir) if f.endswith(.xml)} print(只缺xml的图片数量:, len(imgs - xmls)) print(只缺jpg的xml数量:, len(xmls - imgs)) class_count {chef hat: 0, head: 0} bad_coords 0 total_boxes 0 for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) class_count[name] class_count.get(name, 0) 1 total_boxes 1 if xmin 0 or ymin 0 or xmax w or ymax h: bad_coords 1 print(f, 坐标越界) print(类别统计:, class_count) print(总框数:, total_boxes) print(越界坐标文件数:, bad_coords)脚本逻辑很直接先取文件名集合做差集找出缺配文件再遍历每个xml把size里记录的宽高和所有bndbox坐标比对越界的单独列出来。跑完你会看到两个关键数字——chef hat 4174、head 1553比例大约2.7比1这个不平衡直接决定了训练时的损失函数权重和类别得分阈值。我做训练前习惯把这段脚本输出存成一个txt后面划分数据集、写测试报告都以此为基准免得改来改去把某个类别改丢了。2.3 用统计结果决定数据划分策略这个数据集没有提供train/val的txt划分文件所以划分权在自己手里。随机划分是最省事也最危险的做法因为同一个后厨场景里连续拍的帧之间高度相似随机切分会把高度相似的图片同时塞进训练集和验证集mAP虚高到0.98一到现场就崩。常见做法是先按文件名前缀或目录来源预分组再组级别划分。比如这2090张如果来自4~5个不同环境我一般会做一个场景分组猜测——识别图片的亮度分布、颜色直方图相似度——把明显同一批的图片聚到一起然后按组划分确保验证集和训练集的场景分布错开。这样训出来的mAP可能比随机划分低几个点但更接近真实部署性能不会被数据泄漏骗到。3. 从VOC到YOLO转换脚本与训练接入3.1 为什么不能直接拿VOC喂YOLOYOLOv5、YOLOv8训练时读取的标签不是VOC的xml而是每个图片对应一个同名txt每行是“class_id center_x center_y width height”其中坐标是相对图片宽高的归一化值。如果不做转换直接改data配置硬训会报错或者在训练时读到空标签。转换涉及两个操作坐标格式从xmin/ymin/xmax/ymax转成cx/cy/w/h再每个值除以图片宽度或高度做归一化。3.2 一张表看清VOC、YOLO、COCO格式坐标差异格式坐标表示归一化文件形式附加信息Pascal VOCxmin, ymin, xmax, ymax否每张图一个xml带size、difficult、truncatedYOLO txtclass, cx, cy, w, h是每张图一个txt只有类别id和坐标COCO JSONx, y, w, h左上角起否全量一个json带category_id、segmentationVOC转YOLO最容易出错的点集中在两点一是坐标是绝对像素转cxywh时忘了除以宽高二是类别id映射必须和训练时data yaml里配置顺序一致。这个数据集的类别顺序按说明是[chef hat, head]转换脚本里就必须是chefhat0、head1如果哪天把这个顺序搞错了模型训练出来之后会把帽子和头互相混淆而且你不会立刻看出来直到推理阶段才发现类别标签全部反了。3.3 转换脚本实例下面这个脚本是VOC转YOLO的常用写法我补了一些生产环境里容易踩的检查逻辑。import os import random from xml.etree import ElementTree as ET classes [chef hat, head] xml_dir Annotations img_dir JPEGImages out_label_dir labels out_img_dir images os.makedirs(out_label_dir, exist_okTrue) os.makedirs(os.path.join(out_img_dir, train), exist_okTrue) os.makedirs(os.path.join(out_img_dir, val), exist_okTrue) def convert_annotation(xml_path, out_txt_path): tree ET.parse(xml_path) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) if xmax xmin or ymax ymin: continue cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) file_names [f.split(.)[0] for f in os.listdir(xml_dir) if f.endswith(.xml)] random.seed(42) random.shuffle(file_names) val_split int(len(file_names) * 0.2) for i, name in enumerate(file_names): subset val if i val_split else train os.makedirs(os.path.join(out_label_dir, subset), exist_okTrue) convert_annotation( os.path.join(xml_dir, name .xml), os.path.join(out_label_dir, subset, name .txt) ) src_img os.path.join(img_dir, name .jpg) dst_img os.path.join(out_img_dir, subset, name .jpg) if os.path.exists(src_img) and not os.path.exists(dst_img): os.rename(src_img, dst_img)参数说明classes列表的顺序就是训练时类别id的顺序必须和后续data yaml完全一致val_split0.2表示留20%做验证集如果某个场景的图片特别少建议手动调整而不是靠随机划分坐标计算时除以的是xml里size字段的宽高而不是用PIL实际打开的图片尺寸——两者不一致时以xml为准这样能提前暴露标注和图片不匹配的问题。另外我在脚本里用了os.rename而不是shutil.copy是为了避免重复运行脚本时图片被拷多份如果你不希望动原图改成copy即可。转换完之后目录结构应该是这样的images/ train/ *.jpg val/ *.jpg labels/ train/ *.txt val/ *.txt生成了之后别急着训练抽5个txt文件看一眼确认坐标都在0到1之间。如果出现大于1的数字基本就是某个xml的坐标越界回头去查那个文件。3.4 data yaml与训练命令YOLOv5或YOLOv8的data配置很简单一份yaml文件就能把路径和类别定义清楚。train: images/train val: images/val nc: 2 names: [chef hat, head]然后训练命令按你的算力选模型规模。我个人习惯先用yolov5s或yolov8s跑通流程确认能收敛再回头换大模型。命令示例如下。python train.py --data chefhat.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch 32参数说明img 640是把训练图片统一resize到640x640这和xml里的原始分辨率无关YOLO在训练时自己会做缩放batch大小根据显存调整8G显存跑yolov5s建议16到32之间epochs 100起步正常两天内训练完。如果发现训练早停就把epochs至少调到150再看。这里我多说一句epochs不是越大越好但100轮以内调参往往还没收敛到稳定区后面的loss曲线会让你误判。4. 避坑与常见问题五个坑让新手多折腾一周4.1 下载后xml打不开编码问题现象用labelImg打开某个xml时报错或者Python的ElementTree解析到一半抛异常。原因部分xml文件带BOM头或混入了不可见字符常见于Windows下反复编辑保存过。解决统一用Python的open加encodingutf-8读取如果还报错就做一次清洗把文件开头BOM去掉再解析。做数据完整性校验时把这个逻辑写进脚本能省后面训练中断的麻烦。4.2 把“head”框当成噪声直接删掉现象有人看到head占比小、框定义模糊就只保留chef hat训练结果mAP不升反降。原因head框虽然数量少但它在视觉上框住了人脸和帽子的相对位置相当于一种弱上下文监督。删掉之后模型对“帽子戴歪了”这类样本的回归能力明显变差。解决保留两个类别一起训练推理阶段再做类别过滤如果确实只关心帽子把head当作辅助监督而不是直接删除。4.3 chef hat与head框相互重叠导致后处理错乱现象验证时发现一张图里同一个位置同时检出“chef hat”和“head”且两个框的IoU超过0.8。原因帽子和头部天然嵌套而YOLO的NMS默认按类别独立执行两个类别的框不会互相抑制。解决如果你用它做纯厨师帽合规检查推理时把head去重规则改为“若与chef hat框IoU大于0.5只保留chef hat”如果做人员定位则反过来保head。这个在后处理代码里加一个循环判断就行但很多人在训练阶段没想到这一点等上线了才补过滤逻辑。4.4 验证集mAP很高但现场漏检严重现象训练集val上mAP 0.95部署到另一个厨房后暗光、背光、低头弯腰时大量漏检。原因2090张图片很可能集中在单一或少数几个场景模型学到了背景纹理而不是帽子特征。解决按场景组划分数据而不是随机划分。把不同文件名前缀或亮度分布的图片尽可能均匀分到train和val。如果分发时不知道具体场景来源就用第2章的统计脚本把每张图片的亮度均值、饱和度算出来做一次粗糙聚类后再划组。4.5 自己扩展数据时用了错误的标注习惯现象后续自己标了500张补充数据训练后反而掉点。原因补充数据的框风格和原数据集不一致。原数据集里chef hat的框紧贴帽沿一旦你补充的数据把帽子四周多留了5到10像素框的宽高分布就会分裂模型回归头来回震荡。解决标注前先随机抽50个xml把标注框的平均宽高比、边缘留白统计出来给标注人员设定一套硬性规则。我一般会直接说帽子框必须上下左右各贴帽沿0~3像素头框上边界到头顶、下边界到下巴不许自由发挥。没有这套规则数据集一旦混合回不去的。5. 训练参数与验证方法别让mAP骗了你5.1 训练参数怎么定从yolov5s开始训练这个数据集我的默认起点是yolov5s或yolov8simg640起步。模型再大就容易过拟合因为2090张图片的量不算大而且场景重复度高。batch大小如果显存允许就上32batch太小会导致BN层统计不稳定多卡训练时尤其明显。数据增强建议把hsv_h、hsv_s、hsv_v略微调低一点默认值在YOLOv5里是0.015、0.7、0.4对后厨这种颜色偏单一的场景色相增强太强会把白色厨师帽的色调拉偏让模型误把浅色墙壁当成帽子。我在第一次训练时没改增强参数结果验证集的白色瓷砖墙误检了一大片。后来把hsv_h调到0.01误检明显下降。学习率方面按官方默认配置跑即可但要注意cos lr和linear lr的选择。epochs超过120轮建议开cos衰减收敛更稳loss曲线尾部更平。如果你用的预训练权重是yolov5s.pt那首轮warmup基本不用动。训练进程里重点关注train/box_loss、train/cls_loss和val/box_loss三根曲线val_loss如果先降后升就是过拟合信号这时候回退到最低点对应的epoch不要等最后一个epoch的权重。5.2 验证方法mAP、混淆矩阵与跨场景漏检训练完不要只看val的mAP。因为划分方式如果不够严val数据本身就可能泄漏。我常用的验证组合是先看mAP50和mAP50-95两个数字再看混淆矩阵重点看“chef hat被预测成head”的比值最后拿出测试集视频帧或现场图片做一次可视化推理。这个数据集自带一个演示视频链接虽然视频数据没有参与训练但你可以拿它的帧做测试——相当于一个跨场景泛化测试。把视频每隔10帧抽一张跑一遍模型统计漏检率。如果视频里表现很差而val的mAP很高说明是数据划分泄漏。这一步几乎能筛掉所有“假精度”。混淆矩阵的读取有个细节看chef hat行里它的值分到head列多少。如果分到head列比例超过10%说明两个类别在特征空间里区分度不够要么是head的标注一致性太差要么是帽子遮挡太严重。这时候我会回去统计训练样本里“帽子区域像素占比”——如果帽子在整图中占比很小比如小于3%那说明原图分辨率不够需要裁图训练而不是全图缩放。5.3 head类别如何处置删掉、保留还是做辅助监督关于head类我要说一个比较反常识的结论即使你的最终目标只检测chef hat也建议保留head一起训练。原因有两个层面一是模型在卷积层前面共享特征head框给了一个明确的人头位置监督信号模型更容易把“帽子”和“头”关联起来间接增强了对戴帽位置的定位能力二是在数据增强阶段如果只保留chef hat所有head区域的增强翻转都会造成语义缺失模型在Mosaic增强时看到半个人头反而不利于特征学习。但如果你的部署环境对误检率敏感比如只允许“有没有戴帽子”的布尔输出那推理阶段就要做类别合并规则。我一般按这个优先级处理如果同时检出chef hat和head且两者IoU大于0.6则判定为戴帽合规如果只检出head且置信度大于0.35判定为未戴帽。这样把二分类任务的阈值判断转移到类别空间比单独训一个“帽子有没有”的分类器稳定得多因为复用同一个检测模型不需要额外维护一套分类权重。6. 进阶hard example 挖掘与数据补充策略这个数据集做到能出模型只是第一步。它真正的价值上限在于帮你搭一个难例挖掘的闭环。我的做法是用训练好的模型跑一遍所有验证图片把置信度在0.2到0.5之间的检出框全部导出来——这些是模型“犹豫不决”的样本手工分类成三类标签没标全的、标签标错的、模型本身能力不足的。第一类和第二类直接修xml重新训练第三类才是真正需要补充的数据。这个操作看起来简单但能精准找到误检的来源而不必盲目加数据。补充数据时优先从场景视频里连续抽帧而不是随机截图因为连续帧能覆盖帽子从正面转到侧面的完整姿态变化。这个数据集本身标注了head类所以补充数据时也要遵循同样的head标注边界否则会把类别分布越拉越散。如果补充量有限我建议先保持chef hat类的数量占比不被稀释必要时对head类做欠采样。最后说一个我自己的习惯也算是一条教训拿到任何VOC格式数据集第一件事不是可视化看图片而是先跑统计脚本确认类别数量、xml和jpg的对应关系、坐标范围这三个底线指标。之后再谈转YOLO、训练、调参。这个习惯帮我拦下了好几次训练到一半才发现数据集有问题的情况。希望帮到你。本文还有配套的精品资源点击获取
返回列表