據(jù)集VOC+YOLO雙格式:從標注解讀到Y(jié)OLO訓(xùn)練避坑指南)
簡介這份藥品包裝盒目標檢測數(shù)據(jù)集面向計算機視覺入門者、算法研究及工業(yè)質(zhì)檢項目開發(fā)者提供統(tǒng)一格式的真實場景圖像解決訓(xùn)練檢測模型時缺乏高質(zhì)量標注數(shù)據(jù)的痛點。壓縮包共2000個文件包含1032張藥品包裝盒圖片、VOC格式xml標注和YOLO格式txt標注整體大小約27.95MB便于下載和部署同一圖像同時提供兩種格式標注可在Pascal VOC與YOLO訓(xùn)練框架間無縫切換。數(shù)據(jù)集中僅含box一個類別共1468個矩形框均由labelImg人工框選標注規(guī)范。已有1285人學習/下載拿到后可自行劃分訓(xùn)練集與驗證集用于YOLO、Faster R-CNN等模型訓(xùn)練也可服務(wù)于藥品包裝檢測、藥房自動盤點、包裝缺損識別等場景。1. 藥品包裝盒數(shù)據(jù)集1032張雙格式標注圖省掉從零標框的幾天時間做藥品包裝盒識別時最耽誤時間的不是寫模型而是標數(shù)據(jù)。去過藥廠倉庫的人都知道藥盒反光、瓶身和盒子挨得近、同類包裝尺寸差異不大框稍微偏一點訓(xùn)練出來的效果就差很多。這份藥品包裝盒數(shù)據(jù)集正好解決這個問題1032張圖片已經(jīng)全部標注完成同時給出VOC和YOLO兩種格式按需取用就能直接進訓(xùn)練流程。整理這份筆記時我把目錄結(jié)構(gòu)、坐標格式、訓(xùn)練命令和踩過的坑都過了一遍結(jié)論是它適合兩類人手頭有藥品檢測需求但不想從零標框的開發(fā)者以及想用一份干凈數(shù)據(jù)練習YOLO目標檢測完整流程的入門者。下面按“先看結(jié)構(gòu)→再訓(xùn)練→后避坑”的順序展開。2. 拆開壓縮包前先搞懂VOC與YOLO標注目錄、坐標與類別的三個細節(jié)2.1 目錄結(jié)構(gòu)里到底有什么JPEGImages、Annotations、labels 三件套很多從網(wǎng)上下數(shù)據(jù)集的人第一反應(yīng)是直接解壓找圖片和標注文件然后隨便指定一個路徑丟給訓(xùn)練腳本。這個做法不是不能跑而是很容易讓標注文件和圖片錯位。我的習慣是先看目錄結(jié)構(gòu)再抽樣看兩個標注文件最后才會動訓(xùn)練命令。這份數(shù)據(jù)集既然同時給了VOC和YOLO兩種格式解壓后的目錄一般會沿用Pascal VOC時代留下來的組織方式data/ ├── JPEGImages/ # 全部圖片jpg格式 ├── Annotations/ # VOC格式標注每張圖對應(yīng)一個xml ├── labels/ # YOLO格式標注每張圖對應(yīng)一個txt └── ImageSets/Main/ # 劃分文件 ├── train.txt ├── val.txt └── test.txtJPEGImages放圖片Annotations放VOC的xmllabels放YOLO的txtImageSets/Main里是數(shù)據(jù)集劃分。需要注意不同發(fā)布者打包習慣不一樣有的把labels做成了扁平的labels/*.txt有的直接給出labels/train和labels/val。先不要急著開始訓(xùn)練用命令數(shù)一下三個目錄文件數(shù)是否一致。ls JPEGImages | wc -l ls Annotations | wc -l ls labels | wc -l如果三個數(shù)字不一樣大概率存在漏標或者多余的xml/txt這種情況訓(xùn)練時會直接報錯。1032張圖對應(yīng)的xml和txt理論上都應(yīng)該是1032個但偶爾會因壓縮包傳輸丟文件出現(xiàn)數(shù)量不一致先在這一步確認清楚。2.2 坐標定義不同VOC的絕對框和YOLO的歸一化中心這個數(shù)據(jù)集叫“VOCYOLO格式”實際意思是同一種標注用兩種文件表達。VOC格式存的是XML目標框?qū)懗山^對像素坐標YOLO格式存的是文本每行一個目標內(nèi)容是類別id加歸一化中心點坐標和歸一化寬高。給個例子更直觀。annotation folderJPEGImages/folder filenameimg_0001.jpg/filename size width1920/width height1080/height depth3/depth /size object namemedicine_box/name bndbox xmin420/xmin ymin180/ymin xmax720/xmax ymax520/ymax /bndbox /object /annotation這是標準的Pascal VOC標注img_0001.jpg對應(yīng)的YOLO標簽文件img_0001.txt內(nèi)容只有一行0 0.296875 0.324074 0.156250 0.314815第一格0是類別id后面四個數(shù)分別是 x_center (420 720) / 2 / 1920 0.296875 y_center (180 520) / 2 / 1080 0.324074 w (720 - 420) / 1920 0.15625 h (520 - 180) / 1080 0.314815四個坐標全被歸一化到0到1之間不同分辨率的圖片就可以放進同一個batch訓(xùn)練。很多人第一次看YOLO標注會奇怪為什么只有一個框還要這么多位小數(shù)其實就是把絕對像素換算過去的。如果出現(xiàn)x_center或?qū)捀叽笥?那就是標注越界了后面第5章會詳細說。2.3 一眼看清類別與劃分用一段腳本統(tǒng)計標簽在開訓(xùn)之前我一般會先統(tǒng)計兩類信息類別數(shù)量以及每個類別在train/val/test里的分布。這個數(shù)據(jù)集從標題看是藥品包裝盒最常見的標注是單個medicine_box類但也不排除發(fā)布者把不同藥盒品牌標成了多類。在寫data.yaml之前必須知道到底有幾個類別否則nc寫錯訓(xùn)練直接報錯。import glob from collections import Counter label_files glob.glob(labels/*.txt) cls_counter Counter() all_ids set() for path in label_files: with open(path) as fp: for line in fp: line line.strip() if line: cls_id int(line.split()[0]) all_ids.add(cls_id) cls_counter[cls_id] 1 print(類別id集合:, sorted(all_ids)) print(類別id - 目標數(shù):, dict(cls_counter)) missing [] for img_path in glob.glob(JPEGImages/*.jpg): stem img_path.split(/)[-1].replace(.jpg, ) if not glob.glob(flabels/{stem}.txt): missing.append(stem) print(缺標簽的圖片數(shù):, len(missing), missing[:5])這段腳本做的事情很簡單遍歷所有l(wèi)abels/*.txt把每行第一個數(shù)字拿出來計數(shù)得到類別id是哪些、每個id出現(xiàn)多少次。然后反過來查JPEGImages里的jpg是否都有同名txt有缺就打印出具體文件名方便后面定位。參數(shù)上要注意兩點。第一類別id從0開始是YOLO系約定如果統(tǒng)計結(jié)果里出現(xiàn){1: 1032}而沒有任何0說明發(fā)布者把類別id從1開始編了訓(xùn)練前要把txt批量減1。第二腳本默認labels是扁平目錄如果labels下還有train和val子目錄就把glob路徑改成labels//.txt。這里不要硬編碼絕對路徑方便在解壓目錄直接跑。3. 用YOLO訓(xùn)練藥盒檢測從數(shù)據(jù)重排到第一個best.pt3.1 把雙格式整理成YOLO標準目錄先用2.3的腳本確認了類別和標簽完整性接下來要把文件挪成Ultralytics YOLO認識的標準目錄。YOLOv5、YOLOv8和YOLO11的默認數(shù)據(jù)布局都是datasets/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/如果你拿到的包已經(jīng)按這個布局組織直接跳到3.2。如果只有一個扁平的labels目錄就要按ImageSets/Main下的train.txt、val.txt、test.txt來重新放。這個步驟我一般用Python完成幾秒搞定。import os import shutil from pathlib import Path root Path(.) # 數(shù)據(jù)集解壓根目錄 split_map {train.txt: train, val.txt: val, test.txt: test} for split_file, subset in split_map.items(): split_path root / ImageSets / Main / split_file if not split_path.exists(): continue os.makedirs(root / images / subset, exist_okTrue) os.makedirs(root / labels / subset, exist_okTrue) with open(split_path) as fp: for line in fp: line line.strip() if not line: continue stem line.split(/)[-1] if stem.endswith(.jpg): stem stem[:-4] img_src root / JPEGImages / f{stem}.jpg lbl_src root / labels / f{stem}.txt if img_src.exists(): shutil.copy(img_src, root / images / subset / f{stem}.jpg) else: print(f[warn] 圖片不存在: {img_src}) if lbl_src.exists(): shutil.copy(lbl_src, root / labels / subset / f{stem}.txt) else: print(f[warn] 標簽不存在: {lbl_src})這里有個容易踩的細節(jié)ImageSets/Main里的行內(nèi)容可能是純文件名img_0001也可能是帶路徑的JPEGImages/img_0001.jpg。代碼里用split(/)[-1]取最后一段再判斷是否以.jpg結(jié)尾就是為了兼容這兩種寫法。shutil.copy是復(fù)制而不是移動保留原目錄文件這樣后續(xù)想對比VOC不會破壞原始數(shù)據(jù)。磁盤空間緊張時可以把copy改成move。3.2 寫data.yaml并檢查數(shù)據(jù)集train/val整理好后需要寫一個data.yaml告訴訓(xùn)練器類別名和路徑。放在數(shù)據(jù)集根目錄下我一般命名為medicine_box.yaml。# 數(shù)據(jù)集根目錄建議用絕對路徑避免不同機器工作目錄不一樣 path: /home/user/datasets/medbox train: images/train val: images/val # test 可留空也能在訓(xùn)練后單獨跑test test: images/test nc: 1 names: 0: medicine_box參數(shù)說明path是根目錄train和val都是相對path的路徑。names的鍵從0開始順序和標簽txt里的類別id嚴格對應(yīng)。如果你統(tǒng)計出來是多類names里要按順序列全比如0: box_a, 1: box_b。寫完先用一行命令驗證數(shù)據(jù)集能否加載。yolo detect train datamedicine_box.yaml epochs0 batch1 device0epochs0是Ultralytics的一種快速檢查方式它不會真正訓(xùn)練只做數(shù)據(jù)集加載并打印train/val圖片數(shù)和類別數(shù)。如果這里報錯不要急著調(diào)模型先回頭查目錄結(jié)構(gòu)和yaml。提示多人協(xié)作時絕對路徑會導(dǎo)致別人機器上失效這種情況可以用相對path例如path: ./datasets/medbox。但相對路徑是從終端工作目錄解析容易踩坑所以單機訓(xùn)練我始終用絕對路徑。3.3 訓(xùn)練參數(shù)怎么定epochs、imgsz、batch 與預(yù)訓(xùn)練權(quán)重數(shù)據(jù)集就緒接下來就是訓(xùn)練。以Ultralytics YOLO為例一行命令能跑yolo detect train datamedicine_box.yaml modelyolov8n.pt epochs100 batch16 imgsz640 device0我針對1032張這種量級的選參習慣是預(yù)訓(xùn)練權(quán)重從yolov8n或yolov8s開始先用n跑通流程再看效果換sepochs默認給100訓(xùn)練時會通過早停機制自動截斷不用為了湊數(shù)去改imgsz保持640這是YOLO系列的默認輸入尺寸如果藥盒在圖中比較小可以提高到960或1280但顯存占用和訓(xùn)練時間會明顯上漲batch在顯存允許范圍內(nèi)盡量大16G顯存跑yolov8n時batch16沒問題6G顯存就改batch8。訓(xùn)練過程中終端除了每輪的mAP還會打印box_loss、cls_loss、dfl_loss這幾個yolo損失函數(shù)分量。很多人只看最后的mAP50其實觀察損失曲線更有用如果loss在前20輪不降反升大部分情況是學習率過大或數(shù)據(jù)集劃分有問題如果loss降了但mAP不動就要檢查是不是藥盒目標本身太相似導(dǎo)致分類學不動。如果你想控制數(shù)據(jù)增強訓(xùn)練命令里可以加augment相關(guān)參數(shù)yolo detect train datamedicine_box.yaml modelyolov8s.pt epochs120 batch16 imgsz640 scale0.5 fliplr0.5 mosaic1.0scale控制隨機縮放范圍fliplr是水平翻轉(zhuǎn)概率mosaic是馬賽克增強。藥盒檢測通常不需要太多翻轉(zhuǎn)因為包裝盒上的文字方向有實際含義豎直方向翻轉(zhuǎn)反而會讓模型學到錯誤特征。3.4 訓(xùn)練產(chǎn)物和損失曲線怎么看訓(xùn)練結(jié)束后在runs/detect/train目錄會生成weights/best.pt、weights/last.pt、results.png、confusion_matrix.png、args.yaml等一堆文件。最常用的是兩個權(quán)重best.pt是驗證集效果最好的模型last.pt是最后一輪epoch的模型。日常推理和部署直接取best.pt不要為了“多訓(xùn)幾輪更好”就默認用last.pt。last.pt更適合在斷點續(xù)訓(xùn)時作為預(yù)訓(xùn)練權(quán)重。results.png是訓(xùn)練過程的匯總圖從上到下分別是box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95??催@條曲線如果最后的mAP50在0.5以下大概率是標注不全或類別混淆如果mAP50能到0.8以上但mAP50-95掉得厲害說明框沒有貼合邊界藥盒邊緣被壓線了。這些都是經(jīng)驗值不同場景會有浮動但能幫你在調(diào)參前快速定位問題方向。驗證時還可以看confusion_matrix.png理清哪兩類容易互相誤檢。4. 反向使用VOC格式XML轉(zhuǎn)YOLO腳本與舊工程兼容方案4.1 為什么VOC格式到今天還有用雙格式數(shù)據(jù)集里YOLO txt顯然是新工程首選那XML還有必要留嗎有的而且用到的頻率不低。本地標注工具如果不換labelImg導(dǎo)出就是VOC XML很多公司老代碼庫里的檢測pipeline只寫了讀VOC的Loader比如基于Detectron2或Faster R-CNN的工程還有做數(shù)據(jù)審核的同事習慣在labelImg里打開XML回看標注。這種情況下直接把訓(xùn)練用的txt再轉(zhuǎn)回XML比重新標注快得多。Pascal VOC這套XML標準雖然老但勝在結(jié)構(gòu)透明任何語言都能用標準庫解析不帶額外依賴。所以拿到“VOCYOLO格式”的數(shù)據(jù)集等于同時拿到了一條通往老框架和YOLO訓(xùn)練的通路。而反過來當你從別的數(shù)據(jù)集拿到只有XML的標注想用YOLO跑也需要把XML轉(zhuǎn)成txt。這個轉(zhuǎn)換不應(yīng)該靠手工重復(fù)下面給兩個小腳本第一步是從XML轉(zhuǎn)到Y(jié)OLO。4.2 XML轉(zhuǎn)YOLO txt一個不讀圖片的高效腳本用Python標準庫xml.etree.ElementTree解析XML圖片寬高直接從XML的size節(jié)點讀取不需要額外打開圖片速度會快很多。import xml.etree.ElementTree as ET import glob import os CLASS_NAMES [medicine_box] # 需要和訓(xùn)練用的類別順序保持一致 def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) stem os.path.splitext(os.path.basename(xml_path))[0] lines [] for obj in root.findall(object): name obj.findtext(name) if name not in CLASS_NAMES: continue cls_id CLASS_NAMES.index(name) bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) os.makedirs(out_dir, exist_okTrue) with open(os.path.join(out_dir, f{stem}.txt), w) as fp: fp.write(\n.join(lines)) for xml_file in glob.glob(Annotations/*.xml): xml_to_yolo(xml_file, labels_from_xml)這個腳本有幾個值得說的點。findtext(size/width)能直接拿到size下的width文本比一層層find再.text省事bndbox里的xmin、ymin這些XML節(jié)點文本存的是字符串轉(zhuǎn)float后再做算術(shù)避免字符串拼接造成隱式錯誤最后寫入時小數(shù)位統(tǒng)一保留6位訓(xùn)練解析時不會因為精度問題丟邊界。CLASS_NAMES的順序必須和最終訓(xùn)練yaml里names順序完全一致否則框還是那些框但類別標簽全錯位了。4.3 反過來把YOLO txt還原成VOC XML用于校對如果你想在labelImg里檢查YOLO標注或者喂給只認VOC的老框架就需要反向轉(zhuǎn)換。轉(zhuǎn)換公式是剛才的逆運算def yolo_to_xml(txt_path, img_w, img_h, out_dir): stem os.path.splitext(os.path.basename(txt_path))[0] xml_lines [ annotation, f filename{stem}.jpg/filename, size, f width{img_w}/width, f height{img_h}/height, depth3/depth, /size ] with open(txt_path) as fp: for line in fp: parts line.strip().split() if not parts: continue cls_id, x_cent, y_cent, w, h parts xmin (float(x_cent) - float(w) / 2) * img_w ymin (float(y_cent) - float(h) / 2) * img_h xmax (float(x_cent) float(w) / 2) * img_w ymax (float(y_cent) float(h) / 2) * img_h # CLASS_NAMES 由外部傳入順序與訓(xùn)練時一致 xml_lines.append( object) xml_lines.append(f name{CLASS_NAMES[int(cls_id)]}/name) xml_lines.append( bndbox) xml_lines.append(f xmin{xmin:.0f}/xmin) xml_lines.append(f ymin{ymin:.0f}/ymin) xml_lines.append(f xmax{xmax:.0f}/xmax) xml_lines.append(f ymax{ymax:.0f}/ymax) xml_lines.append( /bndbox) xml_lines.append( /object) xml_lines.append(/annotation) with open(os.path.join(out_dir, f{stem}.xml), w) as fp: fp.write(\n.join(xml_lines))這段代碼要注意的是圖片寬高必須傳入因為YOLO txt里只有歸一化坐標沒有圖片尺寸信息。實際工程里圖片尺寸可以從JPEGImages下同名jpg用PIL讀出來但那樣會慢所以一般先用一次glob讀出所有圖片尺寸存成字典再批量轉(zhuǎn)換。另一個細節(jié)還原出來的xmin/ymin用取整輸出四舍五入到整像素labelImg讀進去不會有問題如果要給訓(xùn)練器用也可以保留小數(shù)不取整。有了這兩個轉(zhuǎn)換函數(shù)這套VOCYOLO雙格式數(shù)據(jù)集就能自由在舊工程和YOLO生態(tài)之間切換。偶爾需要新增數(shù)據(jù)時用labelImg標一批XML再跑一遍4.2腳本轉(zhuǎn)成txt并入原有目錄即可。5. 避坑這類數(shù)據(jù)集最常見的四個翻車點5.1 類別數(shù)對不上訓(xùn)練報 class index out of range 怎么查訓(xùn)練剛開始不到兩分鐘就崩常見報錯是AssertionError: class index ... out of range或者Invalid label file。原因基本是三類data.yaml的nc和names沒有嚴格對齊labels目錄里混入了別的數(shù)據(jù)集txt類別id超過了nc發(fā)布者把類別id從1開始計數(shù)導(dǎo)致最大id超出names長度。解決方法是先跑2.3的統(tǒng)計腳本把整個labels目錄的類別id列出來。你要確認最小id是0最大id小于nc。然后打開classes.txt和xml里的name比對確認names順序不是按字母序而是按訓(xùn)練字典。我遇到過更隱蔽的情況xml里只有box一個類但某個txt里混著一行舊的類別id 5這種通常來自發(fā)布者合并數(shù)據(jù)集時漏刪文件直接用文件路徑定位后刪掉那一行即可。5.2 藥盒緊貼圖邊letterbox后框越界漏檢現(xiàn)象是訓(xùn)練正常但val圖片里靠邊的藥盒經(jīng)常丟框。原因是拍照時藥盒本身頂著圖邊緣XML里的xmax或ymax超過圖片寬高YOLO歸一化后變成1.0x訓(xùn)練時letterbox會在原圖周圍填充灰邊1.0的坐標被映射到填充區(qū)模型學到的框就不準了。解決分兩步。第一步檢查越界標注python -c import glob, xml.etree.ElementTree as ET for f in glob.glob(Annotations/*.xml): r ET.parse(f).getroot() w, h int(r.findtext(size/width)), int(r.findtext(size/height)) for o in r.findall(object): b o.find(bndbox) xmax, ymax float(b.findtext(xmax)), float(b.findtext(ymax)) if xmax w or ymax h: print(f, xmax, ymax, w, h) 這段代碼會打印所有框坐標越界的XML文件名和具體數(shù)值。第二步是處理把越界的xmax/ymax強制clip回圖片邊界再重新生成YOLO txt不要手工改XML。訓(xùn)練參數(shù)上fliplr降到0.3scale控制在0.3到0.8減少隨機縮放對邊緣目標的破壞。藥盒這類物體邊緣特征靠文字放大太多反而丟細節(jié)。5.3 數(shù)據(jù)泄漏train/val里出現(xiàn)幾乎相同的圖現(xiàn)象是訓(xùn)練時mAP50已經(jīng)0.95但val集只有0.6而且隨著epoch增加val曲線不再上升。原因是數(shù)據(jù)劃分批次時連續(xù)拍攝的同場景照片被拆去了train和val藥盒位置、光線都幾乎一樣訓(xùn)練時模型等于見過val圖片指標虛高實際部署立刻掉點。解決方法是劃分前先做相似度去重。簡單做法是用OpenCV的感知哈希每張圖縮成8x8算64位hash漢明距離小于5的視為重復(fù)圖只保留一張import cv2 import numpy as np import glob def phash(img_path, size8): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) resized cv2.resize(img, (size, size), interpolationcv2.INTER_LINEAR) return (resized resized.mean()).astype(np.uint8) def hamming(a, b): return np.count_nonzero(a ! b) imgs glob.glob(JPEGImages/*.jpg) hashes {p: phash(p) for p in imgs} for i in range(len(imgs)): for j in range(i 1, len(imgs)): if hamming(hashes[imgs[i]], hashes[imgs[j]]) 5: print(相似的圖片:, imgs[i], imgs[j])這段腳本復(fù)雜度O(n2)1032張圖沒問題更大的數(shù)據(jù)集要先做索引。相似判斷可能把不同藥盒誤判成近似閾值5可以按實際情況調(diào)。排除重復(fù)圖后再把同一時間戳前綴或同序列號的文件分到同一集合沒有這類元數(shù)據(jù)時隨機多分幾個val集合選均值最穩(wěn)定的一個。5.4 路徑和文件名惹的禍中文空格、相對路徑現(xiàn)象是Windows下壓縮包正常換到Linux服務(wù)器一跑就報Train dataset not found或圖片讀取為None。原因是解壓時Windows目錄名帶空格和中文yaml里path寫的是反斜杠相對路徑對不上還有rar解壓偶爾生成._開頭的AppleDouble文件被訓(xùn)練器當成標注文件。解決是三步走所有目錄改成純英文無空格yaml統(tǒng)一用正斜杠解壓后先跑一句find . -name ._* -delete清理多余文件再數(shù)圖片和標簽數(shù)量缺少單個文件會直接報no labels found。注意有些系統(tǒng)解開rar后文件權(quán)限是只讀建議chmod -R uw dataset一鍵開放寫權(quán)限細節(jié)不多但能折騰很久。6. 用訓(xùn)練結(jié)果做真實推理驗證、調(diào)置信度與導(dǎo)出ONNX6.1 用best.pt跑驗證集看不是只看mAP訓(xùn)練完不要急著把best.pt發(fā)給同事先用val集跑一遍yolo detect val datamedicine_box.yaml modelruns/detect/train/weights/best.pt batch8輸出會包含precision、recall、mAP50、mAP50-95同時把混淆矩陣和預(yù)測圖片寫進runs/detect/val目錄。我一般會重點看預(yù)測圖里藥盒之間的疊放案例藥店照片常出現(xiàn)盒子之間互相壓邊val結(jié)果圖能直觀看到是不是漏檢了壓住的那一盒這種預(yù)測圖比數(shù)字更容易發(fā)現(xiàn)問題。如果mAP50超過0.85但val預(yù)測圖里還是有漏框大概率是標注邊界太小回到數(shù)據(jù)集把緊貼的框重新clip一遍再訓(xùn)。6.2 藥盒推理時的置信度與IOU調(diào)整實際推理時用CLI或者Python接口都一樣關(guān)鍵是置信度不要一成不變。yolo detect predict modelbest.pt sourcetest_images/ conf0.25 iou0.45 saveTrue藥盒反光會造成部分框置信度偏低如果漏檢很多別急著換模型先調(diào)conf。我習慣把conf降到0.15同時把iou從0.45提到0.5讓相鄰的高置信度框不容易被合并藥盒之間的邊界更干凈。如果誤檢變多再把conf提回0.3即可。6.3 導(dǎo)出ONNX給TensorRT和邊緣設(shè)備復(fù)用最后一步是把訓(xùn)練好的權(quán)重導(dǎo)出成ONNXyolo export modelbest.pt formatonnx imgsz640 dynamicFalse simplifyTrue導(dǎo)出后得到一個best.onnx用onnxruntime可以在CPU/GPU上推理也可以轉(zhuǎn)成TensorRT engine跑服務(wù)。dynamicFalse會讓輸入尺寸固定為640部署時省心如果項目里圖片尺寸多變再改dynamicTrue。要注意的是導(dǎo)出ONNX后輸入張量的歸一化方式仍然是像素值除以255外部調(diào)用時需要保持同一套預(yù)處理不要重復(fù)歸一化。我從那以后每次拿到現(xiàn)成數(shù)據(jù)集都不會直接開訓(xùn)而是先花半小時把目錄結(jié)構(gòu)、標簽統(tǒng)計和劃分完整性過一遍確認沒問題再寫訓(xùn)練命令。這個習慣幫我省了很多翻車的時間尤其是在“VOCYOLO雙格式”這種看起來沒什么坑、實際上坑都在文件里的場景。希望幫到你。本文還有配套的精品資源點擊獲取