志檢測(cè)與識(shí)別實(shí)戰(zhàn):從數(shù)據(jù)清洗到部署優(yōu)化)
干這一行的人應(yīng)該都有同感交通標(biāo)志檢測(cè)是目標(biāo)檢測(cè)所有場(chǎng)景里“看起來簡(jiǎn)單、做起來折磨”的典型。標(biāo)志尺寸小、類別多且相似度高、戶外光照隨時(shí)在變?cè)偌由嫌暄┱趽鹾瓦\(yùn)動(dòng)模糊一套模型要在車端那點(diǎn)算力上跑得又準(zhǔn)又快真不是隨便跑幾個(gè)epoch就能交差的。YOLOv5到現(xiàn)在依然是性價(jià)比最高的選擇之一尤其是配合一套干凈的數(shù)據(jù)集和合理的源碼改動(dòng)完全可以在可控成本內(nèi)做出可靠的識(shí)別效果。這篇東西我會(huì)把自己實(shí)際用YOLOv5做交通標(biāo)志檢測(cè)與識(shí)別的完整套路攤開講講從數(shù)據(jù)集的選型清洗、標(biāo)注格式轉(zhuǎn)換、模型配置與訓(xùn)練調(diào)參到推理階段的精度問題和邊緣端部署優(yōu)化每一步都有操作細(xì)節(jié)和踩坑記錄適合正在入門目標(biāo)檢測(cè)的學(xué)生也適合已經(jīng)把YOLOv5跑通但準(zhǔn)備往實(shí)際項(xiàng)目上推的開發(fā)者參考。1. 交通標(biāo)志檢測(cè)的難點(diǎn)為什么偏偏是YOLOv5能頂住交通標(biāo)志檢測(cè)和通用物體檢測(cè)有本質(zhì)區(qū)別。通用檢測(cè)里“人”“車”“貓”“狗”這類目標(biāo)尺寸相對(duì)穩(wěn)定特征冗余度高交通標(biāo)志則是典型的極小目標(biāo)在一個(gè)1920×1080的畫面里一個(gè)限速標(biāo)志可能只有30×30像素甚至更小占整張圖不到0.1%的面積。而且標(biāo)志類別之間差異非常細(xì)限速40和限速50的區(qū)別就是幾個(gè)像素的數(shù)字圓形紅框和圓形藍(lán)框的分界線也非常明確。這些問題疊加在一起對(duì)小目標(biāo)特征提取能力、分類邊界敏感度和實(shí)時(shí)性要求都極其苛刻。傳統(tǒng)方案和早期深度學(xué)習(xí)方案各有各的死穴。基于顏色分割加形狀檢測(cè)的老辦法遇到逆光、陰影、褪色標(biāo)志基本就廢了兩步檢測(cè)器舊版Faster R-CNN那一路精度倒是能看但一張圖跑幾十毫秒到上百毫秒視頻流處理根本扛不住。YOLOv5能在中間找到一個(gè)很合適的平衡點(diǎn)它把檢測(cè)當(dāng)成回歸任務(wù)一步到位沒有獨(dú)立的候選框生成階段速度上的優(yōu)勢(shì)非常明顯——在GTX 1080Ti上跑640分辨率輸入YOLOv5s的推理速度能到8毫秒左右完全滿足實(shí)時(shí)需求而它通過多層特征融合PANet結(jié)構(gòu)保留小目標(biāo)的淺層特征響應(yīng)再配合可調(diào)anchor機(jī)制讓交通標(biāo)志這類小目標(biāo)有了被“看見”的通道這一點(diǎn)在項(xiàng)目實(shí)測(cè)中極其重要。具體到Y(jié)OLOv5的源碼架構(gòu)上它對(duì)交通標(biāo)志場(chǎng)景有三個(gè)天然友好的設(shè)計(jì)Mosaic數(shù)據(jù)增強(qiáng)把四張圖拼成一張訓(xùn)練相當(dāng)于把畫面里的小目標(biāo)數(shù)量成倍放大對(duì)標(biāo)志這種天然稀疏的目標(biāo)非常有效。自適應(yīng)anchor計(jì)算訓(xùn)練前會(huì)自動(dòng)統(tǒng)計(jì)數(shù)據(jù)集中真實(shí)框的寬高分布并重新聚類計(jì)算anchor不需要背著默認(rèn)參數(shù)硬上。多尺度訓(xùn)練與推理訓(xùn)練時(shí)隨機(jī)切換不同輸入尺寸推理時(shí)可按需選擇640、960甚至1280小目標(biāo)場(chǎng)景可以靠“上分辨率”換取召回。當(dāng)然這幾個(gè)優(yōu)勢(shì)能不能全部?jī)冬F(xiàn)關(guān)鍵還看數(shù)據(jù)集和訓(xùn)練策略。接下來我詳細(xì)拆數(shù)據(jù)這塊因?yàn)檫@是整個(gè)項(xiàng)目里最臟最累但性價(jià)比最高的一環(huán)。2. 數(shù)據(jù)集的選型、清洗與標(biāo)注處理實(shí)戰(zhàn)交通標(biāo)志的實(shí)際樣本來源就三條路公開數(shù)據(jù)、自采數(shù)據(jù)、合成數(shù)據(jù)。我建議的路線是“公開為主、自采為輔、合成為補(bǔ)充”前提是你先把公開數(shù)據(jù)集里的坑摸清楚。2.1 公開數(shù)據(jù)集怎么選各有什么坑市面上能直接下載到的交通標(biāo)志數(shù)據(jù)集不少但質(zhì)量參差不齊。我根據(jù)自己做項(xiàng)目的經(jīng)驗(yàn)做了個(gè)橫向?qū)Ρ确奖隳闩袛嘣撚媚膫€(gè)數(shù)據(jù)集特征數(shù)據(jù)A國內(nèi)路況數(shù)據(jù)B歐洲街景數(shù)據(jù)C城市路口圖片數(shù)量上萬張涵蓋高速與城市數(shù)千張場(chǎng)景相對(duì)單一數(shù)千張偏郊區(qū)道路標(biāo)注類別數(shù)十類含限速/禁令/警告等數(shù)十類類別體系完善類別較少但清晰度高小目標(biāo)占比中等大量遠(yuǎn)距離標(biāo)志高多數(shù)標(biāo)志像素極小偏低采集距離較近標(biāo)注精度部分框偏移嚴(yán)重規(guī)整但存在重復(fù)框較規(guī)整適合用途國內(nèi)落地項(xiàng)目首選算法驗(yàn)證與小目標(biāo)專項(xiàng)邊緣端快速白盒驗(yàn)證數(shù)據(jù)A的問題是標(biāo)注質(zhì)量不穩(wěn)定有些大圖的標(biāo)志框框得松松垮垮尤其是模糊的遠(yuǎn)距離標(biāo)志數(shù)據(jù)B的標(biāo)注相對(duì)規(guī)整但歐洲路況和國內(nèi)的路牌風(fēng)格差異大直接拿來當(dāng)主訓(xùn)練集會(huì)水土不服。數(shù)據(jù)C適合做快速驗(yàn)證但類別少不是生產(chǎn)級(jí)選擇。我實(shí)測(cè)的結(jié)論是主力用數(shù)據(jù)A把數(shù)據(jù)B里的小目標(biāo)圖片抽一部分當(dāng)補(bǔ)充數(shù)據(jù)C可以用來快速搭建demo驗(yàn)證改動(dòng)是否有效。三者切換時(shí)要特別注意類別的對(duì)齊映射不同數(shù)據(jù)集里同類標(biāo)志的類別ID可能不一樣稍不留神就是錯(cuò)亂。2.2 標(biāo)注格式統(tǒng)一與清洗代碼直接拿走下載下來的公開數(shù)據(jù)集格式五花八門有的是VOC XML有的是COCO JSON有的干脆是自己的一套txt。YOLOv5需要的是每張圖對(duì)應(yīng)一個(gè)同名txt每行內(nèi)容為“類別 x_center y_center width height”中心點(diǎn)坐標(biāo)和寬高都是相對(duì)圖像寬高的歸一化浮點(diǎn)數(shù)。這里每一步都要小心歸一化坐標(biāo)算錯(cuò)或者圖片讀取尺寸不一致模型訓(xùn)練出來就直接廢了。我通常用一個(gè)腳本來完成從VOC XML或標(biāo)注表到Y(jié)OLO格式的批處理。核心邏輯是遍歷所有XML解析object節(jié)點(diǎn)里的bndbox坐標(biāo)再除以圖片寬高得到歸一化值。關(guān)鍵點(diǎn)是解析前先確認(rèn)圖片實(shí)際尺寸很多數(shù)據(jù)集的XML里沒有width/height字段而直接讀取圖片又可能因?yàn)镋XIF旋轉(zhuǎn)導(dǎo)致寬高顛倒。穩(wěn)妥做法是優(yōu)先從圖片文件本身讀取寬高而不是依賴XML自帶的size字段。清洗環(huán)節(jié)往往被新手忽略但這步對(duì)模型上限的提升比調(diào)參還大。我會(huì)按這三條規(guī)則過一遍數(shù)據(jù)去掉目標(biāo)框面積小于原圖0.001%且又模糊不可辨別的樣本這類樣本對(duì)訓(xùn)練幾乎無益還會(huì)把模型帶偏。去掉標(biāo)注框與標(biāo)志實(shí)際輪廓偏差嚴(yán)重的樣本例如框內(nèi)包含了大面積背景或框只覆蓋了一部分標(biāo)志。去掉類別標(biāo)錯(cuò)的樣本這個(gè)基本只能靠人工抽檢優(yōu)先檢查數(shù)量最多的那幾個(gè)類別比如限速和禁止標(biāo)志往往容易混。清洗一遍之后樣本數(shù)可能從一萬張降到八千張但訓(xùn)練出的模型mAP通常會(huì)漲2~4個(gè)點(diǎn)效果立竿見影。2.3 數(shù)據(jù)增強(qiáng)策略哪些適合交通標(biāo)志YOLOv5自帶增強(qiáng)開關(guān)在hyp.scratch-low.yaml里默認(rèn)已經(jīng)開了Mosaic、隨機(jī)仿射、HSV擾動(dòng)等。針對(duì)交通標(biāo)志場(chǎng)景我會(huì)額外做兩個(gè)調(diào)整加大旋轉(zhuǎn)和透視范圍車輛行駛中看到的標(biāo)志往往有側(cè)傾或透視變形把旋轉(zhuǎn)范圍從默認(rèn)的10度提高到20度透視參數(shù)從0.0調(diào)整到0.2模擬彎道和側(cè)方觀察。加高斯模糊和噪聲對(duì)應(yīng)雨天、塵土、傳感器噪聲場(chǎng)景。在albumentations之類的外部管線里對(duì)輸入圖像以一定概率疊加輕度模糊和椒鹽噪聲注意別加太重否則會(huì)破壞標(biāo)志本身的紋理。限制過度的顏色擾動(dòng)標(biāo)志的顏色是強(qiáng)語義特征HSV saturation的默認(rèn)范圍可以保留但hue的偏移量最好壓低避免藍(lán)色標(biāo)志被偏成紫色導(dǎo)致模型學(xué)偏。這里有個(gè)容易被忽略的原則增強(qiáng)是讓你在不增加標(biāo)注成本的情況下復(fù)現(xiàn)真實(shí)場(chǎng)景分布而不是把圖片改得不像真實(shí)世界。交通標(biāo)志檢測(cè)的高頻失敗場(chǎng)景是逆光和遠(yuǎn)距離所以相比顏色劇烈變化我更推薦在亮度對(duì)比度上做文章并對(duì)整圖的隨機(jī)裁剪保持克制。3. 基于YOLOv5的配置修改、源碼定制與訓(xùn)練調(diào)參數(shù)據(jù)就緒之后進(jìn)入模型側(cè)的工作。這個(gè)階段的核心不是跑通訓(xùn)練那么淺而是讓YOLOv5的配置真正貼合交通標(biāo)志的小目標(biāo)分布和類別特點(diǎn)。3.1 模型版本選型與配置文件改動(dòng)YOLOv5有n/s/m/l/x五個(gè)規(guī)格從輕到重分別是參數(shù)量和精度的遞進(jìn)。交通標(biāo)志檢測(cè)通常部署在車載設(shè)備或邊緣盒子我推薦把YOLOv5s作為起點(diǎn)參數(shù)量大約7.2M處理640×640檢測(cè)速度極快精度也夠用如果你跑下來召回率不達(dá)標(biāo)再考慮升級(jí)到m而不是一上來就上l或x。邊緣端推理最怕模型復(fù)雜化之后反而拖垮幀率。下載源碼之后需要改三個(gè)核心文件數(shù)據(jù)配置yaml、模型結(jié)構(gòu)yaml、訓(xùn)練參數(shù)。數(shù)據(jù)配置yaml比如新建traffic_sign.yaml內(nèi)容大概長(zhǎng)這樣train: ./dataset/images/train val: ./dataset/images/val nc: 32 names: [speed_limit_30, speed_limit_40, speed_limit_50, ...]這里nc要嚴(yán)格改成你的類別數(shù)names列表順序必須和標(biāo)注txt里的類別ID完全對(duì)應(yīng)。最常見的報(bào)錯(cuò)就出現(xiàn)在這里標(biāo)注里類別ID是1~N但names列表從0開始數(shù)導(dǎo)致訓(xùn)練時(shí)標(biāo)簽越界。模型結(jié)構(gòu)yaml的改動(dòng)則更有講究。我把YOLOv5s.yaml里的nc改成自己的類別數(shù)同時(shí)把檢測(cè)器的細(xì)粒度特征層往里加了一層。默認(rèn)配置是檢測(cè)P3、P4、P5三個(gè)尺度的特征層P3對(duì)應(yīng)小目標(biāo)但交通標(biāo)志很多比P3的錨定尺度還要小所以我在源碼里把P2層也接入檢測(cè)頭讓8倍下采樣的大特征圖參與預(yù)測(cè)。這個(gè)改動(dòng)在源碼中不復(fù)雜代價(jià)是訓(xùn)練顯存增加20%左右但對(duì)小目標(biāo)的召回率提升明顯。另外anchor不是必須手動(dòng)的。YOLOv5訓(xùn)練啟動(dòng)時(shí)會(huì)在CheckAnchors階段自動(dòng)計(jì)算數(shù)據(jù)集的anchor但如果你在yaml里預(yù)先指定了一組與數(shù)據(jù)分布更接近的anchor訓(xùn)練收斂會(huì)更快。具體做法是先用k-means對(duì)訓(xùn)練集所有真實(shí)框做聚類得到9個(gè)寬高比組合然后填到模型yaml的anchors字段里。我建議初始anchor可以設(shè)置得更小因?yàn)榻煌?biāo)志的目標(biāo)比例整體偏小。3.2 訓(xùn)練命令與關(guān)鍵超參數(shù)的實(shí)際取舍環(huán)境配置部分直接過Python 3.8、PyTorch 1.10安裝requirements.txt里的依賴即可。訓(xùn)練命令通常長(zhǎng)這樣python train.py --data traffic_sign.yaml --cfg models/yolov5s_traffic.yaml --weights yolov5s.pt --batch-size 16 --epochs 300 --imgsz 640 --device 0 --multi-scale幾個(gè)超參數(shù)單獨(dú)說一下--multi-scale建議打開訓(xùn)練時(shí)輸入尺寸會(huì)在0.5~1.5倍之間隨機(jī)變化對(duì)增強(qiáng)模型對(duì)不同距離標(biāo)志的適應(yīng)力很有幫助代價(jià)是每個(gè)batch的耗時(shí)上升。--batch-size取決于顯存但要注意多尺度模式下顯存波動(dòng)別把顯卡塞滿否則訓(xùn)練途中容易出現(xiàn)OOM中止。--epochs不要迷信300這個(gè)數(shù)我建議選一個(gè)較大值同時(shí)配合早停。YOLOv5自帶早停當(dāng)驗(yàn)證集mAP連續(xù)多輪默認(rèn)100輪不提升時(shí)自動(dòng)停止避免無效訓(xùn)練時(shí)間。訓(xùn)練過程中重點(diǎn)關(guān)注兩個(gè)實(shí)時(shí)指標(biāo)驗(yàn)證集的mAP0.5和Recall。交通標(biāo)志這種小目標(biāo)場(chǎng)景precision可能很高但recall拉胯那說明模型“保守過頭”寧可漏檢也不誤檢這對(duì)實(shí)際應(yīng)用來說往往不夠。我會(huì)在訓(xùn)練日志里同時(shí)盯這兩個(gè)值如果recall低于0.8基本判定需要調(diào)整數(shù)據(jù)分布或增強(qiáng)策略。3.3 源碼級(jí)定制針對(duì)交通標(biāo)志的NMS與后處理改造這部分算是我個(gè)人經(jīng)驗(yàn)里比較值錢的一段。默認(rèn)NMS在候選框重疊時(shí)通過IOU閾值去重對(duì)普通目標(biāo)沒問題但交通標(biāo)志存在同類目標(biāo)相鄰且重疊的情況比如兩個(gè)限速標(biāo)志并排出現(xiàn)在一個(gè)區(qū)域內(nèi)。如果IOU閾值設(shè)得偏高兩個(gè)框可能被當(dāng)成一個(gè)框合并掉直接丟失一個(gè)目標(biāo)。我常用的處理是把NMS替換為DIoU-NMS或Soft-NMS。YOLOv5源碼的general.py里有現(xiàn)成的NMS函數(shù)我通過增加一個(gè)簡(jiǎn)單的掩碼變化實(shí)現(xiàn)Soft版本不再直接把重疊超過閾值的框置信度清零而是按重疊程度衰減其置信度。這樣兩個(gè)并排標(biāo)志都能保留只是置信度稍微下降。實(shí)測(cè)在標(biāo)志密集的路口場(chǎng)景召回率能提升2~3個(gè)點(diǎn)。另外一個(gè)定制是置信度閾值的動(dòng)態(tài)機(jī)制。在低閾值下模型會(huì)吐出一堆低置信度的框包含大量誤檢但直接調(diào)高閾值又會(huì)把難例的正確檢測(cè)一起濾掉。我最后采用的做法是在推理代碼里分別設(shè)置conf_thres用于常規(guī)目標(biāo)對(duì)面積小于一定像素且類別屬于限速、禁令的框使用更低的conf_thres因?yàn)樾∧繕?biāo)本身的置信度天然偏低這一步是純?cè)创a層的微調(diào)效果卻很直接。4. 推理部署、精度評(píng)估與邊緣端優(yōu)化的完整鏈路訓(xùn)練的終點(diǎn)是落地。這個(gè)階段需要同時(shí)處理模型的格式轉(zhuǎn)換、速度優(yōu)化和預(yù)估精度與實(shí)測(cè)精度的落差每一環(huán)都有不少門道。4.1 模型導(dǎo)出與加速PyTorch模型不能直接拿到生產(chǎn)環(huán)境跑我通常走“PyTorch → ONNX → TensorRT”的路線在支持GPU的邊緣設(shè)備上可以獲得非常好的加速收益。python export.py --weights runs/train/exp/weights/best.pt --include onnx --dynamic --simplify python export.py --weights best.onnx --include engine --device 0 --half第一段導(dǎo)出ONNX時(shí)我建議打開--dynamic動(dòng)態(tài)輸入尺寸方便后續(xù)對(duì)不同分辨率做推理--simplify用onnx-simplifier清理掉冗余算子。第二段從ONNX轉(zhuǎn)TensorRT的engine文件時(shí)打開--half半精度浮點(diǎn)在小數(shù)點(diǎn)精度影響可忽略的同時(shí)推理速度往往能翻一倍。實(shí)測(cè)在邊緣設(shè)備上YOLOv5s的TensorRT FP16推理速度能到30~40FPS中等算力足夠支撐實(shí)時(shí)檢測(cè)。如果設(shè)備不支持TensorRT那就直接用ONNX Runtime跑。這種情況下要注意輸入尺寸固定避免動(dòng)態(tài)維度帶來的額外耗時(shí)。有一個(gè)小細(xì)節(jié)TensorRT的batch size最小化要設(shè)置成1或4否則顯存分配會(huì)偏大影響其他任務(wù)。4.2 精度評(píng)估和常見失敗樣本排摸模型練完別急著上線先拿一組留出測(cè)試集跑一輪完整的mAP評(píng)估還要重點(diǎn)挑出失敗樣本逐個(gè)分析。我發(fā)現(xiàn)交通標(biāo)志檢測(cè)的失敗模式高度集中失敗場(chǎng)景典型原因常用處理手段遠(yuǎn)距離小標(biāo)志漏檢特征圖分辨率不足增大輸入分辨率接入P2檢測(cè)層逆光/陰影下漏檢樣本分布缺失增加亮度擾動(dòng)采集逆光樣本廣告牌/圓形標(biāo)識(shí)誤檢背景語義相似增加負(fù)樣本調(diào)整置信度閾值多個(gè)標(biāo)志并列漏檢NMS合并替換Soft-NMS / DIoU-NMS模糊/雨天漏檢紋理信息缺失輕微模糊增強(qiáng)降低conf_thres針對(duì)這些失敗樣本最笨但最有效的方法就是回到數(shù)據(jù)層把失敗樣本歸到一個(gè)“補(bǔ)充集中”人工標(biāo)注后并入訓(xùn)練集再迭代一輪通常效果立竿見影。這比反復(fù)調(diào)超參數(shù)要靠譜得多。4.3 工程化部署中的幀處理與狀態(tài)平滑真正放到視頻流場(chǎng)景里單幀檢測(cè)的不穩(wěn)定性會(huì)被放大。某一幀漏檢了下一幀又檢測(cè)到結(jié)果看起來就是“閃爍”的識(shí)別框非常影響使用體驗(yàn)。我在工程里加了兩個(gè)簡(jiǎn)單機(jī)制來平滑結(jié)果幀間目標(biāo)跟蹤利用目標(biāo)框的中心點(diǎn)位移做輕量級(jí)關(guān)聯(lián)連續(xù)多幀出現(xiàn)在同一位置的標(biāo)志即使某一兩幀漏檢也能保持框的延續(xù)顯示。置信度滑動(dòng)平均對(duì)同一個(gè)跟蹤目標(biāo)的置信度取多幀均值只有均值低于閾值時(shí)才撤銷框避免單幀抖動(dòng)導(dǎo)致的誤判。這些邏輯放在業(yè)務(wù)代碼層面即可不用改模型結(jié)構(gòu)。它們對(duì)真實(shí)場(chǎng)景體驗(yàn)的提升比模型再漲兩個(gè)點(diǎn)mAP還明顯。硬件側(cè)邊緣設(shè)備的功耗和散熱也是實(shí)際要考的。跑TensorRT推理時(shí)GPU占用可能不低長(zhǎng)時(shí)間運(yùn)行要注意設(shè)備散熱。我在車載驗(yàn)證時(shí)遇到過設(shè)備因高溫降頻導(dǎo)致幀率驟降的問題后來在代碼里加了簡(jiǎn)單的推理頻率控制——檢測(cè)幀率和視頻幀率解耦檢測(cè)可以按每2幀或3幀跑一次中間幀用上一幀結(jié)果插值既保平滑又減負(fù)載。我在這條路上最大的體會(huì)是YOLOv5做交通標(biāo)志檢測(cè)70%的成敗在數(shù)據(jù)20%在配置和訓(xùn)練細(xì)節(jié)只有10%在模型結(jié)構(gòu)本身。與其不斷換新模型追指標(biāo)不如先把手頭的數(shù)據(jù)搞干凈、把推理鏈路做扎實(shí)。很多新手盯著mAP看而真正上線的時(shí)候決定項(xiàng)目生死的往往是NMS那一個(gè)框的取舍和邊緣設(shè)備上那幾瓦功耗的控制。最后再分享一個(gè)實(shí)用小技巧訓(xùn)練完成后把results.png里那張PR曲線圖放大看下限速和禁止標(biāo)志那一類如果曲線尾段下降特別陡說明置信度閾值必須單獨(dú)調(diào)別指望一個(gè)統(tǒng)一閾值吃遍所有類別。希望這篇內(nèi)容能讓你在這個(gè)項(xiàng)目上少走幾圈彎路。