做体育赛事智能化这些年,我印象最深的一个项目不是足球也不是篮球,而是游泳馆里那种嘈杂、潮湿、快门闪烁的比赛现场。你可能会觉得,游泳比赛这么规整的赛道环境,检测几个目标还不简单?真上手做才发现,水面反光、运动员出水瞬间的水花、转播视角下细长的泳道线,每一项都能让模型在评测集和现场效果之间出现巨大落差。这篇文章要聊的项目,就是我参与的一个游泳比赛关键检测目标识别任务:以YOLOv5为基础,把检测头替换成EfficientHead,在几乎不增加速度成本的前提下把关键目标识别精度整体拉高,工程源码我也一起整理了。
如果你正在做体育视频分析、赛事自动判罚,或者只是想把YOLOv5的检测头改得更高效,这篇文章里的思路、配置和踩坑记录都可以直接拿过去用。
1. 项目背景:游泳比赛场景为什么难在“检测”
1.1 需求拆解:我们到底要识别什么
项目一开始,业务方给的需求其实很朴素:比赛中要自动判断运动员什么时候入水、什么时候触壁,并辅助裁判做终点名次判定。但“自动判罚”落到视觉技术上,就变成了一连串非常具体的检测目标。
我梳理下来,最终需要识别四类关键目标:
- 头部/泳帽(head_cap):作为运动员位置追踪的关键点,是整个判定链路里最核心的目标。头部框的位置变化直接决定运动员是否入水、是否完成触壁。
- 运动员人体(swimmer_body):用来做更完整的姿态分析和轨迹提取,属于辅助类别。
- 泳道线(lane_line):用来判断运动员有没有串道,也能给追踪算法提供空间约束。泳道线本身细长且存在透视变形,标注和检测都不算轻松。
- 触壁区域(touch_wall):终点池壁附近的判定区域,当头部框和触壁区域发生稳定重叠,才触发“触壁到达”事件。
不要小看这个目标组合。它们尺寸跨度非常大:同一个画面里,运动员人体可能占几百像素,而触壁区域在远景机位下可能只有几十个像素。再加上水花、浪纹、反光这类强干扰背景,普通检测模型很容易出现“对着水花疯狂输出”的尴尬局面。
1.2 为什么基线选YOLOv5而不是更重的模型
选型阶段,团队里也有同事提议直接上YOLOv8或者更重的Transformer检测器。最后我们还是把YOLOv5s定成基线,原因是它在这个场景里有三个不可替代的优势。
第一,YOLOv5的工程生态非常成熟。训练、验证、导出、部署脚本全都是现成的,改检测头也比较容易,不需要为了一个业务项目去重建整个训练管线。第二,游泳比赛需要多路视频实时分析,通常是一台服务器同时接四路以上摄像机。YOLOv5s在满足精度需求的同时,单张640x640推理延迟能控制在5毫秒左右,想象一下四路1080p视频并行处理时的计算压力,这个性价比很关键。第三,YOLOv5的检测头结构相对“正统”,耦合预测的写法改起来非常直接。我们想验证“只用优化检测头”能不能带来收益,YOLOv5是个干净的研究载体。
后来实验结果也证明这个选择是对的。同样是换EfficientHead,YOLOv5s这种轻量基座上的提升幅度比大模型更明显,因为大模型的骨干已经足够强,头的瓶颈效应没有小模型那么突出。
2. 数据工程:标注规范和增强策略
2.1 数据来源与类别设计
数据是整个项目里最花时间的部分。我们拿到的主要是三类素材:比赛场馆固定机位的历史录像、转播机位的直播画面,以及我们自己用多台摄像机拍摄的模拟比赛画面。固定机位视角稳定、光照条件一致,适合训练;转播画面机位经常切换、构图多变,混进去之后能有效提升模型的泛化能力。
清理完无效帧之后,最终保留了约九千张有效图片。按照不同机位和场次划分训练集、验证集,比例接近9:1。这里有一个细节:划分时不能随机打乱,而是必须按“场次”隔离。如果同一场比赛的连续帧同时出现在训练集和验证集里,验证指标会虚高,现场效果反而打折扣。这是很多人容易忽略的坑。
类别设计上,除了前面说的四类目标,我还额外加了一个背景负样本类,专门收集只有水和泳池壁、没有运动员的画面。这些负样本在后期防止误检上起了非常关键的作用。
2.2 标注难点与规范
游泳场景的标注比普通行人检测麻烦得多。头部目标在运动员入水瞬间会被水花完全遮挡,泳道线在远景下细到只有三四个像素宽。我们给标注员定了几条硬性规则:
- 头部/泳帽:只要有超过30%的面积可见就标,被水花完全盖住的不标。
- 运动员人体:从肩膀到脚踝,不包含入水后的下半身虚影。
- 泳道线:把整根泳道线按等间距拆成小段框来标,不标整根长线框。这样既降低了目标的长宽比变异,也让模型更容易学习局部纹理。
- 触壁区域:统一标池壁上的同一块参照物区域,保证所有镜头下的语义一致。
标注规则直接影响模型能学到什么。比如泳道线如果按整根框标,YOLOv5的anchor设计就很难匹配这种极端长宽比的目标,模型会把泳道线和旁边的水纹混在一起。拆成小段之后,检测框变得规整,训练收敛速度快了很多。
2.3 数据增强怎么加才不伤特征
YOLOv5默认的增强策略已经很全面了,Mosaic、MixUp、HSV扰动、随机翻转都是开箱即用。但在游泳场景里,我额外加了几个针对性的增强:
一是水花遮挡模拟。在训练时随机把检测框边缘一部分像素替换成高斯噪声和波浪纹理,模拟运动员出水时的水花遮挡。如果不加这个,模型对水花的鲁棒性很差,动不动就把水花边缘误判成头部轮廓。
二是画面频闪模拟。转播画面在运动员快速划水时经常出现水波纹干扰,我用随机亮度和对比度扰动模拟这种效果。这里要注意幅度别太大,否则会破坏真实纹理,反而干扰泳帽这类小目标的颜色特征。
三是负样本混入。每次迭代有5%的概率让当前batch图片完全没有任何目标,配合Mosaic增强,让模型在无目标帧上学会“克制输出”。
增强不是越狠越好。我试过加入大量随机旋转和裁剪,结果显示泳道线这类细长目标反而掉点。因为旋转会让泳道线的局部形态严重失真,模型学不到稳定的“短线”特征。最终只用了正负10度的小角度旋转。
3. EfficientHead:从“耦合预测”到“解耦高效头”
3.1 原YOLOv5检测头的问题
YOLOv5的默认检测头是耦合的:同一组特征同时用来预测分类和回归。这在通用目标上问题不大,但在游泳比赛这种特殊场景里就暴露了两个毛病。
第一,分类和回归两个任务对特征的需求是矛盾的。分类分支希望特征对“这是什么”敏感,回归分支则希望特征对“位置在哪里”敏感。耦合在一起,相当于让一个人同时兼职两份工作,最后每份都做得不够好。尤其是头部这类小目标,定位偏差一点点,后续触壁判定的时序就会出错。
第二,原始头部的感受野对小目标不友好。P3层虽然分辨率高,但它在被丢进检测头之前已经经过了一层3x3卷积,特征表达没有针对小目标做强化。泳道线小片段和远处运动员的头部,恰恰是需要高分辨率层提供足够细节的目标。
EfficientHead的设计初衷,就是在不引入额外推理负担的前提下,把这两个问题一起解决。
3.2 EfficientHead结构细节
我给EfficientHead设计了四条改造路径,下面逐条讲。
第一条,解耦预测头。把分类和回归拆成两个独立分支。分类分支走“1x1降维 + 3x3卷积”,回归分支走“1x1降维 + 3x3深度可分离卷积”。深度可分离卷积在这里主要是为了控制参数量,否则双分支结构会让模型体积上涨一截。
第二条,共享降维层。原YOLOv5的每个尺度检测头各自做一次降维。EfficientHead改成让三个尺度共享同一个降维卷积,先把neck输出的通道统一压到256,再分发给各个尺度分支。这样做的计算收益非常直观:原来3个尺度各算一遍,现在只需要算一遍,后面只是轻量的分支操作。
第三条,轻量通道注意力。在回归分支的3x3卷积前,插入一个极简的SE注意力模块。先做全局平均池化,再接两个全连接层,最后用Sigmoid生成通道权重。这个模块的参数量不到0.1M,但能让回归分支更关注高亮边缘和泳帽轮廓这类高频信息,抑制平缓的水面纹理。
第四条,尺度感知初始化。每个尺度的回归分支输出前,乘一个可学习的尺度因子,初始值按P3/P4/P5分别设为0.05/0.1/0.2。原因很简单:不同尺度目标的定位幅度差别很大,统一初始化容易让小尺度分支在训练初期震荡。这个细节借鉴了多任务学习中“为不同任务设置不同梯度尺度”的思路。
EfficientHead整体的改动量很小,核心代码可以浓缩成下面这段伪结构:
class EfficientHead(nn.Module): def __init__(self, nc, ch, anchors): super().__init__() self.shared_reduce = Conv(sum(ch), 256, 1) self.reg_branch = nn.Sequential( Conv(256, 256, 3, groups=256), LightAttn(256), nn.Conv2d(256, 4 * len(anchors), 1) ) self.cls_branch = nn.Sequential( Conv(256, 256, 3), nn.Conv2d(256, nc * len(anchors), 1) ) self.scale_factor = nn.Parameter(torch.tensor([0.05, 0.1, 0.2]))这段代码只是结构示意,实际实现里还要处理anchor的reshape和decode逻辑,但在思路上已经能看出EfficientHead比原头部“省”在哪里:共享降维降低了重复计算,解耦分支用深度可分离卷积换回了一部分参数量,注意力模块则花很小的代价给回归分支补上了关键特征增强。
3.3 为什么参数更少反而精度更高
我用最直白的话解释这个反直觉的结果。模型精度提升不取决于参数总量,而取决于每个参数有没有被用在刀刃上。原头部把参数量花在了三个尺度各自独立的降维卷积上,这些卷积在高频特征提取上的效率并不高。EfficientHead把这块预算省出来,转投到解耦分支和通道注意力上,等于用更少的资源做了更聚焦的工作。
泳道线小片段就是一个典型例子。原头部在高分辨率层上直接输出预测,特征图里同时混着水纹、反光和泳道线。加了通道注意力之后,回归分支会把响应集中在泳道线的“线状纹理”上,水纹那种大范围低频特征被明显抑制。在验证集里拉出来对比,泳道线这一类别的AP从81.4涨到了87.9,涨幅是所有类别里最大的。
当然,解耦头也有它的代价。分类分支和回归分支分开后,如果不加共享降维层,FLOPs会明显上涨。EfficientHead正好用共享降维抵消了这个上涨,最终整体推理速度还比原头快了一点点。
4. 训练配置、超参与调优经验
4.1 训练环境与基础配置
训练是在一台工作站上完成的,双RTX 3090,PyTorch 1.13 + CUDA 11.7。这里有个很现实的经验:YOLOv5系列在混合精度训练时容易因为head结构改变出现NaN,改完检测头之后一定要先用FP32试一个短迭代,确认loss正常再开AMP。我第一次直接开AMP,结果第二个epoch的回归loss直接变成NaN。
基础配置直接给出来:
train: data/swim.yaml model: models/yolov5s_effhead.yaml epochs: 150 batch_size: 16 imgsz: 640 optimizer: SGD lr0: 0.01 lrf: 0.05 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3 hsv_h: 0.015 hsv_s: 0.5 hsv_v: 0.3 mosaic: 1.0 mixup: 0.14.2 关键超参说明
初始学习率0.01是个比较稳的起点。检测头结构变了之后,分类和回归分支的梯度幅度不再一致,如果不用余弦退火而是固定学习率,很容易出现分类已经收敛、回归还在震荡的情况。所以我保留了YOLOv5默认的余弦退火策略,并且把lrf调低到0.05,让最后阶段的学习率更低,充分精修回归分支。
batch size 16在双卡上刚好跑满显存。如果显存有限,不建议把batch降到4以下,因为BN层的统计量会不稳定。实在不行就用梯度累积,等效batch保持16。我在调参时对比过一次:batch从16降到8,mAP@0.5大概掉了0.8个百分点,效果是可以感知到的。
warmup_epochs设为3,配合最大学习率0.01,能有效避免头部参数在刚开始就被过大的梯度冲散。尤其是我们还在分类分支里加了注意力模块,冷启动阶段对梯度扰动更敏感。
4.3 训练策略与收敛技巧
我采用了两阶段训练法,这个方法强烈建议抄走。先用COCO预训练权重加载模型,但把EfficientHead模块的参数随机初始化后单独训练头部5个epoch。这个阶段骨干被冻结,相当于让新头在骨干输出的特征上先学会“读图”。之后再解冻全部参数一起训练,收敛速度明显比直接全量训练快,而且最终精度更高。
第二个技巧是最后20个epoch把输入尺寸从固定640切换成多尺度512到736随机采样。这相当于给模型做了最后一轮尺度扰动增强,让它在不同机位、不同拍摄距离的现场切换时更加稳定。实测这一条操作单独贡献了约0.6个点的mAP@0.5:0.95。
第三个技巧是使用EMA指数滑动平均,YOLOv5默认是开启的。这里要提醒的是,改了检测头之后,自定义参数的EMA同样会生效,但如果你在训练中途修改了模型结构定义,比如改了注意力模块的通道数,务必重新加载权重并重建optimizer,否则EMA缓冲区的shape对不上,推理阶段会直接报错。
5. 实测效果:精度、速度和典型case分析
5.1 指标对比
模型最终在验证集上的表现如下表。对比对象是官方YOLOv5s原始头部,以及我改造后的YOLOv5s-EfficientHead。
| 模型 | 参数量 | mAP@0.5 | mAP@0.5:0.95 | 推理耗时(ms) |
|---|---|---|---|---|
| YOLOv5s原始 | 7.2M | 86.4 | 68.7 | 5.6 |
| YOLOv5s-EfficientHead | 6.5M | 89.8 | 72.3 | 5.1 |
参数少了0.7M,推理反而快了0.5毫秒,精度方面mAP@0.5涨了3.4个点,mAP@0.5:0.95涨了3.6个点。这个结果和预期完全一致:参数量下降是因为共享降维层节省了重复计算,速度提升是因为FLOPs减少了,精度提升则来自解耦预测和注意力增强。
把四个类别单独拆开看,头部/泳帽从83.2提升到87.5,人体从91.5提升到92.8,泳道线从81.4提升到87.9,触壁区域从89.3提升到91.2。提升最大的是泳道线和头部两个小目标类别,这也验证了解耦头和注意力设计在细长小目标上的优势。
5.2 典型场景效果分析
看效果不能只看指标。我把模型跑在了一段现场入场画面的逐帧结果上,挑三个案例说。
第一个案例是出发阶段。运动员全部在跳台上,画面里有大量身体重叠。原始YOLOv5会把两个运动员的人体框融合成一个,导致头部框也跟着错位。EfficientHead因为分类分支和回归分支分离了,分类分支能独立判断“这是两个人”,回归分支再分别给出框,重叠区域的漏检明显减少。
第二个案例是入水瞬间。运动员头部刚没入水面,周围全是白色水花。原始模型在这个瞬间经常把最大的那团水花误检成泳帽,EfficientHead的注意力模块有效抑制了这种低频响应,误检率下降了接近一半。不过注意,如果水花恰好覆盖了头部区域超过70%,我们的标注规则本就不要求检测,所以这个case强调的是减少误检,而不是强行召回被完全遮挡的目标。
第三个案例是远端转播画面。运动员在远处只有很小的像素面积,泳道线几乎变成虚线。原始模型对泳道线的召回率掉得很厉害,EfficientHead因为尺度感知初始化对小目标的回归更友好,加上高分辨率层上的注意力增强,在这个场景下依然能稳定输出泳道线片段框。
5.3 在业务侧的实际收益
从业务角度看,这次优化最大的收益不是排行榜上的mAP,而是把“误触发”和“漏触发”两个争吵了很久的问题同时压了下来。触壁事件的判定逻辑是头部框与触壁区域IOU大于0.5且连续稳定3帧。原模型平均每场比赛会有两到三次误触发,比如运动员转身溅起水花时,水花被误检成头部进入了触壁区域。换上EfficientHead之后,误触发量降到平均每两到三场一次。
这个结果让现场裁判愿意在实际计时流程里参考系统输出,而不只是把它当成一个“演示辅助”。模型精度再高,如果在关键判定时刻频繁出错,业务方最终还是会选择相信人眼。EfficientHead在精度上的提升,恰好迈过了业务可信度的那道坎。
6. 部署与业务集成
6.1 模型导出
训练结束之后,导出部署模型是至关重要的一步。我用PyTorch自带的TorchScript先做一次序列化,再转ONNX。导出的时候有几个细节值得注意:EfficientHead里如果有可选的scale_factor参数,要确认它已经被转成普通的张量操作,不能被当成输入节点;输入尺寸固定成640x640,不要用动态尺寸,否则TensorRT优化后的性能会打折扣。
导出命令大致如下:
python models/export.py --weights best_effhead.pt --include torchscript onnx --img-size 640 640导出成功后先用ONNX Runtime做一遍数值对齐,比较模型输出的张量差异,确保abs误差小于1e-3再继续。这个步骤能筛掉大部分结构实现上的低级错误。
之后用TensorRT做FP16优化:
trtexec --onnx=best_effhead.onnx --fp16 --saveEngine=best_effhead.engineFP16引擎在RTX 3090上实测推理耗时约3.2毫秒,相比PyTorch原版有明显加速。游泳比赛现场部署环境通常不具备高配GPU,我另外在边缘设备上做过一批压测,结论是RTX A2000级别跑起来完全没有压力。
6.2 推理服务化设计
多路视频分析场景下,单纯把单张图片推理做快是不够的。我设计了“拉流-解码-推理-业务逻辑”四层结构。每个摄像机一个拉流线程,通过RTSP拉取视频帧;解码之后送入共享队列;推理进程从队列取出图像,执行前处理、TensorRT推理和后处理;最后把目标框和类别传给业务判定模块。
这里有个经验:不要把后处理(NMS)混在GPU推理线程里做。虽然YOLOv5自带的nms很快,但多路视频并发时CPU资源会成为瓶颈。我选择把NMS放到独立的CPU线程池,用torchvision的nms算子,每路视频一个结果队列。实测四路1080p并发时,端到端整体延迟从单路17毫秒只涨到22毫秒左右,系统仍然非常稳定。
6.3 触壁事件的业务逻辑
检测模型只负责输出目标框,最后的“触壁判定”需要结合时间序列逻辑。我定的规则很简单:头部框中心点进入触壁区域框的范围内,且IOU超过0.4,连续3帧稳定保持,就触发一次触壁事件。触发后锁定当前时间戳,并在接下来5秒内抑制该泳道的重复触发。
这个阈值组合是调出来的。IOU阈值如果设到0.6,运动员在触壁瞬间因为轻微反弹会导致漏判;设到0.3,水花经过触壁区域又会引起误判。0.4是一个在测试集上错误率最低的折中值。如果你在自己的场景里复现这套方案,建议用一段带人工标注的比赛录像做参数扫描,而不是直接抄这个数字。
7. 踩坑记录与常见问题速查
7.1 五个绕不开的坑
第一个坑是NaN问题。前面提过,改检测头后直接开AMP很容易NaN。具体原因是解耦分支中的深度可分离卷积在FP16下梯度过大,我最终做法是AMP的grad_scaler里设置了init_scale到1024,并且在loss出现inf时自动跳过该step。
第二个坑是水花误检。单纯靠负样本和注意力模块并不够彻底。后来我把泳帽颜色统计了一下,发现不同泳道的泳帽颜色差异极大,红帽、蓝帽、白帽都有。于是我做了一个按视频帧的临时裁减预测:当某个候选框的置信度介于0.3到0.6之间,且其区域内高频纹理占比极低时,直接降置信度为0。这个规则在后处理阶段剔除了大量水花误检。
第三个坑是泳道线检测断裂。拆段标注解决了框长宽比问题,但模型仍然会在镜头逆光时漏掉泳道线的中段。我加了一个后处理连接算法:将相邻两个泳道线检测框的中心距离小于一定阈值时合并成一条完整线,输出时再按原距离打断成多个线段框。这个逻辑简单但效果非常好,现场逆光场景下漏检率降了一半。
第四个坑是类别不平衡。人体类别数量远多于泳道线,训练后期泳道线的梯度贡献被淹没。我用了最简单的办法:在损失函数里给不同类别设置一个加权系数,泳道线的权重调到1.2,头部调到1.1,人体保持1.0。这个改动没有增加任何计算成本,但把各类别AP差距缩小了约3个点。
第五个坑是机位视角迁移。我们用固定机位训练的模型,迁移到体育频道转播画面的机位后,mAP掉了5个点多。后来分析发现是转播画面下运动员比例和固定机位差异太大。解决办法是最后20个epoch做多尺度+冻结部分底层的微调,并且在部署时对不同机位设置了单独的后处理阈值。
7.2 问题排查速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 训练loss变成NaN | 混合精度梯度爆炸 | 关闭AMP或调小init_scale;检查自定义头是否含有不稳定的DW卷积 |
| 小目标漏检 | 高分辨率层特征未被利用 | 确保P3层输入了有效的高频特征;增大输入尺寸到768 |
| 水花严重误检 | 背景负样本不足 | 增加纯水面负样本帧;配合后处理置信度修正 |
| 泳道线召回低 | 标注方式不对 | 改用分段小框标注;合并相邻检测框的后处理 |
| 多路视频延迟高 | 后处理NMS占CPU | 单独CPU线程池处理NMS结果 |
| 验证指标高但现场差 | 数据集划分为按帧随机划分 | 改为按机位/场次划分验证集 |
这些坑每一个都对应着真实的调试周期。项目中最耗时的一个坑是水花误检,前前后后花了两周才彻底解决,而且最终方案已经超出了纯模型调整的范畴,落到了后处理规则层面。做这类项目一定要有心理准备:现场环境不会因为模型指标好看就放过你。
8. 源码结构与快速复现
8.1 源码目录说明
项目源码尽量保持了和官方YOLOv5核心结构一致,只增加了EfficientHead相关文件。目录大致如下:
swim_detect/ ├── configs/ │ ├── yolov5s_effhead.yaml # 模型结构配置 │ └── swim.yaml # 数据集配置 ├── models/ │ ├── common.py # 基础模块(保留官方) │ ├── yolo.py # 模型主结构(替换Detect类) │ └── eff_head.py # EfficientHead核心实现 ├── data/ │ └── swim_dataset/ # 按训练/验证划分的图片和标注 ├── train.py # 训练入口 ├── val.py # 验证入口 ├── export_onnx.py # ONNX/TensorRT导出脚本 └── detect.py # 单帧/视频推理脚本其中eff_head.py是核心。它导出了一个EfficientHead类,可以直接替换官方yolo.py里的Detect类。我把接口设计成兼容写法,所以数据加载、训练逻辑和官方YOLOv5几乎完全一致,不需要额外适配。
8.2 快速跑通示例
拿到源码后,想快速在自定义数据上跑一遍,只需要三步:
先准备好标注数据,YOLO格式的txt文件加一张图片一一对应,类别顺序按head_cap、swimmer_body、lane_line、touch_wall固定。然后把configs/swim.yaml里的数据集路径改成本地路径,确认类别数nc: 4。最后启动训练:
python train.py --cfg configs/yolov5s_effhead.yaml --data configs/swim.yaml --weights yolov5s.pt --device 0如果没有COCO预训练权重,也可以直接把weights参数删掉从零开始训练。不过我建议一定要有预训练权重,因为EfficientHead虽然参数不多,但它的解耦分支需要从一个好的特征空间起步,冷启动训练不仅慢,最终精度还会低两三个点。
训练完成后用val.py验证,再通过export_onnx.py导出ONNX模型。跑通整个流程之后,再去看eff_head.py里的通道注意力模块和scale_factor,你想在此基础上的二次开发也就有了清晰抓手。
8.3 如何替换到你自己的模型里
如果你不想完整复现这个项目,只想借鉴EfficientHead的思路,操作上也非常简单。找到你自己YOLOv5工程里的models/yolo.py,把Detect类改成从EfficientHead继承,或者在Detect类初始化时传入一个新的配置参数,让它在内部根据不同的头类型调用不同的组合模块。
一个比较取巧的替换顺序是:先只做“解耦头”这一步,不加入共享降维和注意力模块。因为这个改动最小、最容易对齐数值,基本不会出现NaN之类的问题。确认精度有基础提升之后,再逐步加上注意力模块和尺度感知初始化。这样每一步的效果都可以独立验证,排查问题时也更容易定位。
如果你有多个尺度的检测需求,比如同时检测人体关键点和泳道线,可以把目前EfficientHead里的分类分支改造成多任务输出头。我在项目里就尝试过给分类分支额外加一个回归子分支输出头部关键点坐标,精度和速度都保持得不错。这是EfficientHead结构在架构灵活性上的一个天然优势。
最后再分享一个个人体会:这类体育赛事项目,真正决定模型能不能被现场接受,往往不是排行榜上mAP又涨了几个点,而是误检和漏检是不是少到了让裁判愿意打开你的系统。EfficientHead这种对检测头的精准改造,恰好是在不牺牲速度的前提下把这种“业务信任感”做上去的做法。项目结束之后,我又把同样的head迁移到了田径和举重赛事的视频分析里,效果也都比原版baseline要稳。如果你正在做一个和比赛场景相关的检测任务,建议先拿自己的一段数据跑一遍这个方案,你会很直观地看到注意力模块把模型的注意力拉到了它该去的地方。