道路車(chē)輛檢測(cè)數(shù)據(jù)集:1000張圖與VOC/COCO/YOLO標(biāo)簽及訓(xùn)練教程)
簡(jiǎn)介本資源為面向目標(biāo)檢測(cè)初學(xué)者與算法工程師的真實(shí)道路車(chē)輛檢測(cè)數(shù)據(jù)集聚焦YOLO系列模型訓(xùn)練場(chǎng)景可解決車(chē)輛目標(biāo)檢測(cè)任務(wù)中數(shù)據(jù)獲取難、標(biāo)注格式不統(tǒng)一的問(wèn)題。壓縮包共2000個(gè)文件約115.18MB包含1000張真實(shí)道路場(chǎng)景圖片以及對(duì)應(yīng)的VOCxml、COCOjson和YOLOtxt三種格式標(biāo)簽分別存放于不同文件夾可直接用于YOLO系列訓(xùn)練另附數(shù)據(jù)集劃分腳本、YOLO環(huán)境搭建與訓(xùn)練教程等py、html、yaml文件方便按需劃分訓(xùn)練集、驗(yàn)證集與測(cè)試集。目前已有491人學(xué)習(xí)下載。讀者可獲得一套開(kāi)箱即用的車(chē)輛檢測(cè)數(shù)據(jù)與配套工具快速完成環(huán)境配置、數(shù)據(jù)劃分與模型訓(xùn)練并通過(guò)教程掌握Windows與Linux下的實(shí)操流程適合課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)及入門(mén)級(jí)檢測(cè)項(xiàng)目使用。1. 真實(shí)道路車(chē)輛檢測(cè)數(shù)據(jù)集1000 張圖、三套標(biāo)簽、一條能跑通的訓(xùn)練鏈路手上有一批真實(shí)道路場(chǎng)景的行車(chē)圖片想拿來(lái)訓(xùn)練 YOLO 做車(chē)輛檢測(cè)最卡人的從來(lái)不是模型結(jié)構(gòu)而是數(shù)據(jù)這一環(huán)圖片從哪來(lái)、框怎么標(biāo)、VOC 和 COCO 的 XML/JSON 怎么轉(zhuǎn)成 YOLO 的 txt、訓(xùn)練集驗(yàn)證集怎么切、切完類(lèi)別對(duì)不對(duì)得上。這套「1000 張真實(shí)道路車(chē)輛圖片 VOC/COCO/YOLO 三種格式標(biāo)簽 劃分腳本 訓(xùn)練教程」的組合解決的正是這條從原始圖到可訓(xùn)練數(shù)據(jù)集的完整鏈路。它適合兩類(lèi)人一類(lèi)是剛接觸 YOLO 目標(biāo)檢測(cè)、想拿一份現(xiàn)成數(shù)據(jù)把yolov8訓(xùn)練自己的數(shù)據(jù)集流程跑通的新手另一類(lèi)是手里有自采道路圖、需要一套可靠的格式轉(zhuǎn)換與劃分腳本做模板的工程師。下面按「數(shù)據(jù)長(zhǎng)什么樣 → 三種格式怎么互轉(zhuǎn) → 劃分腳本怎么寫(xiě) → 訓(xùn)練怎么配 → 坑在哪」的順序講透。2. 先看清數(shù)據(jù)1000 張真實(shí)道路圖與三種標(biāo)簽格式的對(duì)應(yīng)關(guān)系拿到一個(gè)壓縮包第一件事不是急著解壓訓(xùn)練而是把目錄結(jié)構(gòu)和標(biāo)簽格式摸清楚。真實(shí)道路車(chē)輛檢測(cè)的數(shù)據(jù)組織方式直接決定了后面轉(zhuǎn)換腳本和訓(xùn)練配置怎么寫(xiě)。這一章先把「數(shù)據(jù)是什么」講明白再講「為什么同一批標(biāo)注要存三種格式」。2.1 目錄結(jié)構(gòu)與文件命名約定常見(jiàn)做法是把圖片和標(biāo)簽分開(kāi)放圖片統(tǒng)一放images/標(biāo)簽按格式分目錄。一個(gè)能直接對(duì)接 YOLO 訓(xùn)練的典型結(jié)構(gòu)是這樣road_vehicle_dataset/ ├── images/ # 1000 張真實(shí)道路圖片jpg 為主 │ ├── 000001.jpg │ └── ... ├── annotations_voc/ # VOC 格式每張圖一個(gè)同名 xml │ ├── 000001.xml │ └── ... ├── annotations_coco/ # COCO 格式整份一個(gè) json │ └── instances.json ├── labels_yolo/ # YOLO 格式每張圖一個(gè)同名 txt │ ├── 000001.txt │ └── ... ├── split_dataset.py # 劃分腳本 └── train_yolo.py # 訓(xùn)練入口命名上最關(guān)鍵的一條圖片名、VOC 的 xml 名、YOLO 的 txt 名必須嚴(yán)格同名只差擴(kuò)展名。很多轉(zhuǎn)換腳本翻車(chē)就是因?yàn)閳D片叫000001.jpg標(biāo)簽卻叫1.xml腳本按 stem 匹配時(shí)直接對(duì)不上最后訓(xùn)練時(shí)一堆圖沒(méi)有標(biāo)簽?zāi)P蛯W(xué)了個(gè)寂寞。VOC 的 xml 里車(chē)輛框存在object節(jié)點(diǎn)下包含name類(lèi)別名、bndbox里的xmin/ymin/xmax/ymax左上右下像素坐標(biāo)。COCO 的 json 里框是[x, y, width, height]的絕對(duì)像素值類(lèi)別通過(guò)categories數(shù)組的 id 映射。YOLO 的 txt 每行是class_id cx cy w h全部歸一化到 0~1。這三種格式的坐標(biāo)語(yǔ)義完全不同轉(zhuǎn)換時(shí)最容易錯(cuò)的就是「絕對(duì)像素」和「歸一化」的混用。2.2 為什么同一批標(biāo)注要同時(shí)保留 VOC、COCO、YOLO有人會(huì)問(wèn)既然要訓(xùn) YOLO直接留 YOLO 格式不就行了為什么還要 VOC 和 COCO血淚經(jīng)驗(yàn)是——格式?jīng)Q定了你以后能復(fù)用什么。VOC 格式是很多經(jīng)典檢測(cè)框架和標(biāo)注工具LabelImg 默認(rèn)就導(dǎo)出 VOC的通用交換格式標(biāo)注階段產(chǎn)出的往往就是 xml。COCO 格式則是另一大批模型和評(píng)測(cè)腳本的入口coco2017數(shù)據(jù)集結(jié)構(gòu)那套images/annotations/categories三段式被廣泛沿用很多預(yù)訓(xùn)練權(quán)重和評(píng)估工具只認(rèn) COCO json。YOLO 格式是訓(xùn)練時(shí)真正喂給 dataloader 的。所以合理的工作流是標(biāo)注產(chǎn)出 VOC → VOC 作為中間母格式 → 轉(zhuǎn) COCO 供評(píng)估/復(fù)用 → 轉(zhuǎn) YOLO 供訓(xùn)練。三種格式同時(shí)保留等于給自己留了后悔藥換框架、換評(píng)估指標(biāo)、換預(yù)訓(xùn)練模型時(shí)不用重新標(biāo)一遍。1000 張圖的標(biāo)注成本不低格式齊全比省那點(diǎn)磁盤(pán)空間重要得多。2.3 類(lèi)別體系與車(chē)輛檢測(cè)的常見(jiàn)類(lèi)別劃分真實(shí)道路車(chē)輛檢測(cè)的類(lèi)別劃分直接決定class_id的映射。常見(jiàn)做法有兩種粒度粒度典型類(lèi)別適用場(chǎng)景粗粒度vehicle所有車(chē)一類(lèi)只做有無(wú)車(chē)輛、計(jì)數(shù)、簡(jiǎn)單避障細(xì)粒度car / bus / truck / motorcycle / bicycle需要區(qū)分車(chē)型的交通分析粗粒度標(biāo)注快、類(lèi)別少、訓(xùn)練容易收斂適合 1000 張這種中小規(guī)模數(shù)據(jù)。細(xì)粒度對(duì)標(biāo)注質(zhì)量要求高1000 張里如果某些類(lèi)別樣本只有幾十個(gè)很容易出現(xiàn)類(lèi)別不平衡訓(xùn)練時(shí)小類(lèi)別幾乎學(xué)不到。我的建議是先按粗粒度跑通全流程確認(rèn) mAP 正常后再考慮細(xì)分。類(lèi)別映射必須三處一致VOC 的name、COCO 的categories、YOLO 的class_id。常見(jiàn)翻車(chē)是 VOC 里寫(xiě)的是中文「轎車(chē)」COCO 里映射成carYOLO 里 class_id 又是 0三處對(duì)不上評(píng)估時(shí)混淆矩陣全亂。定好一份classes.txt所有轉(zhuǎn)換腳本都讀它是唯一穩(wěn)妥的做法car bus truck motorcycle bicycle行號(hào)即 class_id從 0 開(kāi)始。這份文件是整條鏈路的「單一事實(shí)來(lái)源」改類(lèi)別只改這里。3. 三種格式互轉(zhuǎn)VOC 轉(zhuǎn) YOLO、VOC 轉(zhuǎn) COCO 的腳本與參數(shù)格式轉(zhuǎn)換是這套數(shù)據(jù)集里最容易出 bug 的環(huán)節(jié)因?yàn)樽鴺?biāo)語(yǔ)義、坐標(biāo)系原點(diǎn)、歸一化基準(zhǔn)三處都可能出錯(cuò)。這一章給出可直接抄的轉(zhuǎn)換腳本并逐段說(shuō)明參數(shù)含義。3.1 VOC 轉(zhuǎn) YOLO歸一化坐標(biāo)與 class_id 映射VOC 的框是絕對(duì)像素xmin/ymin/xmax/ymaxYOLO 要的是歸一化的中心點(diǎn)加寬高。轉(zhuǎn)換公式是cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h對(duì)應(yīng)腳本import os import xml.etree.ElementTree as ET from PIL import Image CLASSES [car, bus, truck, motorcycle, bicycle] CLASS_TO_ID {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue stem os.path.splitext(xml_file)[0] img_path os.path.join(img_dir, stem .jpg) # 用真實(shí)圖片尺寸做歸一化基準(zhǔn)不能用 xml 里的 size 盲信 img_w, img_h Image.open(img_path).size tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_TO_ID: continue # 未定義類(lèi)別直接跳過(guò)避免 class_id 越界 bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 裁剪到圖像邊界防止標(biāo)注越界導(dǎo)致歸一化后 1 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{CLASS_TO_ID[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, stem .txt), w) as f: f.write(\n.join(lines))邏輯說(shuō)明CLASS_TO_ID保證類(lèi)別順序和classes.txt一致歸一化基準(zhǔn)用PIL讀真實(shí)圖片尺寸而不是 xml 里size節(jié)點(diǎn)——標(biāo)注工具的 size 字段經(jīng)常和實(shí)際圖片對(duì)不上這是隱蔽的坑。坐標(biāo)裁剪到邊界是為了防止個(gè)別越界標(biāo)注讓歸一化值超過(guò) 1YOLO 訓(xùn)練時(shí)遇到 1 的框會(huì)直接報(bào)錯(cuò)或靜默丟棄。參數(shù)說(shuō)明xml_dir、img_dir、out_dir三個(gè)路徑按實(shí)際目錄傳CLASSES必須和classes.txt完全一致順序不能變否則 class_id 全錯(cuò)位。3.2 VOC 轉(zhuǎn) COCOjson 結(jié)構(gòu)與 categories 映射COCO 格式是一份大 json包含images、annotations、categories三個(gè)核心數(shù)組。轉(zhuǎn)換時(shí)最容易錯(cuò)的是id的唯一性和category_id的起始值——COCO 官方約定category_id從 1 開(kāi)始0 通常留給背景而 YOLO 從 0 開(kāi)始兩者不能混。import os import json import xml.etree.ElementTree as ET from PIL import Image CLASSES [car, bus, truck, motorcycle, bicycle] def voc_to_coco(xml_dir, img_dir, out_json): coco {images: [], annotations: [], categories: []} # COCO 約定 category_id 從 1 開(kāi)始 for i, name in enumerate(CLASSES, start1): coco[categories].append({id: i, name: name, supercategory: vehicle}) ann_id 1 img_id 1 for xml_file in sorted(os.listdir(xml_dir)): if not xml_file.endswith(.xml): continue stem os.path.splitext(xml_file)[0] img_path os.path.join(img_dir, stem .jpg) img_w, img_h Image.open(img_path).size coco[images].append({ id: img_id, file_name: stem .jpg, width: img_w, height: img_h }) root ET.parse(os.path.join(xml_dir, xml_file)).getroot() for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASSES: continue bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) w xmax - xmin h ymax - ymin if w 0 or h 0: continue # 丟棄退化框 coco[annotations].append({ id: ann_id, image_id: img_id, category_id: CLASSES.index(name) 1, bbox: [xmin, ymin, w, h], # COCO 是 [x,y,w,h] 絕對(duì)像素 area: w * h, iscrowd: 0 }) ann_id 1 img_id 1 with open(out_json, w) as f: json.dump(coco, f)邏輯說(shuō)明bbox用[x, y, w, h]絕對(duì)像素和 YOLO 的歸一化中心點(diǎn)格式完全不同別混。area字段是 COCO 評(píng)估腳本要用的必須填。iscrowd標(biāo) 0 表示普通實(shí)例。category_id用CLASSES.index(name) 1保證從 1 開(kāi)始且和categories對(duì)應(yīng)。參數(shù)說(shuō)明out_json輸出單文件img_id和ann_id必須全局唯一且遞增重復(fù) id 會(huì)讓評(píng)估腳本讀取出錯(cuò)。3.3 轉(zhuǎn)換后的自檢三行命令驗(yàn)證標(biāo)簽一致性轉(zhuǎn)完不要直接開(kāi)訓(xùn)先做一致性自檢。下面三行能擋掉大部分低級(jí)錯(cuò)誤# 1. 圖片數(shù)和標(biāo)簽數(shù)是否一致 ls images/ | wc -l ls labels_yolo/ | wc -l # 2. 有沒(méi)有空標(biāo)簽文件空文件說(shuō)明該圖沒(méi)框或轉(zhuǎn)換失敗 find labels_yolo/ -name *.txt -empty | head # 3. 抽查一行確認(rèn) class_id 在合法范圍、坐標(biāo)都在 0~1 head -n 3 labels_yolo/000001.txt第一行數(shù)量對(duì)不上說(shuō)明有圖沒(méi)標(biāo)簽或有標(biāo)簽沒(méi)圖訓(xùn)練時(shí) dataloader 會(huì)報(bào)錯(cuò)或靜默跳過(guò)。第二行空標(biāo)簽要區(qū)分是「圖里真沒(méi)車(chē)」還是「轉(zhuǎn)換失敗」——前者可以留后者必須查。第三行看坐標(biāo)是否都在 0~1出現(xiàn)大于 1 或負(fù)數(shù)回去查 3.1 里的邊界裁剪。這三步花不了一分鐘能省掉后面幾小時(shí)的排查。4. 劃分腳本訓(xùn)練集/驗(yàn)證集切分與類(lèi)別分布檢查數(shù)據(jù)集劃分看著簡(jiǎn)單實(shí)際是訓(xùn)練翻車(chē)的重災(zāi)區(qū)。隨機(jī)切分如果沒(méi)控制類(lèi)別分布驗(yàn)證集里可能某個(gè)類(lèi)別一個(gè)樣本都沒(méi)有訓(xùn)練日志里那個(gè)類(lèi)別的 mAP 直接是 0你還以為是模型不行。4.1 按 8:2 切分并保證類(lèi)別覆蓋常見(jiàn)做法是 8:2 或 7:3 切分。關(guān)鍵是切分后要檢查每個(gè)類(lèi)別在訓(xùn)練集和驗(yàn)證集里都有足夠樣本。下面腳本先按圖片切分再統(tǒng)計(jì)類(lèi)別分布import os import random import shutil from collections import Counter def split_dataset(img_dir, label_dir, out_root, val_ratio0.2, seed42): random.seed(seed) # 固定種子保證可復(fù)現(xiàn) stems [os.path.splitext(f)[0] for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .png, .jpeg))] random.shuffle(stems) n_val int(len(stems) * val_ratio) val_stems set(stems[:n_val]) train_stems set(stems[n_val:]) for split, subset in [(train, train_stems), (val, val_stems)]: img_out os.path.join(out_root, images, split) lbl_out os.path.join(out_root, labels, split) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for stem in subset: for ext in (.jpg, .png, .jpeg): src os.path.join(img_dir, stem ext) if os.path.exists(src): shutil.copy(src, os.path.join(img_out, stem ext)) break lbl_src os.path.join(label_dir, stem .txt) if os.path.exists(lbl_src): shutil.copy(lbl_src, os.path.join(lbl_out, stem .txt)) # 統(tǒng)計(jì)兩個(gè) split 的類(lèi)別分布 for split in (train, val): counter Counter() lbl_dir os.path.join(out_root, labels, split) for f in os.listdir(lbl_dir): with open(os.path.join(lbl_dir, f)) as fh: for line in fh: if line.strip(): counter[int(line.split()[0])] 1 print(split, dict(sorted(counter.items())))邏輯說(shuō)明seed42固定隨機(jī)種子保證每次切分結(jié)果一致方便復(fù)現(xiàn)和對(duì)比實(shí)驗(yàn)。先 shuffle 再切片避免按文件名順序切導(dǎo)致分布偏差比如按時(shí)間拍的圖前 800 張是白天、后 200 張是夜晚順序切會(huì)讓驗(yàn)證集全是夜晚。最后打印兩個(gè) split 的類(lèi)別計(jì)數(shù)一眼看出有沒(méi)有類(lèi)別缺失。參數(shù)說(shuō)明val_ratio默認(rèn) 0.21000 張圖對(duì)應(yīng) 200 張驗(yàn)證集夠評(píng)估用數(shù)據(jù)量更小時(shí)可以調(diào)到 0.3。seed換值會(huì)得到不同切分做交叉驗(yàn)證時(shí)可以循環(huán)換 seed。4.2 類(lèi)別不平衡時(shí)怎么處理打印出來(lái)的分布如果發(fā)現(xiàn)某個(gè)類(lèi)別在驗(yàn)證集里只有個(gè)位數(shù)或者訓(xùn)練集里某類(lèi)占比不到 5%就要處理。三種常見(jiàn)做法一是分層切分按類(lèi)別把樣本分組后再切保證每個(gè)類(lèi)別在兩個(gè) split 里比例接近。二是過(guò)采樣小類(lèi)別訓(xùn)練時(shí)對(duì)小類(lèi)別圖片重復(fù)采樣。三是調(diào)損失權(quán)重YOLO 訓(xùn)練配置里可以給不同類(lèi)別加權(quán)但多數(shù)實(shí)現(xiàn)不直接暴露這個(gè)參數(shù)改起來(lái)麻煩。對(duì) 1000 張這種規(guī)模最實(shí)用的是分層切分。如果某類(lèi)別總樣本就幾十個(gè)再怎么切驗(yàn)證集也就十幾個(gè)這時(shí)候更該考慮的是合并類(lèi)別——把樣本太少的細(xì)分類(lèi)別并進(jìn)粗類(lèi)比硬訓(xùn)一個(gè)學(xué)不動(dòng)的類(lèi)別劃算。4.3 生成 YOLO 訓(xùn)練用的 data.yaml切分完要生成 YOLO 訓(xùn)練配置。以 Ultralytics 系 YOLO 為例data.yaml長(zhǎng)這樣path: /abs/path/to/road_vehicle_dataset train: images/train val: images/val nc: 5 names: 0: car 1: bus 2: truck 3: motorcycle 4: bicyclepath用絕對(duì)路徑最穩(wěn)相對(duì)路徑在不同工作目錄下啟動(dòng)訓(xùn)練時(shí)經(jīng)常找不到。train/val指向圖片目錄YOLO 會(huì)自動(dòng)去找同級(jí)的labels/目錄把路徑里的images換成labels。nc是類(lèi)別數(shù)必須和names長(zhǎng)度一致寫(xiě)錯(cuò)會(huì)直接報(bào)維度不匹配。names的順序必須和classes.txt、轉(zhuǎn)換腳本里的CLASSES完全一致這是整條鏈路一致性的最后一環(huán)。5. 訓(xùn)練教程從環(huán)境配置到跑通第一個(gè) epoch數(shù)據(jù)準(zhǔn)備好接下來(lái)是訓(xùn)練。這一章給出一條能跑通的最小訓(xùn)練鏈路重點(diǎn)講參數(shù)怎么設(shè)、日志怎么看。5.1 環(huán)境配置與依賴(lài)版本YOLO 訓(xùn)練環(huán)境的核心依賴(lài)是 PyTorch 和對(duì)應(yīng) CUDA。常見(jiàn)做法是用 conda 建獨(dú)立環(huán)境避免和系統(tǒng) Python 沖突conda create -n yolo python3.10 -y conda activate yolo # 按本機(jī) CUDA 版本裝 PyTorch下面以 CUDA 11.8 為例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pillow pyyaml邏輯說(shuō)明PyTorch 版本必須和 CUDA 驅(qū)動(dòng)匹配裝錯(cuò)會(huì)出現(xiàn)torch.cuda.is_available()返回 False訓(xùn)練自動(dòng)掉到 CPU 上1000 張圖一個(gè) epoch 能跑幾十分鐘。裝完先驗(yàn)證python -c import torch; print(torch.__version__, torch.cuda.is_available())輸出里True才說(shuō)明 GPU 可用。這一步別省我見(jiàn)過(guò)太多人訓(xùn)了半天發(fā)現(xiàn)用的是 CPU。5.2 訓(xùn)練命令與關(guān)鍵參數(shù)以 Ultralytics YOLO 為例最小訓(xùn)練命令yolo detect train \ data/abs/path/to/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/train \ nameroad_vehicle參數(shù)說(shuō)明model用預(yù)訓(xùn)練權(quán)重如yolov8n.pt比從零訓(xùn)收斂快得多小數(shù)據(jù)集強(qiáng)烈建議用預(yù)訓(xùn)練。epochs100對(duì) 1000 張圖通常夠配合patience20早停驗(yàn)證指標(biāo) 20 輪不漲就停省時(shí)間。imgsz640是通用尺寸顯存不夠降到 416 或 320。batch16按顯存調(diào)8G 顯存跑 640 尺寸大概能到 16爆顯存就減半。lr00.01是初始學(xué)習(xí)率用預(yù)訓(xùn)練權(quán)重時(shí)這個(gè)值合適從零訓(xùn)可以調(diào)到 0.001。5.3 訓(xùn)練日志與指標(biāo)怎么看訓(xùn)練啟動(dòng)后重點(diǎn)盯幾個(gè)指標(biāo)。box_loss和cls_loss應(yīng)該整體下降如果震蕩劇烈或發(fā)散多半是學(xué)習(xí)率太大或 batch 太小。mAP50是 IoU 閾值 0.5 下的平均精度車(chē)輛檢測(cè)這種目標(biāo)清晰的任務(wù)1000 張圖跑到 0.7 以上算正常。mAP50-95更嚴(yán)格通常比mAP50低 0.2 左右。如果mAP50一直上不去先別懷疑模型回去查數(shù)據(jù)標(biāo)簽是不是有大量空文件、類(lèi)別是不是標(biāo)錯(cuò)、驗(yàn)證集是不是和訓(xùn)練集分布差太多。yolo混淆矩陣總合不唯一這類(lèi)問(wèn)題往往就是類(lèi)別映射錯(cuò)位或標(biāo)簽格式不對(duì)導(dǎo)致的根子在數(shù)據(jù)不在模型。訓(xùn)練完在runs/train/road_vehicle/weights/下會(huì)有best.pt和last.ptbest.pt是驗(yàn)證指標(biāo)最好的權(quán)重推理用它。驗(yàn)證一下yolo detect predict modelruns/train/road_vehicle/weights/best.pt \ source/abs/path/to/test_images saveTrue輸出圖會(huì)畫(huà)上檢測(cè)框肉眼掃一遍看有沒(méi)有系統(tǒng)性漏檢或誤檢比只看數(shù)字更直觀。6. 避坑與排查標(biāo)簽、劃分、訓(xùn)練里最容易翻車(chē)的五件事這一章全是踩過(guò)的坑每條按「現(xiàn)象 → 原因 → 解決」寫(xiě)遇到問(wèn)題直接對(duì)號(hào)入座?,F(xiàn)象一訓(xùn)練啟動(dòng)就報(bào)No labels found或大量圖片被跳過(guò)。原因圖片和標(biāo)簽不同名或標(biāo)簽?zāi)夸浗Y(jié)構(gòu)和data.yaml不匹配。YOLO 默認(rèn)按「圖片路徑里images換成labels、擴(kuò)展名換成.txt」去找標(biāo)簽任何一環(huán)對(duì)不上就找不到。 解決跑 3.3 的自檢命令確認(rèn)圖片數(shù)和標(biāo)簽數(shù)一致、同名對(duì)應(yīng)。目錄結(jié)構(gòu)嚴(yán)格按images/train配labels/train?,F(xiàn)象二訓(xùn)練不報(bào)錯(cuò)但 mAP 一直是 0 或極低。原因class_id 越界或類(lèi)別映射錯(cuò)位。比如data.yaml里nc5但標(biāo)簽里出現(xiàn)了 class_id5 甚至更大YOLO 會(huì)忽略這些框或者 VOC 里類(lèi)別名和classes.txt順序不一致class_id 全錯(cuò)。 解決檢查classes.txt、轉(zhuǎn)換腳本CLASSES、data.yaml的names三處順序完全一致用awk {print $1} labels/*.txt | sort -u看 class_id 范圍是否在0 ~ nc-1?,F(xiàn)象三驗(yàn)證集某個(gè)類(lèi)別 mAP 為 0其他類(lèi)別正常。原因該類(lèi)別在驗(yàn)證集里樣本太少甚至沒(méi)有隨機(jī)切分沒(méi)控制分布。 解決用 4.1 腳本打印類(lèi)別分布確認(rèn)每個(gè)類(lèi)別在 val 里都有樣本樣本實(shí)在太少的類(lèi)別考慮合并或過(guò)采樣。現(xiàn)象四訓(xùn)練中途 loss 變 NaN 或突然飆升。原因?qū)W習(xí)率過(guò)大、batch 太小導(dǎo)致梯度不穩(wěn)或標(biāo)簽里有退化框?qū)捀邽?0 或負(fù)。 解決降lr0到 0.001增大 batch轉(zhuǎn)換腳本里加if w 0 or h 0: continue過(guò)濾退化框3.2 已含?,F(xiàn)象五推理時(shí)框位置整體偏移或大小不對(duì)。原因訓(xùn)練和推理的imgsz不一致或轉(zhuǎn)換時(shí)歸一化基準(zhǔn)用錯(cuò)用了 xml 里的 size 而非真實(shí)圖片尺寸。 解決訓(xùn)練和推理保持同一imgsz轉(zhuǎn)換時(shí)用PIL讀真實(shí)尺寸做歸一化別信 xml 的size。提示這五條里前兩條占了實(shí)際問(wèn)題的七成以上遇到異常先查標(biāo)簽一致性和 class_id 范圍再懷疑模型和超參。7. 進(jìn)階技巧用預(yù)訓(xùn)練權(quán)重和凍結(jié)策略把 1000 張小數(shù)據(jù)集榨出更高精度1000 張圖在目標(biāo)檢測(cè)里屬于小數(shù)據(jù)集從零訓(xùn)很難收斂到理想精度。真正拉開(kāi)差距的是預(yù)訓(xùn)練權(quán)重的用法和訓(xùn)練策略的微調(diào)。我一般會(huì)分兩階段訓(xùn)先凍結(jié)主干只訓(xùn)檢測(cè)頭再解凍全網(wǎng)絡(luò)微調(diào)。第一階段凍結(jié)主干讓檢測(cè)頭先適配你的類(lèi)別yolo detect train \ data/abs/path/to/data.yaml \ modelyolov8n.pt \ epochs30 \ freeze10 \ lr00.01 \ imgsz640 batch16freeze10表示凍結(jié)前 10 層主干部分只更新后面的檢測(cè)頭。這樣收斂快、不容易過(guò)擬合30 輪就能讓檢測(cè)頭初步適配車(chē)輛類(lèi)別。第二階段解凍全網(wǎng)絡(luò)用小學(xué)習(xí)率微調(diào)yolo detect train \ data/abs/path/to/data.yaml \ modelruns/train/road_vehicle/weights/best.pt \ epochs100 \ lr00.001 \ imgsz640 batch16 patience20注意第二階段model指向第一階段產(chǎn)出的best.ptlr0降到 0.001避免大學(xué)習(xí)率把預(yù)訓(xùn)練學(xué)到的特征沖掉。這套兩階段策略在 1000 張量級(jí)上通常比直接訓(xùn)能高出幾個(gè)點(diǎn) mAP。數(shù)據(jù)增強(qiáng)也是小數(shù)據(jù)集的救命稻草。YOLO 默認(rèn)開(kāi)了 mosaic、HSV 抖動(dòng)、隨機(jī)翻轉(zhuǎn)對(duì)道路場(chǎng)景夠用。如果發(fā)現(xiàn)模型對(duì)夜間或雨天泛化差可以針對(duì)性補(bǔ)充這類(lèi)樣本而不是盲目加大增強(qiáng)強(qiáng)度——增強(qiáng)過(guò)頭反而讓模型學(xué)不到真實(shí)分布。驗(yàn)證階段除了看 mAP建議把best.pt在幾十張沒(méi)參與訓(xùn)練的圖上跑一遍肉眼核對(duì)。數(shù)字好看但框飄的情況不少見(jiàn)尤其是小目標(biāo)和遮擋目標(biāo)。我自己的習(xí)慣是每次訓(xùn)完必抽 20 張圖看預(yù)測(cè)結(jié)果比盯日志更能發(fā)現(xiàn)問(wèn)題。這套數(shù)據(jù)集和腳本的價(jià)值不在于省了標(biāo)注那點(diǎn)事而在于它把「格式轉(zhuǎn)換 → 劃分 → 訓(xùn)練 → 排查」這條鏈路完整串起來(lái)了你照著走一遍以后換任何自采數(shù)據(jù)都能復(fù)用這套流程。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取