简介:这份足球数据集面向计算机视觉初学者与目标检测开发者,用于训练和验证足球识别模型,解决单一类别目标检测任务中样本获取与标注成本高的问题。资源包共1645个文件,包含548张jpg图片、548个VOC格式xml标注文件及549个txt格式YOLO标注文件,压缩包约29.23MB,rar格式无需解压密码,解压后图片与两种标注文件分目录存放,可直接导入labelImg或YOLO训练流程查看与使用。所有图片均由labelImg人工标注,类别统一为football,标注过程强调边界框选准确、目标无遗漏,并通过一致性检查保证质量。目前已有141人学习下载。读者可借此快速搭建足球检测实验环境,省去从零采集与标注的时间,同时对比VOC与YOLO两种格式的差异,便于迁移到其他单类目标检测任务中复用。
1. 足球数据集 VOC 与 YOLO 双格式:548 张标注图能直接喂给检测模型吗
上周有个做体育视频分析的朋友甩给我一个压缩包,说是在网上淘到的足球检测数据集,问我能不能直接拿来训 YOLOv8。我解压一看,548 张 jpg 图片,配着 548 个 xml 和 548 个 txt,类别只有一个 football。这种"单类别、双格式、中等规模"的数据集,在目标检测里其实是个很典型的存在——它既不像 COCO 那样庞大到需要分布式加载,也不像自己随手标几十张那样寒酸,刚好卡在"能跑通一个完整训练流程"的甜点区。VOC 格式的 xml 是 labelImg 的原生输出,YOLO 格式的 txt 则是归一化后的中心点加宽高,两套标注指向同一批图,省去了自己写转换脚本的麻烦。适合谁?做足球轨迹追踪、越位判定辅助、训练场球体检测的从业者,或者刚入门想拿一个干净单类别数据集练手 YOLO 的人。但"能直接喂"和"喂得好"是两码事,下面把我拆包、校验、转换、训练一路踩过来的东西摊开讲。
2. 拆开压缩包先别急着训:VOC 与 YOLO 双格式的结构与校验
2.1 三个文件夹到底装了什么
解压后是三个平级目录,常见命名是 JPEGImages、Annotations、labels。JPEGImages 里是 548 张 jpg,尺寸和体积不统一,1-500KB 之间浮动,说明图片来自不同设备或不同裁剪来源,这点后面会影响训练时的 letterbox 行为。Annotations 里是 548 个 xml,每个 xml 对应一张图,里面记录了 filename、size、object 的 bndbox。labels 里是 548 个 txt,每行格式是class_id cx cy w h,数值全部归一化到 0-1。因为只有 football 一个类,class_id 恒为 0。
这里有个容易被忽略的点:VOC 的 xml 里 bndbox 是绝对像素坐标xmin ymin xmax ymax,而 YOLO 的 txt 是归一化中心点格式。两者不是简单换个后缀的关系,中间隔着一层坐标变换。数据集同时给了两套,意味着你可以用 xml 走 VOC 路线,也可以用 txt 走 YOLO 路线,但千万别混着用——我见过有人把 xml 丢给 YOLO 训练脚本,结果脚本按 txt 解析,读出一堆乱码还不报错,loss 直接飙到 nan。
2.2 用脚本做一次完整性体检
拿到任何数据集,我习惯先跑一遍校验,确认图片、xml、txt 三者数量对齐,且没有空标注或越界框。下面这段脚本直接放在数据集根目录跑:
import os import xml.etree.ElementTree as ET from PIL import Image root = "." # 数据集根目录 img_dir = os.path.join(root, "JPEGImages") xml_dir = os.path.join(root, "Annotations") txt_dir = os.path.join(root, "labels") imgs = {os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.endswith(".jpg")} xmls = {os.path.splitext(f)[0] for f in os.listdir(xml_dir) if f.endswith(".xml")} txts = {os.path.splitext(f)[0] for f in os.listdir(txt_dir) if f.endswith(".txt")} print("图片数:", len(imgs), "xml数:", len(xmls), "txt数:", len(txts)) print("图片缺xml:", imgs - xmls) print("xml缺图片:", xmls - imgs) print("图片缺txt:", imgs - txts) # 检查 xml 中框是否越界 for name in list(xmls)[:50]: # 抽样50个,全量可去掉切片 tree = ET.parse(os.path.join(xml_dir, name + ".xml")) r = tree.getroot() size = r.find("size") w, h = int(size.find("width").text), int(size.find("height").text) for obj in r.findall("object"): b = obj.find("bndbox") xmin, ymin = int(b.find("xmin").text), int(b.find("ymin").text) xmax, ymax = int(b.find("xmax").text), int(b.find("ymax").text) if xmin < 0 or ymin < 0 or xmax > w or ymax > h: print("越界框:", name, xmin, ymin, xmax, ymax)逻辑说明:先做集合差集,快速定位缺失配对;再抽样解析 xml,拿 size 里的宽高和 bndbox 比对,越界框在训练时会引发坐标裁剪警告甚至梯度异常。参数上,[:50]是抽样上限,正式校验时去掉切片跑全量,548 张几秒钟就完事。如果输出里"图片缺xml"或"图片缺txt"非空,说明数据集本身有配对缺口,得先补齐再谈训练。
2.3 为什么单类别数据集也要看类别分布
只有 football 一个类,看起来分布没什么可看的,但实际要统计的是"每张图里球的数量"。有的图可能只有一个球,有的图是多人争抢场景里球被部分遮挡,甚至有的图球只露出一个边缘。用下面这行快速统计每张图的标注框数量:
for f in labels/*.txt; do echo "$(wc -l < $f) $f"; done | sort -n | uniq -c -w1这条命令按每行框数排序后统计频次,输出类似120 1、300 2、128 3,意思是 120 张图只有 1 个框,300 张有 2 个框。如果发现大量图片框数为 0,那就是空标注,得排查是漏标还是负样本。单类别数据集里,框数分布直接决定正样本密度,密度太低时模型容易偏向背景,后面训练要调 anchor 或加 focal loss。
3. 从 VOC 到 YOLO:坐标转换脚本与归一化参数怎么设
3.1 转换公式不是简单的除以宽高
VOC 的xmin ymin xmax ymax转 YOLO 的cx cy w h,标准公式是:
cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h注意 img_w 和 img_h 必须从对应 xml 的 size 节点取,不能假设所有图尺寸一致。这个数据集图片体积跨度大,尺寸大概率不统一,如果偷懒用固定值归一化,框会整体偏移。下面是我常用的转换脚本,直接生成 YOLO 需要的 labels 目录:
import os import xml.etree.ElementTree as ET xml_dir = "Annotations" out_dir = "labels_yolo" os.makedirs(out_dir, exist_ok=True) classes = ["football"] # 类别列表,顺序即 class_id for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) 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"): cls = obj.find("name").text if cls not in classes: continue cls_id = classes.index(cls) b = obj.find("bndbox") xmin = float(b.find("xmin").text) ymin = float(b.find("ymin").text) xmax = float(b.find("xmax").text) ymax = float(b.find("ymax").text) 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(os.path.join(out_dir, xml_file.replace(".xml", ".txt")), "w") as f: f.write("\n".join(lines))逻辑说明:遍历 xml,取 size 宽高做归一化分母,遍历 object 取类别名映射到 id,再套公式。参数上,classes列表顺序决定 class_id,单类别就一个 football;:.6f保留六位小数,YOLO 官方推荐精度,太少会累积坐标误差。如果数据集自带的 txt 已经是对的,这个脚本可以用来交叉验证——跑完 diff 一下,差异大的说明原 txt 有问题。
3.2 data.yaml 怎么写才不报错
YOLOv8 训练靠 data.yaml 定位路径和类别,单类别数据集最容易在这里翻车。标准写法:
path: /abs/path/to/football_dataset train: JPEGImages val: JPEGImages names: 0: football关键点:path必须是绝对路径,相对路径在 ultralytics 里经常解析成相对于缓存目录,导致找不到图。train和val这里都指向 JPEGImages,是因为数据集没划分训练验证集,548 张全量当训练用,验证也复用同一批——这在快速验证流程时可行,但正式评估会有过拟合嫌疑,后面第 5 章讲怎么切。names用字典形式,键是 class_id,值是类别名,别写成列表,YOLOv8 对列表格式的兼容性时好时坏。
3.3 图片尺寸不统一时 letterbox 的行为
前面提到图片 1-500KB 不等,尺寸大概率不一致。YOLO 默认 imgsz=640,训练时会对每张图做 letterbox:等比缩放到长边 640,短边补灰边到 640。这意味着小图会被放大,大图被缩小,球体在不同图里的像素占比差异被拉大。如果发现训练早期 loss 震荡,可以先把所有图 resize 到统一尺寸再训,或者把 imgsz 调到 800 以上保留小目标细节。我一般会先跑一次yolo checks确认环境,再用小学习率 warmup 几个 epoch 观察。
4. 训练与推理:548 张图跑 YOLOv8 的参数与显存账
4.1 命令行训练的最小可用配置
548 张单类别图,属于小数据集,直接上大模型容易过拟合。我一般从 yolov8n 或 yolov8s 起步:
yolo detect train \ data=/abs/path/to/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=football_runs \ name=exp1参数说明:epochs=100对小数据集够用,配合patience=20早停,验证指标 20 轮不升就停,省时间。batch=16在 8GB 显存卡上跑 640 分辨率基本稳,显存吃紧就降到 8。lr0=0.01是 SGD 的常见起点,如果用 AdamW 可以降到 0.001。imgsz=640是默认值,球体在图中占比小的场景可以提到 800。project和name控制输出目录,方便多次实验对比。
4.2 显存不够时的三个降级手段
548 张图本身不大,但 YOLOv8 的显存占用主要来自 batch 和 imgsz 的乘积。如果报 CUDA out of memory,按顺序试:先把 batch 减半到 8,再把 imgsz 降到 512,最后换 yolov8n。三个手段里,降 imgsz 对小目标检测伤害最大,因为球体本来像素就少,缩到 512 可能只剩十几个像素,模型学不到纹理。所以优先降 batch,实在不行再动 imgsz。另外可以开amp=True混合精度,显存能省三成左右,YOLOv8 默认就开,不用手动设。
4.3 推理时 conf 和 iou 怎么调
训练完拿模型跑推理,默认 conf=0.25、iou=0.7。足球检测场景里,球经常被球员腿部遮挡,置信度会偏低,我一般把 conf 降到 0.15 先看召回,再根据误检情况往上调。iou 是 NMS 的阈值,单类别且球体不密集时,0.7 够用;如果一张图里出现多个球(比如训练场多球场景),iou 要降到 0.5 避免误删。
yolo detect predict \ model=football_runs/exp1/weights/best.pt \ source=test_images \ conf=0.15 \ iou=0.5 \ save=True跑完看runs/detect/predict下的可视化结果,重点看漏检的球是不是被遮挡、误检的是不是圆形杂物。这一步的反馈直接决定要不要回去补标注。
5. 避坑与排查:标注一致性、类别名和路径的五个血泪坑
5.1 现象:训练 loss 正常但 mAP 一直是 0
原因:data.yaml 里 names 的键和 txt 里的 class_id 对不上。比如 txt 里写的是 0,names 却从 1 开始编号,模型学到的类别和评估时对不上,mAP 自然为 0。解决:打开任意一个 txt 看首列数字,再核对 data.yaml 的 names 键,确保从 0 开始连续。
5.2 现象:报 "No labels found in ..." 但 labels 目录明明有文件
原因:YOLOv8 默认按图片路径替换后缀找标签,比如JPEGImages/IMG_461.jpg会去找JPEGImages/IMG_461.txt,而不是独立的 labels 目录。如果 txt 放在平级 labels 文件夹,需要在 data.yaml 里显式指定,或者把 txt 挪到和图片同目录。解决:要么改目录结构,要么用yolo detect train时确认 ultralytics 版本支持 labels 独立路径,最稳的是把 txt 和 jpg 放一起。
5.3 现象:同一张图里球被标了两次,框几乎重叠
原因:标注一致性检查没做。数据集介绍里提到"对比不同标注者的结果",但实际整理时可能漏了去重。重叠框在训练时会被 NMS 处理掉一部分,但标注层面的重复会让模型学到冗余特征。解决:用 IoU 脚本筛出同图内 IoU 大于 0.9 的框对,人工确认后删掉一个。
5.4 现象:xml 里 filename 和实际图片名不一致
原因:labelImg 标注时改了文件名,或者图片被重命名过,xml 里的 filename 字段没同步。这会导致某些框架按 filename 找图时失败。解决:批量把 xml 里的 filename 改成对应 jpg 的实际名字,用脚本遍历一遍即可,别手动改。
5.5 现象:验证集 mAP 很高,但拿真实比赛视频推理全是漏检
原因:548 张图大概率来自训练场或固定机位,背景单一,和真实比赛转播画面的光照、视角、草地纹理差异大。这是域偏移,不是模型问题。解决:要么补真实场景的图重新标,要么用数据增强(HSV 抖动、随机裁剪、马赛克)提升泛化,YOLOv8 默认开了 mosaic,可以再加degrees和translate参数。
6. 把 548 张用出 5480 张的效果:切分、增强与验证的实操技巧
548 张单类别图,如果直接全量训练,模型见到的场景多样性有限。我的习惯是先切出 15% 做验证集,剩下 85% 训练,切分时按图片内容分层——比如按背景是训练场还是比赛画面分,保证验证集覆盖不同场景。切分脚本用 sklearn 的 train_test_split 对文件名列表操作,然后分别软链或复制到 images/train 和 images/val 目录,labels 同步。
增强方面,YOLOv8 内置的 mosaic 会把四张图拼成一张,等效于增加小目标和多球场景,对足球检测很对路。但 mosaic 在小数据集上开太猛会让早期训练不稳定,我一般前 10 个 epoch 关掉,用close_mosaic=10参数。另外 HSV 的hsv_h=0.015、hsv_s=0.7、hsv_v=0.4是默认值,草地场景可以把 hsv_h 调大一点,模拟不同光照下的绿色偏移。
验证阶段别只看 mAP50,单类别数据集里 mAP50-95 更能反映框的紧致程度。如果 mAP50 高但 mAP50-95 低,说明框位置不够准,回去检查标注的 bndbox 是不是贴边太松。我还会把验证集的预测结果导出来,按置信度排序,人工看最低的那 20 张,往往能发现标注遗漏或误标。
最后说个习惯:每次拿到新数据集,我都会先跑一遍第 2 章那个校验脚本,再跑一遍转换脚本做交叉验证,确认 xml 和 txt 指向一致后才开始训练。这个流程帮我挡掉过至少三次"标注文件和图片错位"的翻车。希望帮到你。
本文还有配套的精品资源,点击获取