據集VOC轉YOLO與去泄漏切分實踐)
簡介這份docx文檔面向電路板PCB缺陷檢測的學習者與算法開發(fā)者用于說明一套VOC與YOLO雙格式數(shù)據集的獲取方式及基本構成幫助讀者在訓練目標檢測模型前快速判斷數(shù)據規(guī)模、類別分布與適用性。壓縮包內共1個docx文件大小約3.38MB正文給出數(shù)據集的百度云下載地址并記錄9666張jpg圖片、9666個VOC格式xml標注和9666個YOLO格式txt標注涵蓋falsecopper、missinghole、mousebite、opencircuit等8類缺陷總標注框數(shù)51549同時說明使用labelImg按類別繪制矩形框。文檔還提示數(shù)據集中包含較多椒鹽噪聲生成的增強圖片并逐類列出標注框數(shù)便于讀者提前了解樣本特點、類別不均衡情況與潛在噪聲影響也方便按類別抽樣、設計損失函數(shù)和劃分訓練驗證集。目前已有232人學習適合需要核對數(shù)據規(guī)模、評估數(shù)據質量或搭建PCB缺陷檢測實驗的讀者參考。1. 9666張PCB缺陷圖能干什么先看清這8類和51549個框拿到一個 PCB 缺陷檢測數(shù)據集第一件事不是急著敲訓練命令而是算清楚它到底能撐起什么量級的任務。這份資源是 VOC YOLO 雙格式9666 張 jpg 對應 9666 個 xml、9666 個 txt8 個類別合計 51549 個矩形框平均每張圖 5.3 個目標屬于典型的多目標小目標工業(yè)質檢場景——一塊板子上同時出現(xiàn)毛刺、鼠咬、開路、針孔很正常。產出方用 labelImg 拉框類別名全英文格式干凈但不含 ImageSets 路徑文件切分要自己做。它適合三類人想跑通工業(yè)缺陷檢測全流程的算法工程師、需要一個真實多類別數(shù)據集做課程設計的在校學生、以及想驗證小目標檢測方案在實際產線上能不能用的人。需要提前說清一點包里摻了數(shù)量不小的椒鹽噪聲增強圖這件事直接決定了后面的切分策略第 4 章會專門拆。2. 拆開數(shù)據包VOC與YOLO雙格式的目錄結構與一次全量體檢解壓之后最先看到的是裸露的 jpg 和 xml/txt 混在一起還是已經按子目錄分好這取決于打包方式。不管哪種先把目錄擺放成兩邊都能讀的結構后面所有腳本都基于這套路徑寫中途改路徑是自找麻煩。2.1 兩套目錄一次擺清別讓路徑成為第一個坑VOC 的慣例是JPEGImages存圖、Annotations存 xml、ImageSets/Main存劃分清單。這份資源沒有 ImageSets所以劃分清單得自己生成。YOLO 的慣例是images與labels平行且兩者內部路徑結構必須完全一致images/train/xxx.jpg對應labels/train/xxx.txt差一層目錄都讀不到。我一般整理成這個形狀VOC 那份保留做溯源和回讀校驗YOLO 那份給訓練框架吃pcb8/ ├── JPEGImages/ # 9666 張 jpg文件名不帶后綴的 stem 作為主鍵 ├── Annotations/ # 9666 個 xml與 jpg 同名 ├── images/ │ ├── train/ val/ test/ └── labels/ ├── train/ val/ test/有一處容易忽略jpg 和 xml 的 stem 必須嚴格一致。實際打包里常出現(xiàn)xxx.JPG大寫后綴、或者 xml 里filename寫的是原圖名而磁盤文件名被改過的情況這兩種都會讓標注工具和訓練腳本對不上號后面第 5 章會具體說癥狀。2.2 八個類別定死 id映射表與框數(shù)分布類別順序是整條鏈路上最容易翻車的地方。YOLO 的 txt 里只有數(shù)字 id沒有類別名id 和名字的綁定關系完全靠data.yaml里的names列表維持。轉換腳本用的順序、yaml 里寫的順序、評估時打印的順序三處必須完全一致錯一位就變成模型訓得很好但 mAP 極低的玄學現(xiàn)場。按資源給出的類別名我固定成下面這張表id 從 0 開始按原始順序排不做任何按框數(shù)重排的小聰明id類別名常見中文叫法框數(shù)占全部框比例0falsecopper冗余銅/殘銅48529.4%1missinghole缺孔47439.2%2mousebite鼠咬邊緣缺口788315.3%3opencircuit開路803315.6%4pinhole針孔745314.5%5scratch劃傷554310.8%6shortcircuit短路673713.1%7spur毛刺630512.2%框數(shù)最多的 opencircuit 8033 與最少的 missinghole 4743 之間大約 1.7 倍差距在工業(yè)數(shù)據集里算相當均衡了不需要一上來就上重采樣。真正麻煩的是框的尺寸——pinhole 和 missinghole 這類目標在整板圖上經常只占十幾到幾十像素這才是影響召回的主因而不是類別不平衡。2.3 全量體檢腳本一次性把數(shù)量、越界框、空標簽、尺寸分布查干凈在寫任何轉換邏輯之前先跑一遍體檢。這一步花三分鐘能省掉后面半天的瞎猜。腳本只依賴標準庫xml.etree足夠解析 labelImg 的輸出。import os from collections import Counter from xml.etree.ElementTree import parse ROOT pcb8 ANN os.path.join(ROOT, Annotations) IMG os.path.join(ROOT, JPEGImages) CLASSES [falsecopper, missinghole, mousebite, opencircuit, pinhole, scratch, shortcircuit, spur] # stem - 文件名用集合運算查缺圖、缺標注 img_stems {os.path.splitext(f)[0] for f in os.listdir(IMG) if f.lower().endswith(.jpg)} xml_stems {os.path.splitext(f)[0] for f in os.listdir(ANN) if f.lower().endswith(.xml)} print(jpg:, len(img_stems), xml:, len(xml_stems)) print(有圖無標注:, len(img_stems - xml_stems)) print(有標注無圖:, len(xml_stems - img_stems)) cls_cnt Counter() # 每類框數(shù) size_cnt Counter() # 圖像分辨率分布 boxes_per_img [] # 每圖目標數(shù) bad_boxes [] # 越界、退化框 empty_xml [] # 零目標的 xml for stem in xml_stems: root parse(os.path.join(ANN, stem .xml)).getroot() W float(root.findtext(size/width)) H float(root.findtext(size/height)) size_cnt[(int(W), int(H))] 1 objs root.findall(object) boxes_per_img.append(len(objs)) if not objs: empty_xml.append(stem) for o in objs: name (o.findtext(name) or ).strip() cls_cnt[name] 1 b o.find(bndbox) x1, y1, x2, y2 (float(b.findtext(k)) for k in (xmin, ymin, xmax, ymax)) # 三個判據未知類別、零/負面積、超出圖像邊界 if name not in CLASSES: bad_boxes.append((stem, unknown_cls, name)) elif x2 - x1 1 or y2 - y1 1: bad_boxes.append((stem, degenerate, (x1, y1, x2, y2))) elif x1 0 or y1 0 or x2 W or y2 H: bad_boxes.append((stem, out_of_bound, (x1, y1, x2, y2), (W, H))) print(框數(shù)統(tǒng)計:, dict(cls_cnt), 總計:, sum(cls_cnt.values())) print(分辨率 Top5:, size_cnt.most_common(5)) print(每圖目標數(shù) min/avg/max:, min(boxes_per_img), round(sum(boxes_per_img) / len(boxes_per_img), 2), max(boxes_per_img)) print(空標注 xml:, len(empty_xml)) print(異???, len(bad_boxes))邏輯很直白用集合差查配對用size節(jié)點查分辨率是否統(tǒng)一用bndbox四個角查退化框和越界框。參數(shù)上唯一要改的是ROOT其余全是從 xml 里現(xiàn)讀的不寫死。跑完重點看三個數(shù)。第一有圖無標注是否為 0不為 0 說明打包時丟了 xml那部分圖只能當負樣本或者直接剔掉別硬塞進訓練集。第二分辨率 Top5是不是只有一項如果出現(xiàn)兩三種分辨率混用letterbox 之外的框架比如按固定尺寸 resize 的會把目標拉變形框的位置隨之偏移。第三異??蚶飋ut_of_bound的數(shù)量labelImg 手拉框貼邊時xmax等于寬度甚至多 1 像素很常見這類框不致命但要 clamp 掉否則歸一化后大于 1部分框架會直接報錯退出??諛俗?xml也別忽略。零目標的圖有兩種用法丟掉或者保留成負樣本幫助降誤檢。工業(yè)質檢里背景干凈的板子多保留一部分負樣本通常有益但比例別超過 5%否則正樣本梯度會被稀釋。3. VOC轉YOLO歸一化換算與一份可回讀校驗的轉換腳本格式轉換本身不復雜麻煩全在邊界上。資源同時給了 xml 和 txt理論上可以直接用 txt但我仍然建議自己從 xml 轉一遍原因是你能控制 id 順序、能順手 clamp 越界框、能記錄轉換前后的框數(shù)對賬出了問題有據可查。3.1 歸一化不是除以寬高那么簡單VOC 的bndbox是像素絕對坐標左上角(xmin, ymin)、右下角(xmax, ymax)labelImg 存的是 0 基坐標。YOLO 要的是中心點加寬高、并且歸一化到 0~1xc (xmin xmax) / 2 / W yc (ymin ymax) / 2 / H bw (xmax - xmin) / W bh (ymax - ymin) / H三個細節(jié)決定成敗。一是xmax允許等于W此時xc bw/2恰好等于 1浮點誤差可能算出 1.0000001得 clamp 到[0, 1]。二是寬度或高度算出來小于 1 像素的框必須先剔除再歸一化否則會生成w0.00003這種在特征圖上完全消失的框訓練時表現(xiàn)為 loss 突然 NaN 或者某類 AP 恒為 0。三是寫文件時保留 6 位小數(shù)足夠YOLO 解析器不在乎精度但保留太多位會讓 txt 體積膨脹。3.2 轉換腳本從 xml 轉 txt單文件對單文件順手做 clamp 和退化框過濾。這份資源已經帶了 txt腳本跑完先備份原 txt再覆蓋方便對比兩邊框數(shù)是否一致。import os from xml.etree.ElementTree import parse ROOT pcb8 ANN os.path.join(ROOT, Annotations) LBL os.path.join(ROOT, labels_all) # 先輸出到臨時目錄校驗通過再替換 CLASSES [falsecopper, missinghole, mousebite, opencircuit, pinhole, scratch, shortcircuit, spur] CLS2ID {c: i for i, c in enumerate(CLASSES)} os.makedirs(LBL, exist_okTrue) stat {imgs: 0, boxes_in: 0, boxes_out: 0, drop_degenerate: 0, drop_unknown: 0} for fn in sorted(os.listdir(ANN)): if not fn.endswith(.xml): continue stem os.path.splitext(fn)[0] root parse(os.path.join(ANN, fn)).getroot() W float(root.findtext(size/width)) H float(root.findtext(size/height)) lines [] for o in root.findall(object): stat[boxes_in] 1 name (o.findtext(name) or ).strip() if name not in CLS2ID: # 拼寫不一致、大小寫混用都在這里被攔下 stat[drop_unknown] 1 continue b o.find(bndbox) x1 float(b.findtext(xmin)); y1 float(b.findtext(ymin)) x2 float(b.findtext(xmax)); y2 float(b.findtext(ymax)) # 先裁到圖像范圍內再判退化順序不能反 x1 max(0.0, min(x1, W)); x2 max(0.0, min(x2, W)) y1 max(0.0, min(y1, H)); y2 max(0.0, min(y2, H)) if x2 - x1 1.0 or y2 - y1 1.0: stat[drop_degenerate] 1 continue xc (x1 x2) / 2.0 / W yc (y1 y2) / 2.0 / H bw (x2 - x1) / W bh (y2 - y1) / H xc min(max(xc, 0.0), 1.0); yc min(max(yc, 0.0), 1.0) bw min(max(bw, 0.0), 1.0); bh min(max(bh, 0.0), 1.0) lines.append(f{CLS2ID[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) stat[boxes_out] 1 # 空文件也要寫出來表示這是一張負樣本而不是缺失標注 with open(os.path.join(LBL, stem .txt), w, encodingutf-8) as f: f.write(\n.join(lines)) stat[imgs] 1 print(stat)參數(shù)上只有ROOT和CLASSES需要按實際改。CLASSES的順序就是最終的 id改這里等于改標簽語義一旦定了就別再動。腳本做了兩件多余的事都是血淚經驗換的。第一未知類別不靜默跳過而是計數(shù)跑完drop_unknown必須是 0不為 0 就說明 xml 里存在拼寫變體比如false_copper、ShortCircuit得回頭清洗不能讓它變成一批無標注圖悄悄進入訓練集。第二空標注寫空文件而不是不寫文件。訓練框架區(qū)分這張圖沒有目標和這張圖的標注丟了前者是合法負樣本后者是數(shù)據錯誤少寫文件會讓框架把這張圖當背景丟掉等于白扔。3.3 回讀校驗把 txt 反算回像素框再和 xml 對賬轉換完直接開訓是最常見的偷懶。我一般加一道反向校驗讀 txt按W/H反算回像素坐標和原 xml 的四個角比誤差超過 1 像素就報出來。這一步能抓住兩類隱蔽錯誤——歸一化時把寬高用反拿 H 除 x以及某個類別的 id 映射整體偏移一位。import os from xml.etree.ElementTree import parse ROOT pcb8 ANN os.path.join(ROOT, Annotations) LBL os.path.join(ROOT, labels_all) max_err 0.0 mismatch 0 for fn in os.listdir(ANN): if not fn.endswith(.xml): continue stem os.path.splitext(fn)[0] root parse(os.path.join(ANN, fn)).getroot() W float(root.findtext(size/width)); H float(root.findtext(size/height)) gt [] for o in root.findall(object): b o.find(bndbox) gt.append(tuple(float(b.findtext(k)) for k in (xmin, ymin, xmax, ymax))) with open(os.path.join(LBL, stem .txt), encodingutf-8) as f: rows [ln.split() for ln in f.read().strip().splitlines() if ln.strip()] if len(rows) ! len(gt): mismatch 1 continue for row, (gx1, gy1, gx2, gy2) in zip(rows, gt): xc, yc, bw, bh map(float, row[1:5]) px1 (xc - bw / 2) * W; py1 (yc - bh / 2) * H px2 (xc bw / 2) * W; py2 (yc bh / 2) * H err max(abs(px1 - gx1), abs(py1 - gy1), abs(px2 - gx2), abs(py2 - gy2)) max_err max(max_err, err) print(框數(shù)不一致的圖:, mismatch, 最大像素誤差:, round(max_err, 3))正常結果應該是mismatch0、最大像素誤差小于 0.01。如果誤差在 1 像素上下說明某處做了 round 取整不影響訓練如果誤差到了幾十像素基本就是寬高用反了回去查公式。4. 椒鹽噪聲增強圖分組切分、采樣與去泄漏這份資源最需要當心的地方不在格式在數(shù)據構成。原始說明里寫得很清楚里面有很多椒鹽噪聲生成的增強圖片。這句話意味著同一張原始板圖對應多張加噪版本它們的內容幾乎相同、標注完全相同。如果按文件隨機切分同一張原圖的噪聲版會同時落進訓練集和驗證集驗證指標會漂亮得不像話上線一測直接打回原形。4.1 同源增強圖跨集指標虛高能有多離譜假設 9666 張里有 3000 張是噪聲增強版隨機按 8:1:1 切粗略估算會有三成左右的驗證圖能在訓練集里找到雙胞胎。模型不需要學會識別缺陷只要記住噪聲紋理和大致位置就能給出高分。這種虛高在驗證集上可能反映為 mAP 漲 10 個點以上而換一個真實產線批次去測掉回來的幅度同樣驚人。判斷依據很簡單把驗證集里的每張圖拿去訓練集做一次近似檢索縮略圖哈希、感知哈希都行命中率高就說明切分有泄漏。做這個檢查花不了多少時間比訓完再懷疑人生劃算得多。4.2 先探測命名規(guī)律再定分組鍵去泄漏的前提是能認出哪些圖同源。多數(shù)增強腳本會在文件名尾部留下痕跡比如board_0123_1.jpg、board_0123_noise.jpg、board_0123_sp.jpg。但也有人直接重排成img_00001.jpg到img_09666.jpg把原始對應關系抹掉那就只能靠內容相似度分組。先打印一批文件名看看規(guī)律別上來就寫死正則import os, re from collections import Counter IMG pcb8/JPEGImages stems [os.path.splitext(f)[0] for f in os.listdir(IMG) if f.lower().endswith(.jpg)] for s in stems[:15]: print(s) # 統(tǒng)計最后一個下劃線后面那段的分布能快速看出增強后綴長什么樣 tail Counter(re.split(r[_\-], s)[-1] for s in stems) print(tail.most_common(20))看tail的分布如果出現(xiàn)大量noise、sp、aug、純數(shù)字這類尾部基本可以確定后綴規(guī)則如果尾部全是隨機字符或者根本看不出模式說明命名被重排過只能走內容哈希。資源里給的是網盤打包命名風格沒法提前斷言所以這一步不能省。4.3 分組切分腳本確定后綴規(guī)則后把 stem 剝掉增強后綴得到group_key同一個 key 下的所有圖必須整組進入同一個集合。按 8:1:1 分測試集可以留也可以不留做課程設計建議留一份完全不碰的 holdout。import os, re, random from collections import defaultdict IMG pcb8/JPEGImages OUT pcb8/splits SUFFIX re.compile(r[_\-](noise|salt|pepper|sp|aug\d*|aug|n\d|\d{1,3})$, re.I) stems sorted(os.path.splitext(f)[0] for f in os.listdir(IMG) if f.lower().endswith(.jpg)) groups defaultdict(list) for s in stems: groups[SUFFIX.sub(, s)].append(s) # 剝掉增強后綴同源圖歸到一組 keys sorted(groups) random.seed(42) # 固定種子保證每次切分結果可復現(xiàn) random.shuffle(keys) n len(keys) n_val max(1, int(n * 0.1)) n_test max(1, int(n * 0.1)) split { val: keys[:n_val], test: keys[n_val:n_val n_test], train: keys[n_val n_test:], } os.makedirs(OUT, exist_okTrue) for name, ks in split.items(): with open(os.path.join(OUT, f{name}.txt), w, encodingutf-8) as f: for k in ks: for s in groups[k]: f.write(s \n) print(name, 組數(shù):, len(ks), 圖片數(shù):, sum(len(groups[k]) for k in ks))關鍵參數(shù)是SUFFIX正則和seed。正則一定要對照 4.2 打印出來的真實后綴寫寫寬了會把正常編號當后綴剝掉導致大量不相干的圖被強行歸成一組訓練集縮水寫窄了等于沒分組泄漏照舊。判斷標準很實在跑完看train/val/test的圖片數(shù)比例是不是接近 8:1:1如果某一組圖片數(shù)異常膨脹多半是正則剝過頭了。4.4 采樣與增強策略噪聲圖該占多少分組切分只解決了泄漏沒解決分布問題。如果噪聲增強圖占了總量的一半訓練集里就會有一半樣本帶著同一種人造噪聲紋理模型很容易把它當成判別特征。我的做法是分兩步。第一步訓練集內部對同源組做上限控制同一group_key最多保留 2 到 3 張增強版其余只在需要做魯棒性對比實驗時按需取用。第二步驗證集和測試集只放原始圖不放噪聲版這樣報出來的指標對應的是真實場景而不是在噪聲分布上的自評。增強策略上也要相應調整。既然數(shù)據里已經有人造椒鹽噪聲訓練側就不該再無腦疊加同類型噪聲增強否則真實分布被推得更遠。常見做法是訓練增強以幾何變換為主——隨機縮放、平移、馬賽克、HSV 抖動噪聲類增強只在做消融時單獨開關。這個取舍在工業(yè)質檢里很重要產線上的噪聲類型和增強腳本生成的噪聲未必一致過度擬合某一種噪聲紋理反而降低泛化。5. 避坑與排查這份數(shù)據集落地時最容易翻車的五處下面五條都是跑這類雙格式工業(yè)數(shù)據集時反復出現(xiàn)的癥狀按現(xiàn)象 → 原因 → 解決記遇到了直接對號入座。5.1 訓練日志里正樣本數(shù)遠小于 51549甚至提示大量背景圖現(xiàn)象開訓第一個 epoch日志里的 instances 數(shù)量只有幾萬甚至更少或者框架提示backgrounds占比異常高。原因images/與labels/的目錄層級沒對齊。YOLO 系列的標簽路徑是把 images 路徑里的images替換成labels推出來的如果一邊是images/train/xxx.jpg、另一邊是labels/xxx.txt推出來的路徑全部落空框架會把所有圖當負樣本。解決用一條命令對賬兩邊 stem 集合必須完全相等且相對路徑一致cd pcb8 diff (find images -name *.jpg | sed s#images/##; s#\.jpg$## | sort) \ (find labels -name *.txt | sed s#labels/##; s#\.txt$## | sort) | head輸出為空才繼續(xù)往下走有輸出就按提示補目錄或改結構。5.2 訓練 loss 正常下降但驗證 mAP 一直在 0.01 附近現(xiàn)象分類 loss 和框回歸 loss 都收斂得不錯評估時各類 AP 幾乎全為 0混淆矩陣一整片糊在背景上。原因id 順序錯位。轉換腳本按字母序排了類別data.yaml里按資源原始順序寫的兩邊對不上模型學到的0 號類和評估時的0 號類根本不是同一個缺陷。解決把 yaml 里的names和轉換腳本里的CLASSES抄到同一份文件里做唯一來源兩邊都從這個文件讀。改完重轉一遍 txt別在原 txt 上打補丁。5.3 圖像分辨率不統(tǒng)一框位置整體偏移現(xiàn)象可視化預測框時框的大小看著對位置整體平移了一段或者框被拉成長條。原因數(shù)據集里混了兩種以上分辨率而訓練配置用的是固定尺寸直接 resize 而不是 letterbox。直接 resize 會改變寬高比歸一化坐標雖然不變但圖像內容被拉伸等效于框位置錯位。解決先看 2.3 腳本輸出的分辨率分布。多分辨率共存時優(yōu)先用 letterbox 的框架配置或者統(tǒng)一預處理到同一尺寸再入庫絕不靠訓練時隱式 resize 兜底。5.4 驗證集指標好得離譜換一批真實圖就崩現(xiàn)象驗證集 mAP 0.9 以上拿產線新拍的板子去測漏檢一大片。原因椒鹽噪聲增強圖和原圖被隨機切分打散同源圖跨了訓練集和驗證集驗證集實際上是訓練分布的近鄰。解決按第 4 章的分組切分重做一遍驗證集只留原圖。重做完指標一般會掉幾個點那才是真實水平。掉得越多說明之前的泄漏越嚴重不是模型退步了。5.5 針孔、缺孔兩類召回明顯偏低其他類正?,F(xiàn)象8 個類里 pinhole 和 missinghole 的 AP 比其余類低一截誤檢倒是不多就是檢不出來。原因這兩類目標在原圖上普遍只有幾十像素縮到 640 輸入后特征圖上不到 8 像素卷積下采樣幾輪就沒了。解決優(yōu)先提高輸入尺寸到 960 或 1280 試一輪對比這兩類的 AP 變化如果顯存不夠改用切圖訓練加切圖推理大圖切片、重疊區(qū)做 NMS 合并。這兩條路我在小目標缺陷上都試過切圖對小目標召回提升最直接代價是推理耗時翻倍得按產線節(jié)拍權衡。6. 訓練與復核把8類mAP拆開看的三個動作6.1 先跑一個不加料的基線基線的作用是給你一個參照系。數(shù)據按第 4 章切好、yaml 寫對之后先跑一個不動任何超參的版本輸入尺寸取 640增強只開幾何變換記錄總 mAP 和每類 AP。配置寫成 yaml 放在項目根目錄和轉換腳本共用同一份類別列表# pcb8.yaml path: /data/pcb8 train: splits/train.txt val: splits/val.txt test: splits/test.txt names: 0: falsecopper 1: missinghole 2: mousebite 3: opencircuit 4: pinhole 5: scratch 6: shortcircuit 7: spur訓練命令按你本地的框架來以常見的檢測器 CLI 為例# 權重換成你本地已有的檢測權重imgsz 先用 640 出基線 yolo detect train datapcb8.yaml modelyolov8s.pt imgsz640 epochs100 batch16 workers8 seed42seed42和切分腳本里的種子保持一致保證可復現(xiàn)workers按機器核數(shù)給給太大會在讀取大量小文件時卡 IO?;€跑完把results.csv存一份后面任何一個改動都拿它對比不做無參照的調參。6.2 盯住 mousebite 和 spur 的混淆而不是總 mAP總 mAP 漲 0.5 個點沒什么信息量按類別拆開才有。這兩類最容易互相串鼠咬是板邊被咬掉一塊的缺口毛刺是銅箔邊緣多出來的尖出物在低分辨率下都表現(xiàn)為邊緣的小塊異?;煜仃嚴锝洺3蓪Τ霈F(xiàn)。我的習慣是先看混淆矩陣如果mousebite → spur或反向的誤分超過該類樣本的 10%就去檢查標注本身——翻二十張這類圖的原圖和框看有沒有同一個缺陷被標成兩個類的情況。數(shù)據集標注以矩形框為準邊界模糊的目標在不同標注者手里本來就有分歧這類問題靠調模型解決不了。6.3 小目標的后悔藥提高輸入尺寸與切圖推理pinhole 和 missinghole 的 AP 是這套數(shù)據的天花板所在。先做一輪imgsz對比640 / 960 / 1280 各跑一次只看這兩類的 AP 變化其余類當對照。如果 960 相比 640 有明顯提升而 1280 收益遞減說明分辨率已經夠用瓶頸在別處如果一直線性漲那就得考慮切圖。切圖推理的實現(xiàn)要點是重疊率給到 0.2 到 0.3切片邊緣的目標在兩個相鄰切片里都會出現(xiàn)合并時靠 NMS 去重。重疊率給低了會切掉目標給高了顯存和耗時都上去。我一般先在驗證集上掃 0.1 / 0.2 / 0.3 三檔挑召回最高的那一檔再上產線。從那以后我每次拿到帶增強圖片字樣的數(shù)據集都強制先跑一遍同源分組和跨集近似檢索再動訓練腳本——這個習慣救過我不止一次虛高的驗證指標比低指標更害人。希望幫到你。本文還有配套的精品資源點擊獲取