據(jù)集:快遞包裹缺陷檢測(cè)從推理到微調(diào)實(shí)戰(zhàn))
簡(jiǎn)介面向快遞包裹與包裝盒質(zhì)檢的YOLOv10缺陷檢測(cè)權(quán)重包模型已訓(xùn)練完成可直接加載權(quán)重進(jìn)行推理檢測(cè)適配物流分揀、倉(cāng)儲(chǔ)品控等自動(dòng)外觀缺陷識(shí)別場(chǎng)景。附帶1200多張快遞包裹與包裝盒缺陷檢測(cè)數(shù)據(jù)集已劃分train、val、test目錄配置好data.yamltxt格式標(biāo)簽也可供YOLOv5、YOLOv7、YOLOv8、YOLOv9等算法復(fù)用訓(xùn)練。資源共2000個(gè)文件主體為982個(gè)txt標(biāo)注與1002個(gè)xml標(biāo)注配合15個(gè)說明文檔和1個(gè)Python腳本壓縮包76.86MB結(jié)構(gòu)清晰便于快速接入。目前已有121人學(xué)習(xí)下載。拿到后可立即用于實(shí)際檢測(cè)驗(yàn)證也能借助現(xiàn)成數(shù)據(jù)集與配置繼續(xù)微調(diào)訓(xùn)練減少數(shù)據(jù)整理和適配工作量適合有一定目標(biāo)檢測(cè)基礎(chǔ)的開發(fā)者直接上手。1. 為什么“1200 多張圖 訓(xùn)練好的權(quán)重”夠直接上產(chǎn)線試跑快遞包裹和包裝盒的缺陷檢測(cè)聽起來是個(gè)小眾方向但干過物流自動(dòng)化和制造業(yè)質(zhì)檢的人都知道它比想象中更常被問到破損、壓痕、污漬、封口不良、膠帶異常這些缺陷形態(tài)雜、背景亂、光照不穩(wěn)定通用目標(biāo)檢測(cè)模型直接拿來用經(jīng)常是訓(xùn)練出來一堆框、實(shí)際漏檢一片。而 YOLOv10 算法配合已經(jīng)訓(xùn)練好的權(quán)重和 1200 多張缺陷檢測(cè)數(shù)據(jù)集正好解決了這個(gè)尷尬——你不需要從零開始標(biāo)數(shù)據(jù)、跑幾百輪訓(xùn)練才能看到效果拿到權(quán)重先跑通推理確認(rèn)邊界后再?zèng)Q定要不要微調(diào)。這篇文章就圍繞這套東西講清楚三件事拿到權(quán)重和數(shù)據(jù)集后該怎么盤、怎么推理、怎么用這批數(shù)據(jù)重新微調(diào)以及快遞包裹場(chǎng)景下那些最容易翻車的坑。2. 拿到權(quán)重和數(shù)據(jù)集后先盤清楚目錄結(jié)構(gòu)、標(biāo)注格式與模型文件的實(shí)際約束2.1 1200 多張圖在這個(gè)量級(jí)意味著什么1200 多張圖在制造業(yè)缺陷檢測(cè)數(shù)據(jù)集里屬于“中小樣本”。像 COCO、DOTA 那種幾萬張的大數(shù)據(jù)集訓(xùn)練策略可以很奔放但 1200 張圖如果還分了六七個(gè)缺陷類別平均每個(gè)類別可能只有一兩百個(gè)實(shí)例這時(shí)候最怕的不是模型容量不夠而是過擬合和類別不均衡。不過對(duì)這個(gè)項(xiàng)目來說1200 張圖不是用來從零訓(xùn)練的它有兩個(gè)實(shí)際作用一是配合已經(jīng)訓(xùn)練好的權(quán)重做驗(yàn)證確認(rèn)模型在你自己拍的樣圖上表現(xiàn)如何二是給你一個(gè)“數(shù)據(jù)集參照物”照它的目錄結(jié)構(gòu)和標(biāo)注格式去整理自己的數(shù)據(jù)。YOLO 格式的數(shù)據(jù)集目錄結(jié)構(gòu)通常長(zhǎng)這樣express_defect/ ├── data.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/每張 images/train/xxx.jpg 圖片對(duì)應(yīng)一個(gè)同名 txt 文件放在 labels/train/xxx.txt 里。這個(gè) txt 的每一行代表一個(gè)目標(biāo)格式固定類別 ID、歸一化后的中心點(diǎn) x、中心點(diǎn) y、歸一化后的寬和高。舉個(gè)例子一行內(nèi)容是2 0.4531 0.7281 0.2344 0.1875含義就是“類別 ID 為 2 的目標(biāo)中心點(diǎn)在圖像寬度的 45.31%、高度的 72.81% 處寬度占整張圖的 23.44%高度占 18.75%”。拿到手之后我建議先別急著推理花十分鐘確認(rèn)三件事第一txt 文件里的類別 ID 是否連續(xù)、是否從 0 開始第二每個(gè)類別下的實(shí)例數(shù)量是否均衡如果有類別只有十幾條標(biāo)注后面推理時(shí)它的置信度基本不會(huì)高第三訓(xùn)練集和驗(yàn)證集圖片是否有重疊有些人整理數(shù)據(jù)時(shí)圖省事把同一批圖既放 train 又放 val會(huì)導(dǎo)致驗(yàn)證指標(biāo)虛高這個(gè)后面坑很大。2.2 權(quán)重文件怎么接進(jìn)你的環(huán)境版本與加載細(xì)節(jié)標(biāo)題里說了“模型已經(jīng)訓(xùn)練好可以直接推理檢測(cè)”所以核心交付物大概率是一個(gè)或多個(gè) pt 后綴的權(quán)重文件。在 ultralytics 框架里訓(xùn)練結(jié)束后 runs/detect/train 目錄下會(huì)同時(shí)生成 best.pt 和 last.ptbest.pt 是驗(yàn)證集上指標(biāo)最好的那一個(gè)last.pt 是最后一輪的權(quán)重。如果你的壓縮包里兩個(gè)文件都有日常推理用 best.pt想接著訓(xùn)練用 last.pt這是默認(rèn)的省心做法。加載權(quán)重時(shí)要注意 PyTorch 版本和 ultralytics 版本的匹配。YOLOv10 是在 2024 年 5 月隨著 ultralytics 框架更新被集成的如果你的 ultralytics 版本太老直接加載會(huì)報(bào) key 不匹配或直接拋異常。我的習(xí)慣是先把 ultralytics 升到當(dāng)前最新版再加載權(quán)重如果項(xiàng)目環(huán)境不允許升級(jí)至少保證 PyTorch 1.8 以上Python 3.8 以上這是 YOLOv10 能跑起來的最低基礎(chǔ)。另外 YOLOv10 和 YOLOv8 的模型結(jié)構(gòu)有差異v10 用了無 NMS 的端到端推理模型內(nèi)部通過 dual head 分配策略和 one-to-one head 直接輸出最終檢測(cè)框不再需要像 v8 那樣做 NMS 后處理。這個(gè)差異帶來一個(gè)實(shí)際好處導(dǎo)出 ONNX 后部署邏輯更簡(jiǎn)單推理鏈路上少一個(gè)超參數(shù)NMS IoU 閾值需要調(diào)。而你拿到的這份權(quán)重如果當(dāng)初是用 YOLOv10 結(jié)構(gòu)訓(xùn)練的注意不要拿它硬套 YOLOv8 的部署腳本輸出張量的解析方式不一樣細(xì)節(jié)在第 5 章的坑里展開。2.3 拿到壓縮包后的檢查清單每類項(xiàng)目交付物的整理習(xí)慣不太一樣但按下面的表檢查一遍能避免很多低級(jí)問題。下表是常見檢查項(xiàng)建議逐條對(duì)照你手里的文件確認(rèn)檢查項(xiàng)關(guān)注點(diǎn)檢查方法images 與 labels 目錄結(jié)構(gòu)是否按 train/val 分好用文件管理器直接看目錄層級(jí)標(biāo)簽與圖片對(duì)應(yīng)關(guān)系每張圖是否都有同名 txt寫腳本統(tǒng)計(jì) images 數(shù)量與 labels 數(shù)量是否一致類別 names 順序names 列表與 txt 中 ID 是否一致打開 data.yaml 與任意一個(gè) txt 對(duì)照驗(yàn)證集規(guī)模val 占總圖比例是否合理一般不低于 10%低于 5% 時(shí)指標(biāo)水分大權(quán)重文件完整性best.pt 能否被 torch.load 加載跑一次 yolo detect predict 試推理是否附帶 data.yaml后續(xù)微調(diào)直接復(fù)用沒有 yaml 時(shí)按第 4 章自己創(chuàng)建這六項(xiàng)里最容易出錯(cuò)的是第三項(xiàng)。很多人訓(xùn)練時(shí)改過類別順序但沒有同步修改已有標(biāo)注文件導(dǎo)致模型推斷出的類別名和實(shí)際標(biāo)注對(duì)不上。拿到權(quán)重后先跑一張圖看看輸出的類別名是否符合預(yù)期再批量跑別一上來就全量推理。3. 一條命令跑通推理從命令行到 Python API 的參數(shù)取舍3.1 最小推理命令先跑通再談優(yōu)化如果是第一次接觸這套權(quán)重我強(qiáng)烈建議先用命令行跑通一張圖出結(jié)果后再上 Python API。命令行方式代碼少、參數(shù)透明出問題也好排查。在虛擬環(huán)境里安裝好 ultralytics 之后進(jìn)入權(quán)重和測(cè)試圖片所在目錄執(zhí)行yolo detect predict \ modelbest.pt \ source./test_images/demo.jpg \ conf0.25 \ iou0.7 \ imgsz640 \ saveTrue這條命令的意思是把 best.pt 加載進(jìn)來對(duì) demo.jpg 做預(yù)測(cè)置信度閾值 0.25IoU 閾值 0.7輸入圖像尺寸 640預(yù)測(cè)結(jié)果畫框后保存到 runs/detect/predict 目錄。第一次跑通后把 source 參數(shù)換成一張包含多個(gè)包裹、有重疊、有光照反光的現(xiàn)場(chǎng)圖看框的位置和置信度分布是否合理。如果圖片路徑是個(gè)目錄ultralytics 會(huì)遍歷整個(gè)目錄做推理。這一步驗(yàn)證有兩個(gè)目的一是確認(rèn)權(quán)重能正常加載、輸出也沒問題二是評(píng)估這套權(quán)重對(duì)你實(shí)際場(chǎng)景的適應(yīng)程度。如果一個(gè)典型的破損包裹圖沒有一個(gè)框的置信度超過 0.5那基本可以判斷訓(xùn)練數(shù)據(jù)里的場(chǎng)景和你的現(xiàn)場(chǎng)差異較大后續(xù)需要微調(diào)而不是直接部署。3.2 Python API 推理把框取出來變成自己的業(yè)務(wù)數(shù)據(jù)命令行適合驗(yàn)證但真正接產(chǎn)線或者做批量后處理還是要用 Python API。YOLOv10 作為 ultralytics 家族的成員推理接口和 YOLOv8 完全一致以下代碼是一個(gè)可以直接改的最小模板from ultralytics import YOLO # 加載訓(xùn)練好的權(quán)重路徑可以是絕對(duì)路徑或相對(duì)路徑 model YOLO(best.pt) # 對(duì)單張圖片做推理conf/iou/imgsz 與命令行參數(shù)一一對(duì)應(yīng) results model.predict( sourcetest_images/demo.jpg, conf0.25, iou0.7, imgsz640, device0, # 用 0 號(hào) GPU沒有 GPU 就填 cpu saveFalse, # 先用 False自己控制保存邏輯 verboseFalse, # 關(guān)掉逐張打印批量時(shí)日志更干凈 ) # results 是列表每個(gè)元素對(duì)應(yīng) source 里的一個(gè)輸入 for r in results: boxes r.boxes print(檢測(cè)到, len(boxes), 個(gè)目標(biāo)) for box in boxes: cls_id int(box.cls[0]) # 類別 ID name model.names[cls_id] # 類別名從權(quán)重自帶 names 里取 conf float(box.conf[0]) # 該框的置信度 x1, y1, x2, y2 box.xyxy[0].tolist() # 框的左上角與右下角坐標(biāo) print(f{name} {conf:.3f} ({x1:.1f}, {y1:.1f}, {x2:.1f}, {y2:.1f}))這個(gè)模板里的關(guān)鍵點(diǎn)是model.names 不是從外部配置文件讀的而是權(quán)重文件內(nèi)部攜帶的類別名列表。也就是說你不需要手動(dòng)維護(hù)一個(gè)類別名數(shù)組只要權(quán)重文件是正確的names 就自動(dòng)對(duì)得上。如果你在推理時(shí)打印出的類別名和預(yù)期不符那問題一定出在訓(xùn)練時(shí) data.yaml 的 names 順序上和第 2 章提到的檢查項(xiàng)呼應(yīng)。批量推理時(shí)要注意內(nèi)存控制。如果一次傳入幾百?gòu)垐D片的路徑列表ultralytics 內(nèi)部會(huì)根據(jù) batch 參數(shù)自動(dòng)分批但默認(rèn)情況下一張一張跑是最穩(wěn)的。我做批量壓測(cè)時(shí)會(huì)先跑 50 張、觀察顯存占用穩(wěn)定后再加大輸入規(guī)模。3.3 推理參數(shù)怎么調(diào)conf、iou、imgsz 的取舍這四個(gè)參數(shù)幾乎決定了你部署后的體驗(yàn)它們之間是聯(lián)動(dòng)的不能孤立地調(diào)。直接用下表做參考再結(jié)合你自己的業(yè)務(wù)邏輯定值參數(shù)默認(rèn)值調(diào)低的影響調(diào)高的影響快遞包裹場(chǎng)景建議conf0.25召回提升誤檢變多輸出框變密漏檢變多但剩下的框更可信先 0.25 看全貌再按現(xiàn)場(chǎng)接受度調(diào) 0.3~0.5iou0.7相鄰框更容易被合并重復(fù)框變少重復(fù)框變多但不會(huì)吞掉小目標(biāo)包裹重疊嚴(yán)重時(shí)降到 0.5imgsz640推理變快小缺陷可能丟失小目標(biāo)壓痕、小破洞更容易檢出有細(xì)微信號(hào)時(shí)先試 1280再評(píng)估速度max_det300輸出目標(biāo)數(shù)上限上限越高越不容易漏目標(biāo)包裹密集時(shí)調(diào)到 400imgsz 是被低估的一個(gè)參數(shù)。訓(xùn)練時(shí)用的什么尺寸推理時(shí)最好用同樣尺寸如果你把訓(xùn)練時(shí) 640 的模型拿到 1280 上推理理論上對(duì)但實(shí)際會(huì)改變特征金字塔的感受野分布小目標(biāo)的檢出能力提高的同時(shí)一些原本被抑制的背景噪聲也會(huì)被放大。所以改 imgsz 之前先確認(rèn)訓(xùn)練時(shí)的 imgsz能查訓(xùn)練結(jié)果的 results.csv 或保存的配置文件就不難確認(rèn)。如果確認(rèn)不了先用默認(rèn) 640再用 1280 做 A/B 對(duì)比看現(xiàn)場(chǎng)最關(guān)鍵的那類缺陷檢出率變化。4. 用你自己的數(shù)據(jù)重新微調(diào)yaml 文件、訓(xùn)練命令與輸出權(quán)重怎么讀4.1 整理自己的標(biāo)注數(shù)據(jù)把圖片和標(biāo)簽組織成 YOLO 格式前面說過1200 多張數(shù)據(jù)集的價(jià)值之一是作為整理自有數(shù)據(jù)的參照?,F(xiàn)實(shí)中你從產(chǎn)線拿到的往往是手機(jī)拍的、工業(yè)相機(jī)拍的、監(jiān)控截取的各種格式圖片標(biāo)注工具可能是 LabelImg、labelme 或者 X-AnyLabeling。最終都要轉(zhuǎn)成上面第 2 章描述的 YOLO 目錄結(jié)構(gòu)。轉(zhuǎn)換這一步最常見的錯(cuò)誤是標(biāo)注框坐標(biāo)沒有歸一化。YOLO 格式要求 x_center、y_center、w、h 全部除以圖片寬高轉(zhuǎn)成 0 到 1 之間的小數(shù)而不是像素值。如果從 labelme 的 JSON 里把 polygon 或 rectangle 的絕對(duì)坐標(biāo)直接寫進(jìn) txt訓(xùn)練時(shí) loss 會(huì)異常大、模型完全學(xué)不進(jìn)去。寫轉(zhuǎn)換腳本時(shí)核心代碼就幾句話import json with open(annotation.json) as f: ann json.load(f) img_w ann[imageWidth] img_h ann[imageHeight] for shape in ann[shapes]: label shape[label] x1, y1 shape[points][0] x2, y2 shape[points][1] # 修正左上角/右下角順序 x_min, x_max sorted([x1, x2]) y_min, y_max sorted([y1, y2]) # 轉(zhuǎn)成 YOLO 歸一化格式 x_center ((x_min x_max) / 2) / img_w y_center ((y_min y_max) / 2) / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h print(label, round(x_center, 6), round(y_center, 6), round(w, 6), round(h, 6))另一個(gè)容易被忽略的點(diǎn)是“空標(biāo)簽圖”。如果某張圖確實(shí)沒有缺陷它可以不帶 txt 文件ultralytics 會(huì)把它當(dāng)作負(fù)樣本不會(huì)報(bào)錯(cuò)。但如果所有圖片都沒有標(biāo)簽訓(xùn)練會(huì)直接卡在 “No labels found” 的警告上幾千張圖白跑。我一般會(huì)在整理完后寫一個(gè)校驗(yàn)?zāi)_本統(tǒng)計(jì)所有圖片對(duì)應(yīng) txt 的行數(shù)分布類別分布以及檢查每條標(biāo)注的寬高是否小于等于 0、中心點(diǎn)坐標(biāo)是否超出 0~1 范圍把臟數(shù)據(jù)提前清掉。4.2 yolov10 yaml 文件怎么創(chuàng)建目錄與類別名的映射在 ultralytics 框架里訓(xùn)練自定義數(shù)據(jù)集yaml 文件是唯一的數(shù)據(jù)入口。它不需要多復(fù)雜卻要求所有字段都準(zhǔn)確。下面是一個(gè)以快遞包裹缺陷為例的 yaml 模板names 列表要和你的標(biāo)簽 ID 嚴(yán)格對(duì)應(yīng)# express_defect.yaml # path 指向數(shù)據(jù)集根目錄建議用絕對(duì)路徑避免換目錄后找不到數(shù)據(jù) path: D:/datasets/express_defect # train 和 val 是相對(duì) path 的目錄 train: images/train val: images/val # 類別數(shù)量必須與 names 長(zhǎng)度一致 nc: 6 # names 順序決定 txt 里類別 ID 的含義 names: 0: break 1: dent 2: stain 3: wrinkle 4: tape_abnormal 5: unsealed創(chuàng)建 yaml 文件時(shí)最容易踩的坑是 path 用了相對(duì)路徑。如果你執(zhí)行訓(xùn)練命令的終端目錄和數(shù)據(jù)目錄不在同一層級(jí)fill 相對(duì)路徑大概率報(bào) FileNotFoundError。我的習(xí)慣是直接寫絕對(duì)路徑macOS/Linux 寫/Users/xxx/datasets/express_defectWindows 寫D:/datasets/express_defect正斜杠和反斜杠在 ultralytics 里都能識(shí)別但正斜杠更保險(xiǎn)。另外 nc 的值如果和 names 個(gè)數(shù)不一致訓(xùn)練不會(huì)報(bào)錯(cuò)但驗(yàn)證時(shí)會(huì)發(fā)現(xiàn)你期望的類別 ID 和模型輸出的 ID 錯(cuò)位——這個(gè)錯(cuò)位最隱蔽因?yàn)闇y(cè)試集上 mAP 可能還挺高但部署時(shí)類別名全反了。4.3 微調(diào)命令在別人權(quán)重基礎(chǔ)上繼續(xù)訓(xùn)練1200 多張圖直接從頭訓(xùn)練不劃算最可靠的做法是加載一個(gè)預(yù)訓(xùn)練權(quán)重再在這個(gè)基礎(chǔ)上微調(diào)。如果你只是想加快收斂用官方提供的 YOLOv10 預(yù)訓(xùn)練權(quán)重比如 yolov10s.pt、yolov10m.pt下載后放到當(dāng)前目錄命令里直接指定路徑。如果你覺得別人訓(xùn)練好的這個(gè)快遞包裹權(quán)重對(duì)你的場(chǎng)景已經(jīng)比較接近也可以拿它作為起點(diǎn)接著訓(xùn)效果通常比從 COCO 預(yù)訓(xùn)練開始更好但前提是類別定義和它一致。下面是一套適合 1200 張左右數(shù)據(jù)量級(jí)的訓(xùn)練命令參數(shù)yolo detect train \ dataexpress_defect.yaml \ modelyolov10s.pt \ epochs120 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ freeze10 \ close_mosaic10 \ ampTrue \ patience30逐項(xiàng)說下為什么這么設(shè)。模型用 yolov10s 是因?yàn)榭爝f包裹缺陷里破損和污漬這類目標(biāo)大小差異很大但整體不算極端小目標(biāo)s 級(jí)能平衡精度和顯存只有幾百兆顯存的舊顯卡換成 yolov10n推理精度會(huì)略降但速度更快訓(xùn)練時(shí)長(zhǎng)也明顯縮短。epochs 設(shè) 120 是因?yàn)?1200 張圖在小數(shù)據(jù)集上 60 到 100 輪基本收斂120 是留出余量配合早停機(jī)制patience30 表示驗(yàn)證集指標(biāo)連續(xù) 30 輪不提升就自動(dòng)停止。freeze10 表示凍結(jié)模型前 10 層參數(shù)這個(gè)在小數(shù)據(jù)集上是防過擬合非常有效的常規(guī)操作——主干網(wǎng)絡(luò)底層學(xué)的是邊緣、紋理這類通用特征凍結(jié)它們讓訓(xùn)練只更新后層能明顯緩解 val loss 與 train loss 差距過大的問題。close_mosaic10 表示最后 10 個(gè) epoch 關(guān)閉 mosaic 增強(qiáng)因?yàn)?mosaic 會(huì)切碎圖片、引入大量拼接邊界長(zhǎng)期開著會(huì)讓模型學(xué)到奇怪的偽影后段關(guān)掉再精調(diào)幾輪是 YOLO 系列訓(xùn)練里常見做法。4.4 訓(xùn)練輸出怎么讀best.pt、last.pt 與 results.csv訓(xùn)練結(jié)束后runs/detect/train 目錄下會(huì)有一批文件真正有用的三樣是 best.pt、last.pt 和 results.csv。results.csv 是每一輪的訓(xùn)練指標(biāo)打開后可以看到 train 和 val 兩套 loss以及 precision、recall、mAP50、mAP50-95 等指標(biāo)曲線。判斷訓(xùn)練有沒有出問題先看 val mAP50 是不是穩(wěn)步上升再對(duì)比 train loss 和 val loss 的走勢(shì)如果 train loss 一路下降但 val loss 在某個(gè) epoch 后掉頭向上就是過擬合的典型信號(hào)。關(guān)于選權(quán)重我平時(shí)主要看兩個(gè)維度一是 val mAP50 峰值在哪個(gè) epoch 附近二是 val loss 最低點(diǎn)在哪個(gè) epoch。如果兩者不一致優(yōu)先選 val loss 最低點(diǎn)對(duì)應(yīng)的那輪權(quán)重因?yàn)?mAP 只統(tǒng)計(jì)置信度閾值下的匹配結(jié)果而 val loss 衡量的是整個(gè)分布的擬合程度后者在真實(shí)場(chǎng)景里往往更穩(wěn)。確認(rèn)權(quán)重文件名不用改best.pt 在訓(xùn)練結(jié)束后就是按指標(biāo)選出來的直接拿去推理就可以。5. 避坑快遞包裹檢測(cè)最容易翻車的 5 個(gè)環(huán)節(jié)5.1 現(xiàn)象驗(yàn)證集指標(biāo)還可以一到產(chǎn)線新批次漏檢一片原因驗(yàn)證集圖片是從訓(xùn)練數(shù)據(jù)里按比例抽出來的和訓(xùn)練集共享同一批相機(jī)、同一批光照條件、同一種擺放方式指標(biāo)自然好看。但產(chǎn)線環(huán)境換一條皮帶線、換一個(gè)光源角度背景紋理和反射特征就變了模型的泛化能力跟不上。解決不要只依賴訓(xùn)練時(shí)的 val 指標(biāo)單獨(dú)留一份“現(xiàn)場(chǎng)壓測(cè)集”。從目標(biāo)部署位置重新拍 100 到 200 張圖包含不同時(shí)間段、不同光照強(qiáng)度、不同包裹破損程度這份數(shù)據(jù)不參與訓(xùn)練只用來做最終評(píng)估。如果壓測(cè)集上的 mAP50 比訓(xùn)練 val 低 0.2 以上說明現(xiàn)場(chǎng)跟訓(xùn)練分布差距太大需要采集現(xiàn)場(chǎng)圖做增量微調(diào)。這一步?jīng)]做好部署再快都是白搭。5.2 現(xiàn)象明顯的破洞和壓痕在圖上清清楚楚模型就是檢不出來原因這類缺陷在整張圖中占的像素比例太小。如果一張 1920x1080 的現(xiàn)場(chǎng)圖里破損區(qū)域只有 40x50 像素輸入 imgsz640 縮放后缺陷可能只剩下十幾個(gè)像素經(jīng)過幾次下采樣特征圖里基本就丟了。解決先用 imgsz1280 試通常能顯著提升小缺陷召回。還不夠的話用 SAHI 這類切片推理工具把大圖切成 640x640 的重疊小塊分別推理再合并結(jié)果缺陷相對(duì)尺寸變大檢出率提升明顯。代價(jià)是推理時(shí)間翻倍但對(duì)離線抽檢和產(chǎn)線慢速輸送場(chǎng)景完全夠用。注意切片重疊率設(shè)個(gè) 0.2 就夠了太高會(huì)引入大量重復(fù)檢測(cè)框。5.3 現(xiàn)象想把模型接進(jìn) OpenCV 的現(xiàn)有缺陷檢測(cè)流程加載 ONNX 后取不到框原因OpenCV DNN 模塊的通用 YOLO 后處理腳本大多是為 YOLOv5/v8 的多個(gè)輸出頭寫的會(huì)去解析三個(gè)尺度的特征圖輸出。而 YOLOv10 無 NMS 的設(shè)計(jì)導(dǎo)出的 ONNX 輸出是一個(gè)固定的端到端檢測(cè)結(jié)果張量格式是 (1, 300, 6) 這樣的候選框數(shù)組每一行是 x1、y1、x2、y2、score、cls沒有三個(gè)輸出的分支直接用舊的 decode 邏輯解析當(dāng)然拿不到框。解決先在 OpenCV 里把輸出張量的形狀打印出來確認(rèn)是端到端格式后寫一段新的后處理取前 N 行按置信度過濾跳過無效行。如果不想自己解析更省事的是用 onnxruntime 配合 ultralytics 官方示例里的 YOLOv10 解碼代碼。說句實(shí)在話除非你的平臺(tái)只能跑 OpenCV DNN否則推薦用 onnxruntime它的算子和速度控制更好部署心智負(fù)擔(dān)也更小。5.4 現(xiàn)象訓(xùn)練一輪下來 loss 降了但 val mAP50 始終在 0.3 以下原因除了數(shù)據(jù)量本身小之外最常見的是數(shù)據(jù)增強(qiáng)配置和閥值設(shè)定不匹配。1200 張圖把 mosaic 增強(qiáng)開到默認(rèn) 1.0每張訓(xùn)練圖里可能有四張圖拼接導(dǎo)致大量訓(xùn)練樣本帶有拼接邊界模型學(xué)到的目標(biāo)邊界是“斷裂”的另外小數(shù)據(jù)集內(nèi)部如果 train/val 劃分不當(dāng)驗(yàn)證集里包含了和訓(xùn)練集高度相似的圖片val loss 和 mAP 也會(huì)互相扯后腿。解決調(diào)低增強(qiáng)強(qiáng)度。mosaic 從 1.0 降到 0.3 到 0.5close_mosaic 提前到第 20 到第 30 個(gè) epoch給模型一段“干凈”的收斂期同時(shí)把 hsv_h、hsv_s、hsv_v 這些顏色增強(qiáng)適當(dāng)調(diào)大??爝f包裹的塑料表面和紙箱對(duì)顏色變化非常敏感適當(dāng)增強(qiáng)顏色抖動(dòng)反而能提升泛化。如果調(diào)完一輪后 mAP50 還上不去回頭檢查 labels 里的標(biāo)注是否有多余的重疊框或者某些類別本身實(shí)例數(shù)太少合并掉不足 20 個(gè)實(shí)例的類別通常能讓整體指標(biāo)明顯抬升。5.5 現(xiàn)象best.pt 與 last.pt 差距巨大線上表現(xiàn)像“黑匣子”原因best.pt 是在驗(yàn)證集上按 mAP 選出來的驗(yàn)證集本身不能代表現(xiàn)場(chǎng)分布這兩者差距大說明訓(xùn)練過程里模型波動(dòng)嚴(yán)重最后的權(quán)重選擇策略更多是“矮子里拔將軍”。如果連 doctored 的 val 都不能穩(wěn)住更別提線上穩(wěn)定運(yùn)行了。解決把訓(xùn)練收斂節(jié)奏調(diào)穩(wěn)而不是追求峰值。訓(xùn)練結(jié)束后不要只看 best.pt把 last.pt 也拿到壓測(cè)集上跑一遍如果兩者差異大說明這是模型本身對(duì)某些類別的不穩(wěn)定表現(xiàn)而不是權(quán)重文件的問題。如果時(shí)間允許可以把 epochs 提高到 180配合早停讓模型在更多 epochs 里尋找穩(wěn)定解最后再選 val loss 最低的幾個(gè) epoch 的權(quán)重做集成平均。這個(gè)經(jīng)驗(yàn)是實(shí)打?qū)崗漠a(chǎn)線上磨出來的單點(diǎn)指標(biāo)的峰值往往不可靠穩(wěn)定解才是能交付的解。6. 進(jìn)階先做批量壓測(cè)再談 ONNX 與 TensorRT 部署權(quán)重拿到手、推理也跑通了這時(shí)候最容易犯的錯(cuò)誤是拿幾張好看的圖就去匯報(bào)效果。我會(huì)建議先做一次批量壓測(cè)把真實(shí)的表現(xiàn)量化出來。做法很簡(jiǎn)單準(zhǔn)備 200 到 500 張覆蓋不同缺陷類型和不同背景的樣圖循環(huán)推理統(tǒng)計(jì)每類的檢出數(shù)量、平均置信度、單幀耗時(shí)把結(jié)果輸出成 JSON 存檔。from ultralytics import YOLO from pathlib import Path import json, time model YOLO(best.pt) image_paths [str(p) for p in Path(batch_images).glob(*.jpg)] t0 time.time() results model.predict( sourceimage_paths, conf0.35, imgsz640, device0, verboseFalse, ) total_time time.time() - t0 records [] for impath, r in zip(image_paths, results): objs [] for box in r.boxes: objs.append({ cls: model.names[int(box.cls[0])], conf: float(box.conf[0]), box: [float(v) for v in box.xyxy[0].tolist()], }) records.append({image: impath, objects: objs}) out {total_time: total_time, fps: len(image_paths) / total_time, records: records} json.dump(out, open(batch_result.json, w), indent2)這里一次傳入所有圖片路徑ultralytics 內(nèi)部會(huì)按 batch 自動(dòng)分批比循環(huán)單張調(diào)用省去大量加載開銷。壓測(cè)的目的是摸清兩個(gè)數(shù)一是 fps判斷能不能滿足產(chǎn)線節(jié)拍二是每類缺陷的檢出數(shù)量如果某個(gè)類別在整個(gè)壓測(cè)集里一框未出基本可以判定這個(gè)類別在當(dāng)前場(chǎng)景下不可用要回到第 4 章的微調(diào)流程去補(bǔ)數(shù)據(jù)。壓測(cè)集要單獨(dú)存放不要隨手從訓(xùn)練集里拷圖否則壓測(cè)結(jié)果就是第 5 章說的“自欺欺人”。批量壓測(cè)通過之后才考慮正式部署形態(tài)。YOLOv10 的端到端設(shè)計(jì)對(duì)部署很友好導(dǎo)出 ONNX 的命令是yolo export modelbest.pt formatonnx imgsz640 halfFalse dynamicFalse simplifyTrue三個(gè)參數(shù)的含義要心里有數(shù)imgsz 固定 640 而不是 dynamic是為了換取更快的推理速度和更小的顯存占用因?yàn)閯?dòng)態(tài)尺寸會(huì)迫使推理框架預(yù)留最大尺寸的顯存half 半精度在部分老顯卡和 edge 設(shè)備上不支持先保持 False確認(rèn)目標(biāo)平臺(tái)支持 FP16 再開simplify 用 onnxsim 簡(jiǎn)化計(jì)算圖省略掉不必要的節(jié)點(diǎn)TensorRT 轉(zhuǎn)換時(shí)能省去很多算子的兼容問題。導(dǎo)出后用 onnxruntime 驗(yàn)證一次輸入輸出維度和 pt 推理結(jié)果的一致性別直接扔給 TensorRT。TensorRT 轉(zhuǎn)換我一般只在延遲要求特別高的時(shí)候做??爝f包裹缺陷檢測(cè)大多不是高速動(dòng)態(tài)抓拍場(chǎng)景ONNX 在普通 GPU 上已經(jīng)能跑到幾十毫秒一幀TensorRT 的收益集中在極端延遲敏感的流水線上如果只是產(chǎn)線抽檢完全沒必要增加這層維護(hù)成本。我現(xiàn)在的習(xí)慣是拿到任何一套訓(xùn)練好的權(quán)重先跑批量壓測(cè)打分再?zèng)Q定是直接部署還是回到數(shù)據(jù)側(cè)微調(diào)最后才碰導(dǎo)出和加速——這三步順序換一下往往會(huì)多走兩輪彎路。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取