简介:本资源为YOLO谢韦尔钢材缺陷检测数据集,面向从事工业质检、目标检测算法学习与竞赛实践的学生、研究人员及工程师,解决钢材表面缺陷样本获取难、标注格式不统一的问题。压缩包共2000个文件,约12.38MB,包含1000张真实场景高质量图片,以及voc(xml)、coco(json)和yolo(txt)三种格式标签,分别存放于不同文件夹,可直接用于YOLO系列目标检测训练。此外还附赠数据集划分脚本、YOLO环境搭建与训练教程,支持按需划分训练集、验证集和测试集,并配有yaml配置文件与说明文档,方便快速复现实验。目前已有587人学习下载,适合需要完整数据与配套脚本的读者参考使用。
1. 谢韦尔钢材缺陷检测数据集到底能解决什么问题
钢材表面缺陷检测是工业质检里最典型的视觉任务之一:产线速度快、缺陷形态杂、正负样本极度不均衡。谢韦尔(Severstal)那场公开钢铁缺陷检测竞赛,把四类高频缺陷——划痕、夹杂物、斑块、凹坑——摆到了台面上,也让一批人第一次意识到,工业缺陷检测的难点不在模型结构,而在数据组织。你手上这份「YOLO谢韦尔钢材缺陷检测数据集(含1000张图片)+对应voc、coco和yolo三种格式标签+划分脚本+训练教程.rar」,本质是把一份竞赛级数据整理成了可直接喂给 YOLO 的工程包:1000 张图、三套标签、一个划分脚本、一份训练教程。它解决的不是「有没有数据」,而是「拿到数据后怎么在半小时内跑通第一条训练曲线」。适合两类人:一类是想入门工业缺陷检测但被数据格式卡住的算法新手,另一类是手里有产线图、想快速验证 YOLO 能不能用的落地工程师。下面我按自己实际处理这类数据包的顺序,把格式转换、划分、训练、排错讲透。
2. 三种标签格式的差异与转换逻辑
2.1 VOC、COCO、YOLO 到底差在哪
很多人拿到三套标签第一反应是「重复劳动」,其实这三套格式服务的是不同环节。VOC 用 XML 存绝对坐标,xmin/ymin/xmax/ymax一目了然,适合人工核对和可视化;COCO 用单个 JSON 存所有图,带images/annotations/categories三段结构,是很多检测框架和评测脚本的通用输入;YOLO 用每图一个 txt,存归一化的class cx cy w h,直接对应 YOLO 的 dataloader。钢材缺陷里「斑块」这类缺陷边界模糊,VOC 的矩形框在人工复核时最直观,而 YOLO 格式在训练时读取最快。三套并存的意义是:标注阶段用 VOC 核对,训练阶段用 YOLO,跨框架评测时用 COCO。
| 格式 | 存储单位 | 坐标类型 | 典型用途 |
|---|---|---|---|
| VOC | 每图一个 XML | 绝对像素 | 人工核对、可视化 |
| COCO | 全量一个 JSON | 绝对像素 | 跨框架评测、mmdetection |
| YOLO | 每图一个 txt | 归一化 0-1 | YOLO 训练直接读取 |
2.2 用脚本把 VOC 转成 YOLO 并校验
数据包里通常已经带好三套标签,但你要会自己转,才能处理后续新增的图。下面这段脚本把 VOC 的 XML 转成 YOLO 的 txt,同时做一次坐标越界校验。
import os import xml.etree.ElementTree as ET # 类别映射,钢材四类缺陷,顺序必须和训练时的 data.yaml 一致 CLASS_MAP = {"scratch": 0, "inclusion": 1, "patch": 2, "pitted": 3} def voc_to_yolo(xml_dir, out_dir, img_w, img_h): os.makedirs(out_dir, exist_ok=True) for name in os.listdir(xml_dir): if not name.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, name)) root = tree.getroot() lines = [] for obj in root.findall("object"): cls = obj.find("name").text.strip() if cls not in CLASS_MAP: continue # 跳过未定义类别,避免训练时报 index 越界 bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 越界裁剪,钢材图边缘缺陷常被标到图外 xmin, xmax = max(0, xmin), min(img_w, xmax) ymin, ymax = max(0, ymin), min(img_h, ymax) cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h if w <= 0 or h <= 0: continue # 裁剪后无效框直接丢弃 lines.append(f"{CLASS_MAP[cls]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") with open(os.path.join(out_dir, name.replace(".xml", ".txt")), "w") as f: f.write("\n".join(lines)) voc_to_yolo("Annotations", "labels", 1600, 256)逻辑说明:CLASS_MAP的顺序决定训练时类别索引,一旦和data.yaml里的names顺序不一致,模型会把划痕学成凹坑。img_w/img_h必须传真实图像尺寸,谢韦尔原始图是 1600×256 的长条图,如果你在预处理里 resize 过,这里要传 resize 后的尺寸。参数上,cx/cy/w/h保留 6 位小数足够,YOLO 读取时对精度不敏感,但w/h为 0 的框一定要丢,否则训练时 loss 会出 NaN。
2.3 COCO 转 YOLO 时最容易忽略的 id 映射
COCO 的category_id往往不是从 0 连续开始的,比如谢韦尔原始 COCO 里可能是 1、2、3、4。直接拿category_id当 YOLO 的 class 会得到一个从 1 开始的索引,训练时第一类永远学不到。正确做法是建一个category_id -> 连续索引的映射表,转换时查表。
import json with open("annotations.json") as f: coco = json.load(f) # 建立连续映射,sorted 保证顺序稳定 cat_ids = sorted(c["id"] for c in coco["categories"]) id2idx = {cid: i for i, cid in enumerate(cat_ids)} img_id2name = {img["id"]: img["file_name"] for img in coco["images"]} img_id2size = {img["id"]: (img["width"], img["height"]) for img in coco["images"]} for ann in coco["annotations"]: idx = id2idx[ann["category_id"]] w, h = img_id2size[ann["image_id"]] x, y, bw, bh = ann["bbox"] # COCO bbox 是左上角 + 宽高 cx = (x + bw / 2) / w cy = (y + bh / 2) / h # 后续按 image_id 聚合写入对应 txt这里的关键是 COCO 的bbox是[x, y, width, height],不是[xmin, ymin, xmax, ymax],很多人第一次转会在这里翻车,框整体偏移半个宽度。另外id2idx用sorted保证每次运行映射一致,避免今天训练和明天训练类别顺序不同。
3. 数据集划分脚本与训练集构建
3.1 按 8:1:1 划分并保证缺陷类别均衡
钢材缺陷数据有个特点:一张图可能同时有多个缺陷,且「凹坑」这类样本远少于「划痕」。如果纯随机划分,验证集里可能一个凹坑都没有,mAP 评估就失真。我一般用分层抽样,先按「图中包含的缺陷类别组合」分组,再在组内按比例抽。
import os import random from collections import defaultdict random.seed(42) # 固定种子,保证划分可复现 def load_label_classes(label_dir): """返回 {图片名: set(类别)}""" img2cls = {} for name in os.listdir(label_dir): if not name.endswith(".txt"): continue cls_set = set() with open(os.path.join(label_dir, name)) as f: for line in f: if line.strip(): cls_set.add(int(line.split()[0])) img2cls[name.replace(".txt", "")] = cls_set return img2cls def split_dataset(img2cls, train_r=0.8, val_r=0.1): # 按类别组合分组,保证稀有组合在三个集合都出现 groups = defaultdict(list) for img, cls_set in img2cls.items(): key = tuple(sorted(cls_set)) groups[key].append(img) train, val, test = [], [], [] for key, imgs in groups.items(): random.shuffle(imgs) n = len(imgs) n_train = int(n * train_r) n_val = int(n * val_r) train += imgs[:n_train] val += imgs[n_train:n_train + n_val] test += imgs[n_train + n_val:] return train, val, test逻辑说明:seed=42是血泪经验,不固定种子的话每次划分结果不同,你复现不了上周的 mAP。groups按类别组合分组,比如「只有划痕」和「划痕+凹坑」是两组,这样稀有组合不会被随机划分全丢进训练集。参数上train_r/val_r按 8:1:1 设,如果数据量小于 1000,建议改成 7:2:1,给验证集多留点样本。
3.2 生成 YOLO 训练所需的目录结构和 data.yaml
YOLO 训练要求固定的目录结构,图片和标签分开放,且路径要写进data.yaml。划分脚本跑完后,按下面结构组织:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamldata.yaml内容如下,path写数据集根目录绝对路径,names顺序必须和转换脚本里的CLASS_MAP完全一致:
path: /data/severstal train: images/train val: images/val test: images/test nc: 4 names: 0: scratch 1: inclusion 2: patch 3: pitted提示:
nc和names数量必须一致,少写一个类别 YOLO 会在加载时直接报错,不会静默跳过。
3.3 用 YOLO 官方命令跑通第一条训练曲线
环境配好后,训练命令本身很短,但参数决定你能不能收敛。钢材缺陷图是 1600×256 的长条图,直接 resize 到 640×640 会把缺陷压扁,我一般用rect=True保持长宽比,配合imgsz=640。
yolo detect train \ data=/data/severstal/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ rect=True \ lr0=0.01 \ patience=20 \ project=runs/severstal \ name=exp1参数说明:model=yolov8n.pt是 nano 版预训练权重,1000 张图用 nano 足够,换 s/m 容易过拟合;rect=True对长条图很关键,不开的话默认方形填充,缺陷区域被压缩;patience=20表示 20 轮 mAP 不涨就早停,钢材数据噪声大,不设早停容易在后期震荡;lr0=0.01是 YOLOv8 默认值,如果 loss 前几轮就爆 NaN,降到 0.001 再试。训练开始后重点看cls_loss和box_loss是否同步下降,只降 box 不降 cls 说明类别映射有问题。
4. 训练中常见的翻车点与排查
4.1 现象:loss 从第一轮就是 NaN
原因:标签里有w或h为 0 的框,或者坐标没归一化直接写了像素值。谢韦尔数据里边缘缺陷被标到图外,转换时没裁剪就会产生负宽高。解决:回到 2.2 的转换脚本,确认w <= 0 or h <= 0的框被丢弃,并检查所有 txt 里数值是否都在 0-1 之间。用一行命令快速排查:
awk '{if($4<=0 || $5<=0 || $4>1 || $5>1) print FILENAME": "$0}' labels/train/*.txt4.2 现象:mAP 一直是 0,但 loss 在降
原因:data.yaml里的names顺序和标签里的 class 索引对不上,模型学的是「索引 0 是划痕」,但验证时按names去匹配,类别全错。解决:打印一张图的标签和data.yaml对照,确认索引 0 对应的类别名一致。另一个可能是验证集路径写错,YOLO 找不到图会静默跳过验证,mAP 恒为 0。
4.3 现象:训练到一半 BN 层崩溃,报NaN in batch norm
原因:batch 太小(比如batch=4)加上长条图,单个 batch 内像素分布差异大,BN 统计量失稳。解决:把batch提到 16 以上,或者改用rect=True减少填充带来的分布偏移。如果显存不够,用梯度累积模拟大 batch,而不是硬降 batch。
4.4 现象:验证集 mAP 高但实际推理漏检严重
原因:划分时验证集和训练集来自同一批连续拍摄的图,缺陷形态高度相似,mAP 虚高。解决:划分脚本里按拍摄批次或时间分组,而不是纯随机。如果数据包里没有批次信息,至少保证seed固定后人工抽查验证集,确认里面有独立场景的图。
4.5 现象:训练速度极慢,GPU 利用率不到 30%
原因:dataloader 的workers默认是 8,但长条图解码慢,CPU 成了瓶颈。解决:把workers提到 16,并确认图片是 JPEG 而不是 PNG,PNG 解码慢一倍。另外cache=True可以把图缓存到内存,1000 张 1600×256 的图约占 1.2GB 内存,机器扛得住就开。
5. 把 1000 张图用到极致的几个进阶技巧
数据量只有 1000 张时,模型很容易过拟合,我一般从三个方向榨干数据价值。第一是离线增强:钢材缺陷的方向不敏感,划痕横竖都有,所以flipud和fliplr都可以开,但mosaic要慎用,四图拼接会让长条图的缺陷比例失真,建议mosaic=0.5而不是默认的 1.0。第二是类别权重:如果「凹坑」只有几十个样本,在data.yaml同级目录放一个hyp.yaml,把cls_pw调高,或者直接在损失里对稀有类加权。第三是预训练权重的选择:yolov8n.pt是在 COCO 上训的,COCO 里没有钢材缺陷,但底层边缘特征可迁移,如果你能找到工业缺陷相关的预训练权重,替换掉 backbone 的前几层,收敛会快很多。
验证方法上,别只看 mAP50,钢材缺陷更关心漏检率。我习惯在验证后单独跑一遍推理,统计每类的召回:
yolo detect val \ model=runs/severstal/exp1/weights/best.pt \ data=/data/severstal/data.yaml \ imgsz=640 \ rect=True \ conf=0.25 \ iou=0.5 \ save_json=Trueconf=0.25是推理阈值,工业场景宁可误检不可漏检的话,降到 0.15 再跑一次对比召回。save_json=True会输出 COCO 格式的预测结果,方便你用 pycocotools 单独算每类 AP。如果某一类 AP 明显低,先别改模型,回去看这类样本的标注框是不是普遍偏大或偏小,钢材缺陷的「斑块」边界模糊,标注一致性差是 mAP 上不去的头号原因。
最后一个习惯:每次改完划分或增强参数,先跑 10 个 epoch 看 loss 曲线趋势,别一上来就 100 轮。我早期图省事直接跑满,结果发现标签映射错了,白等两小时。现在固定先epochs=10快速验证流程通不通,通了再放大。希望帮到你。
本文还有配套的精品资源,点击获取