YOLOv5钢材缺陷检测:从数据集到部署的完整实践指南

发布时间:2026/10/11 19:42:50
YOLOv5钢材缺陷检测:从数据集到部署的完整实践指南
简介YOLOv5钢材缺陷检测资料包面向工业视觉质检与目标检测学习者提供一套覆盖数据、训练与评估的完整缺陷检测方案。内含训练好的模型权重可区分多种钢材缺陷类型并附有使用LabelImg标注的完整数据集图像为JPG格式XML与TXT两种标签分目录存放便于接入不同训练框架。压缩包共164个文件主要包含模型权重、Python脚本、YAML配置、图像数据、XML与TXT标注、训练曲线图及CSV结果文件等整体约106.77MB。已有2275人学习下载。借助该包可获得训练好的检测权重、完整标注数据集、PR曲线与损失曲线等评估结果既能直接进行推理测试也能基于YOLOv5继续训练或二次开发资源整体沿用经典YOLOv5目录组织脚本、配置与说明文档齐备适合目标检测入学者及钢材表面缺陷识别研究者参考。1. YOLOv5钢材缺陷检测这份带权重带数据集的资源解决了产线上最磨人的那一段做钢材表面质检的算法工程师八成都有过这种经历缺陷类型自己门儿清麻点、划伤、夹杂、压入氧化皮各种标准倒背如流可真要训练一个缺陷检测模型——没数据、没标注、训练完PR曲线一塌糊涂。这套YOLOv5钢材缺陷检测资源核心是已经训练好的缺陷检测权重加一份完整标注的数据集。图片是jpg标签同时给了xml和txt两种格式分别存在两个文件夹里模型权重、训练日志、PR曲线、loss曲线都齐了。拿到手能做两件事一是直接加载权重跑推理看检测效果二是把这份数据当起点结合自己的产线样本做增量训练。适合刚接手钢材质检项目的算法工程师也适合那些想从真实缺陷数据入手熟悉YOLOv5训练流程的人。2. 拿到缺陷检测权重前先看数据双格式标签与目录结构2.1 资源里到底有什么先把这个目录翻明白这份压缩包解压之后你会看到类似这样的结构├── yolov5-6.0.iml ├── Dockerfile ├── tutorial.ipynb ├── events.out.tfevents.1679277410.DESKTOP-AJP7QI2.63600.0 ├── results.csv └── (数据集与权重相关目录)events.out.tfevents是TensorBoard的日志文件训练过程中的loss、mAP、PR曲线都从这里来results.csv是每一轮训练结果的表格化记录我一般直接用Excel或pandas打开比TensorBoard加载快得多tutorial.ipynb是个入门教程跟着跑一遍就知道大致流程。这几个文件是训练过程的“黑匣子”后面讲训练参数调整时还会用到。Dockerfile是给那些不想配环境的同学准备的。YOLOv5-6.0这个版本对依赖版本卡得比较死opencv-python、torch、torchvision这几个库的版本不一致就会报奇奇怪怪的错。我自己的习惯是直接基于这份Dockerfile构建镜像把这套环境固化下来后面换机器、给同事复现都省心。2.2 XML与TXT双标签并存两组标注怎么对应这份数据集合关键的地方是标签同时给了xml和txt两种格式分两个文件夹保存。xml是VOC格式的标注用LabelImg标注时默认保存成这种txt是YOLO训练直接要的格式每行一个目标格式是类别id、中心点x、中心点y、宽、高四者都是相对图片宽高的归一化值0到1之间。常见的一个疑问是这两种格式是不是内容完全一样的复制我把标注软件默认导出的xml和按YOLO要求转换的txt对比过内容语义一致但写法不一样annotation folderJPEGImages/folder filenametrain_001.jpg/filename size width640/width height480/height /size object namescratch/name bndbox xmin100/xmin ymin120/ymin xmax320/xmax ymax240/ymax /bndbox /object /annotation对应的txt文件内容则是2 0.328125 0.375 0.34375 0.25之所以给两份是因为工程里两种格式都有场景xml方便人读、方便用脚本做数据增强前后的校验txt是YOLO训练直接吃的格式。我拿到这种双标签资源后第一件事不是直接训练而是把两种格式做交叉校验防止标注软件保存时漏了文件。2.3 用脚本验证标签完整性与类别分布正式训练之前先用一段脚本把数据集的底摸清这是血泪经验换来的习惯。下面这段代码统计数据集里每个类别的样本数、异常标签以及xml转换成yolo格式后是否对齐import os import xml.etree.ElementTree as ET # 统计VOC格式xml里的目标类别分布 xml_dir annotations/xml # 假设xml文件放这个目录 class_count {} for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() for obj in root.iter(object): name obj.find(name).text class_count[name] class_count.get(name, 0) 1 # 顺便检查bbox坐标是否有异常 bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) if xmin 0 or ymin 0 or xmax xmin or ymax ymin: print(f异常框: {xml_file}, {name}, ({xmin}, {ymin}, {xmax}, {ymax})) print(类别分布:, class_count)这段代码有两个作用一是输出类别分布确认缺陷类型和数量是否均匀二是扫描bbox坐标看有没有坐标为负、宽高为0的脏数据。xml里出现这类脏框并不少见尤其是标注最后几个文件时手滑或软件自动保存出错。如果这批脏数据直接拿去训练loss会异常波动mAP也会被拉低而且出问题时你压根想不到是标注数据的锅。再对txt格式做一遍验证确认标签类别id在合法范围内且归一化坐标没有超过图片边界# 验证YOLO格式txt标签的合法性 txt_dir labels/txt label_count {} for txt_file in os.listdir(txt_dir): if not txt_file.endswith(.txt): continue with open(os.path.join(txt_dir, txt_file)) as f: for line in f: parts line.strip().split() class_id int(parts[0]) # 检查五个字段是否齐全 if len(parts) ! 5: print(f字段数量异常: {txt_file} - {line}) continue x_center, y_center, w, h map(float, parts[1:]) if w 0 or h 0 or x_center 0 or x_center 1 or y_center 0 or y_center 1: print(f坐标越界: {txt_file} - {line}) label_count[class_id] label_count.get(class_id, 0) 1 print(YOLO格式类别分布:, label_count)这段验证的意义在于YOLO训练对txt格式的容忍度很低坐标越界、宽高为负这些会导致loss变成NaN类别id如果超出你定义的类别总数训练直接报错。两个脚本跑完确认数据没问题再进下一步——加载权重跑推理。3. 用已训练好的权重先跑通检测detect.py的参数与输出解读3.1 加载缺陷检测权重detect.py命令和你需要改的参数权重文件是已经训练好的放在yolov5-6.0目录下文件名一般是best.pt或last.pt。best.pt是验证集上mAP最高的那轮权重last.pt是最后一轮训练的权重。直接加载best.pt跑推理python detect.py \ --weights best.pt \ --source ../data/images/test \ --img-size 640 \ --conf-thres 0.25 \ --iou-thres 0.45 \ --save-txt \ --project runs/detect \ --name steel_test几个参数我说下实际经验--img-size 640是推理时把图片缩放到640×640。如果训练时用的分辨率也是640保持这个值就行如果训练时用了更大的分辨率比如1280推理也尽量保持一致否则小目标检出率会变。--conf-thres是置信度阈值0.25是YOLOv5的默认值。钢材缺陷检测里如果你想尽可能少漏缺陷把这个值降到0.15甚至0.1都行但代价是误检会变多。--iou-thres是NMS的IoU阈值0.45比较常规。--save-txt会把检测结果的坐标和类别保存成txt文件这对需要对接产线系统的场景很有用。推理完成后结果图片保存在runs/detect/steel_test/下带标签框的图可以直接看效果。3.2 从PR曲线到实际输出资源和模型是匹配的这份资源里带了PR曲线和loss曲线那怎么看这些曲线判断权重好不好PR曲线是Precision-Recall曲线横轴是Recall纵轴是Precision。曲线越往右上角凸说明模型在“尽量多检出缺陷”和“尽量少误检”之间的平衡越好。看PR曲线时可以关注两个点曲线下面的面积AP值和曲线在Recall接近1时的表现。如果曲线在Recall 0.8之后就断崖式下跌说明模型漏检严重。我第一次拿到这套权重先在自己手上的钢材表面图片上跑了一遍检测框的位置和类别基本靠谱但有几个缺陷被标成了相邻类别。原因也简单——钢材缺陷里划伤和压入氧化皮在灰度图下长得确实像人眼都容易混淆。这种形似缺陷在PR曲线上体现为各类别的AP值有明显差异。如果你发现跑出来的结果在某类别上特别差不要急着调模型先把该类别的数据和标注对照看一遍问题大概率在数据侧。3.3 别急着训练先用日志文件确认原训练过程results.csv是这份资源的隐藏信息源它记录了训练过程中每一轮的各项指标。用pandas读一下就能复现整个训练过程import pandas as pd df pd.read_csv(results.csv) print(df.columns) print(df.head(10))正常的列会有epoch、train_loss、val_loss、precision、recall、mAP_0.5、mAP_0.5:0.95这些。重点看两个地方一是loss是否有下降趋势如果loss曲线反复震荡说明学习率设得太大或数据本身有噪声二是mAP是否还在上升如果训练到后期mAP还在爬升说明epoch可以加更多。这个过程的意义在于你拿到的是一个“已经训练好”的权重但不知道它是在多少轮epoch后收敛的、数据怎么划分的、有没有使用预训练权重。通过results.csv你就能还原这批信息。我一般还会顺便看events.out.tfevents如果需要更细致的曲线用TensorBoard加载tensorboard --logdir . --port 6006然后浏览器打开localhost:6006就能看到完整的loss曲线、mAP曲线和PR曲线。到这一步这套资源的底子基本摸清了下一步才是按自己的需求去迭代模型。4. 用这份数据集重新训练自己的钢材缺陷模型参数与流程4.1 把数据集改造成YOLOv5能直接读的目录结构很多人在这一步翻车原因是不清楚YOLOv5的数据集目录规范。这里给出我多次验证过的目录组织方式datasets/ ├── steel_defect/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── steel_defect.yaml注意images和labels的子目录名要严格对应train对train、val对val。图片是jpg标签是txt且txt文件名和图片文件名保持一致后缀不同。在改目录结构时我见过最多的问题是图片在train文件夹、对应的txt却在val文件夹或者文件名大小写不一致导致训练时“No labels found”。所以改完目录后先跑一个脚本验证对应关系import os img_dir datasets/steel_defect/images/train label_dir datasets/steel_defect/labels/train img_files set(f.split(.)[0] for f in os.listdir(img_dir) if f.endswith(.jpg)) label_files set(f.split(.)[0] for f in os.listdir(label_dir) if f.endswith(.txt)) missing img_files - label_files extra label_files - img_files print(f缺标签的图片: {missing}) print(f无图片的标签: {extra})校验通过后编写steel_defect.yaml# 数据集配置文件 path: datasets/steel_defect # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 nc: 4 # 缺陷类别总数 names: [scratch, pit, inclusion, oxide_scale] # 按你统计出的类别反填nc和names必须和你的标注完全对应id从0开始按names列表的顺序排。这里注意txt标签里写的类别id是0、1、2、3对应names列表里第几个名字。如果txt里类别id是2但你的names列表只有两种缺陷训练会直接报“class number out of range”。4.2 训练参数怎么设img-size、batch、epochs和学习率训练命令如下python train.py \ --data steel_defect.yaml \ --weights yolov5s.pt \ --img-size 640 \ --batch-size 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --project runs/train \ --name steel_defect_v1选yolov5s.pt做预训练权重是基于我的经验钢材缺陷检测的目标不算超大物体s模型的容量够用而且显存压力小。如果你的显卡是8G显存--batch-size设为16跑640分辨率刚好如果是6G显存降到8显存不够时优先降batch不要降分辨率分辨率对缺陷检出率的影响更明显。--hyp指定超参数文件hyp.scratch-low.yaml是低数据增强配置适合缺陷数据量不大的场景。如果数据增强太狠缺陷这类小目标容易被增强成“假样本”训练出来的模型在真实产线图片上会水土不服。钢材缺陷数据的增强我建议保守一点只开轻微的马赛克增强和随机翻转。4.3 训练过程中的监控看板训练开始后用results.csv实时监控。我一般训练到30轮左右会看一眼loss和mAP的趋势判断要不要终止重来。加一段实时读取import pandas as pd import time for i in range(200): time.sleep(30) df pd.read_csv(runs/train/steel_defect_v1/results.csv) latest df.iloc[-1] print(fepoch: {latest[epoch]}, ftrain_loss: {latest[train_loss]:.4f}, fval_loss: {latest[val_loss]:.4f}, fmAP_0.5: {latest[mAP_0.5]:.4f})这里的循环是每30秒读一次最新结果。判断依据是如果mAP_0.5连续20轮没有明显增长说明模型已经收敛得差不多了剩下的都是噪声抖动如果train_loss还在稳定下降但val_loss已经回升就是过拟合信号应该提前停掉或者把epoch总量减少30%。训练完成后最好的模型会自动保存为best.pt用于后续推理。训练过程里有个常见基础问题数据本身就存在类别不均衡比如“划伤”占60%“氧化皮”只占5%那么模型会对划伤的检出倾向更强氧化皮很容易漏检。解决思路有三个逐个讲清楚一是数据层面对少样本类别做离线增强——具体操作是复制小类别图片进训练集但注意这里有个陷阱同一张图如果被复制多次而且只喂给训练集模型容易过拟合这批图而不是学到该类别的泛化特征所以复制的同时要做平移、翻转二是损失函数层面YOLOv5原生不支持按类别调loss权重我一般不做修改三是在推理阶段调整置信度阈值——在detect.py时为每个类别设置不同的阈值比如对氧化皮阈值降到0.1对其他保持0.25这个策略不一定有理论依据但工程上管用。5. 避坑记录钢材缺陷检测中常见的五个坑附排查命令5.1 标签与图片不匹配训练直接报No labels found现象执行train.py后训练开始前就报No labels found in ...甚至训练进度条不出现。原因绝大多数是steel_defect.yaml里的path、train、val路径写错导致程序找不到标签文件夹少数是目录结构里图片文件名与标签文件名后缀不一致或文件名里混了空格/中文。解决先核对yaml三点——path必须是数据集根目录、train和val是相对path的文件夹名再用上面4.1的脚本来校验文件名一一对应最后我习惯在train.py命令里加上--exist-ok重新执行偶尔是上一次中断产生的缓存文件混乱导致。5.2 class数量写错推理输出张冠李戴现象训练能正常跑完推理也不报错但输出框上的类别名明显不对比如把一个缺陷类别显示成另一个。原因nc写多了或写少了。比如你的数据集实际有4类但yaml里写了3类训练时标签id3会被忽略或报错如果写了5类模型的输出层会多出一个类别神经元的参数但训练时这个神经元从未被监督到推理时它会乱预测。解决严格按统计出来的类别分布填nc和names。推荐在训练前再跑一遍统计脚本确保类别顺序一致。还有一个细节如果txt标签里的类别id最大值 nc训练会直接报class out of range这个一定要前置检查。5.3 小目标漏检PR曲线的Recall断崖现象推理时面积小的划伤、麻点类缺陷基本检不到面积大的缺陷框都能出PR曲线上小目标类别AP明显偏低。原因钢材缺陷里小目标占比高而YOLOv5在640分辨率下对特别小的目标本身不敏感加上如果训练数据里小目标样本本身就少、标注框又小模型学不到稳定的特征。解决尝试把输入分辨率提到1280同时把--img-size在训练和推理时保持一致如果显存不够就用--rect让训练按宽高比动态调整输入另一个有效做法是开启YOLOv5的--multi-scale训练让模型在训练时看到不同尺度的目标。5.4 Loss曲线反复震荡不收敛现象训练了二十多轮train_loss和val_loss像过山车一样上下跳完全看不到整体下降趋势。原因两个最常见的原因——初始学习率设得太大batch太小导致每个batch的梯度方向差异太大。解决看results.csv里的曲线判断是哪种。如果是高频率震荡把学习率从默认的0.01调到0.001然后在hyp文件里把lr0和lrf都改成较小值重启训练如果是batch太小先看显卡显存余量把batch增大到显存允许的上限。还有一种情况是标注数据本身存在互相矛盾的框比如同一类缺陷有的标了有的没标这种就要回头做数据清洗。5.5 训练时显存溢出OOM现象训练跑到一半报CUDA out of memory中断。原因batch-size超过显存容量常见于大分辨率配合大batch。解决先把--batch-size减半如果还溢出把--img-size从640降到512但注意推理时也要用相同的分辨率否则精度会下降。还有一招是用--cache ram但那是把图片缓存到内存里解决的是IO瓶颈不是显存。6. 权重落地前最后一关模型验证、导出与样本闭环6.1 用PR曲线和F1曲线判断模型是否真的能用训练完一轮不要急着看测试集那几张图的视觉效果按工程标准去验证模型。打开runs/train/steel_defect_v1/目录下的PR_curve.png和F1_curve.png。PR曲线看各缺陷类别的AP排序排出性能最差的那一类F1曲线看置信度阈值设在哪个值能在查准和查全之间取到平衡。如果某类的PR曲线明显低于其他类这个模型直接上产线是不行的回到第4章做数据补充或调参。6.2 导出模型从PyTorch权重到部署接口验证通过后把权重导出成部署友好的格式。YOLOv5-6.0支持多种导出方式python export.py \ --weights best.pt \ --img-size 640 \ --batch-size 1 \ --include onnx导出为ONNX格式在实际项目中更通用后续可以用TensorRT加速尤其在NVIDIA显卡环境下效果明显如果需要ILIKE嵌入式设备可以使用--include openvino或--include coreml比如在树莓派4B部署yolov5的桥在RK3568这类边缘设备上部署一般都是把ONNX或rknn绕一下。导出之后先跑一次onnx的推理做精度对比python detect.py --weights best.pt --source test.jpg python detect.py --weights best.onnx --source test.jpg对比两张结果图确认导出前后检测框和类别没变化再去做后续部署。6.3 误检样本回收形成数据闭环这里我想说一个容易被忽略的习惯部署之后把产线上每批误检、漏检的图片全部收集起来定期回灌到训练集。做法是用当前模型跑产线实拍图把置信度低且实际是缺陷的图片挑出来人工标注后加入数据集增量训练同时把置信度高但实际不是缺陷的图片也挑出来标为背景加进数据集。这个过程跑两三轮你会发现模型在真实产线的表现明显变好比反复调超参数管用得多。从那以后我每次交付任何一个缺陷检测项目都会强制把“误检样本回灌”写进交付文档并在前三周持续跟进观察。模型不是训练完就结束了持续迭代才是它活下来的方式。希望这套带权重带数据集的资源能帮你在钢材缺陷检测这条路上少走几个来回。本文还有配套的精品资源点击获取