简介:面向钢材表面缺陷检测与YOLOv5目标检测的开发者,这份源码包集成了完整的数据集与检测系统,适用于工业质检场景下的缺陷识别、模型训练与算法验证,也适合深度学习初学者通过真实数据动手实践。压缩包共5528个文件,约28.28MB,内含1800张钢材表面jpg图像、1800份xml标注文件与1806个txt标签文件,完整覆盖图像、标注与类别信息;同时提供33个Python脚本、26个YAML参数配置以及CMake等工程构建文件,可直接用于YOLOv5环境搭建与训练调参。目前已有2514人学习浏览,说明该资源具备一定参考热度。拿到后可获得端到端的数据处理与训练方案,包括标签含义说明、模型配置文件、辅助脚本与跨平台编译支持;目录结构按图像、标注、配置与代码分层存放,便于检索,省去从零采集和整理数据的耗时,可快速开展缺陷检测实验或二次开发。
1. YOLOv5钢材表面缺陷数据集检测系统源码:先回答它解决什么问题,再谈怎么跑
钢材表面缺陷检测,就是在产线上找出裂纹、夹杂、麻点、划伤这类瑕疵,并把它们在图像里框出来。YOLOv5钢材表面缺陷数据集检测系统源码.zip 这类压缩包,通常把 YOLOv5 模型、已标注的缺陷数据集、训练和推理脚本放在一起,目标是让你不用从零搭环境,直接用上真实工业数据。这个方向适合三类人:刚入门 YOLOv5 想拿真实数据集练手的,做机器视觉需要快速验证缺陷识别方案的,还有准备毕业设计的学生。但“源码.zip”和“能跑起来的工程”之间,往往还隔着数据格式、环境版本和训练参数三座大山。
2. 钢材表面缺陷数据集怎么转成 YOLO 格式:XML 解析、归一化标签与划分边界
2.1 长条热轧钢板上的六类缺陷,公开数据集怎么选
做钢材缺陷识别,最常被拿来当基线的是东北大学发布的 NEU-DET,也就是在标题里说“钢材表面缺陷数据集”时,九成资料指的是它。这组数据来自热轧带钢表面,图像尺寸固定为 200x200,共 1800 张,每类缺陷 300 张。六类分别是 crazing(网状裂纹)、inclusion(夹杂)、patches(斑块)、pitted_surface(麻点)、rolled-in_scale(氧化铁皮压入)、scratches(划伤)。类别看着少,但每类外观差异非常大:crazing 是分散的细线,pitted_surface 是密集小坑,rolled-in_scale 是大块暗色斑块。同一个类别里缺陷大小跨度也大,有的框只有几十个像素宽,有的几乎占满整张图。
这组数据的特点是“数量不大、目标偏小、背景纹理杂乱”。如果直接套通用目标检测的思路,很容易遇到训练正常、到了产线场景就漏检的情况。最常用的做法是把 200x200 原图放大到 512 或 640 再喂给模型。放大到 640 意味着缺陷相对尺寸变大,检测更友好,代价是放大带来的插值锯齿和显存占用增加;放大到 512 相对折中,我在自己的实验里先跑 512,因为工业现场往往还要叠加低分辨率相机,512 更接近真实退化程度。
另外,NEU-DET 的标注是 VOC 风格 XML,每个 XML 文件对应一张图像。YOLOv5 训练需要的是每张图一个同名 txt 文件,每个框占一行,格式为“类别索引 x_center y_center width height”,四个坐标全部除以图像宽高,归一化到 0 和 1 之间。如果是从零开始标注自己的钢材数据,也需要转成这种格式。很多新人会把 xmin、ymin、xmax、ymax 直接写进 txt,YOLO 读不了,这是常见的第一道坎。
2.2 编写 XML 转 YOLO 脚本,处理空框与越界框
常见做法是写一个一次性转换脚本,扫描 XML 目录,逐个把 bndbox 四坐标换算成中心点坐标和宽高。注意必须用归一化坐标,否则训练读出来是超过 1 的数,loss 直接飙升,而且看起来像学习率问题,实际是标注问题。
# xml2yolo.py import os import xml.etree.ElementTree as ET CLASSES = ['crazing', 'inclusion', 'patches', 'pitted_surface', 'rolled-in_scale', 'scratches'] IMG_W, IMG_H = 200, 200 def convert_xml_to_yolo(xml_path, txt_path): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.find('name').text.strip() if name not in CLASSES: continue cls_id = CLASSES.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) # 防御负坐标和超界框 xmin = max(0.0, min(xmin, IMG_W - 1)) xmax = max(0.0, min(xmax, IMG_W - 1)) ymin = max(0.0, min(ymin, IMG_H - 1)) ymax = max(0.0, min(ymax, IMG_H - 1)) x_center = (xmin + xmax) / 2.0 / IMG_W y_center = (ymin + ymax) / 2.0 / IMG_H w = (xmax - xmin) / IMG_W h = (ymax - ymin) / IMG_H lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) if __name__ == '__main__': xml_dir = 'NEU-DET/ANNOTATIONS' label_dir = 'NEU-DET/labels' os.makedirs(label_dir, exist_ok=True) for xml_name in os.listdir(xml_dir): if not xml_name.endswith('.xml'): continue stem = os.path.splitext(xml_name)[0] convert_xml_to_yolo(os.path.join(xml_dir, xml_name), os.path.join(label_dir, stem + '.txt'))参数说明:CLASSES 的顺序就是类别索引顺序。YOLO txt 里的数字 0 对应 CLASSES[0],也就是 crazing,这个顺序必须和后面 data.yaml 里的 names 完全一致,否则训练时类别标签是错位的。IMG_W 和 IMG_H 这里写死成 200,因为 NEU-DET 固定尺寸;如果是自己的产线数据集,改成实际图像宽高。裁剪那四行是加的一段防御,VOC XML 偶尔会出现 xmax 略大于图宽的情况,不裁剪会让归一化坐标超过 1,YOLO 判定为无效目标,还会污染输出。
转换完抽查一下。我一般会随机挑三五张图,把同名的 txt 打印到控制台,人工对一下数字是否落在 0 到 1 区间,再用 OpenCV 画一次框确认。很多问题都出在序号对应错位上,单独看 txt 看不出,画出来就露馅。
2.3 训练集、验证集的划分,直接决定 mAP 可信度
数据划分看起来是小事,在钢材缺陷上需要注意的地方不少。第一种划分是随机划分,1800 张图直接按 8:1:1 拆成 train、val、test。缺点:如果同一卷钢带截出的连续图像同时被分进训练和验证,模型因为“看见过邻居”而得分虚高,实测 mAP 可能比换带生产时高出好几个点。
第二种更严谨的是按批次划分。按钢带编号分组后再切分 train 和 val,让验证集里不出现同一卷钢带下的相邻图像。钢材表面纹理连续性很强,我推荐按批次划分,这样验证结果更接近真实产线,不会出现评估很漂亮、上机就翻车的尴尬。
划分时还需要保证每个类别在 train 和 val 里都出现。NEU-DET 各类别均匀,问题不大,但自己去现场标数据时,可能有 patched 类只有几十张,随机划分就要做分层抽样。划分脚本参考下面这段:
# split_data.py import os import random from shutil import copy images = sorted(os.listdir('NEU-DET/IMAGES')) random.seed(2024) random.shuffle(images) n = len(images) train_imgs = images[:int(n * 0.8)] val_imgs = images[int(n * 0.8):int(n * 0.9)] test_imgs = images[int(n * 0.9):] for split, imgs in [('train', train_imgs), ('val', val_imgs), ('test', test_imgs)]: for img in imgs: stem = os.path.splitext(img)[0] copy(os.path.join('NEU-DET/IMAGES', img), os.path.join('NEU-DET/images', split, img)) copy(os.path.join('NEU-DET/labels', stem + '.txt'), os.path.join('NEU-DET/labels', split, stem + '.txt'))这段代码会在 images 和 labels 两侧同时把同一份文件分配到对应 split 目录,避免出现“图像有、标签没有”的漏配。如果你不复制,而是用软链接,也必须在两个目录同时建链接。YOLOv5 读 label 是拿图像路径替换后缀、替换目录名来定位 txt 的,一个缺失就会在训练时提示找不到标签文件,连带影响整个训练集统计。
划分完之后,再看一眼三个 split 目录里的文件数量,train 约 1440 张,val 约 180 张,test 约 180 张。数量对不对不重要,重要的是没有空目录,也没有某类只在 train 里出现的情况。
3. YOLOv5 环境配置与源码目录关系:conda 安装、requirements 顺序、data.yaml 别写错
3.1 用 conda 建独立 Python 环境,PyTorch 和 torchvision 必须成对出现
拿到这类源码,第一件事不是急着开 train.py,而是把 Python 环境搭干净。我习惯用 conda,因为它能隔离同一台机器上不同项目的依赖,避免“装好新项目,炸掉老项目”的常见事故。
conda create -n yolo5 python=3.9 -y conda activate yolo5 pip install torch==1.13.1 torchvision==0.14.1 cd yolov5 pip install -r requirements.txtPython 3.9 对 YOLOv5 比较友好,太高或太低都可能遇到 numpy 和 opencv 的二进制兼容问题。torch 和 torchvision 的版本必须一前一后对齐,torch1.13.1 对应 torchvision0.14.1,这是 PyTorch 官方约定。错开一位,就容易出现cannot import name 'functional_pauli' from 'torchvision.transforms'这类表面上跟算子无关、实际上版本错乱的报错,而且往往在跑数据增强那段才崩,排查起来特别费时间。
如果本机已经装好 CUDA 11.7,也可以用pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117指定带后缀的版本。没有特殊需求时直接用默认版本即可。装完验一下:
python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__)" python -c "print(torch.cuda.is_available())"第一行打印的版本要匹配,第二行返回 True 再继续,否则后面训练会在设备分配时报错。
3.2 requirements 安装顺序:为什么先装 torch 再装其它依赖
YOLOv5 的 requirements.txt 会列一堆包:opencv-python、numpy、matplotlib、seaborn、pandas、pyyaml、tqdm、requests、scipy 等。直接pip install -r requirements.txt在多数情况下也能成功,但遇到问题时会非常难定位。先装 PyTorch 再装其余依赖,能让 torchvision 里绑定的 numpy 扩展按正确版本生成。如果先装好 numpy,再装 torch 把 numpy 版本顶掉,OpenCV 和 matplotlib 就可能因为 C 扩展链接关系出现段错误。
pip install numpy==1.23.5 pip install opencv-python==4.8.0.74 pip install -r requirements.txt很多排查现场里,更常见的是ImportError: libGL.so.1: cannot open shared object file。这是 opencv 缺少系统图形库,和 pip 无关,发生在纯容器或精简 Linux 系统上。解决方法是安装系统库:apt-get install -y libgl1 libglib2.0-0。这条在部署阶段几乎是必踩的坑,值得提前装掉。
3.3 源码目录与自建数据集的位置,data.yaml 怎么写不会迷路
下载的 YOLOv5 源码解压后,标准目录骨架大概是:
yolov5/ ├── train.py ├── detect.py ├── val.py ├── export.py ├── data/ # 数据集配置模板 ├── models/ # 模型结构 yaml ├── utils/ # 训练与推理工具 ├── runs/ # 训练结果输出目录 └── requirements.txt这是通用组织方式,不要把自建数据直接丢进yolov5/data下面,它容易和官方模板混在一起,更不要在 data 下放图像和标签混合的目录。惯常做法是让数据集独立放在和yolov5/平级的NEU-DET/目录下,再写一个data.yaml指过去。
# NEU-DET/data.yaml path: ./NEU-DET train: images/train val: images/val test: images/test nc: 6 names: - crazing - inclusion - patches - pitted_surface - rolled-in_scale - scratchespath是数据根目录,train、val、test 写相对 path 的相对路径。YOLOv5 部分版本支持path字段,如果报路径解析错误,可以去掉 path,直接写train: ./NEU-DET/images/train。nc必须和 names 长度一致,多一个少一个都会在训练启动时被断言拦下。这个断言信息写得比较简短,新手常看不出原因,其实只要数一下 names 数量就好。
检查标签路径是否命中:YOLOv5 读标签时,会用训目标图像目录去推导同级 labels 目录。例如图像在NEU-DET/images/train/crazing_003.jpg,它就会去NEU-DET/labels/train/crazing_003.txt找标注。这就要求 images 和 labels 的二级目录结构完全并列,一个叫 train 另一个也叫 train,不能一个叫 train 一个叫 train_txt,后缀不同直接找不到。
4. 训练钢材缺陷检测模型:train.py 参数选择、超参数调整和结果解读
4.1 用预训练权重做迁移学习,batch 和 imgsz 的基本设定
直接从零训练在小数据集上划不来。NEU-DET 只有 1800 张,从随机初始化开始至少要跑几百轮才能收敛,而 YOLOv5 官方在 COCO 上预训练好的yolov5s.pt已经具备通用特征提取能力,在工业缺陷数据上做 100 轮微调就能达到不错的 mAP。所以训练一个实用的检测系统,标准做法是:
python train.py \ --data NEU-DET/data.yaml \ --weights yolov5s.pt \ --img 512 \ --batch-size 16 \ --epochs 100 \ --workers 4 \ --project runs/train_steel \ --name exp01 \ --cache--img是训练输入尺寸,默认值 640。这里故意调到 512,因为原始图只有 200x200,放大到 512 相当于 2.5 倍,既能保住缺陷纹理,又不至于让显存像用 640 那样紧张。如果显卡是 8GB,batch 从 16 降到 8 也还能跑。batch-size 不是越大越好:这批数据类别少、目标小,batch 加大会让梯度方向更平滑,但显存空间也吃得更紧,无法再同时把--img调大。
--cache在数据量小的时候值得开着。它会把图像以内存或磁盘缓存方式加载,减少每次 epoch 的 I/O。钢材图像是灰度单通道,缓存占用不大,开了之后每次训练能省不少时间。--project和--name是输出目录的定制。默认输出在runs/train/exp,第二次跑会变成exp2,非常混乱。改成train_steel加exp01递增,后面翻混淆矩阵和 PR 曲线时,一眼能认出哪轮用了什么参数。
4.2 针对钢材场景调超参数:关闭颜色增强,降低 mosaic 概率
YOLOv5 的默认超参数写在data/hyps/hyp.scratch-low.yaml。更可控的做法是复制一份命名为hyp.steel.yaml,用--hyp hyp.steel.yaml指定。下面是一份我在钢材缺陷上常用的改法:
# hyp.steel.yaml lr0: 0.01 lrf: 0.2 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0 warmup_momentum: 0.8 box: 0.05 cls: 0.5 obj: 1.0 mosaic: 0.3 hsv_h: 0.0 hsv_s: 0.0 hsv_v: 0.0 translate: 0.1 scale: 0.5 fliplr: 0.0 flipud: 0.0为什么要这样改:钢材表面缺陷通常在灰度域上表现明显,色彩空间没有可学信息,关闭 hsv 三个通道能减少增强带来的随机噪声,让模型更关注形状和纹理。fliplr 和 flipud 对方向性缺陷有风险:crazing 一般是沿轧制方向的微小裂纹,如果左右翻转,模型会把“裂纹方向”和“背景条状纹理”混淆,所以全部关掉。mosaic 从默认 1.0 降成 0.3,是因为 mosaic 会把任意四张图拼在一起,在自然图像上有效,但钢板背景高度相似,拼接产生的大量伪边界会让模型额外学习“背景过渡线”,南辕北辙。
超参数不是越多改越好。只拿训练损失来评估调参也片面,要同时看 val 损失。如果 train loss 一直在降、val loss 从某个 epoch 开始反弹,那先理解为过拟合信号,而不是超参数没调好。1800 张图跑到 120 轮左右,mAP50 基本走平。也有人对钢材数据把 cls 调到 0.7 来强调分类,但这对外观相近的 crazing 和 scratches 反而有副作用,除非确认混淆矩阵里分类错误占主导,保守起见用 0.5。
4.3 训练输出怎么看:results.csv、混淆矩阵与 PR 曲线
训练结束后,runs/train_steel/exp01/下会出现一批文件:weights/best.pt、weights/last.pt、results.csv、results.png、confusion_matrix.png、PR_curve.png等。
results.csv每一行代表一个 epoch,列包含metrics/mAP_0.5、metrics/mAP_0.5:0.95、val/box_loss等。先盯着 mAP50 和 mAP50-95:mAP50 衡量框和类别是否大致命中,mAP50-95 对框定位精度更严格。钢材缺陷里小目标多,如果 mAP50 高但 mAP50-95 偏低,说明预测框定位精度没到“精准”级别,运行时需要调整 NMS,或者重新考虑训练尺寸。
混淆矩阵能指出哪两类缺陷互相“认错”。我见过 crazing 和 scratches 的混淆最多,因为它们都是线状缺陷,区别只是纹理深度和连续性。如果这两类互相串,只调 NMS 上限不会好,应该回查标注,看是不是部分框画得太宽松,把背景纹理也包进去了。
PR_curve.png里每个类的曲线会不同。rolled-in_scale 是大块目标,曲线通常很漂亮;crazing 的曲线会掉得快,说明它夹带的背景像素多,误报也多。这些图表不是用来“看个结果”的,是决定要不要回去改标注、改数据增强的依据。
5. 钢材缺陷检测项目避坑指南:五个最容易翻车的地方和现场解决记录
5.1 现象:PyTorch 装好后,一 import torchvision 就报模块找不到
原因:torchvision 版本和 torch 没有对齐。很多人先pip install torch装成 2.x,再装 torchvision 选了 1.x,两者在底层张量实现上有差异,导入到functional_pauli这类模块时直接抛异常,看起来像代码问题,实际是环境问题。
解决:在独立 conda 环境里固定 torch 和 torchvision 版本。先卸载,再成对安装:
pip uninstall torch torchvision -y pip install torch==1.13.1 torchvision==0.14.1 python -c "import torch, torchvision; print(torch.__version__, torchvision.__version__)"如果 pip 解析时又互相顶掉,用conda install pytorch=1.13.1 torchvision=0.14.1让 conda 做依赖解析。装完必须再验一次 import,确认打印的两个版本成对。
5.2 现象:训练启动后 loss 一直不降,前 20 个 epoch 横在 7 左右
原因:九成是标签问题,不是学习率。YOLO 的 loss 在标签异常时会把目标置信度计算成远大于正常范围的值。常见原因有三个:txt 里的坐标没有归一化,出现 x_center=170 这种数;图像和标签没有对应上,label 文件是空文件或缺失;class_id 超过 nc-1。
解决:先抽查一批标签:
import os label_dir = 'NEU-DET/labels/train' for name in os.listdir(label_dir)[:10]: p = os.path.join(label_dir, name) print(name, open(p).read())看打印的坐标是否都在 0 到 1 之间。如果看到class 6就说明索引越界,把 data.yaml 的 names 列表和 XML 转换脚本的 CLASSES 顺序对齐即可。空文件说明 XML 解析失败,回头检查name字段是否被识别。
5.3 现象:训练 mAP0.5 到了 0.9,但拿 detect.py 检测同一批图时漏了很多小缺陷
原因:mAP 评估的是预测框和真实框在某个阈值下的匹配结果,而 detect.py 默认的--conf-thres 0.25会把大量低置信度的小目标框过滤掉。钢材表面缺陷密集且尺寸小,一个缺陷周围可能产生十几个候选框,NMS 在 IOU 阈值 0.45 时把相邻的真缺陷框合并掉,导致漏检。
解决:跑推理时把置信度阈值调低,IOU 阈值也调低:
python detect.py \ --weights runs/train_steel/exp01/weights/best.pt \ --source NEU-DET/images/test \ --img 512 \ --conf-thres 0.08 \ --iou-thres 0.35 \ --save-txt参数说明:--conf-thres从默认 0.25 降到 0.08,模型会保留更多低分框,适合小目标密集场景。--iou-thres从 0.45 调到 0.35,NMS 对重叠框更严格,能把紧邻的两个真实缺陷框都保留下来。代价是会多一些重复框和误检框,需要根据产线要求的精确率和召回率再平衡。
5.4 现象:对图像做过 CLAHE 预处理之后,检测率反而骤降
原因:训练集和测试集上的预处理不一致。CLAHE,也就是对比度受限自适应直方图均衡化,对低对比度钢板很有效,但如果在训练时用了某一组 clipLimit,推理时用了另一组,或者只对验证集做、没对训练集做,模型的输入分布就不一致,mAP 可能出现明显下跌。这个坑特别隐蔽,因为两张图肉眼看起来差别不大。
解决:把 CLAHE 抽成一个复用函数,训练和推理共用同一份代码:
# preprocess.py import cv2 def to_model_input(img, clip=2.0, grid=(8, 8)): clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=grid) # img 必须是单通道灰度图 return clahe.apply(img)参数说明:clipLimit 调得越大,对比度增强越强,但背景噪声也会被放大。钢材表面轻微划伤时我用 2.0,tileGridSize 用 8x8,在 512x512 输入下足够细。如果做训练增强,把它放在数据加载流程的同一位置;如果做离线预处理,训练和推理都要先跑一遍。
5.5 现象:train.py 运行时报 No labels found 或者类别数断言失败
原因:最常见是 images 和 labels 的路径不匹配。YOLOv5 从train字段找到图像目录,然后换算到同级 labels 目录。只要一个在NEU-DET/images/train/,另一个在NEU-DET/labels/train_txt/,就会因为缺标签直接报错。另一个低频原因是图像后缀不统一,有 jpg 也有 png,脚本只处理了 jpg。
解决:先核对文件名和目录名,再用一条命令批量检查:
for f in NEU-DET/images/train/*.jpg; do b=$(basename "$f"); b=${b%.jpg} [ -f "NEU-DET/labels/train/$b.txt" ] || echo "missing: $b" done如果还有 png,把*.jpg换成*.jpg *.png再跑一遍。跨平台搬项目时最容易遇到,Windows 下大小写不敏感,目录叫Images也能读到;一到 Linux 上就爆,所以 data.yaml 里的路径要严格区分大小写。
6. 部署验证技巧:批量推理与 ONNX 导出,先用一张“认得的图”把关
训练完成后,不要只看 mAP 数字。我习惯的做法是,拿一张自己亲手标过、知道每个缺陷在哪里的原图,跑一遍 detect.py,再人工数一遍:图上真实缺陷有多少个,模型框出了几个,多框的和漏框的各是什么类别。如果真实缺陷 20 个,模型只出 15 个,优先怀疑漏检;如果多出 5 个框,优先怀疑误检。这个步骤永远最先做,因为它是成本最低的“模型有没有学歪”体检。
推理命令保持和训练一致的图像尺寸:
python detect.py \ --weights runs/train_steel/exp01/weights/best.pt \ --source NEU-DET/images/test \ --data NEU-DET/data.yaml \ --img 512 \ --conf-thres 0.08 \ --iou-thres 0.35 \ --save-txt--save-txt会同时把检测结果写成一个 txt,方便和标注文件做逐行对比,而不只是看图。输出在runs/detect/exp下,第一轮是 exp,第二轮是 exp2,按需改名。
如果要嵌入现有质检系统,建议把 best.pt 导出成 ONNX,用 ONNX Runtime 做后续推理,不必在产线机器上再搭一套 Python 训练环境。
python export.py \ --weights runs/train_steel/exp01/weights/best.pt \ --include onnx \ --img 512导出后先确认输出的 shape 和置信度格式,再用 ONNX Runtime 跑一遍上面那张“认得”的图,看结果和 PyTorch 推理是否一致。不一致时基本都是预处理差异,尤其是灰度图转三通道的方式。
我现在的习惯是,每次拿到一份钢材缺陷检测源码,不管对方声称训练效果多好,第一件事永远是找一张自己标过、知道答案的图,跑完一遍对比框再谈其它。这个动作帮我挡掉过不少次“mAP 很高、现场不认”的返工。检测系统的短板经常在数据和阈值匹配上,模型只是其中一环,先盯住这一环,后面的事就顺了。希望帮到你。
本文还有配套的精品资源,点击获取