简介:面向工业质检与目标检测场景,这份YOLO钢表面缺陷检测数据集包含约10000张真实场景图片,配套voc(xml)、coco(json)、yolo(txt)三种格式标签且分目录存放,可直接用于YOLO系列模型训练。对缺少工业缺陷样本、想快速搭建检测流程的开发者,可省去手动标注与格式转换的步骤。压缩包共2000个文件,以xml标签为主,另有环境搭建与训练说明的html文档、数据集划分清单及3个Python脚本,整体约212.41MB。预览可见Linux与Windows两套YOLO环境搭建教程,以及将图片和标签按训练集、验证集、测试集自动划分并写入新文件夹的脚本,适合多平台复现。目前已有549人学习下载,覆盖从环境配置、数据划分到模型训练全流程,适合刚接触YOLO的初学者和需要现成工业缺陷数据的工程人员。
1. 从用户需求到落地产品:YOLO钢表面缺陷检测数据集到底解决了什么问题
把YOLO用在钢表面缺陷检测上,是工厂质检里最近很热的方向。一条钢板产线上的划痕、麻点、氧化皮、结疤,以往要靠老质检员肉眼盯,现在越来越多的团队想用深度学习把这套流程自动化。但真正动手时,大部分人卡在第一关:没有一份干净、可用、带完整标注的数据集。标题里这个压缩包把 10000 张钢表面缺陷图像、三种标签格式、划分脚本和训练教程打包在一起,目标很直接——让一个没接触过缺陷检测的人,也能在一到两天内把训练流程跑通。这套资料适合三类人:正在做质检自动化的算法工程师、刚入门目标检测的学习者,以及需要快速验证“YOLO 在钢铁产线上到底行不行”的团队。
2. VOC、COCO与YOLO三种标签格式:同一批钢表面缺陷,三种生存方式
2.1 VOC格式:XML文件里的语义锚点
这里不铺开讲竞赛历史,只说实操意义。VOC 格式的核心是“一张图对应一个 XML 文件”,比如 IMG_0001.jpg 旁边就放着 IMG_0001.xml。XML 里既记载了图像的宽高和通道数,也用一个个<object>块记录每个缺陷框的类别名和像素级坐标。钢表面缺陷里最常见的类别是划痕、麻点、氧化皮这类,它们在 XML 里的表现就是对象名加一个矩形框。
<annotation> <folder>JPEGImages</folder> <filename>IMG_0001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>scratch</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>290</xmax> <ymax>260</ymax> </bndbox> </object> </annotation>上面这段 XML 直观表达了一个事实:IMG_0001.jpg 里有一个类别为 scratch 的缺陷,框的左上角是 (120, 80),右下角是 (290, 260),全部按原图像素坐标记录,没有做任何归一化。VOC 格式最大的好处是肉眼可读、方便人查错。钢表面缺陷的标注错误通常来自类别名写错,或者同一道划痕被拆成两个框,这类问题用文本编辑器打开 XML 就能定位,不需要写任何解析代码。
但 VOC 格式也有明显的短板:主流训练框架很少直接吃 XML。YOLO 训练通常要求 txt 标注,MMDetection 这类框架又要预先把 VOC 转成 COCO 或中间格式。你拿到的这套压缩包把三种格式都给齐了,省掉的正是写解析器、踩格式坑的时间。实际项目里,我最常用的做法是拿 VOC 当“给人看的版本”,拿 YOLO/COCO 当“给框架吃的版本”。
2.2 COCO格式:一个JSON文件能不能解决所有问题
COCO 格式的核心特征是用一个大的 JSON 文件管理整批标注。它和 VOC 的差异在组织方式上:图像的路径、宽高、ID 和所有目标框分开存放在不同数组里,通过 id 互相引用。如果你后续打算用 Detectron2、MMDetection 这类框架,COCO 格式能让数据加载最顺滑。
{ "images": [ { "id": 1, "file_name": "IMG_0001.jpg", "width": 640, "height": 480 } ], "annotations": [ { "id": 1, "image_id": 1, "category_id": 1, "bbox": [120, 80, 170, 180], "area": 30600, "iscrowd": 0 } ], "categories": [ { "id": 1, "name": "scratch" } ] }注意 COCO 的 bbox 顺序是 [x, y, width, height],也就是左上角坐标加框宽高,和 VOC 的 [xmin, ymin, xmax, ymax] 不一样。这个差异是格式转换时最高频的出错点。上面例子里 bbox 是 [120, 80, 170, 180],意思是框左上角在 (120, 80),宽 170、高 180,右下角应该是 (290, 260),恰好和 2.1 节 VOC 例子里同一个目标对应。area 等于 170 乘 180,是直接给出的,不用自己再算一遍。
COCO 格式适合多类别、大规模、需要做实例分割或关键点检测的项目,因为 JSON 结构对不同任务更加统一。在钢表面缺陷场景里,它的实际价值是框架兼容性好、训练配置短。痛点也很明显:整个 JSON 文件一大坨,想打开看看某个框标得对不对非常麻烦。所以我一般把 COCO 当“给框架吃的格式”,把 VOC 当“给人看的格式”。
2.3 YOLO格式:直接进模型的最小单位
YOLO 系列的标签格式最简单,一张图对应一个同名 txt 文件,钢表面缺陷数据转换后看起来就是几行数字:
0 0.3203 0.3542 0.2656 0.3750拆开看:第一个数字 0 是类别 ID,范围是 0 到 nc-1;后面的 0.3203 是目标框中心点的 x 坐标除以图片宽度;0.3542 是中心点 y 除以图片高度;0.2656 是框宽除以图片宽度;0.3750 是框高除以图片高度。所有数值都被归一化到 0 到 1 之间,这正是 YOLO 格式的核心设计:输入分辨率无论是 640 还是 1280,标签都能直接复用,不同分辨率的图像之间迁移很干净。
对钢表面缺陷来说,有个细节值得注意:不少划痕是横跨整张图的长条目标,归一化后框的宽或高会接近 1,长宽比极端。YOLO 默认开启的 mosaic 增强会把四张图拼在一起,这类长条目标很容易被裁掉一半,导致模型学不到完整的划痕形态。遇到这种数据,我一般会先关闭 mosaic 跑一个短实验,对比开启前后验证集上的 recall,再决定要不要保留。
三种格式的关系并不互斥。实际使用中,VOC 用于标注审核,COCO 用于多框架训练,YOLO 用于直接进模型,这套组合拳在钢表面缺陷这种标注质量要求高、类别不平衡明显的任务里特别省事。
2.4 给不同用途人群的选择建议
不同岗位的人拿到三种格式,应该按自己的工作流决定优先用哪一个。下面是三种格式的直接对比:
| 格式 | 坐标体系 | 人眼可读性 | 主流框架支持 | 修改成本 |
|---|---|---|---|---|
| VOC | 绝对像素 | 高 | 低,需要转换 | 低 |
| COCO | 绝对像素 | 低 | 高 | 高 |
| YOLO | 相对坐标 | 中 | 极高 | 中 |
选型逻辑很直接:如果你只围绕 YOLO 打转,直接用 YOLO 格式,路径最短;如果你要在 YOLO 和 Faster R-CNN、DETR 等模型之间横向对比,COCO 格式最省事,因为主流检测框架原生就支持;如果你需要人工校验标签、回传标注结果,VOC 格式最友好。压缩包里三种格式都齐全,意味着你可以按项目阶段随时切换,而不是被某一种格式绑死。
3. 划分脚本上岗:把10000张图拆成 train/val/test 并且不漏一张标签
3.1 解压后的目录结构与命名约定
先厘清一件事:无论压缩包内部目录叫什么,第一步永远是确认文件和标签的命名是否一一对应。一个合格的钢表面缺陷数据集,通常会把图像和标签按功能分目录存放,常见布局如下:
steel/ JPEGImages/ # 10000张jpg Annotations/ # 与图像同名的voc xml labels/ # 与图像同名的yolo txt json/ # coco标注json split_data.py # 划分脚本 README.md # 训练教程说明钢表面缺陷图像常用命名如 IMG_0001.jpg,对应标签应为 IMG_0001.xml、IMG_0001.txt。如果前缀不一致、扩展名不同,或者某张图没有对应标签,后面训练时马上会产生一连串错误。拿到数据的第一天,先把文件对应关系验证一遍,比急着跑实验重要得多。
3.2 划分脚本的核心逻辑
为什么要用脚本切,而不是手动建三个文件夹?因为手动拖拽无法保证随机性、可复现性和类别分布一致性。10000 张图手动拖,既不现实,也容易把同一张钢板不同角度的相似图像分到训练和验证两个集合里,让最后测出来的 mAP 虚高。划分脚本的核心流程是三件事:读图列表、随机洗牌、按比例切割。
import os import random # 固定随机种子,保证每次运行划分结果一致 random.seed(42) IMAGE_DIR = "JPEGImages" TRAIN_RATIO, VAL_RATIO = 0.7, 0.2 images = [f for f in os.listdir(IMAGE_DIR) if f.endswith(".jpg")] random.shuffle(images) total = len(images) train_files = images[: int(total * TRAIN_RATIO)] val_files = images[int(total * TRAIN_RATIO): int(total * (TRAIN_RATIO + VAL_RATIO))] test_files = images[int(total * (TRAIN_RATIO + VAL_RATIO)):] print(f"train={len(train_files)}, val={len(val_files)}, test={len(test_files)}")脚本先收集所有 jpg 文件名,洗牌,再按 7:2:1 切分。随机种子 42 是习惯写法,你可以改成任何整数,重点是保证同一批数据每次重跑脚本得到完全一样的切分结果。训练集、验证集、测试集三份数据互不重叠,验证结果才真实可信。
但这段脚本只切了图片名,标签还没同步。训练时需要图像和同名标签在对应目录里都存在,所以划分时必须把同名标签一起处理:
import shutil for f in train_files: base = os.path.splitext(f)[0] # 图和三件套标签都要拷贝 shutil.copy(os.path.join("JPEGImages", f), "train/images/" + f) for ext in [".xml", ".txt"]: label = base + ext shutil.copy(os.path.join("Annotations", label) if ext == ".xml" else os.path.join("labels", label), "train/" + ("annotations/" if ext == ".xml" else "labels/") + label)这段代码对每张训练图,把 jpg、xml、txt 全部拷贝到新目录。这里我坚持用拷贝而不是移动,因为原始文件保留在原地,就等于给自己留了后悔药:万一划分比例不对或某类样本分布不均,重跑一次脚本就行,不用从压缩包重新解压。很多翻车事故的根源,正是划分时只动了图片没动标签,训练启动后报“label not found”,才发现前几个 epoch 白跑了。
3.3 划分时按类别平衡而不是盲目随机
钢表面缺陷的类别天然不平衡:6000 张划痕、500 张麻点是常态。如果随机洗牌后某一类恰好全落进验证集,模型训练时就没见过该类样本,mAP 自然上不去。更严谨的划分方式是让每个类别在训练集、验证集、测试集中都保持接近原始比例。这里给出一个简化版的分层划分思路:
from collections import defaultdict class_to_images = defaultdict(list) for f in images: base = os.path.splitext(f)[0] label_path = os.path.join("labels", base + ".txt") with open(label_path) as fp: first_line = fp.readline() if not first_line.strip(): continue class_id = first_line.split()[0] # YOLO标签每行第一个数字是类别ID class_to_images[class_id].append(f)上面的脚本用每张图第一个框的类别做分组依据,实现简单但对多缺陷图不够精确。更好的方式是把一张图里所有出现的类别都统计出来,按“包含该类别即归入该组”的思路做分层,保证每一个类别在三个集合里的比例都尽量一致。钢表面缺陷类别少,但样本量偏差大,这一步不能省。
3.4 VOC转YOLO的转换脚本
如果后续你自己标了一批新缺陷,或者服务商只返回 VOC 标注,想把新数据并进这套流程继续用 YOLO 训练,就必须写转换脚本。核心是坐标换算公式:中心点 x 等于左右坐标之和除以 2 再除以图宽,宽等于右减左除以图宽,y 和 h 同理。
import xml.etree.ElementTree as ET class_list = ["crazing", "inclusion", "patches", "pitting", "scratches", "rolled-in_scale"] def voc_to_yolo(xml_path, class_list): tree = ET.parse(xml_path) root = tree.getroot() img_w = float(root.find("size/width").text) img_h = float(root.find("size/height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_list: continue xmin = float(obj.find("bndbox/xmin").text) ymin = float(obj.find("bndbox/ymin").text) xmax = float(obj.find("bndbox/xmax").text) ymax = float(obj.find("bndbox/ymax").text) x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{class_list.index(name)} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return "\n".join(lines)这段代码里有两个边界场景容易踩坑:一是标注工具取整可能导致 xmax 比图宽大 1 到 2 个像素,归一化后 w 变成 1.001,超出 0 到 1 范围;二是鼠标手抖可能画出一个零面积框,w 或 h 等于 0,训练时会让 loss 变成 nan。稳妥的做法是等全部转换完成后,写一个检查函数扫一遍 txt,丢弃超界和零面积的标注。
注意:跑完 voc_to_yolo 之后,务必检查所有生成的 txt,确认每行坐标都在 0 到 1 之间。钢表面缺陷边界模糊,标注手抖的概率比普通物体检测高出不少。
4. 训练教程:从环境搭建到跑通第一个 epoch
4.1 环境准备与预训练模型下载
训练 YOLO 系列模型,最常见的路径是 PyTorch 加 ultralytics 框架,或者直接用 YOLOv5 官方仓库。这里以最常用的 YOLOv8 为例,环境准备只需要两条 pip 命令:
pip install torch torchvision pip install ultralyticstorch 版本要和本机 CUDA 驱动匹配,安装前用 nvidia-smi 看一眼驱动版本,再决定装 cu118 还是 cu121 对应版本。第一次执行训练命令时,框架会自动拉取 yolo 预训练模型下载权重,如果下载缓慢,就手动把 yolov8s.pt 这类文件放到项目根目录。预训练模型的作用不言而喻:钢表面缺陷图像纹理特殊,但从 COCO 上预训练的权重出发做微调,比随机初始化收敛快得多,也稳定得多。
4.2 数据集配置文件
数据划分好之后,需要写一个 yaml 文件告诉框架数据在哪、类别有多少。以 YOLOv8 为例,在项目目录下新建 steel.yaml:
train: /data/steel/images/train val: /data/steel/images/val test: /data/steel/images/test nc: 6 names: ['crazing', 'inclusion', 'patches', 'pitting', 'scratches', 'rolled-in_scale']train、val、test 指向的是 images 目录,YOLO 系列框架默认去相邻的 labels 目录找同名 txt。如果你的 labels 和 images 不在同一父目录下,必须手动调整目录布局。nc 的值必须和 names 列表长度一致,这条看似废话,却是高频错误:有人改了 nc 忘记改 names,或者 names 顺序和标签文件里的类别 ID 对不上,训练出的模型就会把划痕识别成麻点,日志里还看不出来。
4.3 模型选择与损失函数的注意点
钢表面缺陷检测的场景特点是小目标占比高、背景纹理复杂、光照不均。模型选择的第一原则是不要盲目上最大模型。我的经验是用 s 或 m 起步,先看基线效果再说。
| 模型 | 参数量 | 推理速度 | 建议场景 |
|---|---|---|---|
| YOLOv8n | 3.2M | 极快 | 边缘设备快速验证 |
| YOLOv8s | 11.2M | 快 | 产线在线质检首选 |
| YOLOv8m | 25.9M | 中 | 追求更高 mAP,GPU 充足 |
| YOLOv8l/x | 43.7M/68.2M | 慢 | 离线实验,不建议直接上产线 |
关于 yolo 损失函数,框架内部已经处理了分类、定位、置信度三部分损失的平衡,你只需要知道 box 权重和 cls 权重会影响收敛倾向。在钢表面缺陷这种误检代价高的场景,把 box 权重适当拉高,让定位更精确,能减少“框了大半个背景”的情况。不建议一上来就大改损失权重,先把默认参数跑通,再根据验证集行为去调。
4.4 启动训练命令与关键超参数
数据集配置好后,启动训练的命令不复杂:
yolo train model=yolov8s.pt data=steel.yaml epochs=120 batch=16 imgsz=640 device=0 project=runs name=steel_defect参数含义按重要程度排:model 指定预训练权重,同时决定了模型结构;data 指向刚才写的 yaml;epochs 设 120 是经验起点,10000 张图通常 120 epoch 足够收敛;batch 受显存限制,16 是 24G 显存下的常用值,8G 显存先从 batch=8 起步;imgsz 是输入尺寸,如果原始图里缺陷很小,提到 960 能提高召回,但显存和训练时间都会明显上升。
训练启动后,前 10 个 epoch 的 loss 曲线是关键参考。如果 loss 先猛涨再一路下降,多半是学习率偏大,把 lr0 从默认值调低一个数量级再试。训练结束后权重保存在 runs/steel_defect/weights/ 下,有 best.pt 和 last.pt 两种文件。best 是根据验证集 mAP50 最好的一轮保存的,后续测试和生产部署都优先用 best.pt。
提示:训练过程中如果显存溢出,不要只想着调小 batch,先检查 imgsz 是不是设得过大。imgsz 降 32 的倍数,往往比牺牲 batch 更划算。
4.5 训练过程中的监控维度
训练跑到一半,怎么判断正常?看三个东西:loss 曲线是否持续下降且没有明显反弹,验证集 mAP50 是否逐步上升后收敛,训练 loss 和验证 mAP 是否背离。训练 loss 在降而验证 mAP 不涨,就是过拟合信号。YOLO 命令行日志里会输出 P、R、mAP50、mAP50-95 四列,前三个优先看。
钢表面缺陷因为类别不平衡,只看整体 mAP50 不够,要看每一类的 recall。训练结束后的验证阶段,框架会输出每个类别的 AP,如果某一类 AP 明显低于其他类,根源多半在数据层面:样本太少、标注边界不一致、或者同类缺陷形态差异过大。此时回到数据增强和类别平衡上补课,比继续堆 epoch 有效。
5. 钢表面缺陷训练常见问题避坑记录
5.1 划分脚本漏拷标签,训练启动就报错
现象:把划分后的 train 目录喂给 YOLO,启动训练时提示找不到标签文件,或者出现大量 “label not found” warning,训练直接卡住。
原因:划分脚本只复制了 jpg,没有同步复制同名 txt;或者 txt 与图片命名前缀不一致,比如图名是 0001_clean.jpg,标签却叫 0001.txt,无法配对。
解决:写一个核对函数,遍历 train 目录下所有图像,检查同名 txt 是否存在:
missing = [] for img in os.listdir("train/images"): base = os.path.splitext(img)[0] if not os.path.exists(os.path.join("train/labels", base + ".txt")): missing.append(img) print("missing labels:", len(missing), missing[:5])这类问题越早发现越好,不要在跑完十几个 epoch 之后才回头查数据加载。
5.2 BN 崩溃导致 loss 变成 nan
现象:训练到某个 epoch 后,训练 loss 突然变成 nan,GPU 显存占用没有降。重启训练前几十个 batch 正常,过一会儿又崩。
原因:BN 层在统计 batch 内均值方差时遇到异常数值,常见诱因是高学习率、标签归一化坐标超出 0 到 1、或者存在零面积框。yolo 训练中 bn 崩溃是高频问题,和模型结构无关,基本是数据或超参的锅。
解决:先检查 txt 里坐标是否越界;再调低 lr0,从 0.01 降到 0.001;如果训练到一半崩溃且权重已保存,可以从崩溃前的 last.pt 恢复训练。顺序是数据优先、超参其次,不要一上来就换模型结构。
5.3 混淆矩阵总合不唯一,不必恐慌
现象:训练结束后,混淆矩阵表格里行和与列和不相等,无法与测试集图片数严格对应,有人怀疑标签错乱。
原因:目标检测混淆矩阵统计的是预测框和真实目标在 IoU 阈值下的配对结果。有的真实目标没有匹配到任何预测框,有的预测框被 NMS 过滤掉,行列自然不守恒。混淆矩阵总合不唯一是目标检测的先天特性,不是 bug。
解决:不要纠结总加和,重点看对角线比例,以及 background_fp 行。如果背景被大量判成某一类,说明该类缺陷和钢板背景纹理太像,需要回到数据增强或标注层面处理。
5.4 钢表面反光导致召回率低
现象:训练 loss 收敛漂亮,验证 mAP 不低,但现场测试时灰度纹理被误判成划痕,实际缺陷召回率低于 80%。
原因:钢表面的辊纹、光照反射本身长得就像缺陷,只靠常规增强难以让模型学会区分。产线上拍到的图和数据集里的图,光照条件往往差异很大。
解决:训练时增加灰度扰动、对比度、直方图均衡等增强,让模型不过度依赖原始灰度分布。同时注意,钢表面缺陷的主方向通常沿轧制方向,大幅旋转增强会破坏真实物理分布,旋转角度尽量控制在 15 度以内。
5.5 缺陷样本不平衡导致个别类 AP 始终上不去
现象:10000 张图里划痕类占七成,麻点只有 500 张。训练完划痕 AP 到达 0.85,麻点还在 0.3。
原因:少数类参与梯度计算的次数太少,mosaic 增强虽然增加了样本多样性,但对极少数类依然不够。
解决:从数据层面做三件事:按类别限制采样或复制少数类图片;用 offline 增强扩充少数类,比如小尺度平移、加高斯噪声模拟钢板背景;调整分类损失权重,让少数类在 loss 里占更高比例。同时划分数据集时也要保证验证集里留足少数类样本。
6. 验证与进阶:从 mAP 数字到产线可用性
6.1 用测试集做批量预测,先看框再看指标
训练结束不代表交付。拿从未参与训练的 test 目录跑一次批量推理:
yolo predict model=runs/steel_defect/weights/best.pt source=/data/steel/images/test save_txt=True save_conf=True输出结果是每个测试图对应的同名 txt,每行记录类别、归一化坐标和置信度。和 ground truth 叠起来看,重点不是 mAP 多少,而是误报和漏报集中在哪些类别。钢表面产线一天拍上千张图,5% 的误报率意味着每天几十次假警报,现场工人迟早关掉这套系统。这时把置信度阈值从 0.25 提到 0.4 或 0.5,再评估一次,选业务能接受的那档。
6.2 混淆矩阵的读法
打开训练输出的 confusion_matrix.png,不要只扫对角线。我按三步读:先看 background_fp 行,有没有背景被误判成某类缺陷;再看哪两类之间互相混淆,说明两类缺陷外形相近,标注边界可能需要重新统一;最后看对角线偏暗的类别,回到数据增强和样本量上去补。钢表面缺陷的误检往往集中在背景纹理类,这一步能帮你决定是调阈值还是回头补数据。
6.3 导出 ONNX 或 TensorRT 再上产线
验证没问题后,导出部署格式:
yolo export model=runs/steel_defect/weights/best.pt format=onnx imgsz=640导出后用同一张测试图对比原始 PyTorch 输出,框位置和置信度不应有明显变化。如果 ONNX 出现偏移,先检查导出时 imgsz 是否和训练一致,再把动态维度固定为 batch=1。TensorRT engine 按实际部署显卡生成,换卡后重新导出。
做钢表面缺陷检测这几年,我最大的体会是:最该花时间的其实是三种标签格式的一致性和划分脚本的正确性,模型训练反而是链路里最省心的一环。标签乱了,后面所有实验结论都不可信。希望这些落地细节能帮到你,少走几步弯路。
本文还有配套的精品资源,点击获取