檢測優(yōu)化實踐)
搞目標(biāo)檢測的人應(yīng)該有個共識一場足球比賽視頻可能是計算機視覺最容易讓模型當(dāng)場破防的場景之一。鏡頭拉遠的時候場上的球員縮成一小團特寫推進之后同一個球員直接頂滿畫面而足球依然只有幾十個像素。更別提草地、球衣、陰影混在一起的顏色噪聲以及頻繁切換的轉(zhuǎn)播機位。我最近把這件事當(dāng)成一個正經(jīng)的檢測工程來做就是標(biāo)題里這套YOLO11-C3k2-ConverseB方案目標(biāo)是同時穩(wěn)定識別運動員、足球和場地三類目標(biāo)。這篇文章會完整講清楚改進思路、數(shù)據(jù)標(biāo)注方式、訓(xùn)練調(diào)參過程、踩過的坑和最終落地效果適合那些想復(fù)現(xiàn)足球場景檢測、或者正在處理“小目標(biāo)尺度跨度極大”檢測任務(wù)的工程師參考。先說結(jié)論在公開賽事轉(zhuǎn)播素材和自制比賽錄像混合集上這套結(jié)構(gòu)比原版YOLO11s在足球小目標(biāo)上的mAP50-95提升了接近6個點運動員類別在高分辨率輸入下也有可感知的改善同時推理速度沒有明顯劣化。但整個工程里最折磨人的其實不是模型結(jié)構(gòu)而是數(shù)據(jù)。下面按我的實際流程往下拆。1. 足球場景目標(biāo)檢測難點到底在哪1.1 三類目標(biāo)在尺度上的巨大鴻溝看一場足球轉(zhuǎn)播畫面你會發(fā)現(xiàn)模型面對的根本不是“三個普通類別”而是三類尺度天差地別的物體。把國際標(biāo)準(zhǔn)球場尺寸放進去算一筆賬足球場長約105米寬約68米。轉(zhuǎn)播廣角鏡頭通常能覆蓋半場甚至大半場假設(shè)畫面中有約50米寬的區(qū)域映射到1920像素的傳感器寬度上那么每米對應(yīng)約38像素。一個1.8米高的球員在畫面里大約只有68像素高而足球直徑只有22厘米左右換算下來只有8個像素出頭。8個像素意味著下采樣到P3特征層stride 8的時候足球已經(jīng)只剩下大約1個像素的特征響應(yīng)基本等于被洗掉了。這還不算高速運動產(chǎn)生的拖影和模糊。場地則走上另一個極端。如果標(biāo)注整個草皮區(qū)域或完整的場地標(biāo)線它在畫面中通常占據(jù)幾千甚至上萬像素屬于極端大目標(biāo)。絕大多數(shù)目標(biāo)檢測框的回歸頭是為中小物體設(shè)計的對這類“鋪滿全圖”的大目標(biāo)反而容易框得歪歪扭扭邊界抖動嚴(yán)重。我最初用640×640輸入訓(xùn)練時運動員和場地都能跑到不錯的指標(biāo)唯獨足球類的mAP50只有0.35左右基本不可用。這就是尺度鴻溝帶來的直接后果也決定了后面所有技術(shù)選型都要圍繞“如何讓小目標(biāo)活下來”展開。1.2 鏡頭切換與光照波動帶來的分布漂移足球視頻還有一個隱藏屬性鏡頭一直在變。轉(zhuǎn)播會在大遠景、中景、特寫之間反復(fù)橫跳同一個球員在不同鏡頭下的尺度可能相差10倍以上。模型在訓(xùn)練時如果只吃一種尺度的樣本推理時就很容易在另一種鏡頭上翻車。光照問題同樣麻煩。日場、夜場、陰天、燈光不均勻的業(yè)余球場草皮顏色從深綠到偏黃都有。球衣顏色更是五花八門。我一開始只用某幾場比賽的幀做訓(xùn)練結(jié)果換到另一場比賽時球員漏檢率直接漲了十幾個點。這說明足球場景的數(shù)據(jù)增強不是隨便做做而是要刻意制造“跨場地、跨光照、跨鏡頭尺度”的多樣性。這也是為什么后來我堅持把輸入分辨率抬到960甚至1280單純從檢測精度角度看這個選擇帶來的收益比換任何模塊都大。2. 模型選型與改進思路為什么是YOLO11 C3k2 ConverseB2.1 基線YOLO11底子里的優(yōu)勢選YOLO11而不是更早的YOLOv8或YOLOv5主要看中它在骨架和頸部上的兩個變化。一是用C3k2模塊替代了之前的C2f整體參數(shù)更省、計算更集中二是它保留了anchor-free解耦檢測頭和任務(wù)對齊學(xué)習(xí)機制對尺度變化和遮擋的適應(yīng)能力比老一代強不少。在實際對比中YOLO11s在同等輸入下比YOLOv8s的參數(shù)量少約15%推理速度快一點mAP還略高。對于需要在轉(zhuǎn)播流上做實時或準(zhǔn)實時檢測的項目這個底子是劃算的。但直接拿原版模型跑足球場景會暴露兩個痛點一是足球這類小目標(biāo)在下采樣過程中信息損耗太嚴(yán)重二是普通Bottleneck結(jié)構(gòu)對不同尺度目標(biāo)的特征表達“一視同仁”沒有對大目標(biāo)和小目標(biāo)做差異化處理。我的改進就落在這兩個點上分別對應(yīng)C3k2的調(diào)整和ConverseB模塊的引入。2.2 C3k2模塊輕量CSP結(jié)構(gòu)里的關(guān)鍵參數(shù)先補個背景。C3k2是YOLO11里核心的CSP風(fēng)格模塊名字里的k指代內(nèi)部Bottleneck的卷積核尺寸。結(jié)構(gòu)上看它把輸入通道分成兩個分支一個分支直接通過1×1卷積映射另一個分支經(jīng)過1×1卷積后串聯(lián)k個Bottleneck塊最后再拼接起來。k越大模塊感受野越大、非線性表達能力越強但計算量也線性上漲。原版YOLO11默認在淺層用k2的Bottleneck深層用k3。我在復(fù)現(xiàn)時保留了這種分層策略但針對場地這類大目標(biāo)、以及球員這類中等目標(biāo)把深層網(wǎng)絡(luò)的兩個C3k2模塊內(nèi)部的Bottleneck卷積核從3×3擴大到了5×5和7×7的組合。代價是FLOPs增加了一點但換來了對大范圍上下文更好的聚合能力。實驗證明場地標(biāo)線檢測的邊界完整性明顯變好不再頻繁出現(xiàn)“框出去半截”的情況。這里有個容易忽略的細節(jié)C3k2內(nèi)部的Bottleneck通常帶殘差連接而殘差連接要求輸入輸出通道數(shù)一致。所以調(diào)整卷積核尺寸時要同步檢查shortcut分支的通道對齊否則一改就報錯。2.3 ConverseB模塊為三類目標(biāo)定制的特征融合塊ConverseB是這次復(fù)現(xiàn)的項目里比較關(guān)鍵的自定義模塊名字是我自己起的含義是“卷積殘差變體塊”。核心思路很簡單原本C3k2內(nèi)部的Bottleneck是“1×1降維→3×3卷積→1×1升維”的結(jié)構(gòu)對中等目標(biāo)友好但小目標(biāo)經(jīng)過兩次降維升維后空間細節(jié)丟得厲害。我把它改成類倒殘差設(shè)計先做1×1升維再用分組卷積做3×3或者5×5的空間特征提取最后1×1降維到目標(biāo)通道。這個結(jié)構(gòu)最早在移動端網(wǎng)絡(luò)里被驗證過特點是“先漲維度再提特征”能保留更多空間細節(jié)。同時在殘差邊上加了一個可選的通道注意力分支用全局平均池化算出通道權(quán)重對足球這類微弱目標(biāo)進行通道級增強。復(fù)現(xiàn)時我寫過一個最小實現(xiàn)核心邏輯長這樣import torch import torch.nn as nn class ConverseB(nn.Module): def __init__(self, in_channels, out_channels, kernel_size3, stride1, expand_ratio2.0, use_seTrue): super().__init__() hidden_dim int(in_channels * expand_ratio) self.conv1 nn.Conv2d(in_channels, hidden_dim, 1, 1, biasFalse) self.bn1 nn.BatchNorm2d(hidden_dim) self.act1 nn.SiLU(inplaceTrue) self.conv2 nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, kernel_size // 2, groupshidden_dim, biasFalse) self.bn2 nn.BatchNorm2d(hidden_dim) self.act2 nn.SiLU(inplaceTrue) self.conv3 nn.Conv2d(hidden_dim, out_channels, 1, 1, biasFalse) 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(inplaceTrue), 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內(nèi)部部分Bottleneck重點是放在backbone的P3層和neck的上采樣拼接位置。原因很簡單P3層是stride 8分辨率相對最高最適合救小目標(biāo)而上采樣拼接位置決定了下游檢測頭拿到的特征質(zhì)量。模塊替換后我對足球類別做了單類驗證mAP50從0.35漲到了0.42效果確實有但遠不如輸入分辨率從640提到960帶來的0.15漲幅明顯。這也是我想強調(diào)的一點模塊改進是“錦上添花”輸入分辨率和數(shù)據(jù)質(zhì)量才是足球小目標(biāo)檢測的地基。3. 數(shù)據(jù)集與標(biāo)注比模型更難的準(zhǔn)備工作3.1 數(shù)據(jù)來源與視頻切幀方案我使用的訓(xùn)練數(shù)據(jù)分兩部分一部分來自公開可獲取的足球賽事轉(zhuǎn)播錄像和公開數(shù)據(jù)集另一部分是用設(shè)備自己錄制的業(yè)余比賽視頻。公開素材覆蓋頂級聯(lián)賽、杯賽、青年隊比賽分辨率從720p到1080p都有自制素材則集中在中低機位視角、夜場燈光、草地質(zhì)量一般的場地專門用來模擬真實落地場景。切幀不是簡單按固定間隔抽。我先按每秒2幀抽一輪再對運動劇烈的片段追加每秒5幀的密集采樣最后做一遍相似度去重。相似度去重很關(guān)鍵否則連續(xù)幀高度重復(fù)訓(xùn)練集看起來很大有效信息量其實沒多少。我按結(jié)構(gòu)相似度閾值0.85去重最終從約4000段視頻片段中篩出約12000張有效幀。3.2 標(biāo)注規(guī)范球要標(biāo)、人要標(biāo)、場地怎么標(biāo)三類目標(biāo)標(biāo)注規(guī)則各有坑。運動員標(biāo)注相對常規(guī)但規(guī)定必須“緊貼人體外輪廓”。遮擋嚴(yán)重時寧可把框縮到可見部分也不要憑想象把被擋住的位置畫進去。這直接影響后續(xù)NMS合并效果。足球標(biāo)注是整個任務(wù)的核心。標(biāo)準(zhǔn)是只要畫面里能看到完整球體或超過一半的球體輪廓就必須標(biāo)注。哪怕只有6個像素也要畫一個很緊的框。過程中最忌諱“漏標(biāo)”因為漏標(biāo)的足球會變成難負樣本讓模型學(xué)會忽略小目標(biāo)。我后期復(fù)檢時發(fā)現(xiàn)凡是標(biāo)注稀疏的視頻片段模型在該片段上的足球召回率幾乎都會崩掉。場地標(biāo)注我采用的方案是按“場地標(biāo)線”處理包括邊線、底線、中線、中圈、禁區(qū)線。由于檢測框是矩形對線狀目標(biāo)天然不合適實際操作中是把每條標(biāo)線分割成若干段每段用外接矩形標(biāo)注類別統(tǒng)一叫pitch_line。這樣既不增加像素級分割的標(biāo)注成本又能讓模型學(xué)會“這里有場地元素”的空間定位。標(biāo)注工具用通用的開源圖像標(biāo)注工具就行導(dǎo)出成YOLO的txt格式。這里有一個容易出錯的地方Y(jié)OLO格式要求坐標(biāo)全部歸一化到0-1之間且框中心點坐標(biāo)是相對寬高的比例。我見過很多人在標(biāo)注工具里直接把像素坐標(biāo)填進去訓(xùn)練時維度對不上各種報錯。導(dǎo)出后務(wù)必抽檢幾個文件手工核對歸一化坐標(biāo)是否落在合理區(qū)間。3.3 數(shù)據(jù)增強既要Mosaic又要“去Mosaic”YOLO系列默認的Mosaic增強對小目標(biāo)檢測很有幫助它把四張圖拼在一起等效于制造了大量小尺度目標(biāo)。但無腦開Mosaic也有反效果場地標(biāo)線這種大目標(biāo)在拼接時會被裁得支離破碎模型學(xué)到的東西是殘缺的。我的策略是把增強分成兩組。第一組用來提升小目標(biāo)Mosaic、MixUp、隨機透視、HSV擾動全開概率設(shè)為0.5第二組用來保大目標(biāo)對包含場地標(biāo)線的樣本關(guān)閉Mosaic改用隨機平移縮放、水平翻轉(zhuǎn)和輕度旋轉(zhuǎn)概率設(shè)為0.3。同時在訓(xùn)練后期關(guān)閉Mosaic讓模型在接近真實分布的數(shù)據(jù)上精調(diào)。另外針對足球小目標(biāo)稀疏的問題額外加了一個Copy-Paste增強把同一張圖里已標(biāo)注的足球復(fù)制到無球區(qū)域增加了小目標(biāo)出現(xiàn)的頻率。這個操作要注意控制粘貼位置不能跑到人身上或者場地之外。4. 訓(xùn)練配置與關(guān)鍵調(diào)參實錄4.1 遷移學(xué)習(xí)路徑與加載權(quán)重策略我沒有從零開始訓(xùn)練而是加載了COCO預(yù)訓(xùn)練權(quán)重作為起點。不過因為backbone結(jié)構(gòu)里插入了ConverseB模塊特征層和原版權(quán)重并不能完全對齊直接整體加載會報形狀不匹配。我的處理方式是分兩步走先把改進模塊和neck部分的參數(shù)隨機初始化backbone其余層加載原版權(quán)重再凍結(jié)backbone前幾層只訓(xùn)練新增模塊和檢測頭先跑20個epoch讓結(jié)構(gòu)穩(wěn)定下來。之后解凍全部參數(shù)用較低學(xué)習(xí)率微調(diào)。這套流程基本不會出現(xiàn)初期loss爆炸的問題。4.2 關(guān)鍵超參數(shù)與訓(xùn)練策略訓(xùn)練配置我用的是自己調(diào)整過的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說幾個調(diào)參中驗證過的點。輸入分辨率選了960而不是更大是因為訓(xùn)練顯存和推理速度的折中。1280分辨率對足球類確實更好但batch size會掉一半訓(xùn)練時間翻倍最終在轉(zhuǎn)播流上做實時推理也比較吃力。學(xué)習(xí)率用的SGD初始0.005配合warmup。我試過AdamW收斂確實快但最后mAP普遍比SGD低一點可能和檢測任務(wù)的稀疏標(biāo)注特性有關(guān)。類別權(quán)重沒做特殊調(diào)整因為60%時間花在調(diào)分辨率、數(shù)據(jù)、訓(xùn)練節(jié)奏上遠比調(diào)cls loss權(quán)重有效。4.3 訓(xùn)練過程觀察三大階段特征整個訓(xùn)練過程我盯的是三類指標(biāo)總loss、驗證集mAP50、足球類別單獨AP50。前20個epoch是“結(jié)構(gòu)穩(wěn)定期”loss從十幾迅速降到7左右但驗證集mAP基本沒動尤其是足球類完全不漲。這時候千萬別慌我在這個階段加練過幾次最后證明是浪費時間。新增模塊在冷啟動階段本來就需要先學(xué)會提取基礎(chǔ)特征。20到60個epoch是“主漲期”運動員和場地mAP快速上升足球類開始緩慢爬升。到60輪時三類mAP50基本能達到最終水平的85%左右。60到120個epoch是“精修期”漲幅變得很慢但足球類mAP50-95會在這個階段穩(wěn)步改善。這其實是回歸精度在變好邊界框更貼合了。我的經(jīng)驗是小目標(biāo)檢測任務(wù)一定不要早停太早足球這類目標(biāo)需要較長時間才能把框回歸做準(zhǔn)。我最終在第120輪左右保存了best模型繼續(xù)訓(xùn)練到150輪時驗證指標(biāo)基本持平。5. 實驗結(jié)果與問題診斷5.1 對比實驗基線與改進模型的差距我在相同數(shù)據(jù)集上對比了三個配置都采用統(tǒng)一增強策略模型配置輸入尺寸mAP50mAP50-95足球AP50參數(shù)量推理耗時(ms)YOLO11s 原版6400.6810.4230.3529.4M2.8YOLO11s 原版9600.7130.4510.4839.4M6.1YOLO11-C3k2-ConverseB9600.7260.4680.5419.8M6.8表格里的推理耗時是在同一張顯卡上、batch size為1、FP16精度下測的。結(jié)論很直接輸入分辨率提升帶來的增益最大足球AP從0.35拉到0.48引入C3k2-ConverseB后足球AP又漲到0.54運動員和場地的mAP也有小幅提升參數(shù)量只多了0.4M推理只慢了0.7毫秒這個代價完全可以接受。5.2 足球小目標(biāo)漏檢與現(xiàn)場修正精度最高時足球類mAP50是0.54mAP50-95是0.31。說實話這個數(shù)字距離“完美”還遠漏檢主要集中在三種情況高速飛行的球、被球員身體遮擋超過一半的球、以及遠景畫面里不足6像素的球。對第一個問題我試過在數(shù)據(jù)增強里加入運動模糊模擬有一定效果對第二個問題本質(zhì)上需要時序信息單幀檢測物理上無法解決對第三個問題只能靠提高輸入分辨率或切圖推理。切圖推理是立竿見影的手段把1080p畫面切成2×2的四塊每塊縮放到960分辨率分別檢測再合并結(jié)果。足球召回率能再漲幾個點但推理耗時翻倍不止。我的結(jié)論是如果業(yè)務(wù)上對“球”的召回有硬指標(biāo)就得上切圖或者做一個專門的球追蹤器做時序補償如果只是做粗粒度事件分析單幀的0.54基本夠用。5.3 運動員密集人群遮擋問題處理運動員聚集在禁區(qū)內(nèi)爭頂頭球時框會疊成一大團。NMS閾值默認0.45在重疊率極高的場景下會把大量有效框直接合并掉漏檢率明顯上升。我做了兩個調(diào)整一是把NMS的IoU閾值提高到0.5保留更多重疊框二是在后處理時限制單張圖最大檢測數(shù)量從默認300調(diào)到200因為這類場景大量高置信度的重復(fù)框會拖慢后處理。調(diào)整之后擁擠場景的運動員召回率從0.71提升到0.78代價是少量誤檢屬于可接受的范圍。6. 工程落地與避坑建議6.1 推理速度優(yōu)化與模型導(dǎo)出訓(xùn)練完成后需要把模型導(dǎo)出到ONNX再做TensorRT加速。我在導(dǎo)出時固定了輸入尺寸960×960開FP16精度。實測TensorRT FP16比PyTorch FP16推理快1.8倍左右mAP損失在0.5個點以內(nèi)可以忽略。有一個細節(jié)轉(zhuǎn)播流畫面比例是16:9而我的輸入是1:1。直接resize會把畫面拉伸變形檢測框坐標(biāo)還要做逆變換。更好的做法是在預(yù)處理時做letterbox填充保持原比例預(yù)測后再按填充比例映射回原圖。沒有做這一步的話場地標(biāo)線的檢測框位置會系統(tǒng)性偏移。6.2 易錯點速查表現(xiàn)象原因解決方案足球類AP始終不高輸入分辨率不足、足球標(biāo)注漏標(biāo)多提升imgsz到960以上復(fù)檢標(biāo)注質(zhì)量場地標(biāo)線框邊緣抖動大目標(biāo)回歸不穩(wěn)定、標(biāo)注框不一致分線段標(biāo)注統(tǒng)一標(biāo)注粒度換一場比賽后漏檢率暴增過擬合單一場地顏色和光照增強HSV擾動加入多場地樣本訓(xùn)練前期loss不降新增模塊隨機初始化權(quán)重未對齊分階段凍結(jié)/解凍訓(xùn)練推理時邊界框偏移預(yù)處理resize未還原坐標(biāo)用letterbox逆映射多人重疊漏檢NMS閾值過低IoU閾值調(diào)至0.5限制max_det6.3 后續(xù)擴展方向這套檢測模型實際是上層應(yīng)用的底座。我接下來的計劃是在檢測結(jié)果上疊加一個輕量級多目標(biāo)跟蹤器對球員和足球分別做軌跡追蹤然后基于軌跡計算跑動距離、控球時間和傳接球事件。這些數(shù)據(jù)在半自動比賽分析、青訓(xùn)輔助評估這些方向上都有實用價值。場地標(biāo)線檢測也可以進一步換成語義分割分支但工程成本會高不少暫時不在計劃內(nèi)。最后說一句個人體會我踩過最大的坑是把太多精力花在改模塊上直到把輸入分辨率提上去、把標(biāo)注質(zhì)量揪起來才意識到足球小目標(biāo)檢測的瓶頸往往在數(shù)據(jù)和預(yù)處理環(huán)節(jié)。YOLO11-C3k2-ConverseB這套結(jié)構(gòu)能在精度和速度之間站住腳但真正讓它發(fā)揮作用的是一整套圍繞數(shù)據(jù)、訓(xùn)練節(jié)奏和推理部署的組合拳。如果你的項目也在啃類似“小目標(biāo)大目標(biāo)同框”的硬骨頭建議先檢查數(shù)據(jù)和輸入尺寸再回頭審視模型結(jié)構(gòu)這個順序能幫你少走很多彎路。