YOLOv7钢材缺陷检测全流程:双格式数据集、训练与部署指南
简介面向工业质检、智能制造与深度学习入门人群这份YOLOv7钢材缺陷检测资源提供了可直接使用的训练权重、配套数据集与完整工程脚本。模型已训练完毕附带PR曲线、loss曲线、训练日志等文件能帮助读者直观评估检测效果并复现训练过程数据集经过LabelImg人工标注包含jpg原图及xml、txt两种标签格式分别对应VOC与YOLO格式方便切换训练框架。压缩包共166个文件除pt权重、yaml配置与Python源码外还有TensorRT、ONNX Runtime动态批次转换demo以及多个ipynb对比分析笔记整体约203.86MB目录结构清晰便于按模块学习与二次开发。目前已有1288人学习下载。对需要快速落地钢材表面缺陷识别、从零搭建数据标注流程或深入理解YOLOv7工程化部署的读者而言这份资源能明显缩短实践周期省去搜集数据与从零训练的时间。1. YOLOv7 钢材缺陷检测权重、双格式数据集、训练曲线一次给齐做钢材表面缺陷检测的人大多有过这种处境产线图像拉回来一大堆能用的公开数据集却要么没标签、要么只给一个权重文件训练跑起来心里完全没底。这份 YOLOv7 钢材缺陷检测资源属于少见的“全家桶”形态——检测权重已经训练好缺陷类型分好了类附带 PR 曲线、loss 曲线等训练产物图片是 jpg标注由 labelImg 完成且同时保留了 xml 和 txt 两种格式。换句话说模型能直接拿去推理数据也能拿来重新训练还能顺手研究 VOC 与 YOLO 标注格式的转换。适合正在做工业质检、想在 YOLOv7 上快速起跑的人也适合想搞懂标注坐标系转换的学习者。2. 先看懂这批数据双格式标注与 labelImg 的产出约定拿到任何检测资源第一步永远是扒目录结构标注格式决定你后面每一步要不要写转换脚本。这份资源的组织方式很典型图片单独放xml 标注一套txt 标注一套。很多人上来直接把 txt 丢给训练脚本结果类别对不上、坐标飘了折腾半天才发现是标注理解错了。先花十分钟把数据看明白比什么都值。2.1 文件组织jpg、xml、txt 三个角色的分工资源里图片格式为 jpgxml 与 txt 分别保存在两个文件夹中用途完全不同。内容格式作用图片jpg原始样本训练和推理的输入xml 标注XMLVOC 风格labelImg 原生保存产物记录绝对像素坐标适合人工校对、二次裁剪txt 标注纯文本YOLO 风格归一化坐标YOLOv7 训练时直接读取xml 是 labelImg 保存的原生格式字段可读性很强打开一个看看annotation folderJPEGImages/folder filenamesteel_001.jpg/filename size width640/width height640/height depth3/depth /size object namescratches/name bndbox xmin182/xmin ymin224/ymin xmax451/xmax ymax387/ymax /bndbox /object /annotation这里每个object对应一个缺陷框name是缺陷类别名bndbox里的四个值是框的左上角和右下角绝对像素坐标。size里的宽高必须和实际 jpg 图片一致后面做格式转换时全靠它。文件里filename写的是steel_001.jpg实际文件名要能对得上这是标注数据最常见的翻车点之一。对应的 txt 标注只有一行0 0.4945 0.4773 0.4203 0.2547五个数字依次是类别 id、归一化中心点 x、归一化中心点 y、归一化宽度、归一化高度。类别 id 从 0 开始后面四个值都是 0 到 1 之间的小数用图片真实宽高去除出来的。比如上面 xml 里那个框x 中心点就是(182 451) / 2 / 640 0.4945宽度是(451 - 182) / 640 0.4203跟 txt 完全对得上。2.2 labelImg 做了什么标注产物背后的两套坐标系labelImg 默认保存 xml它内部画框时记录的是鼠标点击的绝对像素位置也就是xmin/ymin/xmax/ymax这套坐标系。YOLO 系列训练需要的是归一化坐标因为不同图片尺寸不一样归一化之后模型不用关心输入是 640 还是 1280。这份资源两个文件夹分开放说明作者特意保留了源标注又生成了训练用的 txt属于比较讲究的做法。拿到数据后我建议先做一次可视化检查别直接开训。把 xml 坐标画回原图肉眼确认框的位置、类别名、有没有漏标错标写个小脚本就行import cv2 import xml.etree.ElementTree as ET img cv2.imread(steel_001.jpg) tree ET.parse(steel_001.xml) for obj in tree.findall(object): name obj.find(name).text box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, name, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check.jpg, img)这段做的事情很简单解析 xml 里的每个框用绿色画出来再写上类别名最后输出一张check.jpg。画出来的框如果和缺陷位置贴合说明标注没问题如果明显偏移多半是图片被 resize 过但标注没跟着改或者 xml 里size和实际图片尺寸不一致。这个检查脚本我会建议每个人都留在手里后面每次拿到新数据都用它过一遍能省掉大量训练到一半才发现标签错位的时间。labelImg 操作上没什么玄机w 键画框、d 键下一张、a 键上一张、CtrlS 保存。它支持在 VOC 和 YOLO 两种模式间切换VOC 模式存 xmlYOLO 模式存 txt。但要注意同一张图在两种模式下标注内容是等价的坐标表达方式不同而已不是标注了两遍。2.3 类别 id 的约定数字背后对应哪个缺陷名txt 里的数字 id 不是缺陷名称而是类别表里的下标。常见做法是维护一个classes.txt每行一个类名顺序就是 id 编号的依据。比如classes.txt第一行是scratches那么 txt 里的0就代表划痕。这个顺序一旦定下来训练配置里的names列表必须跟它完全一致。提示txt 里数字 id 对应的类别顺序就是训练时data/*.yaml里names的排列顺序。这两处不一致轻则训练 loss 不收敛重则模型输出的类别全错框位置倒是准的但张冠李戴。判断顺序有没有搞错最快的办法是写两行代码统计所有 txt 里出现的类别 idimport glob ids set() for f in glob.glob(labels/*.txt): with open(f) as fp: for line in fp: ids.add(line.split()[0]) print(sorted(ids))输出结果应该在 0 到类别数减一之间。如果出现空缺或者跳号说明标注时类别没用全或者某个类别的样本数少得可怜这会直接影响后面的训练策略。3. VOC 转 YOLO转换脚本与坐标归一化里的四个边界坑labelImg 明明能直接存 YOLO 格式为什么这份资源偏偏保留了两个文件夹答案很简单xml 是源数据txt 是产物。xml 里记录了绝对坐标、图片尺寸、类别名这些完整信息随时能重新生成任何格式txt 一旦生成反向找回绝对坐标反而麻烦。所以常规做法是 xml 作为唯一真源txt 用脚本生成这份资源的组织方式就是这个思路的体现。3.1 为什么保留 xml 又生成 txt源数据心态把 labelImg 直接切到 YOLO 模式确实能省掉转换这一步。但代价是丢失了绝对坐标和图片尺寸信息后面做数据清洗、按类别抽样、裁图增强全都得回头重新标注。我一般会坚持“xml 是源txt 是生成物”的策略xml 文件夹永远不动txt 随时删掉重新生成。这也是这份资源把两种格式分开存放的合理性所在——xml 给你兜底txt 给你训练两者各司其职。3.2 转换脚本从 xml 到 txt 的完整实现转换逻辑不复杂无非是把xmin/ymin/xmax/ymax换算成归一化的中心点坐标和宽高。直接上脚本import os import xml.etree.ElementTree as ET def voc2yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue # 类别不在列表里直接跳过 cls_id class_names.index(name) box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 越界修正防止归一化后出现负数或大于1 x1 max(0, min(x1, img_w)) x2 max(0, min(x2, img_w)) y1 max(0, min(y1, img_h)) y2 max(0, min(y2, img_h)) dw 1.0 / img_w dh 1.0 / img_h cx (x1 x2) / 2.0 cy (y1 y2) / 2.0 w x2 - x1 h y2 - y1 lines.append(f{cls_id} {cx*dw:.6f} {cy*dh:.6f} {w*dw:.6f} {h*dh:.6f}) out_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, out_name), w) as f: f.write(\n.join(lines))调用方式class_names [scratches, inclusion, patches] # 以资源里的类别清单为准 xml_dir Annotations out_dir labels os.makedirs(out_dir, exist_okTrue) for f in os.listdir(xml_dir): if f.endswith(.xml): voc2yolo(os.path.join(xml_dir, f), out_dir, class_names)逻辑说明分三块第一从size取图片真实宽高这是归一化的分母不能用统一的 640 去套否则不同尺寸图片的标签全错第二class_names列表的顺序决定了 txt 里写的数字 id前面统计出来的 id 集合如果和这个列表对不上说明类别清单错了第三越界修正放在归一化之前先把框裁剪进图片范围内再做除法。3.3 归一化里的四个边界细节这几个坑我用血泪经验换来的列出来你能少走弯路。第一越界框必须裁剪。有些标注框的xmax会超出图片宽度几个像素直接归一化会得到大于 1 的数YOLO 训练时解析标签会告警甚至把 loss 拉高。上面脚本里的min(x2, img_w)就是干这个的转换前先钳制再归一化。第二空 xml 生成空 txt 是合法的。一张没有缺陷的背景图xml 里没有object生成的 txt 是空文件。空标签表示这张图只有背景是正常样本不要当成错误删掉。脚本里\n.join(lines)在 lines 为空时写入的就是空字符串结果正确。第三类别 id 必须和训练配置里的names对齐。你在data/steel.yaml里写names: [scratches, inclusion, ...]txt 里的0就对应scratches。很多人从别处复制一份 yaml 过来改漏了names顺序结果模型训练完输出类别整体错位。第四归一化精度保留六位小数已经足够。图片宽高再大六位小数的精度也到亚像素级别了。写太长占空间写太短框的位置会漂移统一:.6f最稳妥。4. 训练自己的数据集数据配置、超参数与曲线判读数据摸透了接下来就是训练。这份资源自带的权重可以直接用但如果想针对自己的产线数据微调或者想验证这批数据的训练效果整个流程得走一遍。YOLOv7 原理里和调参关系最密切的是训练时的辅助头和动态标签分配——辅助头只在训练时计算推理时去掉所以同一套配置训练显存明显大于推理显存这是正常的别以为是 bug。4.1 数据配置构造 dataset.yaml 与类别对齐YOLOv7 的训练入口是train.py数据侧只需要一个 yaml 文件。把路径和类别写清楚train: ./data/steel/train.txt val: ./data/steel/val.txt nc: 6 # 按数据集实际类别数修改 names: [scratches, inclusion, patches, pitted_surface, rolled-in_scale, crazing]train和val可以指向图片目录也可以指向 txt 文件列表每行一张图片的绝对路径YOLOv7 两种写法都支持。nc是类别总数names的顺序必须和前面说的classes.txt、txt 标签里的数字 id 完全一致这里错一位训练结果就全乱了。不确认类别顺序时用第 2 章那个统计脚本把 id 打出来和names逐个对应。4.2 训练命令与超参数权重控制的关键位置训练命令长这样python train.py \ --data data/steel.yaml \ --weights yolov7.pt \ --batch-size 8 \ --img 640 \ --epochs 100 \ --workers 4 \ --device 0--weights可以填官方 COCO 预训练权重也可以直接填这份资源里给好的钢材缺陷权重做微调。如果数据量不大用预训练权重迁移收敛会快很多这也是这份资源的价值点——你不需要从零开始。--batch-size看显存8G 显存跑 640 输入建议从 4 起步显存不够时别硬顶把--img降到 512 更实在。YOLOv7 的超参数写在独立的 hyp 文件里比如hyp.scratch.p5.yaml几个关键项直接决定训练质量超参数常见取值作用与调节方向lr00.01初始学习率小数据集微调降到 0.001 更稳weight_decay0.0005权重衰减防止模型过度拟合训练集mosaic1.0马赛克增强小数据集建议保持开启label_smoothing0.0标签平滑缺陷类别相似度高时开到 0.1 有改善warmup_epochs3.0预热轮数训练初期学习率先从小值爬升weight_decay就是常说的权重控制里最直接的一环它会让大权重受到惩罚防止过拟合。但工业缺陷数据往往样本量小、背景复杂weight_decay调太大模型会欠拟合调太小 loss 下降慢一般先保持默认看验证集表现再动。如果你之前在 YOLOv8 里训练过自己的数据集会发现 YOLOv7 的数据入口逻辑类似但超参文件是独立的一套别把 YOLOv8 的学习率策略直接套过来。YOLOv7 对lr0更敏感从头训练用 0.01 没问题微调时我习惯直接降到 0.001loss 曲线会平稳很多。4.3 看曲线判断训练是否翻车训练产物里最关键的两个文件是 loss 曲线和 PR 曲线。loss 曲线看趋势训练 loss 逐步下降是正常状态如果验证集 loss 在某个 epoch 后不再下降甚至回升而训练 loss 还在降那就是过拟合信号该提前停了。看 loss 曲线有点像玄学但有一条铁律训练 loss 和验证 loss 之间的 gap 持续扩大必然有问题。PR 曲线看上限曲线越靠近右上角说明模型在召回率和精确率之间平衡得越好。mAP0.5和mAP0.5:0.95两个指标差距过大说明框定位精度不够缺陷边缘抓得不准这时候优先检查标注框是不是画得太松而不是急着换模型。训练结束后runs/train/下会生成多个文件best.pt按验证集 mAP 保存last.pt是最后一轮的权重。两者差异大说明训练末段在震荡可以适当降低学习率再跑几轮。资源里还带了 TensorBoard 日志文件本地想看实时曲线就执行tensorboard --logdir runs/train浏览器打开显示的曲线和results.png内容一致但能缩放、能对比多个实验排查问题方便得多。5. YOLOv7 部署避坑指南从权重到 TensorRT/ONNX 的临门一脚训练完只是第一步把权重搬到推理环境才是真正的考验。这章集中讲部署时的高频坑每条都来自实际跑过的项目照着检查能省一整天。5.1 导出前的准备pt 权重里的隐藏信息.pt文件不只是模型参数它同时打包了 stride、anchor、类别名等元数据导出时这些信息会跟着一起进 ONNX。导出命令python export.py \ --weights runs/train/steel/weights/best.pt \ --img 640 \ --batch 1 \ --simplify \ --include onnx--simplify会调用 ONNX Simplifier 对计算图做精简去掉冗余节点推理速度通常有提升。--img必须和训练时一致YOLOv7 的检测头输出尺寸依赖输入分辨率这里写成 640推理时预处理就必须往 640 上对齐。--batch 1导出的是固定 batch 模型如果后续要动态 batch需要用项目里自带的 Dynamic-Batch TensorRT/ONNX Runtime 那套 notebook 脚本里面已经把动态维度的配置写好了。5.2 避坑记录五条实测高频问题现象一TensorRT 转 engine 后输入可以直接跑 batch4但输出结果完全错乱。原因动态 batch 配置时没有给 optimization profile 设置 min/opt/max 三个档位TensorRT 不知道该按哪个形状做 kernel 选型。解决给输入维度显式指定范围比如min(1,3,640,640)、opt(4,3,640,640)、max(8,3,640,640)重新构建 engine。现象二ONNX 推理结果类别是对的但所有框集体偏移跑到缺陷区域外面。原因导出时固定了 640但推理端预处理用了不同尺寸的 resize或者 letterbox 的 padding 颜色和训练时不一致。YOLOv7 的坐标回归对输入尺寸极其敏感训练和推理的预处理管线必须完全一致。解决推理端严格复用训练时的 letterbox 逻辑包括 Padding 填充值。现象三FP16 推理时漏检明显增多尤其小尺寸的麻点类缺陷。原因FP16 动态范围比 FP32 小小目标特征响应弱半精度量化后这部分响应被压掉了。解决工业缺陷场景优先用 FP32追求速度再用 FP16且必须用验证集数据做校准不能直接拿默认配置硬上。现象四用 txt 标签可视化画出来的框全飘。原因把归一化坐标当成了像素坐标直接用没有乘以图片宽高。解决可视化 txt 标签前先把中心点坐标和宽高乘以图片实际尺寸换算回绝对像素。现象五换一台机器部署直接报缺少.so文件。原因TensorRT 和 ONNX Runtime 的版本跟 CUDA/cuDNN 强绑定环境版本一换底层库对不上。解决尽量用 Docker 固定环境资源里带了 Dockerfile直接基于它构建镜像把 CUDA、TensorRT 版本锁死换机器不用重新折腾。5.3 用 ONNX Runtime 快速验证导出结果导出 ONNX 后别急着上 TensorRT先用 ONNX Runtime 跑一遍确认输出和 PyTorch 推理一致。这也是“yolov7 部署”最稳妥的中间验证步骤import onnxruntime as ort import cv2 import numpy as np sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) # BGR - RGB, HWC - CHW img img[None].astype(np.float32) / 255.0 out sess.run(None, {input_name: img}) pred out[0] # 形状为 (1, 25200, 5 nc) print(pred.shape)25200是 640 输入下三个尺度的候选框总数(80*80 40*40 20*20) * 3。最后一维是5 nc其中前 4 个是坐标第 5 个是目标置信度后面是每个类别的概率。输出形状对得上说明导出链路没问题。项目里自带的YOLOv7-Dynamic-Batch-ONNXRUNTIME.ipynb就是干这个的动态 batch 版本需要额外设置输入输出的维度名照着 notebook 跑一遍即可。6. 新数据集落地的一步可视化回显与三指标判读模型部署完最后一步是拿训练好的权重对真实样本做可视化回显确认框位置、类别名、置信度都对再到现场。我习惯在每批新数据集上强制做一次“单图回显”自检脚本很短import cv2 import torch model torch.hub.load(yolov7, custom, weights/best.pt) img cv2.imread(test.jpg) results model(img) results.show()回显时看三个指标框是否贴住缺陷边缘、类别名是否对应正确、置信度阈值放到 0.25 后有没有漏检。检测结果可以直接和资源配套博客里的实测图对比确认自己的推理链路没跑偏。如果框和缺陷贴合、类别正确再谈批量验证和观察 PR 曲线。不要只看一张图的漂亮结果就收工工业现场的光照、角度、钢材表面状态和数据集里差异可能很大至少准备十张没参与训练的现场图过一遍。从那以后我每次拿到新的标注数据都会强制先跑一遍可视化检查脚本确认 xml、txt 和图片三者对得上才开始训练每次部署新权重也固定先做单图回显再谈批量。这套习惯帮我挡掉了至少一半的无效调试。希望帮到你。本文还有配套的精品资源点击获取