简介:这是一份用于钢材表面缺陷检测的YOLO数据集,面向计算机视觉学习者、工业质检项目开发者及课程实践者,可支撑目标检测入门练习、模型训练与课程设计。压缩包内共2000个文件,以1986个xml标签为主,同时提供json、txt等多种格式标注,覆盖VOC、COCO、YOLO三种主流目标检测规范;三类标签分别存放在不同文件夹中,配合真实场景的高质量图片,可直接用于YOLO系列模型训练与效果评估。资源附赠Linux与Windows双平台YOLO环境搭建教程、训练案例说明,以及训练集/验证集/测试集划分脚本,便于读者根据实际需求灵活重组数据并快速跑通训练流程。数据集整体约61.48MB,轻量易用,目前已有397人学习下载,尤其适合需要规范标签、完整训练指引与可复用脚本的中初级学习者。
1. 谢韦尔钢材缺陷检测:5000 张真实钢板图能把目标检测整条链路跑通吗
在一家钢厂的表面质检工位上,钢板以每秒几米的速度从辊道经过,表面的划痕、氧化皮、麻点和斑块稍纵即逝,人工盯屏漏检率居高不下,产线需要的是一套能在几百毫秒内把缺陷框出来的目标检测模型。谢韦尔钢材缺陷检测数据集就是为这类需求准备的真实工业数据:5000 张钢板表面图像,同时给出 VOC、COCO、YOLO 三种格式标签,外加划分脚本和训练教程。解压之后不用再折腾格式转换,直接把数据丢给 YOLO 就能开训。对想用 YOLO 入门目标检测、又不想拿人造图片练手的人来说,这是很好的起点;对要搭工业质检 demo 的工程师,它也省掉了标注格式混杂的坑。
2. 三种标签格式到底装了什么:VOC、COCO、YOLO 的结构与选型
钢材表面缺陷检测和常规的目标检测有一个很大的不同:原始数据集的标签往往不是框,而是像素级掩码。谢韦尔数据集源自 Kaggle 上的 Severstal Steel Defect Detection 比赛,比赛任务要求参赛者输出缺陷区域的像素级掩码,用 RLE 编码存储在 CSV 文件里。而这个数据包帮你把掩码转成了检测模型真正需要的边界框,再包装成 VOC、COCO 和 YOLO 三种主流格式。理解这三者各自的组织方式,比直接开训更重要,因为后面所有脚本和训练配置都建立在这套结构之上。
2.1 原始标签是像素级掩码,检测任务要的是框
谢韦尔钢材缺陷共分四类:rolled-in scale(含氧化皮)、patches(斑块)、pitting(麻点/蚀斑)、scratch(划痕)。热轧钢板表面缺陷的特点是背景噪声大、缺陷对比度低、划痕细长而氧化皮却可能占据大块面积,同一张图上往往同时存在多个小缺陷。这种数据直接做分割是可行的,但如果只是判断“有没有缺陷、缺陷在哪儿”,目标检测的性价比更高——标注体积小、训练速度快、部署更容易落地。
我解压过这类数据集后一般会先做一件事:统计每张掩码图里的连通域数量和最小外接框尺寸。这一步能直接暴露出后面训练时的大部分问题,比如某些缺陷的框可能只有几个像素宽,低于 YOLO 的默认下采样倍数时基本学不到。常见做法是把掩码 PNG 读取为单通道灰度图,然后用cv2.connectedComponentsWithStats找出每个独立缺陷区域的最小外接矩形,再把矩形写成检测格式。这个思路是理解后面三个转换脚本的基础,因为三种格式的标签最终都来自同一个掩码转框的结果。
2.2 VOC 与 COCO:两种标注规约的“普通话”
Pascal VOC 是目标检测领域历史最悠久的标注格式,核心思想是“一张图对应一个 XML 文件”。XML 里记录文件名、图像尺寸、目标类别,以及每个目标的xmin, ymin, xmax, ymax像素坐标。它的优点是直观,任何人打开 XML 都能看懂;缺点也明显——类别列表分散在各个 XML 里,统计类别分布要遍历整个目录。
COCO 则把所有信息收拢到一个 JSON 文件里,用四个顶层数组组织数据:images存图像 ID 和尺寸,categories存类别 ID 和名称,annotations存每个框的image_id、category_id、bbox和area,另外还带info和licenses两个元信息字段。我在实际项目里更常用 COCO,因为一个 JSON 就能完成类别统计、数据筛选和跨库传递,但新手第一次打开 COCO JSON 时往往被那串嵌套结构绕晕。下面这张表可以直接看清差异:
| 维度 | VOC | COCO | YOLO txt |
|---|---|---|---|
| 标注组织方式 | 一张图一个 XML | 全量数据一个 JSON | 一张图一个 txt |
| 边界框表示 | xmin, ymin, xmax, ymax(像素) | x, y, w, h(像素) | xc, yc, w, h(归一化) |
| 类别定义 | 写在每个 XML 里 | categories 数组统一管理 | data.yaml 的 names 列表 |
| 常用配套工具 | labelImg | labelme / CVAT | CVAT / Roboflow |
| 训练时读取速度 | 需解析 XML,偏慢 | 需解析 JSON,中等 | 纯文本直接读,最快 |
选型上没有绝对的“最优”,只有“适不适合”。如果你的训练代码是基于 Detectron2 或 MMDetection 的,建议直接用 COCO;如果只是跑 YOLO 系列,用 YOLO 格式最省事。
2.3 YOLO 格式:txt 只存归一化坐标,训练时直接读
YOLO 格式是三者中最“简洁粗暴”的:每张图对应一个同名 txt 文件,每一行代表一个目标,格式是class x_center y_center width height,其中位置和宽高都除以了图像宽高,归一化到 0 到 1 之间。比如一张 1280×1024 的图像里,某个框左上角是 (320, 256),右下角是 (960, 768),那么归一化中心就是 (0.5, 0.5),宽高是 (0.5, 0.5),txt 里存的就是0 0.5 0.5 0.5 0.5。
这种格式把坐标从像素空间映射到相对空间,意味着训练时不管输入图片被缩放到 640 还是 1280,标签都不用跟着改。Ultralytics YOLO 在训练时会直接读 txt 文件,跳过 XML 或 JSON 解析,所以读取速度最快。代价是 txt 文件的可读性很差,手工改一个数字都可能导致标注错位,所以数据包的转换脚本就显得尤其重要。
提示:不要迷信“YOLO 格式只能给 YOLO 用”。只要坐标归一化的规则不变,Faster R-CNN 等模型也可以在训练前把 txt 转回绝对坐标使用。
3. 从掩码到三种标注:转换脚本逐段拆解
理解了三种格式之后,你一定想知道转换脚本是怎么运作的。谢韦尔数据集的转换链路一般是:读取掩码 PNG → 连通域分析得到 bbox → 分别写出 VOC XML、COCO JSON、YOLO txt。下面四段脚本基本可以应对这个数据包的标准场景,每一步都建议在解压后的副本上操作,不要在原目录上直接覆盖标签。
3.1 掩码转 bbox:连通域统计与最小外接矩形
掩码图里每一个像素值不为 0 的区域都对应一个缺陷,但同一张图里可能有多个互不相连的缺陷区域,所以要用连通域分析把它们拆开。下面这段代码可以遍历一个掩码目录,输出每个连通域的外接框:
import cv2 import numpy as np from pathlib import Path def mask_to_boxes(mask_path: Path, min_area: int = 16) -> list: """ 把掩码 PNG 转成若干边界框。 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, connectivity=8 ) 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逻辑说清楚:connectedComponentsWithStats返回四个值,第一个num_labels是连通域总数,第二个labels是每个像素所属的编号,第三个stats是每个连通域的统计信息,列顺序固定为x, y, width, height, area。代码从 1 开始遍历,因为 0 永远是背景。min_area这个参数很关键,钢材缺陷里有的划痕细到只有一个像素宽,转换成框以后宽高可能小于 2 个像素,这种框在 YOLO 里基本是负作用,建议丢弃或者用后续的形态学膨胀先合并。
3.2 生成 VOC:一张图一个 XML,用 ElementTree 写
拿到 bbox 列表后,生成 VOC 格式最简单的方式是使用 Python 标准库的xml.etree.ElementTree,不需要额外依赖。注意 XML 里的filename要写成纯文件名,path字段是绝对路径,二者不要混用,否则后续跑 MMDetection 等框架时容易出校验错误:
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), encoding="utf-8", xml_declaration=True)几个关键点:width和height如果直接读图片而不是用掩码尺寸,可以避免掩码被 resize 后坐标错位的隐患;depth字段对灰度图写 1,对 RGB 图写 3,YOLO 训练一般用不上但 VOC 校验会检查。ElementTree.write默认不写 XML 声明,加xml_declaration=True可以保证文件在第三方解析器下兼容。如果要把 VOC 再转为 COCO,一般推荐用voc2coco脚本,但前提是 XML 里的difficult字段必须存在,否则转换会中断,所以这里即使所有目标都填 0 也要写出来。
3.3 生成 COCO:一次 dict 组装,一把 json.dump
COCO JSON 的组织方式和 VOC 截然不同,它是一个嵌套 dict。最容易踩坑的是image_id的分配:必须先给所有图片建立id → 文件名的映射,再填写annotations,否则很容易出现“标注的 image_id 指向了错误的图片”的隐性错误。先看一下核心结构:
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), ...]} 这里约定所有缺陷统一归为类别 1,如果想按四类缺陷分开, 在生成 boxes 时就应保留类别编号。 """ 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, start=1): 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", encoding="utf-8") as f: json.dump(coco_dict, f, indent=2)这段代码里bbox用的是[x, y, width, height]而不是 VOC 的(xmin, ymin, xmax, ymax),两个方向写反是新手最常犯的错。area在 COCO 中建议写真实像素面积,虽然训练时很多框架会重新计算,但用评估工具时会拿它和模型输出做对比。iscrowd=0表示每个框是独立目标,如果后续想过滤重叠标注可以改成 1,但目前你不需要。如果这个数据集包含四类缺陷,你需要把互联域分析和类别 ID 绑定,在掩码图上按像素值区分缺陷类型,再传进category_id。
3.4 生成 YOLO:txt 小文件与 classes.txt 的配合
最后一步是生成 YOLO 训练直接读取的 txt 标签。这里要格外小心归一化的精度:YOLO 坐标系里x_center和width都必须大于 0 且小于等于 1,浮点数越界一个像素都会在训练时报AssertionError。我习惯在写出前做一次 clamp 兜底:
def write_yolo_txt(boxes: list, img_w: int, img_h: int, txt_path: Path) -> None: """ boxes 里每一项建议是 (class_id, x1, y1, x2, y2) 坐标在归一化前先 clamp 到图像范围内。 """ 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 # 归一化后超过 [0,1] 时直接截断 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), encoding="utf-8")这里保留了 6 位小数,是因为 YOLO 训练读取标签时普遍用float()解析,位数太少会导致框偏移几个像素,对小目标影响尤其明显。另外 txt 文件命名要和图片名保持严格一致,比如0001.jpg对应0001.txt,大小写和扩展名都要统一。常见的翻车是图片是.jpeg,txt 是.jpg,导致训练时一半标签读不到。数据包解压后建议先跑一遍脚本检查子目录里图片和标签的文件名是否一一对应,不要相信肉眼。
4. 划分脚本:按图划分才能避免数据泄漏,随机种子必须固定
很多人在数据划分上很随意,直接shuffle一下就把 5000 张图按 8:1:1 分开了。但对钢材缺陷这类数据,划分方式直接决定训练结果的真实性。如果同一张钢板的左右两半被分别分进训练集和验证集,模型实际上记住了钢板的纹理特征而不是缺陷特征,验证指标会虚高,部署到新产线时立刻现原形。
4.1 按图片划分,不要把同一个钢板的多个框拆开
谢韦尔数据集的原始掩码里,常常一张图有多个缺陷框,有的框之间距离非常近,甚至来自同一个物理缺陷被切断后又合并。划分脚本最核心的一条规则:以“图片”为最小单位划分,而不是以“标注框”为最小单位。下面这段脚本是常规的按比例随机划分思路:
from pathlib import Path import random import shutil random.seed(2024) # 固定随机种子,保证每次划分结果一致 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(parents=True, exist_ok=True) (data_root / split_name / "labels").mkdir(parents=True, exist_ok=True) 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)这一步不是可有可无。如果你不固定种子,每次跑脚本都会得到不同的划分,训练结果无法复现,后面做超参数对比时你会发现所有实验都像是在黑匣子里调参。还有一个重要细节:这里用copy2而不是move,是为保留原始目录作为备份。工业数据集往往要反复调整划分比例,如果直接把文件移动了,想改回原比例就得重新解压。
4.2 划分比例、分层策略和类别统计
8:1:1 是常规默认值,但钢材缺陷检测数据集中负样本(无缺陷图片)和四类缺陷的分布并不均匀。如果验证集里某种缺陷特别少,mAP 会被整体拉低,这不能说明模型不行,而是数据划分不均匀。更可靠的做法是先统计每类缺陷在图片级别的出现次数,再按类别做分层划分。下面这个参数表可以作为参考:
| 参数 | 默认值 | 调整建议 |
|---|---|---|
| train_ratio | 0.8 | 数据量小可降到 0.7,但要保证验证集不少于 200 张 |
| val_ratio | 0.1 | 尽量不要低于 0.05,否则验证结果抖动大 |
| test_ratio | 0.1 | 如果没有测试需求可合并进 val |
| seed | 2024 | 每次实验换 seed 相当于换数据分布,不要频繁换 |
| min_area | 16 | 低于该面积的缺陷建议丢,否则训练 loss 会被小框干扰 |
我一般会在划分脚本后面加一段统计输出,打印训练集和验证集中每个类别的图片数。如果出现“验证集完全没有划痕类”的情况,就需要用sklearn.model_selection.train_test_split的stratify参数按类别比例分层。注意这里的“分层”是针对图片级类别标签,一张包含多类缺陷的图在分层时要归到多个类别里,容易重复计数,所以我更推荐自己写一个按类别分配的循环,而不是完全依赖现成 API。
4.3 生成 data.yaml:为 YOLO 训练准备目录与类别映射
划分完成后,YOLOv8 训练前需要一个data.yaml文件,告诉框架图片路径、验证集路径和类别名称,相当于数据集和模型之间的“适配层”。最常见的写法是:
path: dataset train: train/images val: val/images test: test/images names: 0: rolled-in_scale 1: patches 2: pitting 3: scratchpath可以写绝对路径也可以写相对路径,但要注意 YOLO 在解析时会把它和train拼成完整目录,如果数据集根目录不在执行命令的当前目录下,最好写绝对路径。names的顺序必须和生成 YOLO txt 时写入的索引一一对应,0对应rolled-in_scale,1对应patches,如果顺序写反,训练过程不报错,但预测结果全部错位,而且这种错位很难通过 loss 曲线察觉。判断自己的data.yaml是否正确的办法是训练前跑一次yolo detect train data=data.yaml model=yolov8n.pt epochs=0,框架会打印出每个类别的样本数量,你要逐一核对。
5. 常见问题与避坑:从解压到训练会遇到的 5 个坑
这节内容是从真实训练经验里沉淀出来的,每个坑都曾经让人苦等几个小时的训练后才发现。钢材缺陷检测数据集本身不大,但正因为小,任何标签问题都会被放大成明显的 mAP 异常。下面是按出现频率排序的 5 个典型问题。
5.1 空标签被跳过,导致训练时图片和标签对不上
现象:训练启动时输出的训练集图片数为 5000,但实际读完标签后只训练不到 4000 张,还有 1000 张图“消失”了。
原因:谢韦尔数据集包含大量无缺陷背景图,这类图没有对应的缺陷掩码,转换脚本生成 YOLO txt 时直接写了个空文件,或者干脆没生成。Ultralytics YOLO 在读取标签时会把空 txt 认为是“无目标”,但有些版本会把它过滤掉。
解决:在转换脚本里对所有图片统一生成 txt,哪怕是空文件也要写出来。对于确实没有缺陷的背景图,可以单独归到一个negative类,或者把空标签保留,YOLO 训练时会把它们当作背景样本参与训练。检查方法很简单:统计图片目录和标签目录的文件数,两者之差就是出问题的图。
5.2 过小的缺陷在归一化后变成 0,训练直接报错
现象:训练到第一个 epoch 时抛出类似AssertionError: bbox width must be positive的异常,检查后发现某个 txt 里存在0.000000的宽度值。
原因:部分划痕缺陷只有 1 到 2 个像素宽,经过 YOLO 的 mosaic 增强放大后,归一化宽度计算出来小于最低精度阈值,被四舍五入成 0。这类目标对检测模型来说本来就是噪声。
解决:在mask_to_boxes阶段提高min_area阈值,我建议钢材数据最小面积设为 25 到 36 像素,并按宽高分别过滤,比如w < 3 or h < 3直接丢弃。不要只过滤面积,因为面积大但宽度为 1 的细长条仍然会产生非法框。
5.3 三种格式都转了一遍,标签错位了
现象:VOC 的 XML 里某个框位于图像顶部,但转成 COCO 后在底部;或者 YOLO 训练时重复检测出多个异常框。
原因:转换脚本里遍历图片的排序不一致。VOC 是遍历 XML 目录,COCO 是先遍历图片目录,YOLO 是读取图片列表,三个循环的排序方式不同,在文件名未按字典序排列时就会整体错位。
解决:所有循环统一用sorted()排序,并确保转换时按图片名索引 bbox,而不是按列表顺序索引。更稳妥的做法是把图片路径作为字典的唯一键,先构建{图片名: 原始掩码路径}的映射,再基于这份映射生成所有格式。
5.4 解压路径带中文或空格,训练时读不到图片和标签
现象:在 Windows 上解压后一切正常,把整个文件夹传到 Linux 服务器上训练,启动时报大量FileNotFoundError,路径里的中文显示为乱码。
原因:.rar压缩包里的文件在 Windows 下以 GBK 编码解压,而 Linux 默认 UTF-8,如果文件名或目录名含中文,会出现编码错乱。就算文件名全英文,目录嵌套层级过深或包含空格也会有隐患。
解决:一拿到压缩包先解压到纯英文路径下,比如~/data/severstal/,不要放在“桌面/钢材检测”这类目录里。解压后用file命令检查文件编码,或直接在 Linux 上重新解压一次。训练前用find . -name "*.txt" | head抽查几个文件路径是否和data.yaml里的配置完全一致,这一步能省下 90% 的“训练时找不到文件”类报错。
5.5 类别不平衡,mAP 虚高但实际漏检率居高不下
现象:训练完 mAP 达到 0.85,看起来不错,但在新样本上划过一批钢板后发现划痕类几乎全部漏检。
原因:四类缺陷里划痕出现次数可能只占 5% 到 10%,模型学到的“平均分布”偏向高频类。mAP 是各类别 AP 的平均,高频类 AP 高就拉高了整体值,掩盖了低频类的问题。
解决:训练前统计类别分布并打印出来。如果某一类占比过低,有两种常见做法:一是对低频类做过采样,让训练脚本每轮多读几次含该类别的图片;二是降低 NMS 的置信度阈值,因为钢材表面缺陷不像自然场景那样有大量难以区分的背景,误检的代价远低于漏检。从工程角度看,你更应该关注的是“每条钢板的漏检率”而不是 mAP,这指标写进验收报告更有说服力。
6. 训练教程与验证技巧:把 YOLO 跑通并调到能用的三个技巧
6.1 最小可行训练目录结构
数据准备好后,目录结构尽量保持简单。我会把数据集放在dataset/下,子目录是train/images、train/labels、val/images、val/labels,不再额外嵌套。data.yaml放在dataset/根目录下,训练命令和它保持同级。这样做的原因是 YOLO 训练时大量依赖相对路径,结构越扁平越不容易出错。
6.2 复现一个能用的训练:YOLOv8 命令行参数怎么定
用 YOLOv8 训练这个数据集的最小命令是:
yolo detect train data=dataset/data.yaml model=yolov8n.pt \ epochs=100 imgsz=640 batch=16 lr0=0.01三个必调参数是imgsz、batch和lr0。imgsz我倾向设为 800 或 1024,因为钢材图原始分辨率高,划痕类小目标在 640 下可能只剩几个像素,但显存有限时 640 也能跑,前提是min_area过滤得足够狠。batch和显存直接挂钩,10GB 显存跑 640 分辨率时 batch=16 差不多是极限,如果爆显存就减半。lr0别从默认 0.01 乱加,钢材数据背景单一,学习率偏高时 loss 曲线会来回震荡。做实验前建议用train子集跑 10 个 epoch 画出损失曲线,确认 YOLO 的损失函数里分类和框回归两项都在下降,再放手跑长训练。
6.3 验证看什么:从 metrics 到 ONNX 导出
训练结束后不要只看results.png。先用验证集跑一遍,打开生成的混淆矩阵,重点看划痕类是否大面积错分成氧化皮。确认模型可用后,导出 ONNX 是让模型脱离训练框架走向部署的第一步:yolo export model=best.pt format=onnx imgsz=640。导出的 ONNX 可以直接在 TensorRT 里转成 engine,跑在 AGX Orin 这类边缘设备上,这也是很多实际产线的部署路径。
我第一次跑这个数据集时,在空标签统计上翻过车,白白浪费了三个小时一看前几轮 loss 全在抖动,后来才发现是转换脚本把背景图漏写为空 txt。从那以后我养成了习惯:任何数据集到手,先写一段脚本把每张图的标签条数统计出来,再用可视化工具把框画出来随机抽查 50 张,确认框和缺陷位置对得上才启动训练。这个习惯帮我避开了不少标注错位的坑,也希望帮到你。
本文还有配套的精品资源,点击获取