實(shí)戰(zhàn):從數(shù)據(jù)標(biāo)注到部署的完整指南)
簡(jiǎn)介本資源為基于YOLOv5-6.0訓(xùn)練完成的吸煙行為檢測(cè)模型包面向計(jì)算機(jī)視覺(jué)學(xué)習(xí)者、行為識(shí)別方向的研究人員及需要落地吸煙檢測(cè)功能的開(kāi)發(fā)者。包內(nèi)提供YOLOv5m與yolov5s兩個(gè)已訓(xùn)練權(quán)重目標(biāo)類(lèi)別為smoke在數(shù)千張吸煙數(shù)據(jù)上訓(xùn)練準(zhǔn)確率超過(guò)90%并附有PR曲線(xiàn)、loss曲線(xiàn)等訓(xùn)練過(guò)程記錄便于評(píng)估模型表現(xiàn)與復(fù)現(xiàn)實(shí)驗(yàn)。資源共373個(gè)文件以jpg圖像、txt標(biāo)注、yaml配置、py腳本為主另含pt權(quán)重、png曲線(xiàn)圖、md說(shuō)明及Dockerfile等部署文件壓縮包約128.03MB目錄結(jié)構(gòu)完整覆蓋數(shù)據(jù)、訓(xùn)練與推理環(huán)節(jié)。目前已有2392人學(xué)習(xí)下載適合希望快速獲得可用吸煙檢測(cè)模型、對(duì)照曲線(xiàn)分析訓(xùn)練效果或在此基礎(chǔ)上繼續(xù)微調(diào)的中高級(jí)視覺(jué)開(kāi)發(fā)者。1. 吸煙檢測(cè)為什么值得用 YOLOv5 單獨(dú)做一遍廠(chǎng)區(qū)、加油站、化工廠(chǎng)這類(lèi)場(chǎng)景里吸煙檢測(cè)不是識(shí)別一個(gè)人那么簡(jiǎn)單它要的是在監(jiān)控畫(huà)面里實(shí)時(shí)框出煙頭、叼煙動(dòng)作和煙霧并且把誤報(bào)壓到能接受的水平。YOLOv5 之所以在這個(gè)任務(wù)上被反復(fù)拿出來(lái)用是因?yàn)樗鼏坞A段、推理快、對(duì)小目標(biāo)有一定容忍度而且 yolov5-6.0 這個(gè)版本結(jié)構(gòu)穩(wěn)定、生態(tài)成熟改數(shù)據(jù)集、調(diào)超參、導(dǎo)出部署都有現(xiàn)成路徑。yolov5-6.0-smoking_detect.zip這類(lèi)工程包本質(zhì)就是把吸煙當(dāng)成一個(gè)獨(dú)立檢測(cè)類(lèi)別用 YOLOv5 走一遍從數(shù)據(jù)標(biāo)注到訓(xùn)練再到推理的完整鏈路。它適合兩類(lèi)人一類(lèi)是手里已經(jīng)有攝像頭和標(biāo)注數(shù)據(jù)、想快速驗(yàn)證可行性的工程人員另一類(lèi)是想拿一個(gè)真實(shí)小目標(biāo)檢測(cè)任務(wù)練手 YOLOv5 全流程的開(kāi)發(fā)者。下面我按自己實(shí)際跑這類(lèi)項(xiàng)目的順序把選型、數(shù)據(jù)、訓(xùn)練、調(diào)參和踩坑講清楚。2. 把吸煙檢測(cè)拆成 YOLOv5 能吃的任務(wù)類(lèi)別設(shè)計(jì)與數(shù)據(jù)準(zhǔn)備2.1 為什么吸煙檢測(cè)不能直接套用 COCO 的 person 類(lèi)很多人第一反應(yīng)是檢測(cè)到人再判斷有沒(méi)有抽煙這條路在工程上很容易翻車(chē)。COCO 里沒(méi)有 cigarette、smoke 這些類(lèi)person 框只給你一個(gè)人的整體位置煙頭在畫(huà)面里可能只有十幾個(gè)像素靠人體框去裁再分類(lèi)等于把檢測(cè)問(wèn)題硬轉(zhuǎn)成分類(lèi)問(wèn)題中間任何一步框偏了后面全錯(cuò)。更穩(wěn)的做法是直接把吸煙相關(guān)目標(biāo)定義成獨(dú)立類(lèi)別讓 YOLOv5 在整圖上做回歸。常見(jiàn)做法是定義 2 到 3 個(gè)類(lèi)smoking叼煙/手持煙的動(dòng)作區(qū)域、cigarette煙頭本身、smoke明顯煙霧。如果數(shù)據(jù)量不大我一般先合并成一個(gè)smoking類(lèi)跑通再考慮拆分。原因是類(lèi)別越細(xì)小目標(biāo)樣本越稀疏YOLOv5 在 6.0 版本下對(duì)小目標(biāo)的召回本來(lái)就吃數(shù)據(jù)拆太細(xì)會(huì)讓 mAP 掉得很難看。選型上還要注意吸煙檢測(cè)的負(fù)樣本極其重要。工區(qū)里大量手靠近臉白色反光蒸汽都會(huì)被誤判成煙所以數(shù)據(jù)里必須刻意塞入這些困難負(fù)樣本否則模型上線(xiàn)后誤報(bào)會(huì)讓你懷疑人生。2.2 標(biāo)注規(guī)范框到哪、留多少邊距標(biāo)注質(zhì)量直接決定上限。我的習(xí)慣是smoking類(lèi)框住嘴部到煙頭的整體區(qū)域不要只框煙頭也不要框整張臉cigarette類(lèi)緊貼煙頭外沿留 1 到 2 像素邊距遮擋超過(guò) 70% 的目標(biāo)直接標(biāo)ignore或不標(biāo)別硬標(biāo)硬標(biāo)會(huì)教壞模型。標(biāo)注工具用 labelImg 或 CVAT 都行導(dǎo)出 YOLO 格式。YOLO 格式是每行class x_center y_center width height全部歸一化到 0 到 1。這里有個(gè)高頻錯(cuò)誤有人把像素坐標(biāo)直接寫(xiě)進(jìn)去訓(xùn)練時(shí) loss 不降排查半天才發(fā)現(xiàn)沒(méi)歸一化。2.3 目錄結(jié)構(gòu)與 data.yaml 的寫(xiě)法YOLOv5-6.0 對(duì)目錄結(jié)構(gòu)有約定我一般整理成這樣smoking_detect/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml是訓(xùn)練入口寫(xiě)錯(cuò)一個(gè)路徑就白跑# data.yaml train: ../smoking_detect/images/train # 訓(xùn)練集圖片目錄 val: ../smoking_detect/images/val # 驗(yàn)證集圖片目錄 nc: 1 # 類(lèi)別數(shù)先跑通就寫(xiě) 1 names: [smoking] # 類(lèi)別名順序必須和標(biāo)注里的 class id 對(duì)應(yīng)邏輯說(shuō)明train和val指向的是圖片目錄YOLOv5 會(huì)自動(dòng)去找同級(jí)的labels目錄所以 labels 路徑不用寫(xiě)。nc必須等于names長(zhǎng)度否則訓(xùn)練啟動(dòng)就報(bào)維度不匹配。參數(shù)上names的順序就是標(biāo)注文件里 class id 的順序0 對(duì)應(yīng)第一個(gè)名字錯(cuò)位會(huì)導(dǎo)致類(lèi)別全亂。提示先用 50 到 100 張圖跑一個(gè) epoch確認(rèn)能正常讀數(shù)據(jù)、loss 有變化再上全量數(shù)據(jù)。這一步能省掉大量訓(xùn)練半天發(fā)現(xiàn)路徑錯(cuò)的時(shí)間。3. 用 yolov5-6.0 訓(xùn)練吸煙檢測(cè)模型命令、超參與斷點(diǎn)續(xù)訓(xùn)3.1 環(huán)境搭建與依賴(lài)版本YOLOv5-6.0 對(duì) PyTorch 和 CUDA 有版本要求我一般用 conda 隔離環(huán)境避免和系統(tǒng)里其他項(xiàng)目打架# 創(chuàng)建并激活環(huán)境 conda create -n smoking python3.8 -y conda activate smoking # 安裝 PyTorch按自己 CUDA 版本選這里以 cu113 為例 pip install torch1.10.0cu113 torchvision0.11.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安裝 yolov5-6.0 依賴(lài) cd yolov5-6.0 pip install -r requirements.txt邏輯說(shuō)明Python 3.8 是 6.0 版本驗(yàn)證過(guò)的組合太新容易在torch和numpy上出兼容問(wèn)題。requirements.txt里鎖了numpy、opencv-python等版本別手動(dòng)升級(jí)。參數(shù)上CUDA 版本要和驅(qū)動(dòng)匹配nvidia-smi看右上角支持的 CUDA 版本裝高了會(huì)報(bào)no kernel image。3.2 訓(xùn)練命令與關(guān)鍵超參跑通數(shù)據(jù)后訓(xùn)練命令我一般這樣寫(xiě)python train.py \ --data ../smoking_detect/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 150 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name smoking_v1 \ --cache邏輯說(shuō)明--weights yolov5s.pt用官方預(yù)訓(xùn)練權(quán)重做遷移學(xué)習(xí)比從頭訓(xùn)收斂快得多小數(shù)據(jù)集尤其明顯。--img 640是輸入分辨率煙頭小的話(huà)可以提到 960但顯存和速度要權(quán)衡。--cache把圖片緩存到內(nèi)存數(shù)據(jù)量不大時(shí)能明顯加速。參數(shù)上重點(diǎn)說(shuō)三個(gè)--batch-size顯存夠就往上加16 是 8G 顯存的穩(wěn)妥值太小會(huì)讓 BN 統(tǒng)計(jì)不穩(wěn)--hyp6.0 提供 low/mid/high 三檔小數(shù)據(jù)集用 low增強(qiáng)弱一點(diǎn)避免過(guò)擬合--epochs150 是起點(diǎn)看results.csv里 mAP 是否還在漲漲就繼續(xù)。3.3 斷點(diǎn)續(xù)訓(xùn)與多卡訓(xùn)練中斷是常事6.0 支持從last.pt續(xù)python train.py \ --data ../smoking_detect/data.yaml \ --weights runs/train/smoking_v1/weights/last.pt \ --resume邏輯說(shuō)明--resume會(huì)讀取last.pt里保存的優(yōu)化器狀態(tài)和 epoch接著跑不用重頭來(lái)。注意--resume時(shí)其他參數(shù)會(huì)被忽略以 checkpoint 里存的為準(zhǔn)所以別指望續(xù)訓(xùn)時(shí)改 batch size。多卡用--device 0,1但 6.0 的多卡是 DataParallelbatch 會(huì)均分小數(shù)據(jù)集不一定劃算我一般單卡跑。3.4 訓(xùn)練過(guò)程看什么指標(biāo)runs/train/smoking_v1/下有results.csv和results.png。重點(diǎn)看metrics/mAP_0.5整體檢測(cè)能力吸煙檢測(cè)一般能到 0.8 以上算可用metrics/mAP_0.5:0.95更嚴(yán)格小目標(biāo)任務(wù)這個(gè)值通常偏低別慌train/box_loss和val/box_loss如果 train 一直降、val 開(kāi)始漲就是過(guò)擬合該早?;蚣訑?shù)據(jù)。注意驗(yàn)證集里一定要有困難負(fù)樣本否則 mAP 虛高上線(xiàn)后誤報(bào)爆炸。我吃過(guò)這個(gè)虧驗(yàn)證集全是干凈正樣本指標(biāo)好看實(shí)際一跑全是誤檢。4. 吸煙檢測(cè)的推理、導(dǎo)出與部署前驗(yàn)證4.1 單圖與批量推理訓(xùn)練完先本地驗(yàn)證別急著上設(shè)備# 單張圖 python detect.py \ --weights runs/train/smoking_v1/weights/best.pt \ --source ../test_imgs/smoke1.jpg \ --img 640 \ --conf-thres 0.4 \ --iou-thres 0.45 # 整個(gè)目錄 python detect.py \ --weights runs/train/smoking_v1/weights/best.pt \ --source ../test_imgs/ \ --save-txt邏輯說(shuō)明--conf-thres是置信度閾值吸煙檢測(cè)我一般從 0.4 起調(diào)太低誤報(bào)多太高漏檢。--iou-thres控制 NMS 合并重疊目標(biāo)多時(shí)調(diào)低。--save-txt會(huì)把框存成 YOLO 格式方便后續(xù)統(tǒng)計(jì)。參數(shù)上--img要和訓(xùn)練時(shí)一致訓(xùn)練 640 推理 960 會(huì)有尺度偏差雖然 6.0 會(huì)自適應(yīng)但一致性更好。4.2 導(dǎo)出 ONNX 與 TensorRT部署到邊緣設(shè)備前通常先導(dǎo)出# 導(dǎo)出 ONNX python export.py \ --weights runs/train/smoking_v1/weights/best.pt \ --include onnx \ --img 640 \ --batch-size 1 # 導(dǎo)出 TensorRT需要 GPU 環(huán)境 python export.py \ --weights runs/train/smoking_v1/weights/best.pt \ --include engine \ --img 640 \ --batch-size 1 \ --device 0邏輯說(shuō)明ONNX 通用性好TensorRT 在 NVIDIA 設(shè)備上快很多。--batch-size 1是邊緣部署常用值動(dòng)態(tài) batch 要額外指定--dynamic。導(dǎo)出后務(wù)必用val.py在 ONNX 上再跑一遍驗(yàn)證確認(rèn)精度沒(méi)掉。參數(shù)上TensorRT 的--workspace默認(rèn) 4G顯存小就調(diào)低--half開(kāi) FP16 能提速但精度可能掉一點(diǎn)吸煙檢測(cè)這種小目標(biāo)要謹(jǐn)慎。4.3 部署前必須做的三件事第一用真實(shí)攝像頭視頻流跑一遍別只看圖片。視頻里運(yùn)動(dòng)模糊、光照變化會(huì)暴露很多問(wèn)題。第二統(tǒng)計(jì)誤報(bào)率連續(xù)跑幾小時(shí)看每小時(shí)誤報(bào)幾次超過(guò)可接受閾值就回去補(bǔ)負(fù)樣本。第三確認(rèn)推理延遲邊緣設(shè)備上單幀超過(guò) 100ms 就要考慮降分辨率或換更小的模型yolov5n。5. 吸煙檢測(cè)訓(xùn)練與部署的避坑清單5.1 現(xiàn)象訓(xùn)練 loss 不降mAP 一直是 0原因最常見(jiàn)是標(biāo)注格式錯(cuò)比如沒(méi)歸一化、class id 越界或者data.yaml里nc和實(shí)際類(lèi)別數(shù)不一致。其次是圖片和標(biāo)簽文件名對(duì)不上YOLOv5 是按文件名找標(biāo)簽的a.jpg必須對(duì)應(yīng)a.txt。解決先用python utils/general.py里的檢查邏輯或者自己寫(xiě)腳本遍歷 labels確認(rèn)每行 5 個(gè)值、都在 0 到 1 之間、class id 小于nc。文件名不一致就批量重命名。5.2 現(xiàn)象驗(yàn)證集 mAP 很高實(shí)際部署誤報(bào)嚴(yán)重原因驗(yàn)證集和訓(xùn)練集同分布且缺少困難負(fù)樣本。模型沒(méi)學(xué)過(guò)手靠近臉白色反光這些干擾一到真實(shí)場(chǎng)景就亂框。解決從實(shí)際場(chǎng)景里截取誤報(bào)幀標(biāo)成背景不標(biāo)任何目標(biāo)加進(jìn)訓(xùn)練集重新訓(xùn)練。負(fù)樣本比例我一般控制在 1:3 到 1:5 之間太多會(huì)壓制正樣本召回。5.3 現(xiàn)象小煙頭檢測(cè)不到大目標(biāo)正常原因YOLOv5 的 P3 特征圖負(fù)責(zé)小目標(biāo)但如果訓(xùn)練分辨率低、小目標(biāo)樣本少P3 學(xué)不好。另外 anchor 尺寸如果不匹配小目標(biāo)回歸也困難。解決提高--img到 960 或 1280增加小目標(biāo)樣本必要時(shí)用--anchor重新聚類(lèi) anchor。6.0 默認(rèn) anchor 是 COCO 的吸煙檢測(cè)的小目標(biāo)可能需要更小的 anchor。5.4 現(xiàn)象導(dǎo)出 ONNX 后精度下降明顯原因?qū)С鰰r(shí)的預(yù)處理和后處理與 PyTorch 不一致比如歸一化方式、NMS 實(shí)現(xiàn)差異。另外 FP16 導(dǎo)出會(huì)掉精度。解決導(dǎo)出后用val.py --weights best.onnx對(duì)比 mAP差距大就檢查export.py里的--img和--batch-size是否和訓(xùn)練一致。FP16 先別開(kāi)確認(rèn) FP32 沒(méi)問(wèn)題再試。5.5 現(xiàn)象邊緣設(shè)備上推理速度慢原因模型太大用了 yolov5l 或 x或者沒(méi)做 TensorRT 加速或者輸入分辨率過(guò)高。解決換 yolov5n 或 yolov5s導(dǎo)出 TensorRT降--img到 416 或 320。吸煙檢測(cè)如果場(chǎng)景固定、攝像頭距離近320 往往夠用。6. 把吸煙檢測(cè)做穩(wěn)的一個(gè)進(jìn)階習(xí)慣用困難樣本挖掘持續(xù)迭代模型上線(xiàn)不是終點(diǎn)。我自己的習(xí)慣是部署后每周從實(shí)際視頻里抽一批誤報(bào)和漏檢幀人工確認(rèn)后加進(jìn)訓(xùn)練集重新訓(xùn)練一版。這個(gè)循環(huán)跑上三四輪誤報(bào)率通常能降一個(gè)數(shù)量級(jí)。具體做法是寫(xiě)個(gè)腳本把detect.py --save-txt輸出的框和人工復(fù)核結(jié)果對(duì)比自動(dòng)挑出置信度在 0.3 到 0.6 之間的模糊樣本這些樣本對(duì)模型提升最大。# 從檢測(cè)結(jié)果里挑模糊樣本供人工復(fù)核 import os def pick_hard_samples(txt_dir, low0.3, high0.6): hard [] for f in os.listdir(txt_dir): if not f.endswith(.txt): continue with open(os.path.join(txt_dir, f)) as fp: for line in fp: parts line.strip().split() if len(parts) 6: continue conf float(parts[5]) # 第6列是置信度 if low conf high: hard.append(f) break return hard if __name__ __main__: samples pick_hard_samples(../runs/detect/exp/labels) print(f待復(fù)核模糊樣本數(shù): {len(samples)})邏輯說(shuō)明detect.py --save-txt存出的每行是class x y w h conf第 6 列是置信度。挑 0.3 到 0.6 之間的這些是模型拿不準(zhǔn)的人工標(biāo)一遍加進(jìn)訓(xùn)練集比隨機(jī)加樣本有效得多。參數(shù)上low和high按自己模型的置信度分布調(diào)如果模型整體置信度偏低區(qū)間可以下移。另一個(gè)習(xí)慣是固定驗(yàn)證集。每次迭代都用同一批驗(yàn)證圖這樣 mAP 變化才有可比性。我見(jiàn)過(guò)有人每次隨機(jī)切驗(yàn)證集指標(biāo)忽高忽低根本沒(méi)法判斷模型是變好還是變差。最后說(shuō)個(gè)血淚經(jīng)驗(yàn)吸煙檢測(cè)的標(biāo)注一致性比數(shù)量重要。同一個(gè)人叼煙今天框大明天框小模型學(xué)出來(lái)就是抖的。標(biāo)注前先定好規(guī)范找兩個(gè)人交叉檢查一批確認(rèn)一致了再全量標(biāo)。這個(gè)前期投入比后面反復(fù)調(diào)參省時(shí)間得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取