做体育内容分析的时候,最难处理的往往不是算法本身,而是数据。我之前拿通用目标检测模型直接跑足球比赛转播画面,最直观的感受就是:COCO预训练模型在客厅里检测人没问题,一放到足球场上就开始各种翻车——远端球员只有三四十像素高,禁区内十几个人挤成一团,模型要么漏检,要么把几个人框成一个框。折腾了一个多月之后,我干脆自己动手整理了一套基于YOLO标注格式的足球运动员检测数据集,前后迭代了不少版本,正好借这个机会做个复盘,也把这些数据免费分享给有同样需求的朋友。这篇文章不整虚的,主要讲讲这套数据集的结构、YOLO格式落地时的细节,以及我实际训练过程中踩过的坑。适合正在做体育视频分析、需要自建目标检测数据集的开发者和算法工程师参考。
1. 从通用模型到足球专用数据集:为什么这个坑必须自己填
1.1 转播画面里的检测难点到底在哪
先说一个基本判断:体育场景是目标检测领域里非常典型的"看起来简单、做起来崩溃"的任务。很多人以为检测足球运动员就是检测人,拿现成权重就能跑,真上手会发现完全不是那么回事。
第一个难点是目标尺度差异极大。足球转播通常有远景、中景、近景、特写几种镜头,远景画面里球员可能只占几十个像素,跟COCO数据集里那种占画面四分之一的人物框完全是两种量级。用默认的640×640输入尺寸去训练,小目标特征在下采样阶段就被压没了,模型根本学不到有效信息。
第二个难点是密集遮挡。禁区争顶、中场拼抢的时候,五六个人叠在一起是常态,人与人之间的边界非常模糊。通用检测模型对"个体边界"的假设在这种场景下基本失效,容易出现漏检、检测框漂移、两个目标被合并成一个框这类问题。
第三个难点是类别结构不同。足球比赛场景里真正需要区分的对象不只是"球员",还有守门员、裁判、足球本身。这些类别在COCO里根本没有对应关系,你必须自己定义类别体系、自己标注、自己评估。而足球又是全程跑动的小目标,标注难度和数据质量要求都很高。
所以结论很简单:如果目标是做足球数据集的检测分析,找通用模型做backbone没问题,但检测头必须基于自己构建的足球场景数据来训练。这也是我整理这套数据集的根本动机。
1.2 数据集的核心构成与类别设计
这套数据集主要从公开的足球比赛视频片段中抽帧整理而来,经过初筛过滤掉模糊帧、重复帧和含大量场外信息的镜头后,保留了约3900张有效图像。图像分辨率覆盖840×1360到1920×1080,基本贴合真实转播场景的输入分布。
类别体系我前后改过几版,最终确定用以下四个类别:
| 类别 | 中文含义 | 示例数量 | 占比 | 标注难度 |
|---|---|---|---|---|
| player | 场上球员(除守门员外) | 约23500 | 81.1% | 中等 |
| goalkeeper | 守门员 | 约1900 | 6.6% | 中等 |
| referee | 裁判 | 约1700 | 5.9% | 低 |
| football | 足球 | 约1850 | 6.4% | 高 |
整体标注实例数接近2.9万个,平均每张图大约7.4个目标。需要特别说明的是,球员类别占了八成以上,足球目标占比很低但又是实际落地时最关键的检测对象之一,这直接导致了后面提到的类别不均衡问题,我在第4章会详细展开。
我在这套数据集里特意没有加入"观众""教练组""球门"这些附加类别。原因是检测任务的边界越清晰,训练越稳定;如果一开始就把所有出现在画面里的实体都框进去,数据标注成本翻倍,类别之间还会互相干扰,后续要加类别再增量标注就好。
2. 数据采集、清洗与YOLO格式落地的完整链路
2.1 视频抽帧与去重策略
整理数据集的第一步是拿到足够的原始素材。我的做法是先收集多场不同赛事、不同场地、不同光照条件的比赛录像片段,然后用FFmpeg按固定帧率抽帧。
ffmpeg -i match_01.mp4 -vf fps=2 frame_%04d.jpg这里有两个关键细节值得展开说。
第一,抽帧频率不能太高。足球比赛中相邻帧之间的场景变化很小,如果按25fps抽帧,你会得到大量高度相似的连续帧,训练集和验证集之间的信息重叠会非常严重,最终评估出来的mAP虚高,部署到实际视频流里就原形毕露。我最终用的fps=2,也就是每秒抽两帧,既保留动作连续性,又尽量降低帧间冗余。
第二,要按"视频来源"去划分命名前缀。单纯按文件名随机划分不够稳妥,同一场比赛相邻帧很可能因为随机分配出现在train和val两个集合里,这属于严重的数据泄露。我是把每个视频抽出的帧放进独立目录,再按目录整体划分,确保同一场比赛的帧不会同时出现在训练集和验证集。
抽出来的原始帧还需要过一遍质量筛:完全模糊的帧直接删掉;球员占画面比例过小、看起来只是一堆像素颗粒的远景帧先单独存放;含有大量广告牌、球迷特写、场外花絮的帧也一并剔除。这一步看起来简单,但对数据质量影响的权重极高。模糊帧几乎等同于噪声标签,会直接拖低模型收敛效果。
2.2 标注规范与YOLO格式细节
数据集采用YOLO格式存储,每个图像文件对应一个同名的txt文本文件,文本中每一行代表一个目标:
class_id x_center y_center width height所有坐标都做了归一化处理:x_center和y_center是边界框中心点相对图像宽高的比例,width和height是边界框宽高相对图像宽高的比例,取值范围都在0到1之间。这种格式的优点是不受原始图像分辨率影响,训练时无论输入尺寸怎么resize,标注都能直接对齐。
标注工具我用过图形界面工具逐张标注,也试过用预训练模型自动打标再人工修正的方式。早期版本全人工标注,3900张图花了接近两周时间,主要耗在足球小目标上;后期迭代时我先用上一版模型对新增图片做预标注,人工只需要调整错框和漏框,效率提升了至少一倍。
这里列几条我踩过坑之后总结出的标注规范:
- 足球目标如果像素尺寸小于10×10,直接不标注。对小到几乎看不清的足球强行标注,只会给模型引入噪声。
- 球员被大量遮挡时只框可见部分,不要靠脑补去画完整的假想框。模型学到的是"被遮挡时应该输出的边界框样式",统一口径比追求完美框更重要。
- 完全处于画面边缘、只有半个身子的球员,如果可见部分超过30%就正常标注,否则跳过。
- 守门员与普通球员必须分开标注。虽然两者外形相似,但实际应用时守门员的位置信息有独立价值,模型也能学会去区分。
2.3 数据集划分与目录组织
数据整理完毕之后,目录结构按照YOLO惯例组织,方便后续训练直接读取。
football_dataset/ ├── images/ │ ├── train/ # 约2730张 │ ├── val/ # 约780张 │ └── test/ # 约390张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml训练、验证、测试按7:2:1划分。验证集专门用来看训练过程中的指标变化,测试集只在最终评估时拿出来用一次。我在实际迭代中发现,如果频繁拿测试集去调参,容易对测试集产生过拟合,最终部署表现反而下降,所以测试集一定要珍惜着用。
data.yaml的内容也一并给出,后面训练阶段会直接调用。
path: /path/to/football_dataset train: images/train val: images/val test: images/test nc: 4 names: ['player', 'goalkeeper', 'referee', 'football']3. 用YOLO训练足球检测模型的实操流程
3.1 训练环境、模型选择与关键参数
数据集就绪之后,训练本身其实是有固定套路可循的。我训练用的环境是常规的深度学习机器,显卡显存8GB以上就能跑,并不需要特别夸张的配置。如果显存只有6GB,可以把batch-size降到8,输入尺寸降到960,同样能训练,只是指标会略低。
模型方面我最终选用了YOLO系列的m规格版本。n规格版本模型体积小、速度快,适合边缘设备部署,但检测精度尤其是小目标精度明显不足;l规格精度上限更高,但训练时间和显存占用都上去了。m规格在实际测试里精度与速度的平衡最合适,也是我实测下来性价比最高的选择。
训练命令参考如下:
yolo detect train \ data=football.yaml \ model=yolov8m.pt \ epochs=120 \ imgsz=1280 \ batch=16 \ device=0 \ patience=20训练尺寸这一项我特别提一下。早期我用默认的imgsz=640训练,mAP50-95一直卡在0.5左右上不去,原因是足球和远端球员目标太小,640尺寸下这些目标只占几个像素,信息量完全不够。后来把imgsz调到1280,同样的数据和训练轮数,整体mAP50-95直接提升了大约8个百分点。代价是训练时间变长、显存占用变大,但对足球这种小目标密集的场景,高分辨率输入几乎是必须的。
epochs设置120轮,配合patience=20做早停,也就是连续20轮验证集mAP没有提升就提前结束训练,防止后期过拟合。实际上我的训练在第80轮左右就触发了早停,收敛速度比预期快。
3.2 训练质量评估:mAP、精确率与各类别表现
训练结束之后,首先要看的就是各类别的AP值和整体mAP。最终测试集上的指标分布如下:
| 类别 | mAP50 | mAP50-95 | 说明 |
|---|---|---|---|
| player | 0.871 | 0.602 | 目标大、样本多,表现最好 |
| goalkeeper | 0.814 | 0.553 | 与player形态相似,有时混淆 |
| referee | 0.786 | 0.521 | 穿黑色裁判服,夜间场景略差 |
| football | 0.652 | 0.413 | 小目标、样本少,指标最低 |
| 全部类别 | 0.843 | 0.578 | 整体可用性尚可 |
足球类别的AP值明显低于其他类别,这一点完全符合预期。足球目标小、运动速度快、存在运动模糊,是这类数据集里公认最难检测的类别。如果应用场景对足球检测精度要求很高,光靠YOLO单模型还不够,通常需要结合视频帧间的时序信息做追踪或插值修复,这部分我在第4章会补充一些实测经验。
验证集上的PR曲线和混淆矩阵也建议每次训练后都看一眼。实际案例中我就发现goalkeeper和player之间存在少量误检,原因是守门员和普通球员在远距离画面里外形确实相似,如果业务上对这两类必须严格区分,要么增加守门员类别的样本量,要么引入球衣颜色之类的后处理判断,单纯靠检测模型区分是有上限的。
3.3 推理部署与阈值设置建议
模型训练完成后的推理命令也一并给出来,方便直接套用:
yolo detect predict \ model=runs/detect/train/weights/best.pt \ source=test_video.mp4 \ conf=0.25 \ iou=0.45 \ imgsz=1280conf阈值我建议设置在0.25左右,不宜太高。足球目标本身置信度普遍偏低,如果设为0.5,很多真实足球会被过滤掉;设到0.25再配合NMS过滤重叠框,算是比较均衡的选择。视频推理如果需要实时性,输入尺寸可以降到960,牺牲少量精度换取速度,具体看部署场景取舍。
4. 实测踩坑记录:小目标、密集遮挡与类别不均衡
4.1 小目标漏检,分辨率不够就做切片
我在第3章提过把imgsz从640调到1280之后指标有明显提升,但这只是第一步。实际部署到1080P甚至4K转播画面时,远端球员和足球依然可能只有几个像素,模型仍然会漏检。
我实测下来最有效的方法是切片推理。简单说就是把大图切成若干个子图,每个子图独立送入模型检测,再把检测结果映射回原图坐标。比如把1920×1080的帧切成4个960×540的子图,相当于变相提升了目标在输入图像中的像素占比,小目标的特征保留度会好很多。
我用切片推理对比过一组测试视频,足球检测的召回率提升了约15%,代价是推理耗时几乎翻倍。切片重叠率建议设置在0.2左右,避免目标被切在子图边缘导致漏检。如果项目对实时性要求高,可以考虑只在关键帧做切片推理,其他帧用跟踪算法补点,这是比较稳妥的工程折中方案。
4.2 遮挡目标的标注口径直接影响训练稳定性
遮挡问题是足球检测里必踩的坑。早期我标注时比较随意,有时框可见部分,有时凭感觉画完整框,结果训练出来的模型在重叠场景下输出的框抖动很厉害,同一段视频相邻帧的检测框位置会大幅跳变。
后来我统一了标注规则:球员被遮挡时一律只标注可见部分。这样做的前提是模型学会的是"按可见区域输出边界框",推理结果更稳定,追踪算法用起来也更顺手。你可能会有疑问:只框可见部分会不会导致框偏小?实际使用中跟踪和计数任务更看重框的稳定性,偏小的影响远小于框乱跳的影响。
另外,mosaic数据增强在足球密集场景下要谨慎使用。mosaic会把四张图拼在一起,球员被截断的情况大幅增加,边界框标注在拼接边缘会被截掉,相当于在标签里引入了噪声。实测下来把mosaic概率从默认的1.0调低到0.3,验证集指标没有明显波动,但训练早期的loss下降明显更稳定。
4.3 类别不均衡问题与针对性增强
前文说过football类别只占样本量的6%左右,直接用原始数据训练,模型很容易偏向多数类。我尝试过几种方案,效果比较明显的是对足球目标做过采样复制粘贴增强。
具体做法是把标注了足球的样本抽取出来,在训练时对包含足球的图像额外提高采样权重,同时配合轻微的水平翻转、HSV色域调整和缩放抖动。这些增强手段对足球这种快速移动、外观相对固定的目标来说非常有效,因为足球的背景差异大但目标本身形态稳定。
还有一个容易被忽略的细节:下采样过程中的最大池化会削弱小目标响应。对于以足球为代表的小目标类别,可以尝试在backbone输出的高层特征上叠加中层的特征图进行检测,也就是利用YOLO本身的P3、P4、P5多层检测头。训练时不要手动关掉小目标检测层,默认设置下P3层本身就承担了大量小目标检测任务,改配置文件之前先想想自己的调整会不会把这条路堵死。
5. 数据集获取方式与往其他运动场景迁移的思路
5.1 免费获取方式与配套文件说明
这套数据集目前免费分享,获取方式很简单,评论区留言或者私信我都可以拿到网盘链接。压缩包里包含以下内容:
- images目录下所有训练、验证、测试图片
- labels目录下对应的YOLO格式标注文件
- data.yaml配置
- 一份标注规范说明文档,详细写了各类别的定义、遮挡处理规则和小目标标注阈值
说明文档是我建议每个数据集都必须带上的东西。数据集的标签本身只是数字和坐标,真正决定数据质量的是标注的口径和边界条件。我在这份文档里把踩过的坑、定过的规则都写清楚了,拿到手的朋友不用重新摸索一遍标注逻辑,直接按规则扩展自己的新数据就行。
5.2 从足球迁移到篮球、排球等其他运动场景
数据集发布之后,已经有朋友问能不能直接把这套足球检测的权重用到篮球或排球场景上。我的答案是:可以借用,但别指望直接部署,需要做一些迁移适配。
篮球、排球和足球在场地布局、目标密度、摄像机位上有相似性,尤其是球员检测部分,迁移的起点不错。我实测把在足球数据集上训练好的backbone作为预训练权重,在其基础上用小规模篮球数据做微调,收敛速度明显快于从COCO预训练开始训练,最终指标也略高。核心做法是在YOLO的预训练权重上冻结backbone前几层,只训练检测头和深层特征,跑了大概20到30轮就稳定了。
要特别注意的差异点是球类目标的外观和运动特征完全不同。篮球体积大且有明显纹理,排球小而且运动轨迹突然,直接沿用football类别的检测逻辑效果很有限。如果目标是从足球迁移到篮球的球体检测,建议单独收集球体数据进行补充训练,类别结构也要重新调整。
以我目前的经验来看,这套足球数据集在球员检测、人员计数、越位辅助判断这类任务上可以直接落地使用。后续如果做篮球和排球场景的迁移实验效果出来比较理想,我再来补一篇迁移实战的记录。那次的教训就是:数据集的整理比训练本身更耗时也更关键,但一套结构清晰、口径统一的数据集,确实能帮后面省下大把时间。