志識(shí)別實(shí)戰(zhàn):從數(shù)據(jù)集構(gòu)建到訓(xùn)練部署全流程)
簡(jiǎn)介這份資源是一套基于YOLOv5的道路交通標(biāo)志識(shí)別完整項(xiàng)目主要面向畢業(yè)設(shè)計(jì)、期末大作業(yè)及課程設(shè)計(jì)等場(chǎng)景也適合對(duì)目標(biāo)檢測(cè)感興趣的初學(xué)者參考。項(xiàng)目包含Python源碼與配套數(shù)據(jù)集代碼中附有注釋從數(shù)據(jù)處理、模型訓(xùn)練到推理識(shí)別均有覆蓋方便本地部署后直接運(yùn)行。資源包共266個(gè)文件壓縮后大小約423MB文件類型涵蓋py核心代碼、pt模型權(quán)重、yaml配置文件、jpg/jpeg圖像樣本、sh腳本、csv訓(xùn)練記錄及Dockerfile等結(jié)構(gòu)與用途較為清晰。已有439人瀏覽學(xué)習(xí)。內(nèi)容包含訓(xùn)練日志、results.csv指標(biāo)變化、Docker部署文件與部分配置文件便于了解訓(xùn)練過程和環(huán)境搭建方式。對(duì)希望快速完成高分成品項(xiàng)目的學(xué)生而言這套資源提供了一條可復(fù)用的完整實(shí)現(xiàn)路徑可在短時(shí)間內(nèi)上手并用于演示或答辯。1. 從畢業(yè)設(shè)計(jì)題目到可運(yùn)行系統(tǒng)yolov5道路交通標(biāo)志識(shí)別要交付的不只是源碼打開資源站搜“基于yolov5道路交通標(biāo)志識(shí)別程序源碼數(shù)據(jù)集畢業(yè)設(shè)計(jì)”下載包里基本都是同一套東西yolov5源碼、一堆標(biāo)志圖片、標(biāo)注文件、一份說明文檔。很多同學(xué)以為解壓就能跑結(jié)果卡在環(huán)境配置、數(shù)據(jù)集轉(zhuǎn)換和訓(xùn)練參數(shù)上。這個(gè)題目真正考驗(yàn)的是把yolov5源碼、道路交通標(biāo)志數(shù)據(jù)集、訓(xùn)練腳本和演示界面串成一條可復(fù)現(xiàn)的工程鏈路。道路交通標(biāo)志識(shí)別和普通目標(biāo)檢測(cè)最大的區(qū)別在于標(biāo)志屬于典型小目標(biāo)一個(gè)1920×1080畫面里標(biāo)志可能只有40×40像素再加上夜間反光、遮擋和類別不均衡很容易出現(xiàn)“訓(xùn)練mAP高、現(xiàn)場(chǎng)識(shí)別差”的情況。這個(gè)方案適合正在做畢業(yè)設(shè)計(jì)、需要快速產(chǎn)出可運(yùn)行系統(tǒng)的學(xué)生也適合想驗(yàn)證yolov5訓(xùn)練自己數(shù)據(jù)集全流程的工程師。2. 建數(shù)據(jù)集與目錄結(jié)構(gòu)從COCO2017到y(tǒng)olov5訓(xùn)練自己的數(shù)據(jù)集2.1 不要直接拿COCO2017當(dāng)訓(xùn)練集交通標(biāo)志數(shù)據(jù)集怎么選很多教程一上來就讓人下載COCO2017理由是yolov5官方預(yù)訓(xùn)練權(quán)重在COCO上訓(xùn)練過。但COCO2017數(shù)據(jù)集結(jié)構(gòu)是面向80類通用目標(biāo)的train2017里有118K張圖、val2017有5K張圖標(biāo)注存放在annotations/instances_train2017.json等文件里。這套結(jié)構(gòu)里確實(shí)有traffic_sign類別但實(shí)例數(shù)量少而且標(biāo)志類別只占很小一部分。直接用COCO訓(xùn)練交通標(biāo)志識(shí)別等于讓模型在大量無關(guān)背景里找少量小物體收斂慢、誤檢也多。更合適的做法是換用專用數(shù)據(jù)集。常見的有TT100K清華大學(xué)-騰訊公開的交通標(biāo)志數(shù)據(jù)集、CCTSDB長(zhǎng)沙理工的交通標(biāo)志數(shù)據(jù)集、GTSRB德國交通標(biāo)志數(shù)據(jù)集。TT100K標(biāo)注了街景圖片里的標(biāo)志實(shí)例類別多但部分類別樣本極少CCTSDB是國內(nèi)場(chǎng)景類別相對(duì)集中GTSRB雖然主要用于分類但也可以轉(zhuǎn)成檢測(cè)數(shù)據(jù)來擴(kuò)充不同光照下的樣本。下載時(shí)盡量去學(xué)校主頁或論文主頁找原始包資源站打包經(jīng)常出現(xiàn)圖片被壓縮、標(biāo)注缺失、類別名被改成數(shù)字的問題。我一般建議畢業(yè)設(shè)計(jì)選一個(gè)數(shù)據(jù)集做主體不要貪多。比如用TT100K里的停止、限速、禁止通行等5到10個(gè)常用類別再補(bǔ)幾十張自己手機(jī)拍的路口照片。這樣標(biāo)注量控制在1000張左右訓(xùn)練時(shí)間和答辯講稿都好寫。如果直接用官方COCO做遷移反而要處理類不平衡和標(biāo)注過濾工作量會(huì)大很多。2.2 把VOC/JSON標(biāo)注轉(zhuǎn)成YOLO格式轉(zhuǎn)換腳本與參數(shù)說明yolov5訓(xùn)練自己的數(shù)據(jù)集要求每張圖片對(duì)應(yīng)一個(gè)同名txt文件每行格式是class_id x_center y_center width height坐標(biāo)是歸一化到0到1的浮點(diǎn)數(shù)x_center和y_center是框中心點(diǎn)width和height是框的寬高。這個(gè)格式和VOC的xmin、ymin、xmax、ymax不一樣和COCO的bbox也不一樣。下面這段代碼把VOC XML標(biāo)注轉(zhuǎn)成YOLO txtimport os import glob import xml.etree.ElementTree as ET # 類別順序必須和后續(xù)data.yaml里的names保持一致 CLASSES [spd_30, spd_60, stop, crosswalk] def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) with open(os.path.join(out_dir, os.path.basename(xml_path).replace(.xml, .txt)), w) as f: for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) bndbox obj.find(bndbox) x1 float(bndbox.find(xmin).text) y1 float(bndbox.find(ymin).text) x2 float(bndbox.find(xmax).text) y2 float(bndbox.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 防止坐標(biāo)越界標(biāo)注框偶爾會(huì)超出圖片邊界 x_center min(max(x_center, 0), 1) y_center min(max(y_center, 0), 1) w min(max(w, 0), 1) h min(max(h, 0), 1) f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) os.makedirs(labels, exist_okTrue) xml_files glob.glob(xml/*.xml) for xml_file in xml_files: xml_to_yolo(xml_file, labels) print(converted, len(xml_files), files)這段代碼的核心邏輯是先讀圖片真實(shí)寬高再把VOC的絕對(duì)坐標(biāo)換算成相對(duì)坐標(biāo)。CLASSES列表的順序決定每個(gè)類別的數(shù)字id這個(gè)順序在后續(xù)data.yaml里不能再變。代碼里做了0到1的鉗制但遇到x2小于x1這種標(biāo)注錯(cuò)誤時(shí)鉗制沒用需要額外報(bào)錯(cuò)。需要注意VOC XML里size子節(jié)點(diǎn)必須有寬高有些公開數(shù)據(jù)集漏寫了size節(jié)點(diǎn)會(huì)導(dǎo)致img_w拿不到值。轉(zhuǎn)換前最好先統(tǒng)計(jì)一下XML里有多少個(gè)文件缺size字段缺了就直接刪掉對(duì)應(yīng)圖片避免訓(xùn)練到一半報(bào)錯(cuò)。2.3 目錄結(jié)構(gòu)、數(shù)據(jù)劃分和標(biāo)簽自檢yolov5約定的數(shù)據(jù)集目錄結(jié)構(gòu)一般長(zhǎng)這樣datasets/ ├── traffic_sign/ │ ├── images/ │ │ ├── train/ │ │ │ ├── img_001.jpg │ │ │ └── ... │ │ └── val/ │ │ └── ... │ ├── labels/ │ │ ├── train/ │ │ │ ├── img_001.txt │ │ │ └── ... │ │ └── val/ │ │ └── ... │ └── data.yaml圖片和標(biāo)簽要嚴(yán)格同名只是擴(kuò)展名不同。yolov5依賴文件名匹配圖片和標(biāo)簽如果從網(wǎng)上下載的數(shù)據(jù)集文件被重命名過最好用腳本統(tǒng)一改成img_00001.jpg這類定長(zhǎng)編號(hào)避免文件名里有空格或中文。下面是數(shù)據(jù)劃分腳本把a(bǔ)ll目錄里的圖片和標(biāo)簽按15%隨機(jī)分成驗(yàn)證集import os import random import shutil random.seed(42) src_images datasets/traffic_sign/images/all src_labels datasets/traffic_sign/labels/all train_img_dir datasets/traffic_sign/images/train train_lbl_dir datasets/traffic_sign/labels/train val_img_dir datasets/traffic_sign/images/val val_lbl_dir datasets/traffic_sign/labels/val for d in [train_img_dir, train_lbl_dir, val_img_dir, val_lbl_dir]: os.makedirs(d, exist_okTrue) img_files [f for f in os.listdir(src_images) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(img_files) val_count max(int(len(img_files) * 0.15), 1) val_files set(img_files[:val_count]) for img_name in img_files: stem os.path.splitext(img_name)[0] label_src os.path.join(src_labels, stem .txt) if not os.path.exists(label_src): print(fwarning: missing label for {img_name}) continue max_len max(len(img_files) - val_count, 0) # 簡(jiǎn)單進(jìn)度提示 if img_name in val_files: shutil.move(os.path.join(src_images, img_name), os.path.join(val_img_dir, img_name)) shutil.move(label_src, os.path.join(val_lbl_dir, stem .txt)) else: shutil.move(os.path.join(src_images, img_name), os.path.join(train_img_dir, img_name)) shutil.move(label_src, os.path.join(train_lbl_dir, stem .txt)) print(fmoved {img_name}, remaining {len(img_files) - 1}) # 這里只作示意腳本固定隨機(jī)種子保證復(fù)現(xiàn)。劃分前先檢查標(biāo)簽是否存在避免訓(xùn)練時(shí)出現(xiàn)“找不到標(biāo)簽”的報(bào)錯(cuò)。移動(dòng)完成后在datasets/traffic_sign/下建data.yamlpath: datasets/traffic_sign train: images/train val: images/val nc: 4 names: [spd_30, spd_60, stop, crosswalk]path可以寫相對(duì)路徑也可以寫絕對(duì)路徑如果在Windows和Linux之間反復(fù)切換建議直接用絕對(duì)路徑反斜杠問題會(huì)少很多。nc必須和names列表長(zhǎng)度一致names里的順序必須和轉(zhuǎn)換腳本里CLASSES的順序一致。這里如果順序亂了訓(xùn)練不會(huì)報(bào)錯(cuò)但預(yù)測(cè)出來的類別id會(huì)對(duì)應(yīng)到錯(cuò)誤的標(biāo)識(shí)。COCO2017數(shù)據(jù)集結(jié)構(gòu)使用的是instances JSON類別id存在categories字段里和yolov5的txt格式不同。如果硬要從COCO轉(zhuǎn)需要過濾出交通標(biāo)志類別并重新映射id過程不復(fù)雜但容易出錯(cuò)。對(duì)畢業(yè)設(shè)計(jì)來說直接用交通標(biāo)志專用數(shù)據(jù)集再轉(zhuǎn)YOLO格式效率高得多也方便自己控制類別。3. 用yolov5源碼跑通訓(xùn)練超參數(shù)、模型選型與訓(xùn)練命令3.1 yolov5環(huán)境配置與源碼獲取yolov5環(huán)境配置最常見的坑是torch版本和CUDA不匹配。官方requirements.txt里給的是最新torch如果你的顯卡驅(qū)動(dòng)比較舊pip裝出來的torch可能不識(shí)別CUDA。我一般先用conda建獨(dú)立環(huán)境conda create -n yolo python3.9 -y conda activate yolo pip install torch1.13.1cu117 torchvision0.14.1cu117 --index-url https://download.pytorch.org/whl/cu117 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt然后驗(yàn)證torch能不能正常調(diào)用顯卡python -c import torch; print(torch.cuda.is_available())如果是True再跑python train.py --help確認(rèn)yolov5源碼能正常加載。如果環(huán)境里沒有g(shù)it直接去GitHub下載zip包也是一樣的后面所有命令都在解壓后的yolov5目錄下執(zhí)行。需要注意yolov5版本不同訓(xùn)練命令里參數(shù)名略有差異比如老版本用--cfg指定模型配置新版本改成在yaml里寫模型配置。寫代碼時(shí)盡量鎖定一個(gè)版本畢業(yè)設(shè)計(jì)不需要追新。3.2 修改數(shù)據(jù)配置與模型配置數(shù)據(jù)配置在data.yaml里已經(jīng)寫好了接下來是模型配置。yolov5默認(rèn)提供yolov5n、yolov5s、yolov5m、yolov5l、yolov5x五檔模型后綴越小模型越輕量。交通標(biāo)志類別少yolov5s夠用如果推理設(shè)備是CPU或老顯卡選yolov5n。復(fù)制一份模型配置把nc改成自己的類別數(shù)cp models/yolov5s.yaml models/yolov5s_traffic.yaml然后修改models/yolov5s_traffic.yaml核心部分大概是nc: 4 depth_multiple: 0.33 width_multiple: 0.50 anchors: - [10,13, 16,30, 33,23] - [30,61, 62,45, 59,119] - [116,90, 156,198, 373,326]nc必須和data.yaml的nc一致。depth_multiple和width_multiple控制網(wǎng)絡(luò)層數(shù)和通道數(shù)這是yolov5s與yolov5m的區(qū)別畢業(yè)設(shè)計(jì)先不動(dòng)。anchors是預(yù)設(shè)的anchor尺寸默認(rèn)從COCO聚類得到。訓(xùn)練自己數(shù)據(jù)集時(shí)yolov5會(huì)嘗試重新聚類如果想手動(dòng)干預(yù)訓(xùn)練命令里可以加--noautoanchor但除非你已經(jīng)看過聚類結(jié)果否則不建議一開始就關(guān)。3.3 超參數(shù)與訓(xùn)練參數(shù)怎么調(diào)才不玄學(xué)訓(xùn)練命令是整套方案里最核心的一步python train.py \ --data datasets/traffic_sign/data.yaml \ --cfg models/yolov5s_traffic.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --name traffic_sign_run \ --device 0--data指向數(shù)據(jù)集配置--cfg指向模型配置--weights使用官方COCO預(yù)訓(xùn)練權(quán)重這樣能明顯加快收斂。--device 0指定第一張顯卡如果是CPU訓(xùn)練就不加這個(gè)參數(shù)速度會(huì)慢很多。--imgsz 640是網(wǎng)絡(luò)輸入尺寸不是原圖尺寸。交通標(biāo)志在街景里往往偏小把imgsz提高到1280能改善小目標(biāo)檢測(cè)但訓(xùn)練顯存和時(shí)間都會(huì)漲需要一個(gè)平衡。常見參數(shù)建議如下表參數(shù)建議值說明batch-size8到16顯存不足時(shí)優(yōu)先降到4epochs100到150數(shù)據(jù)量少時(shí)100輪足夠imgsz640或1280小目標(biāo)明顯時(shí)用1280workers4到8OOM時(shí)可以設(shè)0排查optimizerSGD默認(rèn)即可穩(wěn)定lr00.01遷移學(xué)習(xí)常駐默認(rèn)值mosaic1.0小目標(biāo)增強(qiáng)有效過度裁切會(huì)引入臟樣本超參數(shù)文件在data/hyps/hyp.scratch-low.yaml。畢業(yè)設(shè)計(jì)階段沒必要把所有超參數(shù)都改一遍只關(guān)注lr0、lrf、mosaic這幾個(gè)就行。lr0是初始學(xué)習(xí)率遷移學(xué)習(xí)用0.01很穩(wěn)lrf是最終學(xué)習(xí)率與初始學(xué)習(xí)率的比值默認(rèn)0.2我習(xí)慣改成0.1讓訓(xùn)練后期更細(xì)膩mosaic是馬賽克增強(qiáng)對(duì)小目標(biāo)有幫助但如果你的標(biāo)注框很小mosaic裁切后可能只剩半個(gè)物體可以降到0.5。3.4 訓(xùn)練輸出與效果評(píng)估訓(xùn)練結(jié)束后結(jié)果在runs/train/traffic_sign_run/下。best.pt是驗(yàn)證集mAP最高的權(quán)重last.pt是最后一次epoch的權(quán)重部署時(shí)優(yōu)先用best.pt。results.png里有l(wèi)oss曲線和mAP曲線答辯時(shí)可以直接放這張圖說明收斂過程。光看最終mAP不夠要檢查驗(yàn)證集loss是否跟著下降。如果train loss一直在降但val loss降不下去說明過擬合優(yōu)先補(bǔ)數(shù)據(jù)增強(qiáng)或換小模型。如果mAP0.5只有0.2左右不要盲目加epoch先回退去看標(biāo)簽可能是類別順序錯(cuò)了也可能是標(biāo)注框過小yolov5把大量小框當(dāng)噪聲濾掉了。訓(xùn)練日志里每一輪會(huì)打印box_obj、cls、obj幾個(gè)loss分量。如果cls占比異常高說明類別學(xué)習(xí)存在問題檢查data.yaml的names順序。如果obj一直不降大概率是anchor和物體尺寸不匹配。遇到這種情況先看runs/train/traffic_sign_run/anchors.png再?zèng)Q定是否手動(dòng)調(diào)整anchor。4. 從模型到演示系統(tǒng)推理、導(dǎo)出與界面跑通4.1 用detect.py跑圖片和視頻推理訓(xùn)練完成后的常規(guī)操作是用detect.py驗(yàn)證效果。命令寫法如下python detect.py \ --weights runs/train/traffic_sign_run/weights/best.pt \ --source data/images/street.jpg \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name traffic_demo--source可以是圖片、文件夾、視頻文件也可以是攝像頭設(shè)備號(hào)0。如果現(xiàn)場(chǎng)演示沒有攝像頭用一段路口視頻是最穩(wěn)妥的選擇幀率不會(huì)因?yàn)镮O變慢。--conf-thres是置信度閾值0.25是默認(rèn)值如果檢測(cè)結(jié)果里漏框多可以降到0.15但誤檢會(huì)增加。--iou-thres是NMS的IoU閾值0.45常規(guī)不用動(dòng)。--save-txt會(huì)把每個(gè)目標(biāo)的框坐標(biāo)和類別存成txt這個(gè)比較好用后續(xù)做紅燈統(tǒng)計(jì)、標(biāo)志數(shù)量統(tǒng)計(jì)都從txt里讀。跑完在runs/detect/traffic_demo/下查看標(biāo)注圖片。如果發(fā)現(xiàn)檢測(cè)框位置正確但置信度普遍低于0.3說明訓(xùn)練數(shù)據(jù)里的標(biāo)志樣本太小或者訓(xùn)練時(shí)imgsz不夠大。此時(shí)可以先試imgsz1280重新訓(xùn)練數(shù)據(jù)增強(qiáng)也適當(dāng)加強(qiáng)。4.2 導(dǎo)出ONNX與部署權(quán)衡畢業(yè)設(shè)計(jì)如果只做訓(xùn)練和detect.py演示工作量偏單薄。更常見的做法是導(dǎo)出ONNX再單獨(dú)寫推理程序。導(dǎo)出命令非常簡(jiǎn)單python export.py --weights runs/train/traffic_sign_run/weights/best.pt --include onnx --imgsz 640導(dǎo)出后會(huì)在權(quán)重同目錄生成best.onnx。ONNX模型可以用onnxruntime加載不需要再安裝完整的yolov5訓(xùn)練環(huán)境pip install onnxruntime-gpu python -c import onnxruntime as ort; print(ort.get_available_providers())這里要注意onnxruntime-gpu同樣有CUDA版本匹配問題如果訓(xùn)練機(jī)的CUDA版本和onnxruntime依賴不一致運(yùn)行時(shí)會(huì)出現(xiàn)“CUDA execution provider”加載失敗。不想折騰的話就用CPU版onnxruntime檢測(cè)一張640×640的圖片在普通臺(tái)式機(jī)上只要幾百毫秒畢業(yè)設(shè)計(jì)演示完全夠用。導(dǎo)出ONNX只是第一步最后一步是用PyTorch直接加載權(quán)重寫推理邏輯因?yàn)镻yTorch調(diào)用對(duì)大多數(shù)學(xué)生更熟悉也省去NMS后處理的麻煩。樹莓派或嵌入式設(shè)備上部署時(shí)再考慮ONNX或OpenVINO模型可以換成yolov5n。4.3 給答辯和用戶看的輕量識(shí)別界面很多畢業(yè)設(shè)計(jì)要求有界面常見的做法是Flask做個(gè)網(wǎng)頁上傳圖片就能顯示檢測(cè)框。核心推理函數(shù)可以這樣寫import cv2 import torch def run_inference(model, img_path, conf0.25): img cv2.imread(img_path) results model(img, confconf) boxes results.xyxy[0].cpu().numpy() # 每行: x1, y1, x2, y2, conf, cls_id for b in boxes: cls_id int(b[5]) label f{model.names[cls_id]} {b[4]:.2f} cv2.rectangle(img, (int(b[0]), int(b[1])), (int(b[2]), int(b[3])), (0, 255, 0), 2) cv2.putText(img, label, (int(b[0]), int(b[1]) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) return img這里的results.xyxy[0]返回的是像素坐標(biāo)直接畫框就行不需要再歸一化。model.names來自訓(xùn)練時(shí)的data.yaml所以如果names是中文OpenCV的putText畫出來會(huì)亂碼。穩(wěn)妥做法是names里存英文或拼音展示時(shí)再映射到中文。Flask部分只需要把函數(shù)包成一個(gè)路由接收上傳圖片返回標(biāo)注后的圖片。前端不需要復(fù)雜一張表格加一個(gè)img標(biāo)簽就能滿足答辯演示。重點(diǎn)是把推理邏輯和訓(xùn)練過程講清楚界面是加分項(xiàng)不是核心。5. 避坑與排查畢業(yè)設(shè)計(jì)里最常見的5個(gè)翻車現(xiàn)場(chǎng)5.1 訓(xùn)練一開始loss就是nan現(xiàn)象訓(xùn)練第一輪loss直接顯示nan然后一直不恢復(fù)訓(xùn)練基本作廢。翻車原因有兩個(gè)高頻點(diǎn)一是標(biāo)簽txt里出現(xiàn)了負(fù)坐標(biāo)或?qū)捀邽?二是類別id超過了nc-1導(dǎo)致loss計(jì)算越界。很多公開數(shù)據(jù)集的標(biāo)注本身不干凈轉(zhuǎn)換腳本沒有過濾是主因。解決訓(xùn)練前掃描一遍所有標(biāo)簽文件把寬高小于等于0的行刪掉把坐標(biāo)異常的樣本挑出來重標(biāo)。也可以寫個(gè)幾行腳本檢查每行字段數(shù)量正常情況下每行必須是5個(gè)值類別id、中心點(diǎn)x、中心點(diǎn)y、寬、高。import os for split in [train, val]: label_dir fdatasets/traffic_sign/labels/{split} if not os.path.exists(label_dir): continue for f in os.listdir(label_dir): if not f.endswith(.txt): continue with open(os.path.join(label_dir, f)) as fp: for line in fp: parts line.split() if len(parts) ! 5 or float(parts[3]) 0 or float(parts[4]) 0: print(bad label, split, f, line.rstrip())這條檢查做完再跑短epoch的小批量訓(xùn)練確認(rèn)loss能降再上全量。5.2 顯存不足只調(diào)batch-size沒用現(xiàn)象6G顯存跑batch-size16直接OOM改成8還是OOM換batch-size4也只是晚崩潰幾分鐘。實(shí)際上顯存占用大頭除了batch-size還有輸入尺寸和模型深度。--imgsz 640比--imgsz 416的顯存占用增加接近2倍yolov5s雖然是輕量模型但梯度回傳時(shí)的中間激活值在batch-size較大時(shí)仍然可能爆顯存。解決先固定imgsz640把batch-size降到4再不行就換yolov5n模型配置。訓(xùn)練時(shí)可以同時(shí)設(shè)--workers 0雖然數(shù)據(jù)加載慢一點(diǎn)但能排除數(shù)據(jù)預(yù)取導(dǎo)致的額外顯存占用。如果畢業(yè)設(shè)計(jì)只有一臺(tái)8G甚至6G顯存的筆記本建議直接用yolov5n訓(xùn)練時(shí)間和顯存壓力都會(huì)小很多。5.3 mAP高但現(xiàn)場(chǎng)識(shí)別不準(zhǔn)小目標(biāo)anchor沒有重聚類現(xiàn)象驗(yàn)證集mAP0.5有0.8但拿一張真實(shí)路口原圖進(jìn)去交通標(biāo)志全沒框出來把圖片放大后模型又能正確識(shí)別。原因很典型默認(rèn)anchor是從COCO數(shù)據(jù)集聚類出來的COCO里大中目標(biāo)多交通標(biāo)志在街景中占比小模型在訓(xùn)練時(shí)會(huì)重新聚類anchor但數(shù)據(jù)里小目標(biāo)數(shù)量不夠聚類結(jié)果仍然偏向中等尺度。解決訓(xùn)練結(jié)束后打開runs/train/traffic_sign_run/anchors.png對(duì)比紅藍(lán)點(diǎn)分布。如果標(biāo)注框尺寸明顯小于默認(rèn)anchor第一組可以把models/yolov5s_traffic.yaml里的anchors改成更小的一組比如[4,5, 8,10, 12,18]同時(shí)把訓(xùn)練imgsz提高到1280。驗(yàn)證時(shí)也要用同樣的imgsz不然模型尺度不匹配推理效果會(huì)打折扣。5.4 中文標(biāo)簽畫成亂碼現(xiàn)象自寫推理腳本里用cv2.putText繪制“停止標(biāo)志”輸出圖片上全是問號(hào)或者什么都不顯示。OpenCV自帶的putText只支持英文字體和數(shù)字不支持中文。模型預(yù)測(cè)的label來自names列表如果names直接寫中文訓(xùn)練本身沒問題但繪圖階段會(huì)翻車。解決最簡(jiǎn)單的方法是在data.yaml里使用英文或拼音例如stop、speed_limit_60繪圖時(shí)再做一個(gè)字典映射到中文。如果必須在圖片上顯示中文用PIL加載中文字體比如Windows下的C:/Windows/Fonts/msyh.ttc把文字渲染到臨時(shí)圖像上再貼回原圖。答辯演示中英文標(biāo)簽也是可以被接受的別在這個(gè)點(diǎn)上浪費(fèi)時(shí)間。5.5 torch.cuda.is_available()返回False現(xiàn)象環(huán)境配置完成后訓(xùn)練時(shí)打印警告“CUDA unavailable”只能用CPU慢慢跑。原因大多是pip安裝torch時(shí)默認(rèn)裝了CPU版本或者torch版本與顯卡驅(qū)動(dòng)不匹配。很多人看到requirements.txt里有torch就直接pip install結(jié)果裝成了不帶CUDA的wheel。解決先卸載已有torch和torchvision再按PyTorch官網(wǎng)指引安裝對(duì)應(yīng)CUDA的wheel。以CUDA 11.7為例pip uninstall -y torch torchvision pip install torch1.13.1cu117 torchvision0.14.1cu117 --index-url https://download.pytorch.org/whl/cu117裝完再驗(yàn)證一次torch.cuda.is_available()如果還是False檢查顯卡驅(qū)動(dòng)版本是否支持目標(biāo)CUDA。這一步是所有后續(xù)訓(xùn)練的地基值得花半小時(shí)踩平。6. 進(jìn)階玩法把超參數(shù)搜索和模型輕量化寫進(jìn)畢設(shè)6.1 用yolov5自帶的超參數(shù)進(jìn)化腳本跑一輪yolov5訓(xùn)練腳本內(nèi)置了超參數(shù)進(jìn)化功能通過--evolve參數(shù)搜索一組mAP更高的超參數(shù)。在數(shù)據(jù)量不大時(shí)可以先跑一個(gè)小代次搜索當(dāng)作亮點(diǎn)python train.py \ --data datasets/traffic_sign/data.yaml \ --cfg models/yolov5s_traffic.yaml \ --weights yolov5s.pt \ --epochs 20 \ --batch-size 8 \ --evolve 50 \ --cache--evolve 50表示搜索50組超參數(shù)組合每組用20個(gè)epoch評(píng)估。這個(gè)命令在普通筆記本上可能要跑幾個(gè)小時(shí)畢業(yè)設(shè)計(jì)如果不追求極致效果直接用默認(rèn)hyp文件就夠了。想手動(dòng)調(diào)的話我最常用的是把lrf改成0.1讓學(xué)習(xí)率在訓(xùn)練后期衰減更緩慢對(duì)小數(shù)據(jù)集更友好。6.2 把yolov5s換成yolov5n或?qū)С鯰ensorRT如果答辯現(xiàn)場(chǎng)要演示攝像頭實(shí)時(shí)識(shí)別而機(jī)器沒有獨(dú)立顯卡yolov5n是最穩(wěn)妥的選擇。訓(xùn)練命令不需要大改把--weights yolov5n.pt和--cfg models/yolov5n_traffic.yaml替換掉即可。yolov5n的參數(shù)只有yolov5s的三分之一不到CPU推理速度能快一倍代價(jià)是精度會(huì)低一些。如果手里有NVIDIA顯卡可以導(dǎo)出TensorRT引擎進(jìn)一步加速python export.py --weights runs/train/traffic_sign_run/weights/best.pt --include engine --imgsz 640 --device 0TensorRT的整合成本比ONNX高不少如果只是答辯演示ONNX加onnxruntime已經(jīng)夠了。我的習(xí)慣是拿到一個(gè)新的數(shù)據(jù)集先把標(biāo)簽檢查腳本跑一遍再用十張圖片做個(gè)冒煙訓(xùn)練確認(rèn)loss正常后才會(huì)跑全量。這套流程幫我避開了很多次標(biāo)注文件翻車。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取