檢測(cè)數(shù)據(jù)集腳手架:從1322張到可用項(xiàng)目)
簡介面向目標(biāo)檢測(cè)入門與項(xiàng)目快速搭建這份Pascal VOC格式數(shù)據(jù)集可直接作為訓(xùn)練腳手架使用。壓縮包共2645個(gè)文件包括1322張jpg圖片、1322個(gè)xml標(biāo)注文件及1份txt說明整體約202.49MB。圖片均由labelImg工具按矩形框標(biāo)注目標(biāo)類別統(tǒng)一為“jsj”標(biāo)注框總數(shù)1341個(gè)類別單一且標(biāo)注規(guī)則統(tǒng)一數(shù)據(jù)干凈穩(wěn)定可直接輸入YOLO、SSD、Faster R-CNN等常見檢測(cè)框架也適合作為數(shù)據(jù)增強(qiáng)、模型調(diào)參或遷移學(xué)習(xí)的基礎(chǔ)集。資源包刻意去掉了分割路徑txt與yolo格式txt只保留最核心的圖片和XML目錄結(jié)構(gòu)十分清爽便于初學(xué)者對(duì)照理解VOC標(biāo)注的組織方式也能讓研究者快速替換或擴(kuò)充自有數(shù)據(jù)。目前已有525人學(xué)習(xí)下載是一份規(guī)格標(biāo)準(zhǔn)、標(biāo)注合理的低成本實(shí)驗(yàn)數(shù)據(jù)下載解壓后即可接入完整檢測(cè)流程無論是課程設(shè)計(jì)還是工程預(yù)研都能顯著減少重復(fù)標(biāo)注與格式轉(zhuǎn)換的耗時(shí)。1. 從“1322張”聊起VOC格式目標(biāo)檢測(cè)數(shù)據(jù)集腳手架到底是什么如果你手頭有一個(gè)目標(biāo)檢測(cè)任務(wù)既沒時(shí)間爬上萬張公開圖也沒預(yù)算雇人從頭標(biāo)最務(wù)實(shí)的做法是先搭一個(gè)能跑通訓(xùn)練、驗(yàn)證、評(píng)估全流程的“腳手架”——這就是VOC格式目標(biāo)檢測(cè)數(shù)據(jù)集腳手架。所謂腳手架不是讓你拿它當(dāng)最終訓(xùn)練集直接上生產(chǎn)而是用它把數(shù)據(jù)格式、標(biāo)簽質(zhì)量、劃分邏輯、轉(zhuǎn)換腳本全部驗(yàn)證一遍再帶著這套流程去擴(kuò)展真實(shí)數(shù)據(jù)。1322張是一個(gè)很聰明的規(guī)模比手工攢的兩三百張厚實(shí)又比COCO2017那種十幾萬張的龐然大物輕得多正好用來試錯(cuò)和走通pipeline。適合誰用呢做畢設(shè)的學(xué)生、小團(tuán)隊(duì)做預(yù)研的算法工程師、以及剛接觸VOC格式想弄懂標(biāo)注文件與訓(xùn)練框架怎么銜接的新人。這篇文章不聊高深理論就講拿到這類數(shù)據(jù)集后怎么體檢、怎么劃分、怎么避坑讓它真正長出可用項(xiàng)目。2. VOC格式的目錄結(jié)構(gòu)與標(biāo)注文件動(dòng)手前先把三件套讀透很多新手拿到數(shù)據(jù)包第一件事就是解壓、打開看幾張圖然后直接扔給訓(xùn)練腳本。等到訓(xùn)練報(bào)錯(cuò)才回頭查目錄結(jié)構(gòu)時(shí)間全浪費(fèi)在黑匣子里。VOC格式的設(shè)計(jì)其實(shí)很樸素它把原始圖片、標(biāo)注信息、訓(xùn)練劃分三件事拆成三個(gè)目錄各管各的。動(dòng)手處理前我建議先把這三個(gè)目錄讀透再碰任何代碼。2.1 目錄三件套JPEGImages、Annotations、ImageSets/Main 各管什么標(biāo)準(zhǔn)的VOC格式數(shù)據(jù)集長這樣dataset_root/ ├── JPEGImages/ # 原始圖片統(tǒng)一為 .jpg ├── Annotations/ # 與圖片同名的 .xml 標(biāo)注文件 └── ImageSets/ └── Main/ # 劃分文件train.txt / val.txt / trainval.txtJPEGImages存圖片Annotations存圖片對(duì)應(yīng)的XML標(biāo)注ImageSets/Main下則是純粹的txt文件每行一個(gè)文件名不帶擴(kuò)展名表示這張圖被劃分到哪個(gè)集合。這三者的關(guān)系是訓(xùn)練框架讀ImageSets里的txt拿到文件名列表再去JPEGImages找圖、去Annotations找標(biāo)簽。用下面這段腳本先確認(rèn)三件套的數(shù)量是否對(duì)得上import os from pathlib import Path root Path(dataset_root) imgs set(p.stem for p in (root / JPEGImages).glob(*.jpg)) anns set(p.stem for p in (root / Annotations).glob(*.xml)) print(f圖片數(shù)量: {len(imgs)}) print(fXML數(shù)量: {len(anns)}) print(f有圖無標(biāo)注: {len(imgs - anns)}) print(f有標(biāo)注無圖: {len(anns - imgs)})這段腳本用集合做差集一秒鐘就能看出圖片和標(biāo)注是否一一對(duì)應(yīng)。正常情況兩個(gè)差集都應(yīng)該是0只要出現(xiàn)任何一個(gè)就說明數(shù)據(jù)包本身不干凈后邊訓(xùn)練時(shí)會(huì)出現(xiàn)找不到文件或讀不到標(biāo)簽的詭異報(bào)錯(cuò)。1322張的數(shù)據(jù)集我一般要求圖片名和XML文件名嚴(yán)格同名連大小寫都必須一致因?yàn)長inux下文件名是區(qū)分大小寫的Windows上能跑的訓(xùn)練腳本換到服務(wù)器上可能直接翻車。2.2 解析一個(gè)XML讀懂VOC標(biāo)注的XML并不復(fù)雜但里面幾個(gè)字段的含義如果理解偏了后邊做過濾、轉(zhuǎn)換全都會(huì)錯(cuò)。拿一個(gè)典型標(biāo)注做拆解annotation folderJPEGImages/folder filename000001.jpg/filename sourcedatabaseUnknown/database/source size width640/width height480/height depth3/depth /size object nameperson/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin100/xmin ymin120/ymin xmax300/xmax ymax420/ymax /bndbox /object /annotationsize節(jié)點(diǎn)記錄圖片真實(shí)寬高bndbox是目標(biāo)框的左上角和右下角坐標(biāo)這些都是訓(xùn)練時(shí)直接要用的。真正容易踩坑的是object節(jié)點(diǎn)里三個(gè)狀態(tài)位pose表示目標(biāo)姿態(tài)多數(shù)數(shù)據(jù)集標(biāo)的是Unspecifiedtruncated表示目標(biāo)是否被圖片邊緣截?cái)?為完整difficult表示這個(gè)目標(biāo)是否難以辨認(rèn)1表示難例訓(xùn)練時(shí)通常應(yīng)該過濾掉。import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) objects [] for obj in root.findall(object): name obj.find(name).text difficult int(obj.find(difficult).text) bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) objects.append({ name: name, difficult: difficult, bbox: [xmin, ymin, xmax, ymax] }) return w, h, objects這里用xml.etree.ElementTree解析標(biāo)準(zhǔn)庫足夠用不必引第三方依賴。注意所有坐標(biāo)字段都是字符串必須int()轉(zhuǎn)換。不少腳手架數(shù)據(jù)集里的XML帶有命名空間或者多余空白find(object)可能返回None穩(wěn)妥做法是先root.findall(object)再逐個(gè)取子節(jié)點(diǎn)取不到就把這張圖標(biāo)成異常樣本別讓訓(xùn)練進(jìn)程崩在XML解析上。2.3 用Python統(tǒng)計(jì)類別分布1322張里到底有多少可用框拿到數(shù)據(jù)先做統(tǒng)計(jì)這是所有預(yù)處理的前提。1322張到底能不能訓(xùn)、適合訓(xùn)幾個(gè)類別、哪些類別框太少需要重點(diǎn)擴(kuò)統(tǒng)計(jì)一次就有答案。常見做法是遍歷全部XML把name字段聚合起來from collections import Counter img_count 0 cls_counter Counter() box_counter Counter() invalid_xml [] for xml_path in (root / Annotations).glob(*.xml): img_count 1 try: w, h, objects parse_voc_xml(xml_path) except Exception as e: invalid_xml.append((xml_path.name, str(e))) continue cls_in_img set() for obj in objects: if obj[difficult] 1: continue cls_counter[obj[name]] 1 cls_in_img.add(obj[name]) for name in cls_in_img: box_counter[name] 1 print(f圖片總數(shù): {img_count}, 解析失敗: {len(invalid_xml)}) print(f類別數(shù): {len(cls_counter)}) for name, cnt in cls_counter.most_common(): print(f{name}: {cnt} 個(gè)框, 出現(xiàn)在 {box_counter[name]} 張圖)上面的統(tǒng)計(jì)做了兩件事按框計(jì)數(shù)看類別是否有壓倒性不平衡按圖片計(jì)數(shù)看某個(gè)類別是不是只在極少數(shù)圖里出現(xiàn)。一張表就能看出問題比如某類別有300個(gè)框但只分布在中50張圖里說明框集中、背景單一模型很容易過擬合。我習(xí)慣把統(tǒng)計(jì)結(jié)果落成下面這樣的清單作為后續(xù)擴(kuò)數(shù)據(jù)的依據(jù)。類別標(biāo)注框總數(shù)出現(xiàn)該類的圖片數(shù)判斷person1247618主類數(shù)量充足car863452主類場(chǎng)景較集中cat336210次類可訓(xùn)但需增強(qiáng)dog158102偏少慎單獨(dú)建模統(tǒng)計(jì)之后還能順手發(fā)現(xiàn)一批空XML、雙重標(biāo)注或坐標(biāo)跑出圖片邊界的臟樣本。這些臟樣本在1322張的規(guī)模下占比可能不高但每一條都會(huì)在訓(xùn)練日志里變成莫名其妙的loss抖動(dòng)寧可提前清理也不要讓框架替你兜底。3. 劃分與格式轉(zhuǎn)換把1322張切成可訓(xùn)的VOC/YOLO/COCO數(shù)據(jù)體檢做完下一步就是劃分和轉(zhuǎn)換。這一步的產(chǎn)出物直接決定你后面用什么框架訓(xùn)練、怎么評(píng)估。很多人隨手按順序切前70%作為訓(xùn)練集完全不做分層結(jié)果小類在驗(yàn)證集里直接消失mAP忽高忽低。這一章把劃分和轉(zhuǎn)換的關(guān)鍵步驟拆開講。3.1 劃分前的數(shù)據(jù)體檢壞圖、斷名、空XML一次清掉我對(duì)數(shù)據(jù)集的規(guī)矩是任何一張圖進(jìn)不了訓(xùn)練集就必須在劃分前被踢出去。體檢腳本要覆蓋四類問題——圖片損壞、圖片與XML失配、XML解析失敗、XML里沒有任何有效框。from PIL import Image def validate_sample(img_path, xml_path): errs [] try: with Image.open(img_path) as im: im.verify() im Image.open(img_path) w, h im.size if w 32 or h 32: errs.append(圖片尺寸過小) except Exception: errs.append(圖片無法打開) if not xml_path.exists(): errs.append(XML缺失) return errs try: _, _, objs parse_voc_xml(xml_path) valid [o for o in objs if o[difficult] 0] if len(valid) 0: errs.append(無有效目標(biāo)框) except Exception as e: errs.append(fXML解析異常: {e}) return errsPIL的verify()只檢查文件完整性不加載像素?cái)?shù)據(jù)速度很快。加一個(gè)最小邊長32像素的判斷是為了防止后面resize時(shí)出現(xiàn)極端插值變形。空XML和全difficult的XML也要單獨(dú)拎出來——它們不是不能進(jìn)數(shù)據(jù)集而是不能直接進(jìn)“有監(jiān)督”訓(xùn)練否則相當(dāng)于拿一批沒有學(xué)習(xí)信號(hào)的照片白跑前向。對(duì)1322張的規(guī)模體檢腳本可以全量跑不需要抽樣。輸出結(jié)果分成兩份一份是壞樣本清單一份是干凈樣本清單。后續(xù)所有劃分、轉(zhuǎn)換都只針對(duì)干凈清單執(zhí)行壞樣本留在原目錄等擴(kuò)展數(shù)據(jù)時(shí)人工復(fù)核。3.2 按類別分層劃分訓(xùn)練集、驗(yàn)證集、測(cè)試集參數(shù)怎么設(shè)劃分的核心訴求是讓每個(gè)類別在三個(gè)集合里的比例盡量接近原始分布。最簡單的做法是用隨機(jī)種子直接切import random random.seed(42) ids list(clean_ids) random.shuffle(ids) train_ids ids[:int(len(ids) * 0.8)] val_ids ids[int(len(ids) * 0.8):int(len(ids) * 0.9)] test_ids ids[int(len(ids) * 0.9):]直接在ImageSets/Main下生成txt文件def write_split(name, ids): with open(root / ImageSets / Main / f{name}.txt, w) as f: for sid in ids: f.write(f{sid}\n) write_split(train, train_ids) write_split(val, val_ids) write_split(test, test_ids)這種做法代碼最省但有個(gè)隱藏問題如果數(shù)據(jù)集里貓只有102張圖隨機(jī)切分后驗(yàn)證集可能只有15張貓圖測(cè)試集更少評(píng)估結(jié)果波動(dòng)巨大。更穩(wěn)的做法是按主類別分層用sklearn的train_test_splitfrom sklearn.model_selection import train_test_split labels_map {} # image_id - 該圖的主類別 for xml_path in (root / Annotations).glob(*.xml): sid xml_path.stem _, _, objs parse_voc_xml(xml_path) valid [o for o in objs if o[difficult] 0] if not valid: continue main_cls max(set(o[name] for o in valid), keylambda c: sum(1 for o in valid if o[name] c)) labels_map[sid] main_cls ids list(labels_map.keys()) y [labels_map[i] for i in ids] train_ids, temp_ids, y_train, _ train_test_split( ids, y, test_size0.3, stratifyy, random_state42) val_ids, test_ids train_test_split( temp_ids, test_size0.5, stratify[labels_map[i] for i in temp_ids], random_state42)分層劃分的數(shù)學(xué)原理很簡單按類別標(biāo)簽做比例抽樣讓每個(gè)類別在train、val、test中的占比和全量一致。三個(gè)集合的比例參數(shù)我一般用train 0.7、val 0.2、test 0.1。1322張的規(guī)模下test保留132張左右用于最終評(píng)估如果樣本特別少的類別寧可把test壓到0.05也要保證驗(yàn)證集里每類至少10張圖。劃分完必須檢查每個(gè)txt的類別分布并把random_state固定下來否則下次跑腳本結(jié)果全變后面所有實(shí)驗(yàn)對(duì)比都失去意義。3.3 轉(zhuǎn)換到Y(jié)OLO txt與COCO json歸一化與ID映射VOC格式不能直接喂給yolov5/yolov8這類框架需要先轉(zhuǎn)成每行一個(gè)目標(biāo)的txt文件。坐標(biāo)歸一化公式是把絕對(duì)坐標(biāo)除以圖片寬高換算成0到1之間的相對(duì)值def voc_to_yolo(xml_path, class_list, out_dir): w, h, objs parse_voc_xml(xml_path) lines [] for obj in objs: if obj[difficult] 1: continue cls_id class_list.index(obj[name]) xmin, ymin, xmax, ymax obj[bbox] dw, dh 1.0 / w, 1.0 / h cx (xmin xmax) / 2.0 * dw cy (ymin ymax) / 2.0 * dh bw (xmax - xmin) * dw bh (ymax - ymin) * dh lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_txt out_dir / (xml_path.stem .txt) out_txt.write_text(\n.join(lines))class_list的順序就是訓(xùn)練時(shí)類別ID的映射表例如[car, person, cat]car0、person1、cat2。這里有一個(gè)新手必踩的坑pyTorch/YOLO框架通常需要配一個(gè)data.yaml里面class_names必須和class_list順序一致順序一旦錯(cuò)位訓(xùn)練出來的模型預(yù)測(cè)結(jié)果就是張冠李戴loss看起來還挺漂亮。如果后續(xù)打算用mmrotate這類旋轉(zhuǎn)框框架或者COCO預(yù)訓(xùn)練權(quán)重遷移則要把VOC轉(zhuǎn)成COCO json。COCO的標(biāo)注結(jié)構(gòu)圍繞三張表組織coco { images: [ {id: 0, file_name: 000001.jpg, width: 640, height: 480} ], annotations: [ {id: 0, image_id: 0, category_id: 1, bbox: [100, 120, 200, 300], # x, y, w, h area: 60000, iscrowd: 0} ], categories: [ {id: 0, name: car}, {id: 1, name: person} ] }注意COCO的bbox四個(gè)值是[x, y, w, h]而VOC的bndbox是[xmin, ymin, xmax, ymax]轉(zhuǎn)換時(shí)要減一遍坐標(biāo)。area建議直接用w*h計(jì)算iscrowd統(tǒng)一寫0category_id從0還是從1開始取決于框架約定多數(shù)現(xiàn)代框架從0開始。轉(zhuǎn)換完成后用json.load重新讀一遍校驗(yàn)“每一張image都有至少一個(gè)annotation、每個(gè)annotation的image_id都能找到對(duì)應(yīng)image”這兩個(gè)條件不滿足訓(xùn)練時(shí)collate就會(huì)報(bào)錯(cuò)。4. 常見問題排查1322張VOC格式數(shù)據(jù)集訓(xùn)練前必查的五個(gè)坑小數(shù)據(jù)集的坑和大型數(shù)據(jù)集不一樣不是分布式訓(xùn)練、顯存不夠這類架構(gòu)問題而是標(biāo)注、編碼、劃分這類看似瑣碎卻能讓實(shí)驗(yàn)白跑的錯(cuò)誤。下面五條都是小數(shù)據(jù)集上高頻出現(xiàn)的真實(shí)問題按現(xiàn)象、原因、解決三段寫清楚照著排查能省下大量反復(fù)調(diào)試的時(shí)間。4.1 XML文件帶BOM或CRLF解析時(shí)直接報(bào)錯(cuò)現(xiàn)象ET.parse(xml_path)偶爾拋出ParseError但用文本編輯器打開XML完全正常同一批文件里只有部分能解析。原因數(shù)據(jù)在Windows下生成文件開頭帶BOM頭或換行符是CRLF某些解析器在非嚴(yán)格模式下能容忍但xml.etree對(duì)文件頭十分敏感。解決讀文件時(shí)用utf-8-sig編碼剝掉BOM再交給ET解析。with open(xml_path, r, encodingutf-8-sig) as f: tree ET.parse(f)如果是整批CRLF問題直接對(duì)Annotations目錄做一次轉(zhuǎn)碼find dataset_root/Annotations -name *.xml -exec sed -i s/\r$// {} \;4.2 類別名大小寫不一致一個(gè)類被拆成兩半現(xiàn)象統(tǒng)計(jì)類別時(shí)出現(xiàn)“Person”“person”“PERSON”三個(gè)條目模型訓(xùn)練時(shí)各自當(dāng)成獨(dú)立類別測(cè)試時(shí)漏檢嚴(yán)重。原因不同標(biāo)注員或半自動(dòng)標(biāo)注腳本生成的標(biāo)簽拼寫風(fēng)格不統(tǒng)一。解決統(tǒng)計(jì)后用映射表統(tǒng)一大小寫最省事的是全部轉(zhuǎn)小寫name_map {Person: person, PERSON: person} for xml_path in (root / Annotations).glob(*.xml): tree ET.parse(xml_path) for obj in tree.findall(object): raw obj.find(name).text obj.find(name).text name_map.get(raw, raw.lower()) tree.write(xml_path, encodingutf-8, xml_declarationTrue)4.3 difficult1的框沒過濾訓(xùn)練和評(píng)測(cè)都吃虧現(xiàn)象訓(xùn)練loss能下降但mAP在某個(gè)類別上始終低一個(gè)量級(jí)把預(yù)測(cè)結(jié)果畫出來發(fā)現(xiàn)模型在努力檢測(cè)一些模糊到人眼都難分辨的目標(biāo)。原因XML里difficult1的難例被直接當(dāng)成正樣本參與訓(xùn)練模型被迫擬合標(biāo)注噪聲。解決轉(zhuǎn)換YOLO txt或構(gòu)建COCO json時(shí)統(tǒng)一跳過difficult1的object上面2.2和3.3的代碼里已經(jīng)體現(xiàn)了這一點(diǎn)。如果在評(píng)測(cè)時(shí)用的是VOC官方mAP指標(biāo)它本來就會(huì)忽略difficult目標(biāo)如果用的是自定義評(píng)測(cè)腳本需要同步處理否則訓(xùn)練集和評(píng)測(cè)集的標(biāo)準(zhǔn)不一致結(jié)果完全失真。4.4 不做分層劃分小類別從驗(yàn)證集里直接消失現(xiàn)象第一輪訓(xùn)練驗(yàn)證集loss正常第二輪只改了隨機(jī)種子mAP從0.62掉到0.41反復(fù)橫跳。原因隨機(jī)劃分把只有幾十張圖的類別全切進(jìn)了訓(xùn)練集驗(yàn)證集里該類別的正樣本數(shù)量為0或極少評(píng)估結(jié)果方差巨大。解決回到3.2用stratify做分層劃分并檢查劃分后每個(gè)集合的類別分布打印結(jié)果。1322張規(guī)模的可用做法是給每個(gè)類別設(shè)一個(gè)下限訓(xùn)練集至少50框、驗(yàn)證集至少10框、測(cè)試集至少10框不滿足就人工調(diào)整或增加增強(qiáng)樣本。4.5 圖片近重復(fù)造成泄漏mAP虛高而實(shí)測(cè)拉胯現(xiàn)象劃分時(shí)明明互斥訓(xùn)練時(shí)loss也很收斂驗(yàn)證mAP上了0.75攢出模型去現(xiàn)場(chǎng)測(cè)試卻只有0.4。原因同一場(chǎng)景的連拍幀、從同一視頻抽出的連續(xù)幀被拆到訓(xùn)練集和驗(yàn)證集內(nèi)容幾乎一樣模型等于提前“見過”驗(yàn)證答案。解決劃分前做去重。輕量做法是計(jì)算每張圖的感知哈希兩兩比較漢明距離閾值以內(nèi)合并成一組組內(nèi)一起劃入同一集合from PIL import Image import imagehash hash_map {} for img_path in (root / JPEGImages).glob(*.jpg): h imagehash.phash(Image.open(img_path)) dup_key None for key in hash_map: if h - hash_map[key] 6: dup_key key break if dup_key is None: hash_map[img_path.stem] h漢明距離閾值6是我從多個(gè)小數(shù)據(jù)集聚類里試出來的經(jīng)驗(yàn)值低于6基本可斷定是同一場(chǎng)景的近似重復(fù)。查出來的重復(fù)組要做成互斥分組后再劃分這比直接刪圖更穩(wěn)妥因?yàn)槟承﹫?chǎng)景的連續(xù)幀對(duì)訓(xùn)練仍有價(jià)值只是不能跨集出現(xiàn)。5. 1322張?jiān)趺蠢^續(xù)長成項(xiàng)目增強(qiáng)、半自動(dòng)標(biāo)注與可視化驗(yàn)證數(shù)據(jù)集腳手架的定位是起步不是終點(diǎn)。最后這一步是把它從“能跑通”推向“有點(diǎn)用”的關(guān)鍵手段三件事按順序做增強(qiáng)、標(biāo)注迭代、質(zhì)量驗(yàn)證。5.1 用帶邊界框同步的數(shù)據(jù)增強(qiáng)把數(shù)量做大增強(qiáng)的本質(zhì)是讓模型看到更多不改變語義的形態(tài)變化。對(duì)目標(biāo)檢測(cè)來說任何幾何變換都必須同步作用于圖片和邊界框坐標(biāo)。albumentations庫把這件事封裝得很好import albumentations as A transform A.Compose([ A.RandomCrop(width512, height512, p0.5), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.Rotate(limit15, p0.3, border_mode0), ], bbox_paramsA.BboxParams(formatpascal_voc, label_fields[labels])) # 對(duì)單張圖調(diào)用 aug transform( imageimg_array, bboxes[xmin, ymin, xmax, ymax, ...], labels[0, 1, ...] )增強(qiáng)參數(shù)建議做保守設(shè)置水平翻轉(zhuǎn)0.5、亮度對(duì)比度0.3、旋轉(zhuǎn)±15度、隨機(jī)裁剪0.5。對(duì)小數(shù)據(jù)集強(qiáng)度過高會(huì)導(dǎo)致模型見過太多假樣本反而削弱真實(shí)場(chǎng)景泛化。做增強(qiáng)時(shí)一個(gè)重要技巧是只增強(qiáng)訓(xùn)練集絕不增強(qiáng)驗(yàn)證集和測(cè)試集。5.2 半自動(dòng)標(biāo)注迭代讓模型反哺標(biāo)注效率擴(kuò)數(shù)據(jù)最耗時(shí)的是標(biāo)注環(huán)節(jié)。1322張訓(xùn)出的初版模型雖然精度不高但足以做“預(yù)標(biāo)注”把新采集的圖片送進(jìn)模型預(yù)測(cè)生成帶置信度的框再用labelImg或CVAT人工修正。人工只需要?jiǎng)h掉低置信度框、調(diào)整位置標(biāo)注效率通常能提升一倍以上。迭代中要注意過濾置信度低于0.3的預(yù)測(cè)框并保留difficult標(biāo)記給邊緣目標(biāo)。5.3 可視化抽查畫框報(bào)告才是驗(yàn)證質(zhì)量的最后一步訓(xùn)練前最后一步不是啟動(dòng)訓(xùn)練而是把劃分后的樣本畫框可視化。用一個(gè)簡單腳本在每張圖上畫出全部有效框輸出成九宮格圖片import cv2 img cv2.imread(str(img_path)) for xmin, ymin, xmax, ymax in bboxes: cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2)抽查至少50張圖重點(diǎn)看三點(diǎn)小目標(biāo)框是否貼合、截?cái)嗄繕?biāo)是否正確標(biāo)注、遮擋重疊目標(biāo)是否被漏標(biāo)。這一步發(fā)現(xiàn)的錯(cuò)誤往往比自動(dòng)化校驗(yàn)更多因?yàn)楹芏鄻?biāo)注問題是語義層面的腳本檢測(cè)不出來。我自己做小數(shù)據(jù)集項(xiàng)目時(shí)習(xí)慣固定一套流程先統(tǒng)計(jì)、再體檢、分層劃分、轉(zhuǎn)換格式、可視化抽查確認(rèn)一切干凈之后才把數(shù)據(jù)喂給訓(xùn)練框架。堅(jiān)持這個(gè)習(xí)慣1322張的數(shù)據(jù)量也能在yolov5、yolov8這些框架上跑出一個(gè)可靠的baseline為后續(xù)擴(kuò)大數(shù)據(jù)打下扎實(shí)基礎(chǔ)。數(shù)據(jù)規(guī)??梢孕×鞒滩荒軄y流程順了擴(kuò)充只是時(shí)間問題。希望這些經(jīng)驗(yàn)幫到你少走彎路。本文還有配套的精品資源點(diǎn)擊獲取