OLO:鋼材缺陷檢測(cè)數(shù)據(jù)集格式轉(zhuǎn)換與訓(xùn)練實(shí)戰(zhàn))
簡(jiǎn)介這是一份用于鋼材表面缺陷檢測(cè)的YOLO數(shù)據(jù)集面向計(jì)算機(jī)視覺(jué)學(xué)習(xí)者、工業(yè)質(zhì)檢項(xiàng)目開(kāi)發(fā)者及課程實(shí)踐者可支撐目標(biāo)檢測(cè)入門(mén)練習(xí)、模型訓(xùn)練與課程設(shè)計(jì)。壓縮包內(nèi)共2000個(gè)文件以1986個(gè)xml標(biāo)簽為主同時(shí)提供json、txt等多種格式標(biāo)注覆蓋VOC、COCO、YOLO三種主流目標(biāo)檢測(cè)規(guī)范三類(lèi)標(biāo)簽分別存放在不同文件夾中配合真實(shí)場(chǎng)景的高質(zhì)量圖片可直接用于YOLO系列模型訓(xùn)練與效果評(píng)估。資源附贈(zèng)Linux與Windows雙平臺(tái)YOLO環(huán)境搭建教程、訓(xùn)練案例說(shuō)明以及訓(xùn)練集/驗(yàn)證集/測(cè)試集劃分腳本便于讀者根據(jù)實(shí)際需求靈活重組數(shù)據(jù)并快速跑通訓(xùn)練流程。數(shù)據(jù)集整體約61.48MB輕量易用目前已有397人學(xué)習(xí)下載尤其適合需要規(guī)范標(biāo)簽、完整訓(xùn)練指引與可復(fù)用腳本的中初級(jí)學(xué)習(xí)者。1. 謝韋爾鋼材缺陷檢測(cè)5000 張真實(shí)鋼板圖能把目標(biāo)檢測(cè)整條鏈路跑通嗎在一家鋼廠的表面質(zhì)檢工位上鋼板以每秒幾米的速度從輥道經(jīng)過(guò)表面的劃痕、氧化皮、麻點(diǎn)和斑塊稍縱即逝人工盯屏漏檢率居高不下產(chǎn)線需要的是一套能在幾百毫秒內(nèi)把缺陷框出來(lái)的目標(biāo)檢測(cè)模型。謝韋爾鋼材缺陷檢測(cè)數(shù)據(jù)集就是為這類(lèi)需求準(zhǔn)備的真實(shí)工業(yè)數(shù)據(jù)5000 張鋼板表面圖像同時(shí)給出 VOC、COCO、YOLO 三種格式標(biāo)簽外加劃分腳本和訓(xùn)練教程。解壓之后不用再折騰格式轉(zhuǎn)換直接把數(shù)據(jù)丟給 YOLO 就能開(kāi)訓(xùn)。對(duì)想用 YOLO 入門(mén)目標(biāo)檢測(cè)、又不想拿人造圖片練手的人來(lái)說(shuō)這是很好的起點(diǎn)對(duì)要搭工業(yè)質(zhì)檢 demo 的工程師它也省掉了標(biāo)注格式混雜的坑。2. 三種標(biāo)簽格式到底裝了什么VOC、COCO、YOLO 的結(jié)構(gòu)與選型鋼材表面缺陷檢測(cè)和常規(guī)的目標(biāo)檢測(cè)有一個(gè)很大的不同原始數(shù)據(jù)集的標(biāo)簽往往不是框而是像素級(jí)掩碼。謝韋爾數(shù)據(jù)集源自 Kaggle 上的 Severstal Steel Defect Detection 比賽比賽任務(wù)要求參賽者輸出缺陷區(qū)域的像素級(jí)掩碼用 RLE 編碼存儲(chǔ)在 CSV 文件里。而這個(gè)數(shù)據(jù)包幫你把掩碼轉(zhuǎn)成了檢測(cè)模型真正需要的邊界框再包裝成 VOC、COCO 和 YOLO 三種主流格式。理解這三者各自的組織方式比直接開(kāi)訓(xùn)更重要因?yàn)楹竺嫠心_本和訓(xùn)練配置都建立在這套結(jié)構(gòu)之上。2.1 原始標(biāo)簽是像素級(jí)掩碼檢測(cè)任務(wù)要的是框謝韋爾鋼材缺陷共分四類(lèi)rolled-in scale含氧化皮、patches斑塊、pitting麻點(diǎn)/蝕斑、scratch劃痕。熱軋鋼板表面缺陷的特點(diǎn)是背景噪聲大、缺陷對(duì)比度低、劃痕細(xì)長(zhǎng)而氧化皮卻可能占據(jù)大塊面積同一張圖上往往同時(shí)存在多個(gè)小缺陷。這種數(shù)據(jù)直接做分割是可行的但如果只是判斷“有沒(méi)有缺陷、缺陷在哪兒”目標(biāo)檢測(cè)的性?xún)r(jià)比更高——標(biāo)注體積小、訓(xùn)練速度快、部署更容易落地。我解壓過(guò)這類(lèi)數(shù)據(jù)集后一般會(huì)先做一件事統(tǒng)計(jì)每張掩碼圖里的連通域數(shù)量和最小外接框尺寸。這一步能直接暴露出后面訓(xùn)練時(shí)的大部分問(wèn)題比如某些缺陷的框可能只有幾個(gè)像素寬低于 YOLO 的默認(rèn)下采樣倍數(shù)時(shí)基本學(xué)不到。常見(jiàn)做法是把掩碼 PNG 讀取為單通道灰度圖然后用cv2.connectedComponentsWithStats找出每個(gè)獨(dú)立缺陷區(qū)域的最小外接矩形再把矩形寫(xiě)成檢測(cè)格式。這個(gè)思路是理解后面三個(gè)轉(zhuǎn)換腳本的基礎(chǔ)因?yàn)槿N格式的標(biāo)簽最終都來(lái)自同一個(gè)掩碼轉(zhuǎn)框的結(jié)果。2.2 VOC 與 COCO兩種標(biāo)注規(guī)約的“普通話”P(pán)ascal VOC 是目標(biāo)檢測(cè)領(lǐng)域歷史最悠久的標(biāo)注格式核心思想是“一張圖對(duì)應(yīng)一個(gè) XML 文件”。XML 里記錄文件名、圖像尺寸、目標(biāo)類(lèi)別以及每個(gè)目標(biāo)的xmin, ymin, xmax, ymax像素坐標(biāo)。它的優(yōu)點(diǎn)是直觀任何人打開(kāi) XML 都能看懂缺點(diǎn)也明顯——類(lèi)別列表分散在各個(gè) XML 里統(tǒng)計(jì)類(lèi)別分布要遍歷整個(gè)目錄。COCO 則把所有信息收攏到一個(gè) JSON 文件里用四個(gè)頂層數(shù)組組織數(shù)據(jù)images存圖像 ID 和尺寸categories存類(lèi)別 ID 和名稱(chēng)annotations存每個(gè)框的image_id、category_id、bbox和area另外還帶info和licenses兩個(gè)元信息字段。我在實(shí)際項(xiàng)目里更常用 COCO因?yàn)橐粋€(gè) JSON 就能完成類(lèi)別統(tǒng)計(jì)、數(shù)據(jù)篩選和跨庫(kù)傳遞但新手第一次打開(kāi) COCO JSON 時(shí)往往被那串嵌套結(jié)構(gòu)繞暈。下面這張表可以直接看清差異維度VOCCOCOYOLO txt標(biāo)注組織方式一張圖一個(gè) XML全量數(shù)據(jù)一個(gè) JSON一張圖一個(gè) txt邊界框表示xmin, ymin, xmax, ymax像素x, y, w, h像素xc, yc, w, h歸一化類(lèi)別定義寫(xiě)在每個(gè) XML 里categories 數(shù)組統(tǒng)一管理data.yaml 的 names 列表常用配套工具labelImglabelme / CVATCVAT / Roboflow訓(xùn)練時(shí)讀取速度需解析 XML偏慢需解析 JSON中等純文本直接讀最快選型上沒(méi)有絕對(duì)的“最優(yōu)”只有“適不適合”。如果你的訓(xùn)練代碼是基于 Detectron2 或 MMDetection 的建議直接用 COCO如果只是跑 YOLO 系列用 YOLO 格式最省事。2.3 YOLO 格式txt 只存歸一化坐標(biāo)訓(xùn)練時(shí)直接讀YOLO 格式是三者中最“簡(jiǎn)潔粗暴”的每張圖對(duì)應(yīng)一個(gè)同名 txt 文件每一行代表一個(gè)目標(biāo)格式是class x_center y_center width height其中位置和寬高都除以了圖像寬高歸一化到 0 到 1 之間。比如一張 1280×1024 的圖像里某個(gè)框左上角是 (320, 256)右下角是 (960, 768)那么歸一化中心就是 (0.5, 0.5)寬高是 (0.5, 0.5)txt 里存的就是0 0.5 0.5 0.5 0.5。這種格式把坐標(biāo)從像素空間映射到相對(duì)空間意味著訓(xùn)練時(shí)不管輸入圖片被縮放到 640 還是 1280標(biāo)簽都不用跟著改。Ultralytics YOLO 在訓(xùn)練時(shí)會(huì)直接讀 txt 文件跳過(guò) XML 或 JSON 解析所以讀取速度最快。代價(jià)是 txt 文件的可讀性很差手工改一個(gè)數(shù)字都可能導(dǎo)致標(biāo)注錯(cuò)位所以數(shù)據(jù)包的轉(zhuǎn)換腳本就顯得尤其重要。提示不要迷信“YOLO 格式只能給 YOLO 用”。只要坐標(biāo)歸一化的規(guī)則不變Faster R-CNN 等模型也可以在訓(xùn)練前把 txt 轉(zhuǎn)回絕對(duì)坐標(biāo)使用。3. 從掩碼到三種標(biāo)注轉(zhuǎn)換腳本逐段拆解理解了三種格式之后你一定想知道轉(zhuǎn)換腳本是怎么運(yùn)作的。謝韋爾數(shù)據(jù)集的轉(zhuǎn)換鏈路一般是讀取掩碼 PNG → 連通域分析得到 bbox → 分別寫(xiě)出 VOC XML、COCO JSON、YOLO txt。下面四段腳本基本可以應(yīng)對(duì)這個(gè)數(shù)據(jù)包的標(biāo)準(zhǔn)場(chǎng)景每一步都建議在解壓后的副本上操作不要在原目錄上直接覆蓋標(biāo)簽。3.1 掩碼轉(zhuǎn) bbox連通域統(tǒng)計(jì)與最小外接矩形掩碼圖里每一個(gè)像素值不為 0 的區(qū)域都對(duì)應(yīng)一個(gè)缺陷但同一張圖里可能有多個(gè)互不相連的缺陷區(qū)域所以要用連通域分析把它們拆開(kāi)。下面這段代碼可以遍歷一個(gè)掩碼目錄輸出每個(gè)連通域的外接框import cv2 import numpy as np from pathlib import Path def mask_to_boxes(mask_path: Path, min_area: int 16) - list: 把掩碼 PNG 轉(zhuǎn)成若干邊界框。 mask_path: 單張掩碼圖路徑 min_area: 面積小于該值的連通域直接丟棄 返回 [(x1, y1, x2, y2), ...] mask cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) if mask is None: return [] num_labels, labels, stats, _ cv2.connectedComponentsWithStats( mask, connectivity8 ) boxes [] for i in range(1, num_labels): # 0 是背景 x, y, w, h, area stats[i] if area min_area: continue x1 int(x) y1 int(y) x2 int(x w - 1) y2 int(y h - 1) boxes.append((x1, y1, x2, y2)) return boxes邏輯說(shuō)清楚connectedComponentsWithStats返回四個(gè)值第一個(gè)num_labels是連通域總數(shù)第二個(gè)labels是每個(gè)像素所屬的編號(hào)第三個(gè)stats是每個(gè)連通域的統(tǒng)計(jì)信息列順序固定為x, y, width, height, area。代碼從 1 開(kāi)始遍歷因?yàn)?0 永遠(yuǎn)是背景。min_area這個(gè)參數(shù)很關(guān)鍵鋼材缺陷里有的劃痕細(xì)到只有一個(gè)像素寬轉(zhuǎn)換成框以后寬高可能小于 2 個(gè)像素這種框在 YOLO 里基本是負(fù)作用建議丟棄或者用后續(xù)的形態(tài)學(xué)膨脹先合并。3.2 生成 VOC一張圖一個(gè) XML用 ElementTree 寫(xiě)拿到 bbox 列表后生成 VOC 格式最簡(jiǎn)單的方式是使用 Python 標(biāo)準(zhǔn)庫(kù)的xml.etree.ElementTree不需要額外依賴(lài)。注意 XML 里的filename要寫(xiě)成純文件名path字段是絕對(duì)路徑二者不要混用否則后續(xù)跑 MMDetection 等框架時(shí)容易出校驗(yàn)錯(cuò)誤from xml.etree import ElementTree as ET from pathlib import Path import cv2 def write_voc_xml(img_path: Path, boxes: list, xml_path: Path) - None: img cv2.imread(str(img_path)) h, w img.shape[:2] root ET.Element(annotation) ET.SubElement(root, filename).text img_path.name ET.SubElement(root, path).text str(img_path.resolve()) size ET.SubElement(root, size) ET.SubElement(size, width).text str(w) ET.SubElement(size, height).text str(h) ET.SubElement(size, depth).text str(img.shape[2]) for x1, y1, x2, y2 in boxes: obj ET.SubElement(root, object) ET.SubElement(obj, name).text defect ET.SubElement(obj, pose).text Unspecified ET.SubElement(obj, truncated).text 0 ET.SubElement(obj, difficult).text 0 bndbox ET.SubElement(obj, bndbox) ET.SubElement(bndbox, xmin).text str(x1) ET.SubElement(bndbox, ymin).text str(y1) ET.SubElement(bndbox, xmax).text str(x2) ET.SubElement(bndbox, ymax).text str(y2) ET.ElementTree(root).write(str(xml_path), encodingutf-8, xml_declarationTrue)幾個(gè)關(guān)鍵點(diǎn)width和height如果直接讀圖片而不是用掩碼尺寸可以避免掩碼被 resize 后坐標(biāo)錯(cuò)位的隱患depth字段對(duì)灰度圖寫(xiě) 1對(duì) RGB 圖寫(xiě) 3YOLO 訓(xùn)練一般用不上但 VOC 校驗(yàn)會(huì)檢查。ElementTree.write默認(rèn)不寫(xiě) XML 聲明加xml_declarationTrue可以保證文件在第三方解析器下兼容。如果要把 VOC 再轉(zhuǎn)為 COCO一般推薦用voc2coco腳本但前提是 XML 里的difficult字段必須存在否則轉(zhuǎn)換會(huì)中斷所以這里即使所有目標(biāo)都填 0 也要寫(xiě)出來(lái)。3.3 生成 COCO一次 dict 組裝一把 json.dumpCOCO JSON 的組織方式和 VOC 截然不同它是一個(gè)嵌套 dict。最容易踩坑的是image_id的分配必須先給所有圖片建立id → 文件名的映射再填寫(xiě)annotations否則很容易出現(xiàn)“標(biāo)注的 image_id 指向了錯(cuò)誤的圖片”的隱性錯(cuò)誤。先看一下核心結(jié)構(gòu)import json from pathlib import Path def build_coco_json(images: list, all_boxes: dict, out_path: Path) - None: images: 圖片路徑列表 all_boxes: {圖片名: [(x1, y1, x2, y2), ...]} 這里約定所有缺陷統(tǒng)一歸為類(lèi)別 1如果想按四類(lèi)缺陷分開(kāi) 在生成 boxes 時(shí)就應(yīng)保留類(lèi)別編號(hào)。 ann_id 1 coco_dict { info: {description: severstal steel defect}, licenses: [{id: 1, name: MIT}], categories: [{id: 1, name: defect}], images: [], annotations: [], } for img_id, img_path in enumerate(images, start1): h, w cv2.imread(str(img_path)).shape[:2] coco_dict[images].append({ id: img_id, file_name: Path(img_path).name, width: w, height: h, }) for x1, y1, x2, y2 in all_boxes[Path(img_path).name]: box_w x2 - x1 1 box_h y2 - y1 1 coco_dict[annotations].append({ id: ann_id, image_id: img_id, category_id: 1, bbox: [x1, y1, box_w, box_h], area: box_w * box_h, iscrowd: 0, }) ann_id 1 with open(out_path, w, encodingutf-8) as f: json.dump(coco_dict, f, indent2)這段代碼里bbox用的是[x, y, width, height]而不是 VOC 的(xmin, ymin, xmax, ymax)兩個(gè)方向?qū)懛词切率肿畛7傅腻e(cuò)。area在 COCO 中建議寫(xiě)真實(shí)像素面積雖然訓(xùn)練時(shí)很多框架會(huì)重新計(jì)算但用評(píng)估工具時(shí)會(huì)拿它和模型輸出做對(duì)比。iscrowd0表示每個(gè)框是獨(dú)立目標(biāo)如果后續(xù)想過(guò)濾重疊標(biāo)注可以改成 1但目前你不需要。如果這個(gè)數(shù)據(jù)集包含四類(lèi)缺陷你需要把互聯(lián)域分析和類(lèi)別 ID 綁定在掩碼圖上按像素值區(qū)分缺陷類(lèi)型再傳進(jìn)category_id。3.4 生成 YOLOtxt 小文件與 classes.txt 的配合最后一步是生成 YOLO 訓(xùn)練直接讀取的 txt 標(biāo)簽。這里要格外小心歸一化的精度YOLO 坐標(biāo)系里x_center和width都必須大于 0 且小于等于 1浮點(diǎn)數(shù)越界一個(gè)像素都會(huì)在訓(xùn)練時(shí)報(bào)AssertionError。我習(xí)慣在寫(xiě)出前做一次 clamp 兜底def write_yolo_txt(boxes: list, img_w: int, img_h: int, txt_path: Path) - None: boxes 里每一項(xiàng)建議是 (class_id, x1, y1, x2, y2) 坐標(biāo)在歸一化前先 clamp 到圖像范圍內(nèi)。 lines [] for cls_id, x1, y1, x2, y2 in boxes: x1 max(0, min(x1, img_w - 1)) x2 max(0, min(x2, img_w - 1)) y1 max(0, min(y1, img_h - 1)) y2 max(0, min(y2, img_h - 1)) if x2 x1 or y2 y1: continue xc (x1 x2) / 2 / img_w yc (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 歸一化后超過(guò) [0,1] 時(shí)直接截?cái)?xc min(max(xc, 0.0), 1.0) yc min(max(yc, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) txt_path.write_text(\n.join(lines), encodingutf-8)這里保留了 6 位小數(shù)是因?yàn)?YOLO 訓(xùn)練讀取標(biāo)簽時(shí)普遍用float()解析位數(shù)太少會(huì)導(dǎo)致框偏移幾個(gè)像素對(duì)小目標(biāo)影響尤其明顯。另外 txt 文件命名要和圖片名保持嚴(yán)格一致比如0001.jpg對(duì)應(yīng)0001.txt大小寫(xiě)和擴(kuò)展名都要統(tǒng)一。常見(jiàn)的翻車(chē)是圖片是.jpegtxt 是.jpg導(dǎo)致訓(xùn)練時(shí)一半標(biāo)簽讀不到。數(shù)據(jù)包解壓后建議先跑一遍腳本檢查子目錄里圖片和標(biāo)簽的文件名是否一一對(duì)應(yīng)不要相信肉眼。4. 劃分腳本按圖劃分才能避免數(shù)據(jù)泄漏隨機(jī)種子必須固定很多人在數(shù)據(jù)劃分上很隨意直接shuffle一下就把 5000 張圖按 8:1:1 分開(kāi)了。但對(duì)鋼材缺陷這類(lèi)數(shù)據(jù)劃分方式直接決定訓(xùn)練結(jié)果的真實(shí)性。如果同一張鋼板的左右兩半被分別分進(jìn)訓(xùn)練集和驗(yàn)證集模型實(shí)際上記住了鋼板的紋理特征而不是缺陷特征驗(yàn)證指標(biāo)會(huì)虛高部署到新產(chǎn)線時(shí)立刻現(xiàn)原形。4.1 按圖片劃分不要把同一個(gè)鋼板的多個(gè)框拆開(kāi)謝韋爾數(shù)據(jù)集的原始掩碼里常常一張圖有多個(gè)缺陷框有的框之間距離非常近甚至來(lái)自同一個(gè)物理缺陷被切斷后又合并。劃分腳本最核心的一條規(guī)則以“圖片”為最小單位劃分而不是以“標(biāo)注框”為最小單位。下面這段腳本是常規(guī)的按比例隨機(jī)劃分思路from pathlib import Path import random import shutil random.seed(2024) # 固定隨機(jī)種子保證每次劃分結(jié)果一致 data_root Path(dataset) image_dir data_root / images label_dir data_root / labels images sorted(image_dir.glob(*.jpg)) random.shuffle(images) train_ratio, val_ratio 0.8, 0.1 train_cut int(len(images) * train_ratio) val_cut int(len(images) * (train_ratio val_ratio)) splits { train: images[:train_cut], val: images[train_cut:val_cut], test: images[val_cut:], } for split_name, split_images in splits.items(): (data_root / split_name / images).mkdir(parentsTrue, exist_okTrue) (data_root / split_name / labels).mkdir(parentsTrue, exist_okTrue) for img_path in split_images: shutil.copy2(img_path, data_root / split_name / images / img_path.name) label_file label_dir / (img_path.stem .txt) if label_file.exists(): shutil.copy2(label_file, data_root / split_name / labels / label_file.name)random.seed(2024)這一步不是可有可無(wú)。如果你不固定種子每次跑腳本都會(huì)得到不同的劃分訓(xùn)練結(jié)果無(wú)法復(fù)現(xiàn)后面做超參數(shù)對(duì)比時(shí)你會(huì)發(fā)現(xiàn)所有實(shí)驗(yàn)都像是在黑匣子里調(diào)參。還有一個(gè)重要細(xì)節(jié)這里用copy2而不是move是為保留原始目錄作為備份。工業(yè)數(shù)據(jù)集往往要反復(fù)調(diào)整劃分比例如果直接把文件移動(dòng)了想改回原比例就得重新解壓。4.2 劃分比例、分層策略和類(lèi)別統(tǒng)計(jì)8:1:1 是常規(guī)默認(rèn)值但鋼材缺陷檢測(cè)數(shù)據(jù)集中負(fù)樣本無(wú)缺陷圖片和四類(lèi)缺陷的分布并不均勻。如果驗(yàn)證集里某種缺陷特別少mAP 會(huì)被整體拉低這不能說(shuō)明模型不行而是數(shù)據(jù)劃分不均勻。更可靠的做法是先統(tǒng)計(jì)每類(lèi)缺陷在圖片級(jí)別的出現(xiàn)次數(shù)再按類(lèi)別做分層劃分。下面這個(gè)參數(shù)表可以作為參考參數(shù)默認(rèn)值調(diào)整建議train_ratio0.8數(shù)據(jù)量小可降到 0.7但要保證驗(yàn)證集不少于 200 張val_ratio0.1盡量不要低于 0.05否則驗(yàn)證結(jié)果抖動(dòng)大test_ratio0.1如果沒(méi)有測(cè)試需求可合并進(jìn) valseed2024每次實(shí)驗(yàn)換 seed 相當(dāng)于換數(shù)據(jù)分布不要頻繁換min_area16低于該面積的缺陷建議丟否則訓(xùn)練 loss 會(huì)被小框干擾我一般會(huì)在劃分腳本后面加一段統(tǒng)計(jì)輸出打印訓(xùn)練集和驗(yàn)證集中每個(gè)類(lèi)別的圖片數(shù)。如果出現(xiàn)“驗(yàn)證集完全沒(méi)有劃痕類(lèi)”的情況就需要用sklearn.model_selection.train_test_split的stratify參數(shù)按類(lèi)別比例分層。注意這里的“分層”是針對(duì)圖片級(jí)類(lèi)別標(biāo)簽一張包含多類(lèi)缺陷的圖在分層時(shí)要?dú)w到多個(gè)類(lèi)別里容易重復(fù)計(jì)數(shù)所以我更推薦自己寫(xiě)一個(gè)按類(lèi)別分配的循環(huán)而不是完全依賴(lài)現(xiàn)成 API。4.3 生成 data.yaml為 YOLO 訓(xùn)練準(zhǔn)備目錄與類(lèi)別映射劃分完成后YOLOv8 訓(xùn)練前需要一個(gè)data.yaml文件告訴框架圖片路徑、驗(yàn)證集路徑和類(lèi)別名稱(chēng)相當(dāng)于數(shù)據(jù)集和模型之間的“適配層”。最常見(jiàn)的寫(xiě)法是path: dataset train: train/images val: val/images test: test/images names: 0: rolled-in_scale 1: patches 2: pitting 3: scratchpath可以寫(xiě)絕對(duì)路徑也可以寫(xiě)相對(duì)路徑但要注意 YOLO 在解析時(shí)會(huì)把它和train拼成完整目錄如果數(shù)據(jù)集根目錄不在執(zhí)行命令的當(dāng)前目錄下最好寫(xiě)絕對(duì)路徑。names的順序必須和生成 YOLO txt 時(shí)寫(xiě)入的索引一一對(duì)應(yīng)0對(duì)應(yīng)rolled-in_scale1對(duì)應(yīng)patches如果順序?qū)懛从?xùn)練過(guò)程不報(bào)錯(cuò)但預(yù)測(cè)結(jié)果全部錯(cuò)位而且這種錯(cuò)位很難通過(guò) loss 曲線察覺(jué)。判斷自己的data.yaml是否正確的辦法是訓(xùn)練前跑一次yolo detect train datadata.yaml modelyolov8n.pt epochs0框架會(huì)打印出每個(gè)類(lèi)別的樣本數(shù)量你要逐一核對(duì)。5. 常見(jiàn)問(wèn)題與避坑從解壓到訓(xùn)練會(huì)遇到的 5 個(gè)坑這節(jié)內(nèi)容是從真實(shí)訓(xùn)練經(jīng)驗(yàn)里沉淀出來(lái)的每個(gè)坑都曾經(jīng)讓人苦等幾個(gè)小時(shí)的訓(xùn)練后才發(fā)現(xiàn)。鋼材缺陷檢測(cè)數(shù)據(jù)集本身不大但正因?yàn)樾∪魏螛?biāo)簽問(wèn)題都會(huì)被放大成明顯的 mAP 異常。下面是按出現(xiàn)頻率排序的 5 個(gè)典型問(wèn)題。5.1 空標(biāo)簽被跳過(guò)導(dǎo)致訓(xùn)練時(shí)圖片和標(biāo)簽對(duì)不上現(xiàn)象訓(xùn)練啟動(dòng)時(shí)輸出的訓(xùn)練集圖片數(shù)為 5000但實(shí)際讀完標(biāo)簽后只訓(xùn)練不到 4000 張還有 1000 張圖“消失”了。原因謝韋爾數(shù)據(jù)集包含大量無(wú)缺陷背景圖這類(lèi)圖沒(méi)有對(duì)應(yīng)的缺陷掩碼轉(zhuǎn)換腳本生成 YOLO txt 時(shí)直接寫(xiě)了個(gè)空文件或者干脆沒(méi)生成。Ultralytics YOLO 在讀取標(biāo)簽時(shí)會(huì)把空 txt 認(rèn)為是“無(wú)目標(biāo)”但有些版本會(huì)把它過(guò)濾掉。解決在轉(zhuǎn)換腳本里對(duì)所有圖片統(tǒng)一生成 txt哪怕是空文件也要寫(xiě)出來(lái)。對(duì)于確實(shí)沒(méi)有缺陷的背景圖可以單獨(dú)歸到一個(gè)negative類(lèi)或者把空標(biāo)簽保留YOLO 訓(xùn)練時(shí)會(huì)把它們當(dāng)作背景樣本參與訓(xùn)練。檢查方法很簡(jiǎn)單統(tǒng)計(jì)圖片目錄和標(biāo)簽?zāi)夸浀奈募?shù)兩者之差就是出問(wèn)題的圖。5.2 過(guò)小的缺陷在歸一化后變成 0訓(xùn)練直接報(bào)錯(cuò)現(xiàn)象訓(xùn)練到第一個(gè) epoch 時(shí)拋出類(lèi)似AssertionError: bbox width must be positive的異常檢查后發(fā)現(xiàn)某個(gè) txt 里存在0.000000的寬度值。原因部分劃痕缺陷只有 1 到 2 個(gè)像素寬經(jīng)過(guò) YOLO 的 mosaic 增強(qiáng)放大后歸一化寬度計(jì)算出來(lái)小于最低精度閾值被四舍五入成 0。這類(lèi)目標(biāo)對(duì)檢測(cè)模型來(lái)說(shuō)本來(lái)就是噪聲。解決在mask_to_boxes階段提高min_area閾值我建議鋼材數(shù)據(jù)最小面積設(shè)為 25 到 36 像素并按寬高分別過(guò)濾比如w 3 or h 3直接丟棄。不要只過(guò)濾面積因?yàn)槊娣e大但寬度為 1 的細(xì)長(zhǎng)條仍然會(huì)產(chǎn)生非法框。5.3 三種格式都轉(zhuǎn)了一遍標(biāo)簽錯(cuò)位了現(xiàn)象VOC 的 XML 里某個(gè)框位于圖像頂部但轉(zhuǎn)成 COCO 后在底部或者 YOLO 訓(xùn)練時(shí)重復(fù)檢測(cè)出多個(gè)異常框。原因轉(zhuǎn)換腳本里遍歷圖片的排序不一致。VOC 是遍歷 XML 目錄COCO 是先遍歷圖片目錄YOLO 是讀取圖片列表三個(gè)循環(huán)的排序方式不同在文件名未按字典序排列時(shí)就會(huì)整體錯(cuò)位。解決所有循環(huán)統(tǒng)一用sorted()排序并確保轉(zhuǎn)換時(shí)按圖片名索引 bbox而不是按列表順序索引。更穩(wěn)妥的做法是把圖片路徑作為字典的唯一鍵先構(gòu)建{圖片名: 原始掩碼路徑}的映射再基于這份映射生成所有格式。5.4 解壓路徑帶中文或空格訓(xùn)練時(shí)讀不到圖片和標(biāo)簽現(xiàn)象在 Windows 上解壓后一切正常把整個(gè)文件夾傳到 Linux 服務(wù)器上訓(xùn)練啟動(dòng)時(shí)報(bào)大量FileNotFoundError路徑里的中文顯示為亂碼。原因.rar壓縮包里的文件在 Windows 下以 GBK 編碼解壓而 Linux 默認(rèn) UTF-8如果文件名或目錄名含中文會(huì)出現(xiàn)編碼錯(cuò)亂。就算文件名全英文目錄嵌套層級(jí)過(guò)深或包含空格也會(huì)有隱患。解決一拿到壓縮包先解壓到純英文路徑下比如~/data/severstal/不要放在“桌面/鋼材檢測(cè)”這類(lèi)目錄里。解壓后用file命令檢查文件編碼或直接在 Linux 上重新解壓一次。訓(xùn)練前用find . -name *.txt | head抽查幾個(gè)文件路徑是否和data.yaml里的配置完全一致這一步能省下 90% 的“訓(xùn)練時(shí)找不到文件”類(lèi)報(bào)錯(cuò)。5.5 類(lèi)別不平衡mAP 虛高但實(shí)際漏檢率居高不下現(xiàn)象訓(xùn)練完 mAP 達(dá)到 0.85看起來(lái)不錯(cuò)但在新樣本上劃過(guò)一批鋼板后發(fā)現(xiàn)劃痕類(lèi)幾乎全部漏檢。原因四類(lèi)缺陷里劃痕出現(xiàn)次數(shù)可能只占 5% 到 10%模型學(xué)到的“平均分布”偏向高頻類(lèi)。mAP 是各類(lèi)別 AP 的平均高頻類(lèi) AP 高就拉高了整體值掩蓋了低頻類(lèi)的問(wèn)題。解決訓(xùn)練前統(tǒng)計(jì)類(lèi)別分布并打印出來(lái)。如果某一類(lèi)占比過(guò)低有兩種常見(jiàn)做法一是對(duì)低頻類(lèi)做過(guò)采樣讓訓(xùn)練腳本每輪多讀幾次含該類(lèi)別的圖片二是降低 NMS 的置信度閾值因?yàn)殇摬谋砻嫒毕莶幌褡匀粓?chǎng)景那樣有大量難以區(qū)分的背景誤檢的代價(jià)遠(yuǎn)低于漏檢。從工程角度看你更應(yīng)該關(guān)注的是“每條鋼板的漏檢率”而不是 mAP這指標(biāo)寫(xiě)進(jìn)驗(yàn)收?qǐng)?bào)告更有說(shuō)服力。6. 訓(xùn)練教程與驗(yàn)證技巧把 YOLO 跑通并調(diào)到能用的三個(gè)技巧6.1 最小可行訓(xùn)練目錄結(jié)構(gòu)數(shù)據(jù)準(zhǔn)備好后目錄結(jié)構(gòu)盡量保持簡(jiǎn)單。我會(huì)把數(shù)據(jù)集放在dataset/下子目錄是train/images、train/labels、val/images、val/labels不再額外嵌套。data.yaml放在dataset/根目錄下訓(xùn)練命令和它保持同級(jí)。這樣做的原因是 YOLO 訓(xùn)練時(shí)大量依賴(lài)相對(duì)路徑結(jié)構(gòu)越扁平越不容易出錯(cuò)。6.2 復(fù)現(xiàn)一個(gè)能用的訓(xùn)練YOLOv8 命令行參數(shù)怎么定用 YOLOv8 訓(xùn)練這個(gè)數(shù)據(jù)集的最小命令是yolo detect train datadataset/data.yaml modelyolov8n.pt \ epochs100 imgsz640 batch16 lr00.01三個(gè)必調(diào)參數(shù)是imgsz、batch和lr0。imgsz我傾向設(shè)為 800 或 1024因?yàn)殇摬膱D原始分辨率高劃痕類(lèi)小目標(biāo)在 640 下可能只剩幾個(gè)像素但顯存有限時(shí) 640 也能跑前提是min_area過(guò)濾得足夠狠。batch和顯存直接掛鉤10GB 顯存跑 640 分辨率時(shí) batch16 差不多是極限如果爆顯存就減半。lr0別從默認(rèn) 0.01 亂加鋼材數(shù)據(jù)背景單一學(xué)習(xí)率偏高時(shí) loss 曲線會(huì)來(lái)回震蕩。做實(shí)驗(yàn)前建議用train子集跑 10 個(gè) epoch 畫(huà)出損失曲線確認(rèn) YOLO 的損失函數(shù)里分類(lèi)和框回歸兩項(xiàng)都在下降再放手跑長(zhǎng)訓(xùn)練。6.3 驗(yàn)證看什么從 metrics 到 ONNX 導(dǎo)出訓(xùn)練結(jié)束后不要只看results.png。先用驗(yàn)證集跑一遍打開(kāi)生成的混淆矩陣重點(diǎn)看劃痕類(lèi)是否大面積錯(cuò)分成氧化皮。確認(rèn)模型可用后導(dǎo)出 ONNX 是讓模型脫離訓(xùn)練框架走向部署的第一步y(tǒng)olo export modelbest.pt formatonnx imgsz640。導(dǎo)出的 ONNX 可以直接在 TensorRT 里轉(zhuǎn)成 engine跑在 AGX Orin 這類(lèi)邊緣設(shè)備上這也是很多實(shí)際產(chǎn)線的部署路徑。我第一次跑這個(gè)數(shù)據(jù)集時(shí)在空標(biāo)簽統(tǒng)計(jì)上翻過(guò)車(chē)白白浪費(fèi)了三個(gè)小時(shí)一看前幾輪 loss 全在抖動(dòng)后來(lái)才發(fā)現(xiàn)是轉(zhuǎn)換腳本把背景圖漏寫(xiě)為空 txt。從那以后我養(yǎng)成了習(xí)慣任何數(shù)據(jù)集到手先寫(xiě)一段腳本把每張圖的標(biāo)簽條數(shù)統(tǒng)計(jì)出來(lái)再用可視化工具把框畫(huà)出來(lái)隨機(jī)抽查 50 張確認(rèn)框和缺陷位置對(duì)得上才啟動(dòng)訓(xùn)練。這個(gè)習(xí)慣幫我避開(kāi)了不少標(biāo)注錯(cuò)位的坑也希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取