
简介这份足球数据集面向计算机视觉入门与目标检测实践者尤其适合正在做足球识别、体育视频分析或课程设计的学生与开发者。数据以VOC和YOLO双格式提供包含548张左右jpg图片及一一对应的xml与txt标注文件标注类别统一为football使用labelImg完成可直接接入YOLO系列或VOC训练流程省去自行采集与标注的成本。压缩包为rar格式无需解压密码共1645个文件其中549个txt、548个xml、548个jpg整体约29.23MB解压后按图片、xml、txt三个文件夹分门别类便于快速查看与训练。标注过程强调边界框准确、目标不遗漏并做一致性检查质量相对可靠。目前已有141人学习下载适合作为检测模型训练、格式转换与标注规范学习的练手素材。1. 548 张足球数据集VOC 与 YOLO 双格式标注到底怎么选、怎么转手里拿到一份 548 张左右的足球场景数据集标注格式是 Pascal VOC 的 XML想直接喂给 YOLOv8 训练却卡在格式不认或者反过来拿到的是 YOLO 的 txt想用 MMDetection、Detectron2 这类框架又得转回 VOC。这个来回转换的过程几乎每个做目标检测落地的工程师都踩过。足球场景有它的特殊性球员密集、球体小、远景模糊、遮挡严重548 张这个量级属于典型的小样本微调场景格式选错或者转换时坐标算错模型直接学不到东西。这篇笔记就围绕「VOC 和 YOLO 两种标注格式在足球数据集上的落地」展开从格式差异、转换脚本、参数校验到训练前检查把能复现的步骤和踩过的坑一次讲清楚适合刚入门 YOLO 训练、手里正攥着一批足球标注数据不知道下一步怎么走的人。2. VOC 与 YOLO 标注格式的差异与选型逻辑2.1 两种格式的坐标体系到底差在哪Pascal VOC 的标注是一个图像对应一个 XML 文件核心节点是object里面记录类别名和bndbox边界框用xmin、ymin、xmax、ymax四个值表示单位是像素原点在图像左上角。YOLO 的标注是一个图像对应一个 txt 文件每行一个目标格式是class_id x_center y_center width height后四个值全部是归一化到 0 到 1 之间的浮点数x_center、y_center是框中心点相对图像宽高的比例width、height是框宽高相对图像宽高的比例。差异的本质是VOC 存绝对像素坐标YOLO 存相对比例坐标而且 YOLO 把类别从字符串变成了从 0 开始的整数索引。这个索引顺序由你的data.yaml里names列表的顺序决定一旦训练中途改了顺序之前训的权重基本报废这是血泪经验。2.2 足球场景下为什么优先选 YOLO 格式548 张的足球数据集目标以球员和足球为主球员之间遮挡频繁足球在远景镜头里可能只有十几个像素。这种场景下主流做法是走 YOLO 系列原因有三点。第一YOLO 格式是 Ultralytics 系框架的原生输入yolov8训练自己的数据集这条路径的资料最全从data.yaml配置到model.train()调用都有成熟范例小样本微调时能快速验证。第二YOLO 的归一化坐标对图像缩放天然友好你做mosaic、letterbox这类增强时不用反复换算像素减少出错面。第三足球这类小目标密集场景YOLO 的 anchor-free 头和efficient head改进方向在社区里迭代快548 张配合预训练权重做迁移学习收敛比从零训 VOC 系框架稳。当然如果你后续要接 MMDetection 做mmrotate训练dota数据集那种旋转框任务VOC 或 COCO 格式更顺但纯水平框足球检测YOLO 格式是首选。2.3 选型前必须确认的三个前提动手转格式之前先确认三件事否则转了也白转。第一类别列表是否固定。足球数据集常见类别是person和ball两类但有些标注里把goalkeeper、referee单独列类你得先统计所有 XML 里出现过的name值统一成最终类别表。第二图像和标注是否一一对应。548 张图对应 548 个 XML 是理想情况实际常出现有图无标注或有标注无图转换脚本要能报出这些孤儿文件。第三坐标是否越界。VOC 的xmax偶尔会等于或超过图像宽度YOLO 归一化后会大于 1训练时虽然不一定报错但会引入噪声框。这三条确认完再进转换环节。3. 从 VOC 转 YOLO脚本、参数与校验3.1 转换脚本的完整实现下面这个脚本处理 548 张量级的数据集足够用核心是把每个 XML 读出来按图像实际宽高做归一化写到同名 txt。注意classes列表的顺序就是最终class_id的顺序必须和data.yaml完全一致。import os import xml.etree.ElementTree as ET from PIL import Image # 类别顺序即 class_id 顺序必须与 data.yaml 的 names 一致 classes [person, ball] def convert_bbox(size, box): VOC 绝对坐标 - YOLO 归一化中心点坐标 dw 1.0 / size[0] dh 1.0 / size[1] xmin, ymin, xmax, ymax box x (xmin xmax) / 2.0 * dw y (ymin ymax) / 2.0 * dh w (xmax - xmin) * dw h (ymax - ymin) * dh # 裁剪到 [0,1]防止越界框污染训练 x min(max(x, 0.0), 1.0) y min(max(y, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) return x, y, w, h def convert(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) skipped [] for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_file) tree ET.parse(xml_path) root tree.getroot() # 用图像真实尺寸不要信 XML 里的 size 节点实测常有错 img_name root.find(filename).text img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): skipped.append(img_name) continue with Image.open(img_path) as im: w_img, h_img im.size lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bndbox obj.find(bndbox) box ( float(bndbox.find(xmin).text), float(bndbox.find(ymin).text), float(bndbox.find(xmax).text), float(bndbox.find(ymax).text), ) x, y, w, h convert_bbox((w_img, h_img), box) lines.append(f{cls_id} {x:.6f} {y:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, xml_file.replace(.xml, .txt)) with open(out_path, w) as f: f.write(\n.join(lines)) print(skipped (no image):, skipped) convert(./annotations, ./images, ./labels)逻辑说明convert_bbox里先算归一化再裁剪顺序不能反否则越界框裁剪后中心点会偏。用PIL读真实图像尺寸而不是 XML 的size节点是因为标注工具导出的尺寸字段经常和实际图对不上这是翻车高发点。skipped列表把有标注无图的文件单独记下来转换完必须人工看一眼。参数说明classes列表决定类别映射改这里等于改整个数据集的标签语义x:.6f保留六位小数YOLO 训练读取时精度足够写太多位没必要输出目录./labels要和图像目录结构对应Ultralytics 默认按images/和labels/平行目录找。3.2 转换后的三项校验转完不能直接开训先做三项校验。第一项行数和类别分布。统计每个 txt 的行数如果某个文件 0 行说明该图所有目标类别都不在classes里要么补类别要么删图。第二项坐标范围。写个脚本扫所有 txt任何一行的后四个值超出[0,1]就打印文件名正常转换后不该有。第三项可视化抽查。随机抽 20 张把 YOLO 坐标反算回像素画框和原图叠一起看重点看足球这种小目标有没有框偏。这三项做完数据集才算干净。3.3 data.yaml 的写法与路径陷阱YOLO 训练靠data.yaml找数据路径写错是最常见的「模型不收敛」假象来源。标准写法如下path: /home/user/football_dataset train: images/train val: images/val nc: 2 names: [person, ball]path是数据集根目录train和val是相对path的子路径指向图像目录YOLO 会自动把images替换成labels去找同名 txt。陷阱在于如果你的验证集和训练集图像放在同一目录只靠 txt 区分这套路径机制会失效必须物理分目录。另外nc必须等于len(names)写错会在加载时直接报错这个报错信息还算友好能定位到。4. 从 YOLO 转回 VOC反向转换与场景4.1 什么时候需要反向转反向需求主要出现在三种场景。一是你要用 MMDetection 或 Detectron2 做对比实验这些框架吃 COCO 或 VOC。二是你要把标注交给只认 XML 的标注团队做二次校对。三是你要接某些可视化工具或评估脚本它们只解析 VOC。反向转换的核心是把归一化坐标乘回图像宽高再写 XML 结构类别从整数索引还原成字符串。4.2 反向转换脚本与图像尺寸依赖import os from PIL import Image classes [person, ball] def yolo_to_voc(label_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for txt_file in os.listdir(label_dir): if not txt_file.endswith(.txt): continue img_name txt_file.replace(.txt, .jpg) img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): continue with Image.open(img_path) as im: w_img, h_img im.size xml_lines [ annotation, f filename{img_name}/filename, f sizewidth{w_img}/widthheight{h_img}/heightdepth3/depth/size, ] with open(os.path.join(label_dir, txt_file)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, x, y, w, h map(float, parts) cls_id int(cls_id) xmin int((x - w / 2) * w_img) ymin int((y - h / 2) * h_img) xmax int((x w / 2) * w_img) ymax int((y h / 2) * h_img) xml_lines.append( object) xml_lines.append(f name{classes[cls_id]}/name) xml_lines.append( f bndboxxmin{xmin}/xminymin{ymin}/ymin fxmax{xmax}/xmaxymax{ymax}/ymax/bndbox ) xml_lines.append( /object) xml_lines.append(/annotation) with open(os.path.join(out_dir, txt_file.replace(.txt, .xml)), w) as f: f.write(\n.join(xml_lines)) yolo_to_voc(./labels, ./images, ./annotations_voc)逻辑说明反向转换强依赖图像真实尺寸因为 YOLO 坐标是比例没有图像宽高就还原不出像素。int()截断会带来最多 1 像素误差对检测训练无影响但如果你的下游任务要求像素级精确改用round()。类别还原靠classes[cls_id]所以classes顺序必须和当初转 YOLO 时一致这也是为什么建议把类别表单独存一个文件。参数说明img_name的扩展名写死.jpg如果你的足球数据集是.png这里要改或者用os.path.splitext动态匹配。输出 XML 的depth写 3 是惯例不影响解析。4.3 反向转换后的完整性检查反向转完用 VOC 的解析库重新读一遍所有 XML确认object数量和原 txt 行数一致bndbox四个值都为正且xmax xmin、ymax ymin。足球小目标在截断后偶尔会出现xmax xmin的退化框这种框在 VOC 里合法但在某些评估脚本里会除零发现后直接删掉该目标。5. 足球数据集标注与转换的避坑排查5.1 类别索引错位导致模型学成「一锅粥」现象训练 loss 能降但验证时所有框都预测成同一类或者person和ball完全颠倒。原因data.yaml的names顺序和转换脚本里的classes顺序不一致比如脚本里是[person, ball]yaml 里写成了[ball, person]class_id 0的含义就反了。解决把类别表抽成一个单独的classes.txt转换脚本和data.yaml都从它读杜绝两处手写。5.2 XML 里的 size 节点和真实图像不符现象转换后框整体偏移或缩放比例不对可视化时框明显偏小或偏大。原因标注工具导出的size里width、height和实际图像尺寸不一致脚本如果信了 XML 的 size 就会算错归一化。解决一律用PIL或cv2读真实图像尺寸XML 的 size 节点只做参考不参与计算。5.3 小目标足球被过滤或退化现象训练集里足球样本很少模型几乎不预测ball类。原因足球在远景里可能只有 10 到 20 像素某些标注规范会忽略小于某阈值的框或者转换时坐标取整后宽高变成 0。解决转换后统计ball类的宽高分布如果大量框宽高小于 5 像素考虑放大图像或对足球区域做裁剪增强而不是硬训。5.4 图像与标注文件名不匹配现象转换脚本跑完labels目录里文件数远少于images。原因图像是IMG_001.jpg标注是IMG_001.xml但内部filename写的是img_001.jpg大小写或前缀不一致。解决转换时以 XML 文件名去掉扩展名为准去匹配图像而不是信filename节点匹配不上的单独输出清单人工处理。5.5 训练时提示找不到标签但路径明明对现象YOLO 启动训练后警告大量「no labels found」但labels目录里 txt 都在。原因Ultralytics 按images替换成labels找标签如果你的目录叫labels_yolo或图像和标签不在平行结构里就找不到。解决严格用images/train对labels/train、images/val对labels/val的平行结构目录名不要自创。6. 548 张小样本下让标注真正发挥价值的两个技巧第一个技巧是转换后做一次「标注质量热力图」。写个脚本统计每张图的框数量、每类的框数量、框的平均面积把 548 张按框密度排序框密度异常高或异常低的图优先人工复查。足球场景里框密度突然飙高的图往往是观众席被误标密度骤降的图可能是漏标。这一步花二十分钟能省掉后面几轮无效训练。第二个技巧是训练前用mosaic增强的边界做一次预演。YOLO 默认开mosaic它会把四张图拼一起如果你的标注里有大量贴边目标拼接后框会被裁切模型学到的是残缺目标。做法是先用rectFalse跑一个短周期看train_batch0.jpg这类可视化输出确认拼接后框没被切得离谱再决定要不要调mosaic概率。我自己习惯把mosaic从默认 1.0 降到 0.5 跑足球小样本因为 548 张本身多样性有限过度拼接反而让足球这种小目标更难学。最后说个习惯每次转完格式我都会把classes.txt、data.yaml、转换脚本三个文件放同一个目录存档并在脚本头部注释写清转换日期和源数据版本。足球数据集这种 548 张的量级后面大概率还要补标、扩类、重训没有这份存档三个月后你自己都不记得当时class_id 1到底是球还是球门。希望帮到你。本文还有配套的精品资源点击获取