
简介面向钢材表面缺陷检测这一工业视觉场景这份资料包整合了YOLOv5模型训练成果与配套数据集适合目标检测初学者、工业质检项目开发者直接上手或微调复用。模型已针对几种典型缺陷完成训练附带PR曲线、loss曲线等评估记录便于了解收敛效果数据集由LabelImg标注图片为jpg格式标签则同时提供xml与txt两种格式分别存放于对应文件夹兼容主流训练流程。资源压缩包内共164个文件涵盖yaml配置、Python脚本、训练权重pt、标注xml及结果csv等类型整体约106.77MB目录结构与YOLOv5工程一致便于按模块对照学习。已有2275人学习浏览资料附有检测结果参考信息便于对比模型表现。获得全套模型与标注数据后可直接进行推理验证或继续迭代训练省去从零标注和调参的时间成本。1. 钢材缺陷检测用YOLOv5落地权重和数据集才是决定成败的部分传统质检靠人工目检一条产线四位质检员三班倒每人每天看成千上万张图漏检率始终压不下去。换上YOLOv5之后同样的相机、同样的工位单张推理时间压到几十毫秒一批缺陷样本就能把模型拉起来。这里有个反直觉的事实真正决定检测效果的不是网络结构改了多大而是缺陷检测权重和数据集是否匹配你产线的真实缺陷形态。YOLOv5是目标检测里落地最顺手的框架之一配套的预训练权重能直接迁移但钢材表面的麻点、划伤、氧化皮这些缺陷纹理和光照变化极大通用的COCO权重直接上场对细小缺陷基本不闻不问。这篇文章写给想把这套方案真正跑通、而不是只在笔记本上训练个demo的工程人员从数据集构建、训练调参、部署推理到踩坑复盘整套路径都按可以复现的标准来。2. 把原始图像变成YOLOv5能吃的数据集标注格式转换与增强2.1 钢材缺陷数据集的原始形态公开集与现场采集的差异钢材表面缺陷检测的数据来源主要有两条路。第一条是公开的钢材表面缺陷数据集这类数据通常已经把图片分类整理好有的甚至带了缺陷的类别标签。常见公开钢材表面缺陷数据集大部分是按六类左右划分麻点、划伤、夹杂、氧化皮、裂纹、压入氧化铁皮。用这类数据起步训练是可以的但要注意它们大多是在实验室条件下拍的背景干净、光照均匀缺陷尺寸也偏大。真到了产线现场钢板表面有油污、水渍、氧化色光线从不同角度打过来原来在公开集上效果很好的权重到了现场往往直接掉两三个点。第二条路是现场采集。一般做法是在质检工位上加一台工业相机在产线运行过程中连续采集图像然后人工筛选出含有缺陷的图再逐张标注。现场采集的数据才是真正能打的弹药但成本也高一条产线跑一天可能才能攒出几百张有效缺陷图而且还要面对缺陷类别不均衡的问题——划伤最常见夹杂和裂纹可能一周也见不到几次。最现实的策略是公开数据集做预训练现场数据做迁移学习微调两条路组合起来用。2.2 从VOC/COCO标注转到YOLO格式转换脚本与边界处理YOLOv5要求的标注格式是每张图对应一个同名txt文件每行是“类别id 中心点x 中心点y 宽度 高度”全部归一化到0到1。而公开数据集给的一般是VOC的XML或者COCO的JSON格式不能直接喂给YOLOv5必须先转。转换脚本是绕不开的第一道工序。import os import xml.etree.ElementTree as ET from pathlib import Path # classes.txt: 每行一个类别名, 顺序就是类别id def voc_to_yolo(xml_path, classes, out_label_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 边界值钳制到图像范围内, 防止越界标签 xmin max(0.0, min(xmin, w_img - 1)) xmax max(0.0, min(xmax, w_img - 1)) ymin max(0.0, min(ymin, h_img - 1)) ymax max(0.0, min(ymax, h_img - 1)) # 转YOLO格式: 中心点坐标 宽高, 全部除以图像宽高归一化 x_center (xmin xmax) / 2.0 / w_img y_center (ymin ymax) / 2.0 / h_img w (xmax - xmin) / w_img h (ymax - ymin) / h_img # 过滤掉宽或高为0的无效框 if w 0 or h 0: continue lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: return None out_path Path(out_label_dir) / (Path(xml_path).stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) return out_path这个脚本的核心逻辑是解析XML里的bndbox坐标再按图像宽高做归一化。需要注意两点一是xmin、xmax这类值在有些标注工具里可能超出图像边界必须钳制否则训练时YOLOv5的标签增强会把越界框处理成异常样本二是类别映射靠classes列表的索引列表顺序一旦定了就别改否则后面用权重推理时标签全对不上。另外COCO格式转YOLO的思路是一样的只是解析JSON的嵌套结构换成pycocotools的api读取每个annotation的bbox字段x,y,w,h再换算成中心点格式。2.3 数据增强不能直接照搬哪些变换适合钢材表面缺陷YOLOv5内置了比较强的数据增强但钢材表面缺陷和自然场景目标有本质差异——缺陷是细长的划痕、小面积麻点和背景纹理混杂在一起。默认增强里有些操作胶着。例如mosaic拼图它把四张图拼在一起训练对提升小目标检测有好处但钢材图像本身纹理重复拼接后容易让模型学到“纹理断裂的地方就是缺陷”推理时误检率会上去。我一般对钢材表面缺陷的数据集做如下配置开启hsv_h、hsv_s、hsv_v的小幅随机扰动模拟现场光照波动开启scale和fliplr模拟相机安装角度偏差关闭mosaic或只在前30个epoch开启。理由是钢材表面的缺陷尺度跨度不大mosaic合成图里的小缺陷会被缩得过于离谱反而干扰模型学习真实缺陷的形态。translate和perspective保持默认即可augment参数在YOLOv5的data yaml里的写法是增强的开关不是数值。还有一类增强是加噪声和模糊。钢材表面有颗粒感摄像头传感器也有噪点适度添加高斯噪声可以让模型更鲁棒但添加强度要控制好。在图里直接把缺陷淹没的增强就是反向操作了。2.4 数据集划分与类别均衡训练/验证/测试怎么切YOLOv5的数据集目录结构一般是images和labels两个大目录下边再分train和val两套子目录。划分时建议按90%训练、10%验证来切测试集另外单独留一批现场采集图训练阶段完全不碰。切分必须保证类别分布均匀尤其是缺陷样本少的类别。如果直接随机切可能出现的惨案是小样本类别全部掉进训练集验证集一个没有训练过程看着指标很好看但实际上模型没见过这个类别在真实分布里的表现。import random from pathlib import Path def split_dataset(img_dir, label_dir, train_ratio0.9, seed42): random.seed(seed) imgs sorted(Path(img_dir).glob(*.jpg)) sorted(Path(img_dir).glob(*.png)) random.shuffle(imgs) n_train int(len(imgs) * train_ratio) train_imgs imgs[:n_train] val_imgs imgs[n_train:] # 检查每个类别在train和val中都有样本 def count_classes(img_list): cls_count {} for img_path in img_list: label_file Path(label_dir) / (img_path.stem .txt) if not label_file.exists(): continue for line in label_file.read_text().strip().splitlines(): cls_id int(line.split()[0]) cls_count[cls_id] cls_count.get(cls_id, 0) 1 return cls_count train_cls count_classes(train_imgs) val_cls count_classes(val_imgs) for cls_id in set(train_cls.keys()) | set(val_cls.keys()): if train_cls.get(cls_id, 0) 0 or val_cls.get(cls_id, 0) 0: print(f类别 {cls_id} 在train/val中分布不均: train{train_cls.get(cls_id,0)}, val{val_cls.get(cls_id,0)})这段代码在切分后检查每个类别的分布如果发现某个类别在验证集里一个样本都没有就需要调整随机种子重新切或者手动抽取几张带该类别的图像放进验证集。另一个更省事的做法是先用这个脚本跑一次切分再用YOLOv5训练时观察验证集的每个类别指标如果某个类别的召回率异常低先检查验证集里这个类别的样本量是不是太少。3. 从预训练权重到迁移学习yolov5s训练命令与超参数3.1 选哪个YOLOv5规格s/m/l的取舍与预训练权重下载YOLOv5的官方权重有n、s、m、l、x五档钢材表面缺陷检测场景里最常用的是yolov5s和yolov5m。s的参数量小推理速度快在边缘设备上能跑到实时m的精度更高但显存占用和推理延迟都往上走。选型的判断标准不是盲目上大的而要看你的缺陷目标尺寸和产线对速度的要求。如果检测目标是几个像素宽的细小划痕s模型的特征提取能力往往不够m是底线。如果缺陷普遍是黄豆大小的夹杂和麻点s就够用。预训练权重下载下来后第一件事是校验能不能加载。注意YOLOv5官方权重和源码版本有对应关系v6.0的源码配v6.0的权重如果拿v5.0的权重加载到v6.0的代码里结构对不上会直接报错。解决办法是用官方提供的对应版本权重或者在你自己的训练脚本里让模型随机初始化不推荐收敛慢且精度低。用预训练权重做迁移学习的做法是让模型保留在COCO上学到的通用特征提取能力只替换最后的检测头。3.2 训练一条最稳的命令参数逐项解释训练命令的写法直接决定你能复现什么结果。把YOLOv5仓库克隆下来进入目录按下面的命令启动训练python train.py \ --data steel.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 120 \ --workers 8 \ --device 0 \ --project runs/steel_detect \ --name exp_steel_v1 \ --patience 20先解释每一行。--data指定数据集配置文件steel.yaml里要写清楚train、val路径和类别数量、类别名。--weights填预训练权重的路径yolov5s.pt放在YOLOv5主目录下。--img是训练输入尺寸640是默认值如果你的缺陷很小可以试1280但显存占用会翻倍。--batch-size按显卡显存定16G显存跑yolov5s640用16是安全的。--epochs我一般设120钢材缺陷类别少过拟合比欠拟合更常见。--patience是早停耐心值20个epoch内验证集mAP没提升就停止这个参数能避免你挂机浪费几天。steel.yaml的关键内容长这样:# steel.yaml train: ./datasets/steel/images/train val: ./datasets/steel/images/val nc: 6 names: [pitted_surface, inclusion, scratches, patches, rolled-in_scale, crazing]注意names列表的顺序一定要和标注txt文件里类别id的顺序一致不能按字母排重排一遍。3.3 训练过程监控别只盯着loss曲线很多人训练开始后只看终端里打出来的loss数值这是不够的。终端输出每行都有三个关键指标box_loss、obj_loss、cls_loss但最重要的判断依据是验证集上的P、R、mAP50这几个指标。命令行里最直观的是看训练完每个epoch打印的那行指标。如果P和R都低说明模型没学到缺陷特征如果P高R低说明检测框宁缺毋滥漏检严重如果P低R高说明模型在疯狂误检。更直观的方式是打开TensorBoard。启动训练后YOLOv5会自动开启日志服务用浏览器访问本机端口就能实时看到每张训练图的标注框、预测框和置信度。观察样本的可视化往往比看曲线更能发现问题——例如标签框错位、类别标注错误、增强策略产生畸形样本这些在曲线上要绕半天才能定位。等训练完成后在runs/steel_detect/exp_steel_v1/目录下会生成confusion_matrix.png和results.png直接看混淆矩阵判断哪些类别容易互相混在一起。3.4 epoch、batch和anchor三个最影响结果的旋钮样本量在几百张时epoch加到200以上基本就开始过拟合了。钢材表面缺陷的类别特征强模型在60到100个epoch之间就能收敛到不错的效果120是相对安全的数字。batch-size的影响更多在训练稳定性上batch太小比如2或4会让BN层的统计量波动过大loss曲线剧烈震荡这时优先调大batch而不是调学习率。如果显存不够降低batch的同时要相应降低学习率否则前几个epoch就会出现loss爆炸。anchor的自动计算是YOLOv5的一个玄学点。官方默认会按你的数据集形状自动聚类anchor训练启动时终端会打印AutoAnchor的结果。但钢材缺陷的长宽比很极端——划伤可能是1:10的长条形麻点是1:1的圆点。默认anchor跑出来的效果就是把细长缺陷拆成好几段来框。解决方法是显式关闭自动anchor的重新计算或者检查data yaml里anchors这个字段手动按数据集的缺陷长宽比设置。用下面的命令先统计你的标签框长宽比分布import numpy as np from pathlib import Path label_dir Path(./datasets/steel/labels/train) ratios [] for txt in label_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): w float(line.split()[3]) h float(line.split()[4]) if w 0 and h 0: ratios.append(w / h) print(f长宽比分布: p10{np.percentile(ratios, 10):.2f}, p50{np.percentile(ratios, 50):.2f}, p90{np.percentile(ratios, 90):.2f})统计出来的p10和p90范围就是anchor长宽比的合理先验区间。把算出来的形状写进yaml文件的自定义anchors字段再训练你会发现细长缺陷的召回率能提几个点。4. 把训练好的权重部署到产线ONNX转换与后端推理4.1 为什么要转ONNX而不是直接用PyTorch权重训练完拿到的是PyTorch的pt权重但产线上一般不会装一整套PyTorch环境。常见选择是转成ONNX再用ONNX Runtime推理或者转成TensorRT在NVIDIA显卡上跑到极致性能。ONNX是中间表示好处是跨平台、跨语言C和Python都能调用也方便后续量化压缩。PyTorch直接部署也不是不行但环境重、启动慢、版本依赖容易出问题。产线设备一般是一台工控机配一张显卡系统环境常年不更新能少装一个框架就少装一个。4.2 转出可用的ONNX导出脚本与opset设置YOLOv5官方仓库自带导出脚本命令行操作就可以。以下是直接可用的命令python export.py \ --weights runs/steel_detect/exp_steel_v1/weights/best.pt \ --include onnx \ --opset 12 \ --img 640 \ --batch-size 1 \ --simplify解释关键参数--include onnx表示只导出ONNX格式--opset 12是算子集的版本ONNX Runtime 1.10以上都支持太低有些算子导不出来太高在旧设备上跑不了--img 640必须和训练时一致否则推理结果会偏移--simplify会调用onnx-simplifier做计算图优化把一些冗余算子合并掉。导出完成后在同目录下生成best.onnx大小和pt差不多。转换完必须验证输出是否正常。用下面的脚本加载ONNX模型跑一张训练图import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) img cv2.imread(test.jpg) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img img.astype(np.float32) / 255.0 img np.transpose(img, (2, 0, 1)) input_tensor np.expand_dims(img, axis0) outputs session.run(None, {session.get_inputs()[0].name: input_tensor}) print(f输出shape: {[o.shape for o in outputs]})如果输出的shape不是[1, 25200, 85]或者[1, 84, 8400]这种先检查opset和导出版本是否匹配。最常见的坑是用新版本YOLOv5导出时模型输出是三个尺度的特征图而后处理代码还是老的单输出写法直接导致推理结果全乱。4.3 后处理把模型输出变成缺陷框ONNX模型输出的原始张量不能直接画框必须做解码和NMS。YOLOv5的输出头包含回归框信息x,y,w,h的偏移量、目标置信度和类别概率。NMS非极大值抑制用来去掉重复框保留每类缺陷最准确的一个。下面是一个标准的后处理实现可以直接用在ONNX Runtime的推理流程里import cv2 import numpy as np def yolo_decode(output, conf_thres0.25, iou_thres0.45): # 新版本YOLOv5输出是 [1, 84, 8400] 的结构, 先转置 pred output[0] if isinstance(output, list) else output if pred.shape[1] 84 and pred.shape[2] 8400: pred np.transpose(pred, (0, 2, 1)) # [1, 8400, 84] # 第一个维度是batch, 单张图直接取第0个 pred pred[0] # [8400, 84] # 前4列是框坐标, 第5列是目标置信度, 其余是类别得分 boxes pred[:, :4] obj_conf pred[:, 4:5] cls_conf pred[:, 5:] # 最终置信度 目标置信度 x 类别置信度最大值 cls_max_conf cls_conf.max(axis1, keepdimsTrue) cls_ids cls_conf.argmax(axis1, keepdimsTrue) final_conf obj_conf * cls_max_conf # 用置信度阈值过滤 keep final_conf.flatten() conf_thres boxes, final_conf, cls_ids boxes[keep], final_conf[keep], cls_ids[keep] # 框坐标还原到原图尺寸这里假设输入是640x640, 输出也是这个坐标空间 # 注意: 这里boxes的坐标在0~640之间, 要缩放到实际图像尺寸 if len(boxes) 0: return np.empty((0, 6)) # 转换为(x, y, w, h)格式方便NMS处理 x_center, y_center, w, h boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3] x1 x_center - w / 2 y1 y_center - h / 2 x2 x_center w / 2 y2 y_center h / 2 nms_boxes np.stack([x1, y1, x2, y2], axis1) indices cv2.dnn.NMSBoxes(nms_boxes.tolist(), final_conf.flatten().tolist(), conf_thres, iou_thres) if len(indices) 0: return np.empty((0, 6)) result [] for i in indices: i i[0] if isinstance(i, np.ndarray) else i result.append([nms_boxes[i][0], nms_boxes[i][1], nms_boxes[i][2], nms_boxes[i][3], final_conf[i][0], cls_ids[i][0]]) return np.array(result)这个后处理函数是部署的核心90%的推理问题都出在解码这一段。我在实际项目中见过最典型的错误是直接把模型的原始输出当坐标用画出来的框要么全部堆在图像一角要么全部是反的。原因是YOLOv5在训练时对框的编码是相对于每个网格单元的偏移量必须按上面的方式解码。另外这里的坐标空间是模型输入尺寸640x640要映射回原始图像分辨率需要在调用后处理之前记录原始图片的缩放比例并对x1、y1、x2、y2做对应的缩放。4.4 产线部署的稳定性和速度从ONNX Runtime到TensorRTONNX Runtime在CPU上跑yolov5s大概200到300毫秒GPU上能到30毫秒左右。如果产线要求60fpsONNX Runtime在普通工控机上还是紧巴巴的需要换TensorRT来做int8量化推理延迟能压到10毫秒以内。转换流程也是官方工具链先把pt转ONNX再用trtexec把ONNX转成TensorRT engine。但TensorRT的int8量化是个坑直接量化模型精度可能掉3到5个点需要在量化时提供校准数据集。校准数据集从训练集里抽几百张有代表性的缺陷图让TensorRT统计激活值的分布。这一步做得好精度损失能控制在1个点以内。另一个稳定性的坑是模型预热。ONNX Runtime在第一次推理时会有比较长的初始化时间这不奇怪。但在产线程序里如果每启动一次就重新加载模型并推理第一帧这帧的时间就会超时。常见做法是程序启动时先加载模型然后跑一张空图预热之后再进入正式的检测循环。此外模型的输入尺寸要和训练时的预处理完全一致——包括resize的方式和像素归一化的公式。YOLOv5用的是等比缩放加灰色填充如果你在部署代码里用了直接拉伸检测框的位置会整体偏移。5. 钢材缺陷检测实测避坑五个高频问题与排查5.1 小缺陷漏检严重模型对小目标不敏感现象细小的划痕、麻点在训练集里明明标注了评测时召回率就是上不去。推理时小于一定像素宽度的缺陷完全检测不到。原因一是模型的输入尺寸太小。640的输入如果缺陷在原始图像里只有十几个像素缩放到640后可能只剩2到3个像素特征图上的响应基本被背景吞掉。二是下采样次数过多YOLOv5的骨干网络会做5次下采样小目标对应的特征图在后两层的位置基本丢失了。解决优先把训练尺寸调大到1280试试。显存不够的话可以试多尺度训练YOLOv5的--multi-scale参数会在训练中随机缩放输入尺寸提升模型对小目标的鲁棒性。另一个方向是启用SAHI切片推理把原图切块后分别检测再合并结果但会增加推理时间适合离线检测场景线体上速度要求高的话不太合适。5.2 误检一堆把正常纹理当成了缺陷现象模型在验证集上指标还行到现场一跑钢板表面的正常纹理、水渍、光线反光全被框出来了。框的数量比真实缺陷多十倍。原因训练数据的缺陷多样性不够。公开数据集的缺陷形态和现场有差异模型学到的是一种泛化的纹理异常模式而不是具体的缺陷模式。尤其当现场图像里包含训练集没见过的纹理时任何偏离背景模式的地方都会被当成目标。解决加现场负样本重新训练。收集一批确认无缺陷的现场图像直接作为没有标签的图加入训练集。YOLOv5支持空标签的图片训练时会把这些图当作负样本强制模型学习“这些纹理不是缺陷”。这个做法是提升误检率最快的手段比调置信度阈值有用得多。推理时的置信度阈值也可以从默认的0.25往上调到0.4左右但只是缓解治标不治本。5.3 训练Loss不降或震荡超参数不对现象loss曲线在前20个epoch内没有明显下降或者下降后突然拉升然后反复震荡。原因最常见的是学习率设置过高。YOLOv5默认的学习率是0.01但这个值是在COCO这种大规模数据集上标定的。钢材缺陷数据集只有几百到几千张学习率过高会让权重在最优解附近来回跳跃无法收敛。另一个原因是batch-size和显存不匹配导致训练中断YOLOv5会尝试自动降batch而降下来之后学习率没有等比调整。解决把--lr0从0.01往下调调到0.001到0.005之间观察loss是否稳定下降。batch-size降到4或以下时学习率要对应降到0.001以下同时把--weight-decay调高到0.0005防止过拟合。如果loss在初始阶段就直接暴增检查数据集的标签有没有异常值比如归一化坐标超过1的框。5.4 现场光照一变就失效数据分布偏移现象同一套权重白天检测效果正常傍晚车间灯光开启后误检和漏检同时上升或者换了一条产线之后效果大幅退化。原因钢材表面是强反光材质光照角度和强度的变化会彻底改变缺陷的表观特征。训练集里的光照条件是单一的模型本质上只学会了在特定亮度分布下识别缺陷。解决现场采集数据时要有意识覆盖不同光照条件。白天、夜晚、灯光全开、灯光半开每种情况都采一批。如果做不到多时段采集就在数据增强里把hsv_v的范围加大让训练图像的整体亮度分布变宽。另一个有效做法是推理前先做图像预处理——计算整张图的平均亮度做一次线性拉伸后再送入模型把不同光照下的图像归一化到相近的亮度范围。5.5 部署后检测框乱跳帧间推理结果不稳定现象同一块缺陷在连续的几帧画面里这一帧检测到了下一帧消失再下一帧又出现。框的位置也在小幅摆动。原因推理时每一帧都独立做NMS没有做时序上的平滑。缺陷目标在移动的钢板上每一帧的位置都在变但只要缺陷本身是真实的它的位置变化是连续的完全不连续就可能是误检或漏检。解决加上一个简单的帧间追踪逻辑。用最近三到五帧的检测框做重叠度判断如果当前帧的框和前一帧没有重叠先不直接丢弃而是标记为新目标连续三帧都检测不到再判定为消失。这个逻辑用OpenCV的Tracker或自己维护一个目标列表都可以实现。更重要的是明确区分产线场景是连续检测还是定格拍照——连续检测时用上述的策略定格拍照时每帧独立判断即可不需要追踪。6. 模型的召回率瓶颈用数据清洗和难例挖掘再提一档训练跑完、部署上线后不要以为模型效果就定死了。钢材表面缺陷检测的长尾效应非常明显——最经典的几个类别划伤、麻点很快就能到90%以上的召回率但夹杂、裂纹这类低频缺陷可能永远卡在60%左右。把这一档提上去的关键不在模型结构而在数据策略。一个有效的做法是难例挖掘。把线上推理中所有漏检的图像模型没框出来但人工复核发现确实有缺陷全部收集起来重新标注后加入训练集。具体来说每天从漏检库里抽取一部分和原训练集合并后做一轮增量训练轮次控制在20个epoch以内学习率降到初始值的十分之一。这个循环做两到三轮之后模型对低频缺陷的召回率会有肉眼可见的提升。另一个需要养成的习惯是先清洗数据再开训。数据集里常有误标注的样本——把氧化色当成划伤、把纹理边界当成裂纹。带错标签训练的效果比不训练还糟糕它会直接拉乱混淆矩阵。每轮训练前花两小时把训练集抽查一遍重点关注类别边界模糊的样本以及和上一轮模型预测结果冲突的样本。我用text/plain的工具打开label文件检查异常值坐标也会直接可视化地查看标注框和缺陷实际位置的偏差误差超过5个像素的都必须修正。还有一招对现场特别有用建一份每日回放的回归测试集。固定抽取各产线日常拍摄的图像按天归档到独立的测试目录。每次调整模型或权重后拿这份固定测试集跑一遍对比mAP、误检率、漏检数这三个数的变化趋势。不要信任单次采样的实验数据钢材缺陷检测的波动本来就大一次跑完看着涨了零点几个点的改动隔天重复测试可能就跌回去了。多跑三轮取中位数判断效果稳定与否再决定是否上线。做钢材缺陷检测这几年我最大的教训是模型迭代的速度赶不上产线变化的速度——换了光源、换了钢材牌号、换了相机位置一切都要重新验证。所以现在我做任何改动都先跑回归测试再小的改动也要在测试集上把原来的成绩复现一遍。这是用翻车换来的习惯。希望帮到你。本文还有配套的精品资源点击获取