
简介面向工业质检场景的YOLOv5钢材表面缺陷检测资源包适合需要开展目标检测训练或部署的开发者与研究人员。压缩包共2000个文件以1971张jpg图像、1954个txt标签、1800个xml标注及配套yaml配置为主同时包含pt权重、Python训练脚本、网络结构图和工程配置可用于复现缺陷检测流程并在此基础上二次开发。整体约501.32MB数据与代码组织较为完整。已有2503人学习下载。资源覆盖常见缺陷类型与多尺度检测方法附带了模型训练、测试脚本及推理示例能帮助理解数据标注格式、YOLOv5模型构建与调参思路适合作为钢铁表面质检项目的起步模板。1. 钢材表面缺陷与YOLOv5为什么目标检测比传统视觉更适合这类活工业质检里钢材表面缺陷检测是个老问题真正让人头疼的并不是“能不能检出”而是“能不能在产线光照变化、表面纹理干扰、缺陷尺度极小的情况下稳定检出”。肉眼盯久了会疲劳传统CV靠阈值加形态学处理只能锁死单一产线环境换一条产线、换一批钢种就得重新调参。YOLOv5之所以被大量用在钢材表面缺陷数据集上核心原因是它把目标定位和分类做到同一个网络里推理速度快还能用迁移学习快速收敛。这篇就围绕“yolov5钢材表面缺陷数据集”这套资源从数据构成、标注格式转换、训练参数调优到踩坑记录完整拆一遍。2. 钢材表面缺陷数据集六个类别分布与两张容易被忽略的属性表2.1 六大缺陷类别的形态差异直接决定检测难度排序钢材表面缺陷数据集里的图像通常来自工业相机采集的热轧带钢表面原始图像多为灰度图分辨率不高但每类缺陷的形态差异非常明显。常见的公开版本包含六个类别裂纹、夹杂物、斑块、麻点、氧化铁皮、划痕。每一类在视觉上的表现完全不同麻点是一簇密集的小暗点尺度小、边缘模糊和表面纹理容易混淆划痕是细长的线性结构方向随机氧化铁皮是块状覆盖物边缘不规则且对比度低夹杂物和斑块相对面积更大但灰度分布不均匀常常带有阴影。这六个类别的检测难度并不均等。按我自己的实测经验小目标类的麻点和氧化铁皮最难因为YOLOv5在640×640输入下要把原始图放大小目标的像素占比被进一步压缩而划痕这类线性目标虽然面积不小但长宽比极端普通anchor匹配容易漏检。所以拿到数据集后第一件事不是直接跑训练而是先做一次类别分布统计和几个困难样本的可视化确定哪些类别需要特殊对待。2.2 目录结构与标注格式开局先摸底别急着解压训练下载后先看目录结构常见组织形式有两种一种是images和annotations平级标注用VOC XML格式另一种是已经处理好的YOLO格式images和labels平级。我一般会先执行一段命令把结构摸清find . -maxdepth 2 -type d | sort find . -name *.xml | wc -l find . -name *.txt 2/dev/null | head -n 5 head -n 3 annotations/0001.xml # 先看一条标注长什么样逻辑很简单先看有没有XML、有没有TXT再判断是否需要格式转换。如果同时存在两种标注以源数据集自带的XML为准因为XML里保留了原始坐标信息转成YOLO格式时不容易丢字段。这里有两个属性特别容易忽略。第一图像是灰度图还是三通道图。YOLOv5在模型定义里默认接收三通道输入如果读取时没有正确转成三通道后面会报shape不匹配或训练直接崩。第二原始标注的坐标系。VOC XML的xmin、ymin是像素坐标YOLO格式要求归一化的中心点坐标和宽高换算公式是固定的但浮点精度保留几位直接影响小目标的IoU匹配我习惯保留6位。这两张“属性表”虽然不起眼但格式转换脚本里90%的bug都出在这两个地方。接下来就进入转换环节。3. 把原始标注转成YOLO格式一次转换脚本与四个边界条件3.1 VOC XML转YOLO TXT批量转换脚本与输出校验如果数据集是VOC XML标注需要先转成YOLOv5认的TXT格式。每个XML对应一个TXT文件每行一条目标格式为类别ID、归一化的中心点x、中心点y、宽度w、高度h。下面这个脚本我一般会改改直接用import os import cv2 import xml.etree.ElementTree as ET CLASS_NAMES [Cr, In, Pa, PS, RS, Sc] # 按数据集给出的类别顺序填写 def voc_to_yolo(xml_path, out_dir, img_dir): tree ET.parse(xml_path) root tree.getroot() name os.path.splitext(os.path.basename(xml_path))[0] img_path os.path.join(img_dir, name .jpg) img cv2.imread(img_path, cv2.IMREAD_COLOR) # 强制三通道读取 h, w img.shape[:2] lines [] for obj in root.iter(object): cls obj.find(name).text.strip() if cls not in CLASS_NAMES: print(f{xml_path} 中出现未定义类别 {cls}跳过) continue box obj.find(bndbox) x1 max(0, float(box.find(xmin).text)) y1 max(0, float(box.find(ymin).text)) x2 min(w, float(box.find(xmax).text)) y2 min(h, float(box.find(ymax).text)) if x2 x1 or y2 y1: print(f{name} 中有一个空框已忽略) continue xc (x1 x2) / 2.0 / w yc (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h label CLASS_NAMES.index(cls) lines.append(f{label} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, name .txt), w) as f: f.write(\n.join(lines))这段脚本的逻辑是先把图像读进来取宽高因为标注坐标是绝对像素值再遍历XML里的每个object取出类别名和bndbox四点坐标然后用归一化公式把矩形框转成[x_center, y_center, width, height]最后写入TXT。参数说明里最关键的是cv2.imread的第二个参数我写了cv2.IMREAD_COLOR这样即使是灰度图也会以三通道的形式读进来h和w的取值不会因为通道数引起后面处理的麻烦。四个边界条件必须在脚本里处理坐标越界要裁剪到图像范围内框的宽或高小于等于0要丢弃类别名在类别列表里找不到要跳过而不是直接索引报错灰度图读取时不能依赖默认参数必须显式指定读成三通道。第一批转换完成后我建议随机抽5个TXT文件和原XML对比把类别ID、中心点坐标、宽高在纸上算一遍确认无误再进训练这一步能省掉后面大量排查时间。3.2 训练集与验证集划分按类别做分层切分数据集划分不能直接随机打乱因为六类缺陷数量可能不均衡随机切分很容易让某个小类别在整个验证集里一个样本都不出现这会让你无法评估该类的真实召回率。我一般会用sklearn的train_test_split按类别做分层切分import os import random from sklearn.model_selection import train_test_split label_dir labels image_dir images all_files [f.replace(.txt, ) for f in os.listdir(label_dir) if f.endswith(.txt)] # 给每个样本打上类别标签用于分层 def sample_classes(fname): with open(os.path.join(label_dir, fname .txt)) as f: return sorted(set(line.split()[0] for line in f)) strata {f: -.join(sample_classes(f)) for f in all_files} train_files, val_files train_test_split( list(strata.keys()), test_size0.2, random_state42, stratifylist(strata.values()) ) for split, files in [(train, train_files), (val, val_files)]: os.makedirs(fimages/{split}, exist_okTrue) os.makedirs(flabels/{split}, exist_okTrue) for f in files: os.rename(fimages/{f}.jpg, fimages/{split}/{f}.jpg) os.rename(flabels/{f}.txt, flabels/{split}/{f}.txt)这段代码先用每个样本的类别组合作为分层标签stratify参数会尽量让不同类别在训练集和验证集中保持相同比例。这样划分出来的验证集更能反映模型在真实样本分布上的表现。随机种子固定为42方便复现结果。如果发现某个类别样本量确实太少可以只对大类做分层切分小类单独留出固定几件作为验证避免验证集中小类样本为零。3.3 类别清单文件与数据配置文件让YOLOv5认账数据准备好后还需要一个data.yaml和classes.txt否则训练时YOLOv5不知道该预测几个类别。配置文件里有几个字段必须写对path指向数据集根目录、train和val写相对路径、nc是类别总数、names是类别名列表。我经常看到有人把names顺序写错导致训练出来的模型预测类别名完全对不上这个问题后面会专门讲。4. 训练配置YOLOv5默认参数在钢材缺陷数据上的调优清单4.1 数据配置与基础训练命令YOLOv5训练入口是train.py命令行参数多到记不全但钢材表面缺陷这个场景下真正需要动的参数不多。先看一个我常用的标准命令python train.py \ --data steel.yaml \ --weights yolov5s.pt \ --epochs 100 \ --batch-size 16 \ --imgsz 640 \ --multi-scale \ --cache ram \ --workers 4 \ --project runs/steel \ --name defects_v1这段命令的逻辑是用yolov5s.pt作为预训练权重加载在COCO上已经学过的通用视觉特征然后迁移到钢材缺陷检测任务imgsz设为640虽然原始图只有200×200左右但YOLOv5会把图缩放到640相当于做了上采样对小目标检测有一定帮助--multi-scale会在训练过程中每10轮随机选择不同输入尺寸增加模型对不同尺寸缺陷的适应性--cache ram把图像缓存到内存减少每轮读取磁盘的IO开销。参数说明里epochs不用一上来就拉到300先跑100轮看趋势如果100轮后mAP还在持续上升再追加。batch-size取决于显卡显存16是常见选择如果显存只有8G降到8即可。workers建议4到8太小会让显卡等数据太大可能占用系统内存。--weights yolov5s.pt也可以用yolov5m.pt或yolov5l.pt但钢材缺陷的原始分辨率低轻量模型通常够用重模型反而容易过拟合。4.2 超参调整从哪里下手最有效YOLOv5默认的hyp.scratch.yaml在通用目标检测上表现不错但钢材缺陷有几个特殊性小目标多、图像对比度低、缺陷灰度接近背景。我一般会复制一个超参文件只改三个地方hsv_h、hsv_s、hsv_v这三个控制色彩增强强度。钢材表面是灰度图色彩增强几乎没意义反而可能把缺陷的灰度对比度破坏掉我一般直接将三个值调小或置零。还有一个参数值得调anchor_t默认是4.0表示anchor和真实框的宽高比超过4就认为不匹配。钢材缺陷中划痕类长宽比极端宽高比可能超过6如果不调anchor_t这类缺陷在训练初期容易被忽略。常见做法是先跑完第一轮打开runs/steel/defects_v1/里的labels_correlogram.jpg和anchors.png看anchor匹配情况再决定是否调整。我一般会把anchor_t放宽到5.0配合--multi-scale一起用划痕类召回率能明显提升。4.3 验证命令与性能指标怎么读训练完成后别只看results.png里的曲线还要单独跑一次验证python val.py \ --data steel.yaml \ --weights runs/steel/defects_v1/weights/best.pt \ --imgsz 640 \ --confusion \ --save-json--confusion会生成混淆矩阵图这个图比mAP数字更能暴露问题哪个类别之间互相混淆、哪个类别漏检最多一目了然。--save-json会把COCO格式的检测结果导出方便下游分析。看验证结果时mAP0.5是主要参考但钢材缺陷场景下要更关注每个类别的Recall因为生产线上漏检的代价远远大于误检。5. 避坑指南灰度通道、极端长宽比与类别不均衡的五个真实翻车现场5.1 数据侧的三个坑通道、标签和路径坑一训练报错shape mismatch或invalid argument。现象是模型训练到一半直接崩报错里提到图像通道数不等于3。原因大概率是原始图像是灰度单通道读取时被直接当成了二维数组。解决方法是读取cv2.imread(path, cv2.IMREAD_COLOR)或者在数据加载的自定义Dataset里加一步np.stack([img] * 3, axis-1)如果用的是YOLOv5自带加载器也可以在datasets.py里统一转三通道。坑二转换出的TXT里类别ID全对不上。现象是训练loss正常下降但预测时把划痕标成麻点。原因常常是data.yaml里的names列表顺序和转换脚本里的CLASS_NAMES顺序不一致。解决方法是写一个校验脚本读一张图、读标签、画框同时打印类别名肉眼确认一次。坑三验证集很好但推理很差。现象是val mAP很高换到真实产线拍回来的图片就几乎检不出。原因多半是验证集和训练集来自同一个数据集分布太接近另一个可能是验证集划分时没有按类别分层导致某些困难类别没进验证集。解决方法是重新按3.2节的分层划分方式切数据并额外留出一批真实场景图片做盲测。5.2 训练侧的坑NMS抑制、多卡与过度增强坑四小目标漏检严重尤其是麻点和氧化铁皮。现象是recall上不去偶尔有预测框但置信度很低。原因有两个方面一是原始分辨率低小目标本身像素少二是NMS默认的IoU阈值把相邻的多个低置信度框全部抑制掉。解决方法是推理时把--iou-thres从0.45调到0.3或0.35让相近的框更不容易被合并在训练侧可以考虑把--batch-size调大稳定梯度再配合--multi-scale增加小目标的多尺度样本。坑五多卡训练结果比单卡还差。现象是明明用了4张卡mAP反而比单卡低好几个点。原因通常是--batch-size设置的是总batch实际每张卡的batch等于总batch除以卡数每卡batch太小导致BN统计不稳定。解决方法是显式设置--batch-size 64让每张卡拿到16或者启用--sync-bn来同步BN统计量数据量不大时直接单卡训练更省心。6. 工程落地用混淆矩阵与批量推理做一次真实可用的验收训练完、验证完模型离“能上线”还差最后一步拿一批没参与过训练的真实样本做批量推理并且用混淆矩阵判断错在哪里。我习惯先把验证集里的每张图都跑一遍推理再把预测结果和真实标签合并画混淆矩阵。具体命令是python detect.py \ --source ./samples \ --weights runs/steel/defects_v1/weights/best.pt \ --imgsz 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --save-conf--save-txt会输出每个检测框的类别、置信度和归一化坐标--save-conf把置信度也写进TXT这样后续做数据统计时不用再解析图片。跑完后打开runs/detect/exp/里的标注图重点看两类错误一是真实缺陷没有框二是同一缺陷被打了多个不同类别的框。前者说明模型对该类特征学得不够后者说明类别特征重叠需要回到训练集补充难例。还有一个容易被忽视的验证点把推理结果按类别统计置信度分布。如果某个类别所有预测的置信度都集中在0.3以下说明模型对这个类只是“勉强认识”上线后会漏检。我会把这类预测单独导出挑出置信度最低的50张图看看是不是标注框本身偏小、对比度太低或者是标注类别本身有歧义。针对这些样本做一次手动清洗再微调一轮通常能救回来不少。从那以后我每次换新数据集跑YOLOv5都会强制走一遍灰度通道检查、标签坐标抽检和混淆矩阵分析这三件事。这三个动作帮我筛掉了绝大多数训练翻车现场也让模型在真正上线前就心里有底。希望帮到你。本文还有配套的精品资源点击获取