资讯详情 钢材缺陷检测实战:YOLOv7权重配置与数据集训练避坑指南
📅 2026/10/25 10:37:30
简介本资源面向从事工业质检与深度学习目标检测的开发者、学生及研究人员提供一套可直接复现的YOLOv7钢材缺陷检测方案解决钢材表面缺陷自动识别与模型训练问题。压缩包共166个文件约203.86MB包含31个Python脚本、36个yaml配置文件、14个ipynb实验笔记、2个pt权重文件以及jpg图片、xml与txt双格式标签、png曲线图、sh脚本和Dockerfile等覆盖训练、推理、导出与部署环节。资源中已训练好检测模型并附PR曲线、loss曲线等评估结果便于直接验证效果同时提供使用labelimg标注的钢材缺陷数据集图片为jpg格式标签同时保存为xml和txt两种格式方便适配不同训练框架。目前已有1288人学习下载读者可据此快速搭建训练与测试流程理解缺陷类别划分、数据组织方式及模型评估指标并在此基础上迁移到其他工业缺陷检测任务。1. 钢材缺陷检测为什么总在产线翻车YOLOv7 权重与数据集到底怎么配产线上跑钢材缺陷检测最常听到的抱怨不是「模型不会检」而是「昨天还好好的今天换了一批钢卷就全是误报」。钢材表面缺陷检测这个场景难就难在缺陷本身对比度低、形态细长、和氧化铁皮、油污、划痕这些正常纹理高度相似再加上产线光照、相机角度、钢种切换带来的域偏移一个在实验室 mAP 0.85 的模型上线后掉到 0.5 是家常便饭。YOLOv7 之所以在这个方向被反复提起是因为它在 640 输入下能兼顾小目标和推理速度配合合适的钢材缺陷检测权重和一份标注质量过关的数据集单卡就能做到接近实时。这篇笔记面向的是准备自己训一版钢材缺陷检测权重、或者拿到一份数据集不知道怎么调参的工程师从数据组织、权重加载、训练参数到上线排查把能复现的路径和踩过的坑讲清楚。2. 钢材缺陷数据集怎么组织从原始标注到 YOLOv7 可训练格式2.1 钢材缺陷的类别定义与标注边界钢材表面缺陷常见类别包括裂纹crack、夹杂inclusion、划痕scratch、氧化铁皮压入rolled-in scale、斑块patch、麻点pitted surface等不同钢厂、不同数据集命名差异很大。做钢材缺陷检测数据集第一步不是急着转格式而是先把类别体系定死。我的习惯是先拉 200 张图做一轮人工过一遍把「这条到底算划痕还是算裂纹」这种边界样本单独拎出来定一份判定规则文档。规则里要写清楚长度超过多少像素算裂纹、宽度阈值多少、和背景对比度低于多少的算不算可标注缺陷。标注框的松紧直接影响训练。钢材缺陷很多是细长条标注时如果框贴得太紧增强后容易把缺陷裁掉框放太松又会让模型学到大量背景。常见做法是让框在缺陷外扩 2 到 4 个像素细长缺陷用旋转框更合适但 YOLOv7 原生是水平框如果缺陷长宽比普遍超过 8:1建议先评估是否值得换检测头否则水平框会把大量正常区域包进来。类别不平衡在钢材数据集里非常突出裂纹、夹杂这类缺陷可能只占全量的 3% 到 5%。不要一上来就上重采样先统计每类实例数如果某类少于 200 个实例优先补数据而不是调 loss 权重。2.2 从 VOC/COCO 转成 YOLOv7 的 txt 标签拿到手的钢材缺陷数据集常见是 VOC 的 XML 或 COCO 的 jsonYOLOv7 训练要的是每张图一个 txt每行class x_center y_center width height全部归一化到 0 到 1。下面这个脚本处理 VOC 转 YOLO顺带做了越界裁剪和空标签过滤。import os import xml.etree.ElementTree as ET # 类别顺序必须和训练时 data.yaml 里的 names 完全一致 CLASSES [crack, inclusion, scratch, rolled_scale, patch, pitted] CLASS_TO_ID {c: i for i, c in enumerate(CLASSES)} def convert_voc(xml_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) skipped 0 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() size root.find(size) w int(size.find(width).text) h int(size.find(height).text) lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in CLASS_TO_ID: continue # 未定义类别直接丢弃避免训练时报错 bbox obj.find(bndbox) x1 max(0, float(bbox.find(xmin).text)) y1 max(0, float(bbox.find(ymin).text)) x2 min(w, float(bbox.find(xmax).text)) y2 min(h, float(bbox.find(ymax).text)) if x2 - x1 2 or y2 - y1 2: continue # 过滤掉退化框 xc (x1 x2) / 2.0 / w yc (y1 y2) / 2.0 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{CLASS_TO_ID[name]} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}) if not lines: skipped 1 continue # 无有效目标的图不生成标签训练时会被忽略 txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) print(fdone, skipped {skipped} empty images) convert_voc(./annotations, ./images, ./labels)逻辑上做了三件事类别映射、坐标裁剪、退化框过滤。参数上CLASSES的顺序是硬约束训练配置里的names必须逐字对应顺序错了模型会把裂纹学成划痕。坐标裁剪用max(0, ...)和min(w, ...)是因为部分标注框会超出图像边界不裁会导致归一化后出现负值或大于 1 的值YOLOv7 的 dataloader 虽然能跑但会静默产生错误标签。x2 - x1 2这个阈值是经验值钢材缺陷里小于 2 像素的框基本是标注噪声留着只会拉低精度。2.3 数据集划分与 data.yaml 配置划分比例不要机械用 8:1:1。钢材缺陷样本少的时候验证集和测试集各留 10% 就够但要保证每个类别在三个集合里都出现。更稳的做法是按钢卷号或采集批次划分而不是随机划分否则同一卷钢的相似图像会同时出现在训练和验证集里验证指标虚高上线就翻车。# steel_defect.yaml train: /data/steel/images/train val: /data/steel/images/val test: /data/steel/images/test nc: 6 names: [crack, inclusion, scratch, rolled_scale, patch, pitted]nc和names长度必须一致路径写绝对路径最省事相对路径是相对训练脚本执行目录容易在换机器时找不到。如果数据集里某类实例特别少可以在 yaml 里加download字段留空但别指望自动补数据。3. YOLOv7 权重怎么选和怎么加载从官方预训练到自训练钢材权重3.1 官方权重与自训练权重的取舍YOLOv7 官方发布的权重有yolov7.pt、yolov7x.pt、yolov7-tiny.pt等区别在骨干宽度和深度。钢材缺陷检测里如果缺陷尺寸偏小、纹理细yolov7.pt是性价比最高的起点yolov7-tiny.pt适合边缘设备但小目标召回会明显下降yolov7x.pt精度略高但显存和推理时间都上去了产线单卡多路时基本不考虑。自训练钢材缺陷检测权重的正确姿势是加载官方 COCO 预训练权重做初始化而不是从零训。COCO 上学到的边缘、纹理特征对钢材表面同样有效能显著加快收敛。加载时用--weights yolov7.ptYOLOv7 会自动匹配同名层类别数不同的检测头会被重新初始化这是正常行为日志里会提示哪些层被跳过。如果拿到的是别人训好的钢材缺陷权重先确认它的类别顺序和你的data.yaml是否一致。我见过直接换 yaml 就训的结果模型把夹杂检成裂纹排查了半天才发现是类别索引错位。加载权重后建议先跑一次验证看每类 AP 是否合理再决定要不要继续训。3.2 训练命令与关键参数python train.py \ --weights yolov7.pt \ --cfg cfg/training/yolov7.yaml \ --data data/steel_defect.yaml \ --hyp data/hyp.scratch.p5.yaml \ --epochs 150 \ --batch-size 16 \ --img-size 640 640 \ --device 0 \ --workers 8 \ --name steel_v7 \ --project runs/train--hyp选hyp.scratch.p5.yaml是 P5 结构的默认超参钢材缺陷如果小目标多可以把mosaic保持 1.0 但把mixup降到 0.05 以下mixup 在低对比度缺陷上容易把缺陷叠没。--img-size640 是平衡点缺陷普遍小于 32 像素时可以考虑 960但 batch 要相应降到 8 或 4。--workers设成 CPU 核数的 0.7 倍左右设太高反而因为 IO 争抢变慢。训练过程中重点看三个指标box_loss是否稳定下降、每类mAP0.5是否均衡、val/val_loss是否在后期反弹。钢材缺陷数据集小的时候30 轮左右就能看出趋势如果 50 轮某类 AP 还接近 0基本是标注或类别定义出了问题继续训没意义。3.3 权重文件的结构与推理加载YOLOv7 的.pt本质是 pickle 序列化的模型字典包含model、ema、optimizer等键。推理时用torch.load加载后取model.state_dict()或直接用attempt_load。下面是最小推理脚本。import torch from models.experimental import attempt_load from utils.datasets import letterbox from utils.general import non_max_suppression, scale_coords import cv2 import numpy as np device torch.device(cuda:0 if torch.cuda.is_available() else cpu) # 加载自训练钢材缺陷权重map_location 防止在无 GPU 机器上报错 model attempt_load(runs/train/steel_v7/weights/best.pt, map_locationdevice) model.eval() img0 cv2.imread(test_steel.jpg) img letterbox(img0, 640, stride32, autoTrue)[0] img img[:, :, ::-1].transpose(2, 0, 1) # BGR 转 RGB, HWC 转 CHW img np.ascontiguousarray(img) img torch.from_numpy(img).to(device).float() / 255.0 img img.unsqueeze(0) with torch.no_grad(): pred model(img)[0] # conf_thres 0.25, iou_thres 0.45 是钢材缺陷的常用起点 pred non_max_suppression(pred, 0.25, 0.45, classesNone, agnosticFalse) for det in pred: if det is not None and len(det): det[:, :4] scale_coords(img.shape[2:], det[:, :4], img0.shape).round() for *xyxy, conf, cls in det: print(int(cls), float(conf), [int(v) for v in xyxy])attempt_load会自动处理 EMA 权重比手动torch.load更稳。letterbox保持长宽比补边避免直接 resize 把细长缺陷压变形。conf_thres设 0.25 是召回优先产线上如果误报多再往上调但别超过 0.5否则小缺陷全丢。scale_coords把框映射回原图尺寸漏了这步画出来的框位置全错。4. 钢材缺陷检测训练避坑5 个真实翻车记录4.1 验证集 mAP 很高上线全是误报现象本地验证 mAP0.5 到 0.9产线跑起来正常钢卷上到处是框。原因数据集划分时同一卷钢的图像被随机分到了训练和验证集模型记住了这批钢的纹理特征换批次就失效。解决按钢卷号或采集时间段划分数据集验证集必须来自训练时没见过的批次重新训一版再看指标。4.2 训练 loss 不降日志里大量 ignored现象box_loss一直卡在 0.8 以上日志频繁出现ignored或标签数为 0。原因标签 txt 里的类别索引超出了nc范围或者图片路径和标签路径没对上dataloader 找不到标签就当空图处理。解决写个脚本遍历所有 txt检查每行第一个数是否小于nc再核对 images 和 labels 目录的文件名是否一一对应缺标签的图要么补标要么从训练列表里删掉。4.3 小缺陷召回极低大缺陷正常现象裂纹、麻点这类小目标 AP 只有 0.2斑块、划痕正常。原因输入尺寸 640 下小缺陷下采样后只剩几个像素加上默认 anchor 对小目标不友好。解决把--img-size提到 960 或 1280同时用--hyp里调小anchor_t或重新聚类 anchor如果显存不够改用yolov7-tiny加切片推理但切片边界要留重叠区否则跨切片的缺陷会被切断。4.4 换机器后权重加载报 missing keys现象attempt_load提示大量missing keys和unexpected keys。原因训练用的 cfg 和推理时的模型结构不一致常见于自己改了 yaml 里的通道数或检测头。解决推理时用训练时同一份 cfg或者直接用best.pt里保存的model结构不要手动重建模型再 load_state_dict。如果确实要改结构先确认改动的层名和权重键的对应关系。4.5 推理速度远低于预期现象单张图推理要 200ms 以上达不到产线节拍。原因没开半精度、没做 warmup、或者 NMS 在 CPU 上跑。解决推理时加model.half()并把输入转half()前 10 次推理做 warmup 不计时NMS 用torchvision.ops.nms的 GPU 版本。另外letterbox的autoTrue会取最小补边比固定尺寸略快但差异不大主要瓶颈还是在模型本身。5. 把钢材缺陷权重用稳阈值标定与增量迭代的实操技巧训练完一版权重只是开始真正决定产线可用性的是阈值标定和后续迭代。我一般会在验证集上跑一遍test.py导出每张图的预测结果然后按类别画 PR 曲线选一个让误报和漏检平衡的conf_thres。钢材缺陷里不同类别的合适阈值差别很大裂纹通常要低阈值保召回斑块可以高阈值压误报所以别用一个全局阈值按类别分别设。# 按类别标定阈值统计每类在不同 conf 下的 precision/recall import numpy as np def calibrate_by_class(preds, gts, classes, thresholdsnp.arange(0.1, 0.9, 0.05)): result {} for c in classes: best_f1, best_t 0, 0.25 for t in thresholds: tp fp fn 0 for img_id in preds: p [x for x in preds[img_id] if x[cls] c and x[conf] t] g [x for x in gts[img_id] if x[cls] c] # 简化匹配IoU 0.5 视为命中实际用匈牙利匹配更准 matched 0 for pg in g: if any(iou(pg[box], pp[box]) 0.5 for pp in p): matched 1 tp matched fn len(g) - matched fp len(p) - matched prec tp / (tp fp 1e-6) rec tp / (tp fn 1e-6) f1 2 * prec * rec / (prec rec 1e-6) if f1 best_f1: best_f1, best_t f1, t result[c] {thres: round(best_t, 2), f1: round(best_f1, 3)} return result这段逻辑是按类别扫阈值用 F1 选最优点。实际产线还要考虑业务代价漏检一条裂纹的代价远高于多报几个斑块所以裂纹的阈值可以再往下压 0.05。iou函数自己实现或用torchvision的都行关键是匹配规则要和评估脚本一致否则标出来的阈值和实际表现对不上。增量迭代上我的习惯是每上线一批新钢种或新批次先抽 100 到 200 张跑推理把置信度在 0.15 到 0.35 之间的样本挑出来人工复核确认是漏检还是误报然后把这些难例加进训练集用当前权重做初始化再训 20 到 30 轮。这样迭代比重新训全量快得多也不容易把已经学好的类别带偏。权重文件按steel_v7_20240115.pt这种带日期的命名存好出问题能回滚这就是产线模型的后悔药。最后说个血泪经验别在训练完就急着把权重丢上线先在产线相机上采一批实时图跑离线验证确认光照、镜头、传输链路都没引入新分布再小流量灰度。钢材缺陷检测没有一劳永逸的权重只有持续迭代的流程。希望帮到你。本文还有配套的精品资源点击获取