檢測(cè)數(shù)據(jù)集:格式解析與訓(xùn)練避坑指南)
簡(jiǎn)介面向計(jì)算機(jī)視覺與農(nóng)業(yè)智能化方向的算法工程師、科研人員及高校學(xué)生這份數(shù)據(jù)集提供超過7000張葡萄成熟度標(biāo)注樣本類別覆蓋未熟、半熟、成熟等4個(gè)等級(jí)并已統(tǒng)一轉(zhuǎn)換成YOLO格式可直接用于YOLOv5、YOLOv8等全系列模型訓(xùn)練免去繁瑣的格式轉(zhuǎn)換。壓縮包內(nèi)共2000個(gè)文件以txt格式的標(biāo)注標(biāo)簽為主另附1個(gè)show.py可視化腳本整體大小226.63MB目錄按訓(xùn)練集、驗(yàn)證集及class類別文本清晰組織解壓即可接入主流檢測(cè)流程。目前已有338人學(xué)習(xí)下載。配套的show.py腳本能把檢測(cè)框直接繪制到對(duì)應(yīng)圖像上便于快速核查標(biāo)注質(zhì)量、類別分布和數(shù)據(jù)劃分顯著縮短預(yù)覽與清洗周期。無論用于葡萄成熟度識(shí)別實(shí)驗(yàn)、果園產(chǎn)量預(yù)估落地還是作為目標(biāo)檢測(cè)教學(xué)案例均能直接復(fù)用。1. 目標(biāo)檢測(cè)數(shù)據(jù)集為什么先拿葡萄成熟度試手?jǐn)?shù)據(jù)格式?jīng)Q定訓(xùn)練能走多遠(yuǎn)一個(gè)目標(biāo)檢測(cè)數(shù)據(jù)集的核心資產(chǎn)不是圖片數(shù)量而是標(biāo)簽格式和類別定義。葡萄成熟度檢測(cè)這份資源把 labelme 的 JSON 標(biāo)注處理成了 YOLO 可直接訓(xùn)練的 txt 格式超過 7000 張圖像、4 個(gè)成熟度類別覆蓋訓(xùn)練集、驗(yàn)證集和對(duì)應(yīng)標(biāo)簽文件可以拿來跑 YOLO 全系列網(wǎng)絡(luò)訓(xùn)練。對(duì)剛接觸檢測(cè)的開發(fā)者來說它最大的價(jià)值是省掉了自己寫轉(zhuǎn)換腳本的環(huán)節(jié)但前提是你得先看懂標(biāo)簽文件里存的到底是什么。這篇筆記就按工程落地的順序把格式原理、可視化驗(yàn)證、踩坑點(diǎn)依次拆開講清楚確保你拿到資源后當(dāng)天就能跑通驗(yàn)證流程。2. YOLO 標(biāo)簽格式與四類成熟度劃分先讀懂 txt 里到底存了什么2.1 從 labelme JSON 到 YOLO txt轉(zhuǎn)換邏輯和 7000 張圖的資產(chǎn)結(jié)構(gòu)這份數(shù)據(jù)集的源頭是 labelme 標(biāo)注標(biāo)注結(jié)果最終被處理成了 YOLO 格式的 txt 文件。我見過不少人在這一步翻車拿到的 txt 用記事本打開是一堆數(shù)字不知道每個(gè)數(shù)字代表什么更不知道坐標(biāo)是絕對(duì)像素還是歸一化值。YOLO 的標(biāo)簽格式每行固定 5 個(gè)字段逗號(hào)或空格分隔都可以但大部分實(shí)現(xiàn)里用空格類別ID 目標(biāo)中心X 目標(biāo)中心Y 目標(biāo)寬度 目標(biāo)高度關(guān)鍵點(diǎn)在于X、Y、W、H 全部是相對(duì)圖像的歸一化值范圍在 0 到 1 之間。比如一張 1920×1080 的圖像里某個(gè)葡萄串中心在像素坐標(biāo) (960, 540)寬度是 480 像素那這一行就寫成0 0.5 0.5 0.25 xxx。歸一化的好處是圖像分辨率變化后標(biāo)簽依然有效640 輸入和 1280 輸入共用同一套 txt不需要重新標(biāo)。從 labelme 的 JSON 轉(zhuǎn)成這種格式核心邏輯是把多邊形的頂點(diǎn)坐標(biāo)換算成外接矩形的中心點(diǎn)與寬高def labelme_to_yolo(vertices, img_w, img_h, class_id): xs [pt[0] for pt in vertices] ys [pt[1] for pt in vertices] x1, x2 min(xs), max(xs) y1, y2 min(ys), max(ys) x_center (x1 x2) / 2.0 / img_w y_center (y1 y2) / 2.0 / img_h box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h return f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}這里vertices是 labelme 標(biāo)注文件里points字段的坐標(biāo)列表每個(gè)點(diǎn)是圖像上的絕對(duì)像素坐標(biāo)。取所有點(diǎn)的最小和最大值組成外接框是目標(biāo)檢測(cè)數(shù)據(jù)轉(zhuǎn)換里最常見也是最穩(wěn)妥的做法。注意最后除以img_w和img_h這一步漏掉的話訓(xùn)練時(shí)框會(huì)全部錯(cuò)位而且肉眼還不容易發(fā)現(xiàn)。class_id的取值不是標(biāo)注文本里的中文名而是類別列表里的索引這點(diǎn)在下一節(jié)單獨(dú)說。解壓這份資源后目錄結(jié)構(gòu)按常見做法大概是這樣的路徑內(nèi)容images/train/訓(xùn)練集原圖jpg 或 png 混合images/val/驗(yàn)證集原圖labels/train/訓(xùn)練集 yolo txt 標(biāo)簽與圖片同名labels/val/驗(yàn)證集 yolo txt 標(biāo)簽classes.txt類別清單一行一個(gè)類名順序決定 class idshow.py可視化腳本把標(biāo)注框畫到圖上注意 txt 標(biāo)簽的文件名和圖片名并不完全相等常見命名里帶隨機(jī)后綴比如圖片是00248-3249807169_png.png標(biāo)簽是00248-3249807169_png.rf.cc6b53b9066b3e78eef7f5c2b643472b.txt本質(zhì)還是同一份文件的一一對(duì)應(yīng)只是加了一串哈希后綴防止重名覆蓋。后面訓(xùn)練時(shí)如果用目錄式加載靠文件名前綴配對(duì)是沒問題的。2.2 class 文件與類別映射四類成熟度對(duì)應(yīng)的真實(shí)含義摘要里寫類別個(gè)數(shù)是 4分別是未熟、半熟、成熟等。實(shí)際類別名稱要以classes.txt里的文本為準(zhǔn)這份文件就是類別順序的最終定義。每次拿到數(shù)據(jù)集第一件事就是打開它確認(rèn) id 順序cat classes.txt一個(gè)典型的類別清單可能是這樣的unripe half-ripe ripe overripe對(duì)應(yīng)關(guān)系也就確定了txt 每行第一個(gè)數(shù)字是 0那這個(gè)框的類別就是unripe是 1 就是half-ripe。這個(gè)邏輯聽起來簡(jiǎn)單但它是整個(gè)數(shù)據(jù)集最容易被忽略的地基。后面任何腳本只要依賴類別順序全部以這個(gè)文件為一致來源不要憑標(biāo)注時(shí)的記憶去判斷某個(gè)框是哪種成熟度。四類成熟的邊界本身也是個(gè)工程問題。葡萄成熟度是連續(xù)物理變化標(biāo)注時(shí)打上的類別標(biāo)簽卻有明確斷點(diǎn)。實(shí)踐中半熟和成熟兩類在顏色上經(jīng)常接近特別是青提這類品種標(biāo)注人員同一個(gè)人的前 500 張和后 500 張都可能出現(xiàn)標(biāo)準(zhǔn)漂移。這屬于目標(biāo)檢測(cè)數(shù)據(jù)集里典型的語義邊界模糊不是 bug但會(huì)影響最終 mAP 上限。緩解手段通常有兩個(gè)一是抽檢類別間的混淆情況把容易混的圖片歸到同一類或干脆刪掉模糊樣本二是在 loss 里給中間類別加權(quán)讓模型不偏向樣本多的類別。還有一個(gè)值得養(yǎng)成的習(xí)慣檢查每張圖的標(biāo)簽是否都是 4 個(gè)類別內(nèi)的數(shù)字。因?yàn)?class id 一旦越界訓(xùn)練框架通常直接報(bào)錯(cuò)中斷或者把越界樣本吞掉導(dǎo)致訓(xùn)練集和驗(yàn)證集數(shù)據(jù)量對(duì)不上。檢查方法后面避坑章會(huì)給具體命令。3. 用 show.py 把標(biāo)注框畫回去7000 張圖的可視化檢查三步走3.1 show.py 腳本詳解從 txt 畫框到圖像拿到數(shù)據(jù)集先別急著訓(xùn)練第一件事是可視化抽查。show.py 的作用就是把 txt 里的歸一化坐標(biāo)換算回圖像像素坐標(biāo)然后把框畫在原圖上讓你能直觀看到標(biāo)注和真實(shí)目標(biāo)的貼合程度。這套檢查雖然原始但能擋住 80% 的數(shù)據(jù)問題。常見視覺腳本的寫法是import os import sys import cv2 def draw_yolo_boxes(image_path, label_path, color(0, 255, 0), thickness2): img cv2.imread(image_path) if img is None: print(f[ERROR] cannot read image: {image_path}) return None h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh parts xc, yc, bw, bh float(xc), float(yc), float(bw), float(bh) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness) cv2.putText(img, cls_id, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, thickness) return img if __name__ __main__: image_path sys.argv[1] label_path os.path.splitext(image_path)[0] .txt if not os.path.exists(label_path): # 部分標(biāo)簽帶隨機(jī)后綴找不到時(shí)在同目錄做前綴匹配 for f in os.listdir(os.path.dirname(image_path)): if f.startswith(os.path.splitext(os.path.basename(image_path))[0]) and f.endswith(.txt): label_path os.path.join(os.path.dirname(image_path), f) break result draw_yolo_boxes(image_path, label_path) if result is not None: cv2.imwrite(show_result.jpg, result) print(saved to show_result.jpg)這段腳本里最關(guān)鍵的是坐標(biāo)換算(xc - bw / 2) * w把歸一化的中心點(diǎn)和寬高還原成左上角和右下角的絕對(duì)像素坐標(biāo)。畫框前cv2.imread的判空處理不是擺設(shè)路徑里帶中文或圖片損壞時(shí) OpenCV 會(huì)靜默返回 None不加判斷就可能把空白圖保存下來讓你誤以為標(biāo)簽全空了。后面那段前綴匹配是為了應(yīng)對(duì)這數(shù)據(jù)集里帶隨機(jī)哈希后綴的 txt 文件保證show.py 圖片名單命令能正確找到標(biāo)簽。跑的方式很簡(jiǎn)單python show.py images/train/00248-3249807169_png.png跑完打開show_result.jpg核對(duì)三點(diǎn)框是不是緊貼真實(shí)目標(biāo)框的數(shù)量和圖片里實(shí)際目標(biāo)數(shù)量是否一致類別 id 顯示是否和成熟度一目了然對(duì)應(yīng)。抽樣量我一般會(huì)取訓(xùn)練集的 1% 到 2%7000 張里至少看夠 50 張?jiān)龠M(jìn)訓(xùn)練環(huán)節(jié)。3.2 訓(xùn)練集與驗(yàn)證集劃分文件命名與目錄組織這份資源已經(jīng)按 YOLO 的常見目錄式結(jié)構(gòu)分好了 train 和 val。目錄式組織的含義是訓(xùn)練時(shí)框架自動(dòng)讀取images/train下的圖片并在labels/train找對(duì)應(yīng) txt。這種組織方式對(duì)新手最友好不用手動(dòng)維護(hù)路徑清單文件。不過有一類訓(xùn)練框架需要的是train.txt和val.txt里面每行寫一張圖片的絕對(duì)或相對(duì)路徑。目錄式轉(zhuǎn)清單式其實(shí)很簡(jiǎn)單find images/train -name *.jpg -o -name *.png | sort train.txt find images/val -name *.jpg -o -name *.png | sort val.txt執(zhí)行完檢查行數(shù)是不是和 images 子目錄里文件數(shù)一致。注意train.txt的路徑要和工程目錄的基準(zhǔn)一致因?yàn)楹芏嗫蚣軙?huì)先把圖片路徑解析出來再拼上labels目錄去讀標(biāo)簽路徑基準(zhǔn)不一致的典型錯(cuò)誤是把images/train/xxx.jpg寫成train/xxx.jpg結(jié)果訓(xùn)練時(shí)大量圖片找不到標(biāo)簽直接被跳過了。數(shù)據(jù)劃分比例的確認(rèn)也值得做。成熟度檢測(cè)場(chǎng)景下4 類中半熟和成熟的樣本往往集中在多個(gè)連續(xù)的采集批次里如果驗(yàn)證集恰好落在某一種成熟度出現(xiàn)高峰的時(shí)間段驗(yàn)證 mAP 會(huì)虛高。經(jīng)驗(yàn)做法是用ls images/val | wc -l看驗(yàn)證集大小確認(rèn)劃分沒有把某個(gè)類別單獨(dú)切出去。真要嚴(yán)格劃分時(shí)可以用按類別分層抽樣的腳本而不是純隨機(jī)切分。純隨機(jī)在 7000 張規(guī)模下問題通常不大但如果你后面決定合并其他來源的數(shù)據(jù)分層抽樣就必不可少。4. 常見問題與排查標(biāo)注錯(cuò)位、類別缺失和路徑玄學(xué)4.1 框和圖像對(duì)不上不是 show.py 的鍋現(xiàn)象用 show.py 畫出來的框整體偏移或者框明顯比目標(biāo)大一圈但文件里的數(shù)字看起來都在 0 到 1 之間。原因最常見的是 txt 沒有真正歸一化。有些中途處理過的標(biāo)簽文件中心點(diǎn)和寬高仍然用的絕對(duì)像素值只是數(shù)值碰巧在 0 到 1 范圍之外或者被錯(cuò)誤地除以了一個(gè)固定值另一種場(chǎng)景是把 labelme 轉(zhuǎn)換腳本里的img_w / img_h換成了一次性的縮放比例而原圖分辨率不統(tǒng)一導(dǎo)致部分圖片錯(cuò)位。解決先隨機(jī)抽 3 張圖手動(dòng)讀取圖像尺寸并把 txt 的第一個(gè)框換算回像素坐標(biāo)看看左上角是否落在圖像范圍內(nèi)。如果框坐標(biāo)有大于 1 或小于 0 的情況說明標(biāo)簽還是絕對(duì)坐標(biāo)需要重新走一遍歸一化轉(zhuǎn)換。注意 YOLO 訓(xùn)練時(shí)會(huì)把這類非法框直接丟棄但不會(huì)提示你丟了多少所以很多人訓(xùn)練完了都不知道訓(xùn)練數(shù)據(jù)里有一部分框壓根沒參與計(jì)算。4.2 類別總是報(bào)錯(cuò)或訓(xùn)練中斷txt 里的 class id 越界現(xiàn)象訓(xùn)練剛開始幾百步就報(bào)class id out of range之類的錯(cuò)誤或者干脆訓(xùn)練結(jié)束驗(yàn)證時(shí)報(bào) mAP 為 0。原因txt 標(biāo)簽里出現(xiàn)了大于等于 4 的數(shù)字。發(fā)生這類情況通常是標(biāo)注階段類別名沒統(tǒng)一比如同一個(gè)人前一批用halfRipe后一批改成half-ripe轉(zhuǎn)換腳本把它們當(dāng)成兩個(gè)類別索引排到了第 4 位甚至第 5 位。由于這個(gè)數(shù)據(jù)集來源標(biāo)注批次多越界樣本并不集中報(bào)錯(cuò)也只是在讀取到那一行時(shí)才觸發(fā)。解決訓(xùn)練前先全量掃描一次標(biāo)簽?zāi)夸浾页龇欠悇e idgrep -R ^[^0-3] labels/ | head -20^[^0-3]匹配的是行首第一個(gè)字符不是 0、1、2、3 的標(biāo)簽行。把產(chǎn)生的清單輸出后要么刪掉這些標(biāo)簽和對(duì)應(yīng)圖片要么修正類別 id。但要記住修正時(shí)必須回到 classes.txt 的順序核對(duì)一次而不是憑印象把 4 改成 2因?yàn)?4 對(duì)應(yīng)的可能并不是“半熟”而是標(biāo)注人員口誤導(dǎo)致的另一個(gè)類別名。4.3 訓(xùn)練反復(fù)不收斂或 mAP 很低問題先查數(shù)據(jù)再查網(wǎng)絡(luò)現(xiàn)象loss 在前面幾十個(gè) epoch 降得很慢訓(xùn)練結(jié)束后會(huì)發(fā)現(xiàn)驗(yàn)證集上某類別的 AP 特別低比如未熟和半熟之間互相誤檢嚴(yán)重。原因典型的數(shù)據(jù)原因有三個(gè)。一是半熟樣本數(shù)量明顯少于其他三類模型在這個(gè)類別上欠擬合二是成熟度的邊界標(biāo)注存在大量主觀差異同一個(gè)果實(shí)兩張圖里一個(gè)標(biāo)成半熟一個(gè)標(biāo)成成熟三是有空標(biāo)簽文件wc -l為 0 的 txt 對(duì)應(yīng)圖片沒有框而在成熟度場(chǎng)景里有些圖只是遠(yuǎn)景沒有被標(biāo)到。解決先用一行命令統(tǒng)計(jì)類別分布再?zèng)Q定策略awk {print $1} labels/train/*.txt | sort | uniq -c | sort -rn參數(shù)含義很直白$1是每行第一個(gè)字段也就是類別 idsort | uniq -c統(tǒng)計(jì)每個(gè) id 出現(xiàn)的次數(shù)。輸出的數(shù)字里如果某個(gè)類別不到總量的 10%那就是典型的長(zhǎng)尾。處理辦法是給模型 config 里的loss 權(quán)重上做手腳給少數(shù)類更高的權(quán)重更簡(jiǎn)單粗暴的是把多數(shù)類隨機(jī)下采樣讓 4 類比接近 4:4:4:4但前提是總量仍然夠訓(xùn)練。標(biāo)注主觀差異屬于數(shù)據(jù)噪聲能做的只有把混亂樣本單獨(dú)挑出來手動(dòng)合并修正。這些操作在訓(xùn)練前做比訓(xùn)練后返工省下不止半天時(shí)間。5. 把數(shù)據(jù)集接進(jìn)訓(xùn)練流程驗(yàn)證集劃分與成熟度樣本平衡這套葡萄成熟度數(shù)據(jù)集真正好用的地方在于它已經(jīng)是 YOLO 能直接吃的格式。接到訓(xùn)練流程時(shí)我習(xí)慣在正式訓(xùn)練前固定跑一遍自查形成一個(gè)固定套路python show.py images/train/00248-3249807169_png.png awk {print $1} labels/train/*.txt | sort | uniq -c | sort -rn ls labels/train/*.txt | wc -l第一行抽看標(biāo)注質(zhì)量第二行看類別分布第三行確認(rèn)標(biāo)簽總數(shù)量。這三條命令執(zhí)行時(shí)間不超過一分鐘但能把你從“訓(xùn)練了一整天、驗(yàn)證時(shí)發(fā)現(xiàn)數(shù)據(jù)集有問題”的血淚里救出來。類別樣本平衡這塊我的處理習(xí)慣是不輕易刪樣本。刪樣本會(huì)讓模型丟失對(duì)邊界情況的判斷力尤其是成熟度這種連續(xù)變化場(chǎng)景刪掉低置信度的半熟樣本測(cè)試時(shí)遇到表現(xiàn)稍差的果實(shí)反而更容易誤判。正確做法是把少數(shù)類做簡(jiǎn)單的數(shù)據(jù)增強(qiáng)復(fù)刻或者調(diào)整訓(xùn)練時(shí)的采樣權(quán)重。部分 YOLO 訓(xùn)練代碼里可以直接指定類別權(quán)重參數(shù)把半熟這類目標(biāo)設(shè)為 1.5 到 2 倍權(quán)重比改變損失函數(shù)本身風(fēng)險(xiǎn)更小。跑一輪短訓(xùn)練驗(yàn)證分類正確率如果提升不顯著再回到數(shù)據(jù)層面。關(guān)于 mAP 的驗(yàn)證有一點(diǎn)容易被忽略看驗(yàn)證集 mAP 時(shí)不要只看 overall 值要按類別分別看。此數(shù)據(jù)集如果某個(gè)類別上 AP 比其他三類低 20 個(gè)點(diǎn)以上基本可以斷言是數(shù)據(jù)問題而不是網(wǎng)絡(luò)能力問題。把驗(yàn)證集的預(yù)測(cè)結(jié)果輸出成混淆矩陣能直接看出是未熟誤判成半熟還是相反這個(gè)方向同時(shí)決定了數(shù)據(jù)的修正方式是合并模糊類別還是補(bǔ)充特定成熟度的圖片。從那以后我每次拿到任何標(biāo)注好的目標(biāo)檢測(cè)數(shù)據(jù)集都強(qiáng)制走一遍這套流程先看 classes.txt再統(tǒng)計(jì)類別分布抽至少 20 張圖可視化最后才進(jìn)入訓(xùn)練配置。這個(gè)順序不起眼但讓我避開了好幾次標(biāo)簽格式錯(cuò)誤帶來的無效訓(xùn)練。如果你也正打算拿這份葡萄成熟度數(shù)據(jù)集跑自己的實(shí)驗(yàn)建議把上面三步當(dāng)成踏入訓(xùn)練前的固定儀式能讓你省下的是真正寶貴的時(shí)間希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取