據(jù)集全解析:VOC標注、YOLO轉(zhuǎn)換與目標檢測避坑指南)
簡介面向目標檢測入門與實戰(zhàn)的Pascal VOC格式西瓜數(shù)據(jù)集共1702張標注圖片類別為單一“watermelon”標注框總數(shù)2812個適合訓(xùn)練評估Faster R-CNN、YOLO等檢測模型也可作為學(xué)習(xí)labelImg標注規(guī)范與VOC格式轉(zhuǎn)換的練習(xí)數(shù)據(jù)。資源包內(nèi)含1702個jpg圖像、1702個xml標注文件與1個txt說明文件文件總數(shù)3405個壓縮包約167.78MB不含分割路徑與YOLO格式txt下載后可直接接入PyTorch、TensorFlow等常見訓(xùn)練流程。所有標注由人工通過labelImg按矩形框完成可信度較高數(shù)據(jù)集僅保證標注合理準確不承諾模型精度使用前需自行評估。已有485人學(xué)習(xí)下載適合需要規(guī)范VOC格式小規(guī)模數(shù)據(jù)集的目標檢測研究者、畢業(yè)設(shè)計開發(fā)者作為基線實驗數(shù)據(jù)。1. 這份西瓜數(shù)據(jù)集能干什么1702 張 VOC 標注的邊界與底氣做目標檢測的人八成都有過這種經(jīng)歷模型結(jié)構(gòu)調(diào)了一周精度上不去最后發(fā)現(xiàn)是數(shù)據(jù)集里混了錯標、漏標甚至 XML 和圖片對不上號。這份西瓜數(shù)據(jù)集屬于少見的老實型——1702 張 JPG 配 1702 個 XMLPascal VOC 格式labelImg 標注的矩形框單類別 watermelon框總數(shù) 2812 個沒有 YOLO txt也沒有分割路徑文件。它不附帶權(quán)重賣點是標注準確、格式干凈正好用來練 VOC 目標檢測數(shù)據(jù)集的完整處理流程也適合農(nóng)業(yè)檢測做遷移學(xué)習(xí)預(yù)實驗。新手能借它跑通管線熟手能拿單類別數(shù)據(jù)快速驗證網(wǎng)絡(luò)改動。2. 先看懂 VOC 標注再動手XML 結(jié)構(gòu)、目錄約定與 labelImg 的產(chǎn)物拿到數(shù)據(jù)集后第一件事不是解壓就跑訓(xùn)練而是打開一個 XML 看清楚里面記了什么。我見過太多人拿著 VOC 數(shù)據(jù)集直接轉(zhuǎn) YOLO轉(zhuǎn)完發(fā)現(xiàn)框全亂回頭查才發(fā)現(xiàn)是對原始格式的理解出了偏差。VOC 之所以長期作為目標檢測的中間標準格式就是因為它的 XML 把圖片文件名、圖像尺寸、目標類別、矩形框坐標全部用純文本存下來任何語言都能解析也方便人工打開檢查、用 diff 工具對比差異。2.1 一個 XML 文件里到底存了什么隨便挑一個 firc_watermelon_xxxx.xml 打開內(nèi)容結(jié)構(gòu)大致如下數(shù)值僅為示意annotation folderwatermelon_dataset/folder filenamefirc_watermelon_1089.jpg/filename path/home/user/watermelon/firc_watermelon_1089.jpg/path source databaseUnknown/database /source size width1920/width height1080/height depth3/depth /size segmented0/segmented object namewatermelon/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin80/ymin xmax640/xmax ymax530/ymax /bndbox /object /annotationfilename 字段必須和同名的 jpg 完全一致包括大小寫和擴展名。絕大多數(shù)訓(xùn)練腳本是按 basename 去匹配 xml 和 jpg 的這里錯一個字符就等于這張圖的標注直接被丟棄。path 字段是 labelImg 自動寫進去的絕對路徑指向標注者當時保存圖片的位置。這份數(shù)據(jù)集經(jīng)過打包、下載、解壓之后這個路徑基本是失效的后面避坑章節(jié)會講它怎么坑人。size 字段是整份 XML 里最容易被忽略、但最關(guān)鍵的部分。它記錄的是原圖的寬、高和通道數(shù)也就是 bndbox 坐標所在的坐標系。轉(zhuǎn) YOLO 格式時歸一化分母就是這里的 width 和 height而不是訓(xùn)練時設(shè)定的輸入尺寸 imgsz。如果 size 和真實圖片尺寸對不上轉(zhuǎn)換出來的所有坐標都是錯的。object 節(jié)點可以在一張圖里出現(xiàn)多次因為一張圖可能有好幾個西瓜。1702 張圖共 2812 個框平均一張 1.65 個說明場景里有大量多目標樣本解析腳本里必須用 findall(object) 把每一組框都取出來而不能只取第一個。bndbox 里四個值是整數(shù)像素坐標xmin/ymin 是框左上角xmax/ymax 是框右下角整體是閉區(qū)間習(xí)慣。truncated 表示目標是否被截斷difficult 表示是否難分這兩個是 VOC 競賽時期留下的字段這份數(shù)據(jù)集里都是 0現(xiàn)代檢測框架基本不讀可以無視。pose 也是歷史遺留同樣不用管。真正要盯死的只有 filename、size、name、bndbox 這四組。2.2 目錄怎么歸置VOC 標準布局與這份數(shù)據(jù)集的現(xiàn)狀這份數(shù)據(jù)集的發(fā)布形態(tài)是扁平目錄jpg 和 xml 混在同一層不帶原版 PASCAL VOC 的嵌套目錄也沒有 ImageSets/Main 下的 train.txt、val.txt 劃分文件。這意味著很多默認按 VOCdevkit 約定讀取數(shù)據(jù)的老牌框架——比如以前常用的 SSD 系列、faster-rcnn 的 voc 分支——會直接找不到圖片列表。我一般建議先重排成標準布局再繼續(xù)結(jié)構(gòu)如下目標路徑放什么說明VOCdevkit/VOC2007/JPEGImages全部 1702 張 jpg文件名保持 firc_watermelon_數(shù)字.jpg 不變VOCdevkit/VOC2007/Annotations全部 1702 個 xml與 jpg 同名一一對應(yīng)VOCdevkit/VOC2007/ImageSets/Maintrain.txt / val.txt / test.txt只寫去掉擴展名的文件名一行一個如果只打算用 YOLOv8 這類新框架其實不需要 ImageSetsdata.yaml 直接指定圖片目錄和標注目錄就能訓(xùn)。但我仍然建議按標準布局歸置理由很簡單目錄結(jié)構(gòu)是數(shù)據(jù)集的接口將來換框架或者復(fù)現(xiàn)論文代碼時標準布局可以直接被讀而扁平結(jié)構(gòu)總要再寫一層適配。處理目錄這種事我的原則是寧可按最嚴格的約定擺也不要用「能跑就行」的隨意結(jié)構(gòu)否則每換一個代碼庫就返工一次。2.3 labelImg 產(chǎn)物的兩個底色命名規(guī)范和隱藏的 path 字段這份數(shù)據(jù)集由 labelImg 標注文件名是 firc_watermelon_數(shù)字.jpg 這種純 ASCII 命名沒有空格也沒有中文字符這看起來是小事其實很關(guān)鍵。labelImg 保存 xml 時沿用打開圖片的文件名很多公開數(shù)據(jù)集里混著中文名、括號、空格命令行工具在路徑拼接時直接報錯。這份數(shù)據(jù)集命名規(guī)范化做得相當?shù)轿蛔约貉a充標注時也應(yīng)該堅持「全小寫 ASCII 下劃線 序號」的約定。另一個底色還是 path 字段。labelImg 每個 xml 都會寫入當時的絕對路徑你在這份數(shù)據(jù)集里看到的 path 大概率指向標注者的某個本地目錄在自己機器上不存在。當前主流框架解析 VOC 時只按文件名在同目錄找圖不讀 path所以不影響使用。但如果你以后拿到其他 VOC 數(shù)據(jù)集時遇到「圖片加載失敗、xml 明明在」的情況第一個要懷疑的就是這個字段而不是去檢查網(wǎng)絡(luò)或緩存。3. 訓(xùn)練前的數(shù)據(jù)體檢用兩個腳本把 1702 張圖的壞樣本篩干凈跳過體檢直接開訓(xùn)是目標檢測新手付出代價最多的一步。數(shù)據(jù)集里的問題樣本會讓 loss 曲線表現(xiàn)得很玄學(xué)——時高時低、不收斂、收斂到錯誤的局部最優(yōu)而這些問題在訓(xùn)練前用一個腳本就能暴露。我每逢新數(shù)據(jù)集都強制自己先跑一遍體檢這個習(xí)慣救過我很多次下面兩個腳本可以直接抄。3.1 統(tǒng)計腳本框數(shù)、分辨率分布、每圖框數(shù)用一段標準 Python 就能把整個數(shù)據(jù)集的統(tǒng)計信息拉出來import os import xml.etree.ElementTree as ET xml_dir Annotations total_boxes 0 img_sizes {} # 分辨率 - 出現(xiàn)次數(shù) per_img_counts [] # 每張圖的框數(shù) class_counts {} # 類別名 - 框數(shù) for name in os.listdir(xml_dir): if not name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, name)) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) img_sizes[(w, h)] img_sizes.get((w, h), 0) 1 objs root.findall(object) per_img_counts.append(len(objs)) for obj in objs: cname obj.find(name).text.strip().lower() class_counts[cname] class_counts.get(cname, 0) 1 total_boxes 1 print(總框數(shù):, total_boxes) print(分辨率分布:, img_sizes) print(每圖框數(shù) 最小/最大:, min(per_img_counts), max(per_img_counts)) print(平均每圖框數(shù): %.2f % (total_boxes / len(per_img_counts))) print(類別統(tǒng)計:, class_counts)這段代碼用 xml.etree.ElementTree 解析每個 xml分別統(tǒng)計總框數(shù)、圖像分辨率分布、每張圖的框數(shù)范圍以及類別名歸一化后的框數(shù)。注意我特意對類別名做了 strip().lower()這是為了在體檢階段就抓出「大小寫不一致」這種后續(xù)必炸的隱患。運行結(jié)果里 class_counts 應(yīng)該只有 watermelon: 2812 這一項只要多出任何一個鍵哪怕是個空格都說明數(shù)據(jù)里混了臟標。分辨率分布信息很重要。如果 img_sizes 里出現(xiàn)多種分辨率比如一部分 1920x1080、一部分 640x480訓(xùn)練時 resize 到統(tǒng)一尺寸后小圖的框會顯得更大這對模型的尺度魯棒性是個考驗要不要做多尺度訓(xùn)練就由這個數(shù)據(jù)決定。每圖框數(shù)分布則決定 mosaic 增強的收益。這份數(shù)據(jù)集平均 1.65 個框、最多若干框說明框不算密集mosaic 可以放心開滿。如果某些圖有 5 個以上的框說明場景里有密集目標驗證時要注意漏檢率。3.2 壞樣本篩查零面積、越界、文件名失配統(tǒng)計完數(shù)量還要查質(zhì)量四類問題必須零容忍文件名失配、框?qū)捀咝∮诘扔?0、坐標越界、xml 里 object 缺失。import os import xml.etree.ElementTree as ET xml_dir Annotations img_dir JPEGImages problems [] imgs set(os.listdir(img_dir)) for name in os.listdir(xml_dir): if not name.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, name)) root tree.getroot() filename root.find(filename).text if filename not in imgs: problems.append(f{name}: 找不到圖片 {filename}) size root.find(size) W, H int(size.find(width).text), int(size.find(height).text) objs root.findall(object) if not objs: problems.append(f{name}: 沒有任何 object) for obj in objs: bb obj.find(bndbox) xmin, ymin int(bb.find(xmin).text), int(bb.find(ymin).text) xmax, ymax int(bb.find(xmax).text), int(bb.find(ymax).text) if xmax - xmin 0 or ymax - ymin 0: problems.append(f{name}: 零面積框 ({xmin},{ymin},{xmax},{ymax})) if xmin 0 or ymin 0 or xmax W or ymax H: problems.append(f{name}: 越界框 ({xmin},{ymin},{xmax},{ymax}) vs {W}x{H}) print(問題數(shù)量:, len(problems)) for p in problems: print(p)逐條說明判斷邏輯filename not in imgs 抓的是 xml 與 jpg 不同名或缺失的情況零面積框通常是標注時連續(xù)點了兩個相同坐標這種框喂給任何檢測頭都會讓損失變成垃圾值越界框大多出現(xiàn)在圖片邊緣的西瓜被拉出了畫布labelImg 允許你這么保存但 YOLO 的隨機裁剪增強會把它們算成錯位樣本。這個腳本輸出為空才說明數(shù)據(jù)可以進入轉(zhuǎn)換階段。一個邊界情況說明xmax W 這種「頂?shù)接疫吔纭沟目蛟趪栏穸x里不算越界但很多增強函數(shù)用 [0, W) 區(qū)間做隨機裁剪所以我會在轉(zhuǎn)換腳本里把 xmax 統(tǒng)一 clamp 到 W-1。寧可損失 1 像素也不要在增強階段翻車。3.3 單類別數(shù)據(jù)集的假象場景單一不等于泛化類別只有 watermelon 一個訓(xùn)練起來確實快但要注意一個容易誤判的點文件名前綴相同都是 firc_watermelon_ 開頭說明這批圖很可能是同一批采集、同一個場景系列。如果全部來自同一塊地的同一時段模型學(xué)到的是「那塊地的西瓜」換個光照、換個土壤背景精度就崩這在小目標檢測任務(wù)里尤其明顯。判斷方法很土但有用隨機抽 20 張圖拼成一張網(wǎng)格圖肉眼看背景變化程度——背景顏色、拍攝距離、西瓜遮擋程度有沒有明顯差異。如果有說明數(shù)據(jù)多樣性夠如果看起來像同一組視頻抽幀那訓(xùn)練前得考慮加外部數(shù)據(jù)或做更強的色彩增強。這種肉眼抽檢聽起來玄學(xué)但它比任何指標都能暴露場景單一問題。4. 把 VOC 轉(zhuǎn)成 YOLO 格式轉(zhuǎn)換腳本、8:1:1 劃分與訓(xùn)練起點參數(shù)這份數(shù)據(jù)集沒有 YOLO 的 txt 文件所以要用 YOLOv8 這類框架訓(xùn)練自己的數(shù)據(jù)集必須先做格式轉(zhuǎn)換。轉(zhuǎn)換本身不復(fù)雜復(fù)雜的是坐標計算的正確性和訓(xùn)練配置的合理起點。用 YOLOv8 訓(xùn)練自己的數(shù)據(jù)集時這一步是繞不開的也是在各種「目標檢測數(shù)據(jù)集處理」教程里被講得最含糊的部分這里把公式和參數(shù)一次說清。4.1 轉(zhuǎn)換腳本XML 到 txt 的坐標換算import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() size root.find(size) W float(size.find(width).text) H float(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip().lower() if name not in class_names: continue cid class_names.index(name) bb obj.find(bndbox) xmin float(bb.find(xmin).text) ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text) ymax float(bb.find(ymax).text) x_center (xmin xmax) / 2.0 / W y_center (ymin ymax) / 2.0 / H bw (xmax - xmin) / W bh (ymax - ymin) / H # clamp 到 [0, 1)避免邊界框在增強階段越界 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{cid} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) if lines: stem os.path.splitext(os.path.basename(xml_path))[0] with open(os.path.join(out_dir, stem .txt), w, encodingutf-8) as f: f.write(\n.join(lines) \n) class_names [watermelon] os.makedirs(yolo_labels, exist_okTrue) for name in os.listdir(Annotations): if name.endswith(.xml): convert_voc_to_yolo(os.path.join(Annotations, name), yolo_labels, class_names)關(guān)鍵公式x_center (xmin xmax) / 2 / W把像素中心點除以圖片寬度得到 0~1 的歸一化中心坐標寬度同理。為什么除以 W 而不是 W-1因為尺寸歸一化遵循 [0,1] 約定與大多數(shù)框架內(nèi)部實現(xiàn)一致坐標 clamp 到 1.0 以內(nèi)是為了兼容隨機裁剪。cid class_names.index(name) 決定類別 id這里 class_names 只有 watermelon 一個元素所有框的 cid 都是 0。注意如果以后擴展類別class_names 的順序會改變歷史模型輸出的類別 id舊權(quán)重不能直接接著訓(xùn)。注意這里的分母 W 必須來自 XML 的 size 字段而不是你訓(xùn)練時設(shè)定的 imgsz。很多新人在這里把分母寫錯導(dǎo)致轉(zhuǎn)換出的坐標全部超界。轉(zhuǎn)換完建議做一個抽查隨機挑 3 個 txt用 Python 讀出來再乘回圖片寬高看是否和 xml 里的 bndbox 對得上。這個逆向檢查 30 秒能寫完但能擋住 90% 的坐標翻車。4.2 數(shù)據(jù)集劃分與 data.yaml 的規(guī)范寫法劃分腳本必須固定隨機種子這直接決定實驗?zāi)芊駨?fù)現(xiàn)import os import random random.seed(42) imgs sorted(os.listdir(JPEGImages)) random.shuffle(imgs) n len(imgs) train_imgs imgs[:int(n * 0.8)] val_imgs imgs[int(n * 0.8):int(n * 0.9)] test_imgs imgs[int(n * 0.9):] def write_list(path, items): with open(path, w) as f: for it in items: f.write(fJPEGImages/{it}\n) write_list(train.txt, train_imgs) write_list(val.txt, val_imgs) write_list(test.txt, test_imgs) print(ftrain{len(train_imgs)} val{len(val_imgs)} test{len(test_imgs)})參數(shù)說明random.seed(42) 必須寫在 shuffle 之前保證每次運行得到相同劃分。之所以強調(diào)這個是因為目標檢測實驗里 train/val 劃分不一致會讓兩次實驗的 mAP 差出 0.1 以上你根本分不清是模型改動的功勞還是數(shù)據(jù)劃分的隨機性。8:1:1 是最常用的默認劃分如果之后要做超參搜索可以把 test 單獨留出來只在 val 上調(diào)參最后統(tǒng)一用 test 出報告。配套的 data.yaml 長這樣path: /data/watermelon_dataset train: train.txt val: val.txt test: test.txt names: 0: watermelonpath 填數(shù)據(jù)集的絕對路徑train/val/test 既可以寫 txt 路徑也可以寫目錄路徑Y(jié)OLOv8 兩種都認。names 下的 ID 和轉(zhuǎn)換腳本里的 class_names 下標必須一致這里 0 對應(yīng) watermelon兩邊都是 0不能錯位。如果訓(xùn)練時報錯說類別數(shù)對不上優(yōu)先檢查這一處。4.3 單類別任務(wù)的起點參數(shù)先跑通基線再談優(yōu)化單類別目標檢測的坑比多類別少類別不平衡和難例挖掘的負擔都輕了但參數(shù)起點還是要有譜。我一般按下面這組來參數(shù)建議值理由imgsz640多數(shù)公開檢測任務(wù)的標準分辨率先跑基線batch168G 顯存夠用不夠就降到 8epochs100單類別收斂快100 輪足夠看趨勢mosaic1.0單類別無類別混淆風險開滿close_mosaic10最后 10 輪關(guān)閉 mosaic讓精修階段不受裁剪干擾如果體檢階段發(fā)現(xiàn)框里小目標占比高寬或高小于 32 像素的框超過兩成把 imgsz 提到 960 比盲目加層更有效顯存不夠就保持 640先接受小目標漏檢后續(xù)再考慮切片推理。第一次訓(xùn)練的目標只是確認 loss 能降、val 曲線能穩(wěn)定別一上來就堆技巧否則出了問題連基線都沒得對比。5. 避坑手冊VOC 數(shù)據(jù)集在訓(xùn)練中的五個經(jīng)典翻車現(xiàn)場這一章寫的是跑 VOC 數(shù)據(jù)集時反復(fù)出現(xiàn)的坑按「現(xiàn)象 → 原因 → 解決」整理成五條。前三條在數(shù)據(jù)側(cè)后兩條在訓(xùn)練側(cè)。很多人以為目標檢測模型微調(diào)崩了是超參設(shè)置問題其實大多數(shù)時候是數(shù)據(jù)側(cè)早就埋了雷訓(xùn)練只是把雷踩響而已。5.1 坑一XML 里的絕對路徑在換機器后讀不到圖現(xiàn)象把數(shù)據(jù)集從下載目錄拷到服務(wù)器訓(xùn)練數(shù)據(jù)加載階段直接報 FileNotFoundError或者 mmdetection 彈出找不到圖片的報錯但 xml 明明都在。原因labelImg 在每個 xml 里寫了絕對路徑指向標注者的本機目錄換機器后這個路徑自然失效。大多數(shù)現(xiàn)代框架按 filename 在同目錄找圖不受影響但部分老腳本優(yōu)先讀 path一讀就掛。解決批量清空或改寫 path 字段。最省事的做法是在轉(zhuǎn)換腳本里直接忽略 path只按「xml 所在目錄 filename」拼接圖片路徑。我習(xí)慣把 jpg 和 xml 放同一目錄、文件名保持完全一致這樣任何框架都繞不開這個坑。5.2 坑二圖片縮放過但 XML 坐標沒縮放框全偏現(xiàn)象為省顯存把 1920x1080 原圖壓成 640x360 喂給訓(xùn)練mAP 從 0.8 掉到 0.4預(yù)測框明顯整體偏向某個方向。原因只縮小了圖片沒同步改 XML 的 size 和所有 bndbox 坐標。VOC 坐標是絕對像素值坐標系一變標注就全錯位。很多「圖片壓縮腳本」只處理了 jpgxml 被晾在一邊。解決要么不壓縮直接用原圖尺寸配合對應(yīng) imgsz 訓(xùn)練要么壓縮后寫一個等比縮放腳本scale_x 新寬 / 原寬所有 xmin/xmax 乘 scale_xy 坐標同理并同步更新 size 字段??s放后重新跑一遍第 3 章的壞樣本篩查確認沒有新增越界。5.3 坑三類別名大小寫不一致訓(xùn)練時類別數(shù)莫名變 2現(xiàn)象訓(xùn)練日志里 nc2 而不是 1loss 曲線反復(fù)橫跳驗證集上預(yù)測框全是空的。原因絕大部分 xml 里類別是 watermelon但有少數(shù) xml 寫成了 Watermelon 或者帶個尾部空格。data.yaml 里只注冊了小寫名多出來的「類別」就被當成第二類。解決轉(zhuǎn)換腳本里強制 name.strip().lower()并在體檢腳本里輸出 class_counts 集合跑完肉眼看是否只有 watermelon 一個鍵。這類臟數(shù)據(jù)在轉(zhuǎn)換階段攔掉比等到訓(xùn)練階段排查成本低一個量級。5.4 坑四越界框在 mosaic 增強里炸出 NaN loss現(xiàn)象訓(xùn)練到中途 loss 變 NaN換 batch 也沒用關(guān)掉 mosaic 就恢復(fù)正常。原因個別 xml 的 xmax 比圖片 width 大 1~2 像素這是標注時把框拖出畫布邊界的產(chǎn)物。YOLO 的 mosaic 和隨機裁剪會對這些越界坐標做插值越界值參與損失計算后產(chǎn)生無窮大。解決轉(zhuǎn)換腳本里對四個坐標統(tǒng)一 clampxmax 最多 clamp 到 W-1ymax 到 H-1同時保留第 3 章體檢腳本的輸出作證據(jù)。記住原則是修數(shù)據(jù)不是改模型別為了一個臟框去調(diào)損失函數(shù)。5.5 坑五劃分腳本不固定隨機種子兩次實驗對不上現(xiàn)象同一組超參跑兩次mAP 差 0.1 以上懷疑代碼有 bug排查半天發(fā)現(xiàn)是 train/val 劃分每次都不一樣。原因劃分腳本每次運行都重新 shuffle沒有固定 random.seed或者用 set 做過去重迭代順序不穩(wěn)定。1702 張圖里 val 集換掉幾十張結(jié)果自然不可比。解決劃分腳本頂部固定 random.seed(42)并把生成的 train.txt、val.txt、test.txt 提交到版本管理。此后任何實驗都只讀這三個 txt不管誰跑、在哪臺機器跑劃分永遠一致。這條習(xí)慣救過我很多次對比實驗。6. 讓 2812 個框發(fā)揮出最大價值增強取舍與畫框回圖的驗證習(xí)慣6.1 單類別場景下的增強取舍類別只有 watermelon 時增強可以做得比多類別更激進mosaic 開滿、fliplr0.5、HSV 色彩擾動適度調(diào)高因為不存在把 A 類物體增強成 B 類物體的混淆風險。真正的瓶頸往往在小目標——田里遠處的水墨瓜框可能只有幾十個像素。我的建議是先按 hsv_h0.015、hsv_s0.7、hsv_v0.4、scale0.5 跑 50 輪看 val 曲線確認沒有漏檢小目標再談進階如果小目標確實漏得厲害優(yōu)先把 imgsz 提到 960而不是堆增強算子。6.2 一個堅持到現(xiàn)在的驗證習(xí)慣訓(xùn)練后畫框回圖訓(xùn)練完先別急著看 mAP。寫一個十幾行的腳本把 val 集的標簽框或預(yù)測框畫回原圖拼成網(wǎng)格圖肉眼掃一遍。這個動作能同時驗證三件事坐標有沒有錯位、小目標有沒有漏、增強后的標注是否還對得上原圖。from PIL import Image, ImageDraw def draw_boxes(img_path, boxes, out_path): im Image.open(img_path).convert(RGB) d ImageDraw.Draw(im) for xmin, ymin, xmax, ymax in boxes: d.rectangle([xmin, ymin, xmax, ymax], outline(255, 0, 0), width3) im.save(out_path) # boxes 必須是像素坐標傳 YOLO 歸一化坐標時先乘回 img_w 和 img_h draw_boxes(val_sample.jpg, [(120, 80, 640, 530)], check.jpg)參數(shù)說明outline 是框線的 RGB 顏色width3 是線條粗細圖片分辨率大時可以調(diào)到 5。從那以后我每次接 VOC 數(shù)據(jù)集都強制自己先跑體檢腳本再轉(zhuǎn)格式、再訓(xùn)練、最后畫框回圖確認這套流程已經(jīng)成了肌肉記憶。數(shù)據(jù)集的坑大多不在模型而在你默認它沒問題這份西瓜數(shù)據(jù)集標注本身是干凈的但處理每一步數(shù)據(jù)時仍值得用同樣的認真去對待希望幫到你。本文還有配套的精品資源點擊獲取