搞目标检测的人应该有个共识:一场足球比赛视频,可能是计算机视觉最容易让模型当场破防的场景之一。镜头拉远的时候,场上的球员缩成一小团;特写推进之后,同一个球员直接顶满画面,而足球依然只有几十个像素。更别提草地、球衣、阴影混在一起的颜色噪声,以及频繁切换的转播机位。我最近把这件事当成一个正经的检测工程来做,就是标题里这套YOLO11-C3k2-ConverseB方案,目标是同时稳定识别运动员、足球和场地三类目标。这篇文章会完整讲清楚改进思路、数据标注方式、训练调参过程、踩过的坑和最终落地效果,适合那些想复现足球场景检测、或者正在处理“小目标+尺度跨度极大”检测任务的工程师参考。
先说结论:在公开赛事转播素材和自制比赛录像混合集上,这套结构比原版YOLO11s在足球小目标上的mAP50-95提升了接近6个点,运动员类别在高分辨率输入下也有可感知的改善,同时推理速度没有明显劣化。但整个工程里最折磨人的其实不是模型结构,而是数据。下面按我的实际流程往下拆。
1. 足球场景目标检测,难点到底在哪
1.1 三类目标在尺度上的巨大鸿沟
看一场足球转播画面,你会发现模型面对的根本不是“三个普通类别”,而是三类尺度天差地别的物体。
把国际标准球场尺寸放进去算一笔账:足球场长约105米,宽约68米。转播广角镜头通常能覆盖半场甚至大半场,假设画面中有约50米宽的区域映射到1920像素的传感器宽度上,那么每米对应约38像素。一个1.8米高的球员,在画面里大约只有68像素高;而足球直径只有22厘米左右,换算下来只有8个像素出头。8个像素意味着下采样到P3特征层(stride 8)的时候,足球已经只剩下大约1个像素的特征响应,基本等于被洗掉了。这还不算高速运动产生的拖影和模糊。
场地则走上另一个极端。如果标注整个草皮区域或完整的场地标线,它在画面中通常占据几千甚至上万像素,属于极端大目标。绝大多数目标检测框的回归头是为中小物体设计的,对这类“铺满全图”的大目标反而容易框得歪歪扭扭,边界抖动严重。
我最初用640×640输入训练时,运动员和场地都能跑到不错的指标,唯独足球类的mAP50只有0.35左右,基本不可用。这就是尺度鸿沟带来的直接后果,也决定了后面所有技术选型都要围绕“如何让小目标活下来”展开。
1.2 镜头切换与光照波动带来的分布漂移
足球视频还有一个隐藏属性:镜头一直在变。转播会在大远景、中景、特写之间反复横跳,同一个球员在不同镜头下的尺度可能相差10倍以上。模型在训练时如果只吃一种尺度的样本,推理时就很容易在另一种镜头上翻车。
光照问题同样麻烦。日场、夜场、阴天、灯光不均匀的业余球场,草皮颜色从深绿到偏黄都有。球衣颜色更是五花八门。我一开始只用某几场比赛的帧做训练,结果换到另一场比赛时,球员漏检率直接涨了十几个点。这说明足球场景的数据增强不是随便做做,而是要刻意制造“跨场地、跨光照、跨镜头尺度”的多样性。
这也是为什么后来我坚持把输入分辨率抬到960甚至1280,单纯从检测精度角度看,这个选择带来的收益比换任何模块都大。
2. 模型选型与改进思路:为什么是YOLO11 + C3k2 + ConverseB
2.1 基线YOLO11底子里的优势
选YOLO11而不是更早的YOLOv8或YOLOv5,主要看中它在骨架和颈部上的两个变化。一是用C3k2模块替代了之前的C2f,整体参数更省、计算更集中;二是它保留了anchor-free解耦检测头和任务对齐学习机制,对尺度变化和遮挡的适应能力比老一代强不少。
在实际对比中,YOLO11s在同等输入下比YOLOv8s的参数量少约15%,推理速度快一点,mAP还略高。对于需要在转播流上做实时或准实时检测的项目,这个底子是划算的。
但直接拿原版模型跑足球场景,会暴露两个痛点:一是足球这类小目标在下采样过程中信息损耗太严重,二是普通Bottleneck结构对不同尺度目标的特征表达“一视同仁”,没有对大目标和小目标做差异化处理。我的改进就落在这两个点上,分别对应C3k2的调整和ConverseB模块的引入。
2.2 C3k2模块:轻量CSP结构里的关键参数
先补个背景。C3k2是YOLO11里核心的CSP风格模块,名字里的k指代内部Bottleneck的卷积核尺寸。结构上看,它把输入通道分成两个分支,一个分支直接通过1×1卷积映射,另一个分支经过1×1卷积后串联k个Bottleneck块,最后再拼接起来。k越大,模块感受野越大、非线性表达能力越强,但计算量也线性上涨。
原版YOLO11默认在浅层用k=2的Bottleneck,深层用k=3。我在复现时保留了这种分层策略,但针对场地这类大目标、以及球员这类中等目标,把深层网络的两个C3k2模块内部的Bottleneck卷积核从3×3扩大到了5×5和7×7的组合。代价是FLOPs增加了一点,但换来了对大范围上下文更好的聚合能力。实验证明,场地标线检测的边界完整性明显变好,不再频繁出现“框出去半截”的情况。
这里有个容易忽略的细节:C3k2内部的Bottleneck通常带残差连接,而残差连接要求输入输出通道数一致。所以调整卷积核尺寸时,要同步检查shortcut分支的通道对齐,否则一改就报错。
2.3 ConverseB模块:为三类目标定制的特征融合块
ConverseB是这次复现的项目里比较关键的自定义模块,名字是我自己起的,含义是“卷积残差变体块”。核心思路很简单:原本C3k2内部的Bottleneck是“1×1降维→3×3卷积→1×1升维”的结构,对中等目标友好,但小目标经过两次降维升维后,空间细节丢得厉害。
我把它改成类倒残差设计,先做1×1升维,再用分组卷积做3×3或者5×5的空间特征提取,最后1×1降维到目标通道。这个结构最早在移动端网络里被验证过,特点是“先涨维度再提特征”,能保留更多空间细节。同时在残差边上加了一个可选的通道注意力分支,用全局平均池化算出通道权重,对足球这类微弱目标进行通道级增强。
复现时我写过一个最小实现,核心逻辑长这样:
import torch import torch.nn as nn class ConverseB(nn.Module): def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, expand_ratio=2.0, use_se=True): super().__init__() hidden_dim = int(in_channels * expand_ratio) self.conv1 = nn.Conv2d(in_channels, hidden_dim, 1, 1, bias=False) self.bn1 = nn.BatchNorm2d(hidden_dim) self.act1 = nn.SiLU(inplace=True) self.conv2 = nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, kernel_size // 2, groups=hidden_dim, bias=False) self.bn2 = nn.BatchNorm2d(hidden_dim) self.act2 = nn.SiLU(inplace=True) self.conv3 = nn.Conv2d(hidden_dim, out_channels, 1, 1, bias=False) self.bn3 = nn.BatchNorm2d(out_channels) self.use_se = use_se if use_se: self.se = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(out_channels, out_channels // 4, 1), nn.SiLU(inplace=True), nn.Conv2d(out_channels // 4, out_channels, 1), nn.Sigmoid() ) self.shortcut = nn.Identity() if (stride == 1 and in_channels == out_channels) else None def forward(self, x): identity = x out = self.act1(self.bn1(self.conv1(x))) out = self.act2(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) if self.use_se: out = out * self.se(out) if self.shortcut is not None: out = out + identity return out这个块替换掉了C3k2内部部分Bottleneck,重点是放在backbone的P3层和neck的上采样拼接位置。原因很简单,P3层是stride 8,分辨率相对最高,最适合救小目标;而上采样拼接位置决定了下游检测头拿到的特征质量。
模块替换后,我对足球类别做了单类验证,mAP50从0.35涨到了0.42,效果确实有,但远不如输入分辨率从640提到960带来的0.15涨幅明显。这也是我想强调的一点:模块改进是“锦上添花”,输入分辨率和数据质量才是足球小目标检测的地基。
3. 数据集与标注:比模型更难的准备工作
3.1 数据来源与视频切帧方案
我使用的训练数据分两部分:一部分来自公开可获取的足球赛事转播录像和公开数据集,另一部分是用设备自己录制的业余比赛视频。公开素材覆盖顶级联赛、杯赛、青年队比赛,分辨率从720p到1080p都有;自制素材则集中在中低机位视角、夜场灯光、草地质量一般的场地,专门用来模拟真实落地场景。
切帧不是简单按固定间隔抽。我先按每秒2帧抽一轮,再对运动剧烈的片段追加每秒5帧的密集采样,最后做一遍相似度去重。相似度去重很关键,否则连续帧高度重复,训练集看起来很大,有效信息量其实没多少。我按结构相似度阈值0.85去重,最终从约4000段视频片段中筛出约12000张有效帧。
3.2 标注规范:球要标、人要标、场地怎么标
三类目标标注规则各有坑。
运动员标注相对常规,但规定必须“紧贴人体外轮廓”。遮挡严重时,宁可把框缩到可见部分,也不要凭想象把被挡住的位置画进去。这直接影响后续NMS合并效果。
足球标注是整个任务的核心。标准是:只要画面里能看到完整球体或超过一半的球体轮廓,就必须标注。哪怕只有6个像素也要画一个很紧的框。过程中最忌讳“漏标”,因为漏标的足球会变成难负样本,让模型学会忽略小目标。我后期复检时发现,凡是标注稀疏的视频片段,模型在该片段上的足球召回率几乎都会崩掉。
场地标注我采用的方案是按“场地标线”处理,包括边线、底线、中线、中圈、禁区线。由于检测框是矩形,对线状目标天然不合适,实际操作中是把每条标线分割成若干段,每段用外接矩形标注,类别统一叫pitch_line。这样既不增加像素级分割的标注成本,又能让模型学会“这里有场地元素”的空间定位。
标注工具用通用的开源图像标注工具就行,导出成YOLO的txt格式。这里有一个容易出错的地方:YOLO格式要求坐标全部归一化到0-1之间,且框中心点坐标是相对宽高的比例。我见过很多人在标注工具里直接把像素坐标填进去,训练时维度对不上,各种报错。导出后务必抽检几个文件,手工核对归一化坐标是否落在合理区间。
3.3 数据增强:既要Mosaic,又要“去Mosaic”
YOLO系列默认的Mosaic增强对小目标检测很有帮助,它把四张图拼在一起,等效于制造了大量小尺度目标。但无脑开Mosaic也有反效果:场地标线这种大目标在拼接时会被裁得支离破碎,模型学到的东西是残缺的。
我的策略是把增强分成两组。第一组用来提升小目标:Mosaic、MixUp、随机透视、HSV扰动全开,概率设为0.5;第二组用来保大目标:对包含场地标线的样本关闭Mosaic,改用随机平移缩放、水平翻转和轻度旋转,概率设为0.3。同时在训练后期关闭Mosaic,让模型在接近真实分布的数据上精调。
另外,针对足球小目标稀疏的问题,额外加了一个Copy-Paste增强,把同一张图里已标注的足球复制到无球区域,增加了小目标出现的频率。这个操作要注意控制粘贴位置,不能跑到人身上或者场地之外。
4. 训练配置与关键调参实录
4.1 迁移学习路径与加载权重策略
我没有从零开始训练,而是加载了COCO预训练权重作为起点。不过因为backbone结构里插入了ConverseB模块,特征层和原版权重并不能完全对齐,直接整体加载会报形状不匹配。
我的处理方式是分两步走:先把改进模块和neck部分的参数随机初始化,backbone其余层加载原版权重;再冻结backbone前几层,只训练新增模块和检测头,先跑20个epoch让结构稳定下来。之后解冻全部参数,用较低学习率微调。这套流程基本不会出现初期loss爆炸的问题。
4.2 关键超参数与训练策略
训练配置我用的是自己调整过的YAML,核心项如下:
task: detect mode: train model: yolov11s_custom.yaml data: soccer_court.yaml hyp: lr0: 0.005 lrf: 0.01 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 warmup_momentum: 0.8 box: 7.5 cls: 0.5 dfl: 1.5 hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 mosaic: 0.5 mixup: 0.1 epochs: 150 batch: 8 imgsz: 960 workers: 8 optimizer: SGD说几个调参中验证过的点。
输入分辨率选了960而不是更大,是因为训练显存和推理速度的折中。1280分辨率对足球类确实更好,但batch size会掉一半,训练时间翻倍,最终在转播流上做实时推理也比较吃力。
学习率用的SGD初始0.005,配合warmup。我试过AdamW,收敛确实快,但最后mAP普遍比SGD低一点,可能和检测任务的稀疏标注特性有关。
类别权重没做特殊调整,因为60%时间花在调分辨率、数据、训练节奏上,远比调cls loss权重有效。
4.3 训练过程观察:三大阶段特征
整个训练过程我盯的是三类指标:总loss、验证集mAP50、足球类别单独AP50。
前20个epoch是“结构稳定期”,loss从十几迅速降到7左右,但验证集mAP基本没动,尤其是足球类完全不涨。这时候千万别慌,我在这个阶段加练过几次,最后证明是浪费时间。新增模块在冷启动阶段本来就需要先学会提取基础特征。
20到60个epoch是“主涨期”,运动员和场地mAP快速上升,足球类开始缓慢爬升。到60轮时,三类mAP50基本能达到最终水平的85%左右。
60到120个epoch是“精修期”,涨幅变得很慢,但足球类mAP50-95会在这个阶段稳步改善。这其实是回归精度在变好,边界框更贴合了。我的经验是:小目标检测任务一定不要早停太早,足球这类目标需要较长时间才能把框回归做准。
我最终在第120轮左右保存了best模型,继续训练到150轮时验证指标基本持平。
5. 实验结果与问题诊断
5.1 对比实验:基线与改进模型的差距
我在相同数据集上对比了三个配置,都采用统一增强策略:
| 模型配置 | 输入尺寸 | mAP50 | mAP50-95 | 足球AP50 | 参数量 | 推理耗时(ms) |
|---|---|---|---|---|---|---|
| YOLO11s 原版 | 640 | 0.681 | 0.423 | 0.352 | 9.4M | 2.8 |
| YOLO11s 原版 | 960 | 0.713 | 0.451 | 0.483 | 9.4M | 6.1 |
| YOLO11-C3k2-ConverseB | 960 | 0.726 | 0.468 | 0.541 | 9.8M | 6.8 |
表格里的推理耗时是在同一张显卡上、batch size为1、FP16精度下测的。
结论很直接:输入分辨率提升带来的增益最大,足球AP从0.35拉到0.48;引入C3k2-ConverseB后,足球AP又涨到0.54,运动员和场地的mAP也有小幅提升,参数量只多了0.4M,推理只慢了0.7毫秒,这个代价完全可以接受。
5.2 足球小目标漏检与现场修正
精度最高时,足球类mAP50是0.54,mAP50-95是0.31。说实话这个数字距离“完美”还远,漏检主要集中在三种情况:高速飞行的球、被球员身体遮挡超过一半的球、以及远景画面里不足6像素的球。
对第一个问题,我试过在数据增强里加入运动模糊模拟,有一定效果;对第二个问题,本质上需要时序信息,单帧检测物理上无法解决;对第三个问题,只能靠提高输入分辨率或切图推理。
切图推理是立竿见影的手段:把1080p画面切成2×2的四块,每块缩放到960分辨率分别检测,再合并结果。足球召回率能再涨几个点,但推理耗时翻倍不止。我的结论是:如果业务上对“球”的召回有硬指标,就得上切图或者做一个专门的球追踪器做时序补偿;如果只是做粗粒度事件分析,单帧的0.54基本够用。
5.3 运动员密集人群遮挡问题处理
运动员聚集在禁区内争顶头球时,框会叠成一大团。NMS阈值默认0.45,在重叠率极高的场景下会把大量有效框直接合并掉,漏检率明显上升。
我做了两个调整:一是把NMS的IoU阈值提高到0.5,保留更多重叠框;二是在后处理时限制单张图最大检测数量,从默认300调到200,因为这类场景大量高置信度的重复框会拖慢后处理。调整之后,拥挤场景的运动员召回率从0.71提升到0.78,代价是少量误检,属于可接受的范围。
6. 工程落地与避坑建议
6.1 推理速度优化与模型导出
训练完成后,需要把模型导出到ONNX再做TensorRT加速。我在导出时固定了输入尺寸960×960,开FP16精度。实测TensorRT FP16比PyTorch FP16推理快1.8倍左右,mAP损失在0.5个点以内,可以忽略。
有一个细节:转播流画面比例是16:9,而我的输入是1:1。直接resize会把画面拉伸变形,检测框坐标还要做逆变换。更好的做法是在预处理时做letterbox填充,保持原比例,预测后再按填充比例映射回原图。没有做这一步的话,场地标线的检测框位置会系统性偏移。
6.2 易错点速查表
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 足球类AP始终不高 | 输入分辨率不足、足球标注漏标多 | 提升imgsz到960以上,复检标注质量 |
| 场地标线框边缘抖动 | 大目标回归不稳定、标注框不一致 | 分线段标注,统一标注粒度 |
| 换一场比赛后漏检率暴增 | 过拟合单一场地颜色和光照 | 增强HSV扰动,加入多场地样本 |
| 训练前期loss不降 | 新增模块随机初始化,权重未对齐 | 分阶段冻结/解冻训练 |
| 推理时边界框偏移 | 预处理resize未还原坐标 | 用letterbox+逆映射 |
| 多人重叠漏检 | NMS阈值过低 | IoU阈值调至0.5,限制max_det |
6.3 后续扩展方向
这套检测模型实际是上层应用的底座。我接下来的计划是在检测结果上叠加一个轻量级多目标跟踪器,对球员和足球分别做轨迹追踪,然后基于轨迹计算跑动距离、控球时间和传接球事件。这些数据在半自动比赛分析、青训辅助评估这些方向上都有实用价值。场地标线检测也可以进一步换成语义分割分支,但工程成本会高不少,暂时不在计划内。
最后说一句个人体会:我踩过最大的坑是把太多精力花在改模块上,直到把输入分辨率提上去、把标注质量揪起来,才意识到足球小目标检测的瓶颈往往在数据和预处理环节。YOLO11-C3k2-ConverseB这套结构能在精度和速度之间站住脚,但真正让它发挥作用的,是一整套围绕数据、训练节奏和推理部署的组合拳。如果你的项目也在啃类似“小目标+大目标同框”的硬骨头,建议先检查数据和输入尺寸,再回头审视模型结构,这个顺序能帮你少走很多弯路。