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