☰
钢材缺陷检测实战:VOC/COCO转YOLO格式与训练指南
2026/9/28 17:06:36 网站建设 项目流程

简介:YOLO谢韦尔钢材缺陷检测数据集,面向目标检测学习者与工业质检场景,包含5000张真实场景钢材表面图片,覆盖多样缺陷类型。数据采用labelimg标注,提供VOC、COCO、YOLO三种格式标签,分别存放于对应文件夹,可无缝接入YOLO系列模型直接训练。压缩包共2000个文件,以1986个xml标签、5个txt说明、3个py脚本及6个html教程为主,整体大小61.48MB,结构清晰便于按需使用。包内附带YOLO环境搭建教程(Windows与Linux版本)、训练案例教程及数据集划分脚本,可自行生成训练集、验证集、测试集并完成从环境配置到模型训练的全流程实操。适合需要规范化钢材缺陷检测数据、快速上手YOLO训练流程的研究者或开发者参考使用,目前已有397人学习下载。

1. 谢韦尔钢材缺陷检测:先认清你要训的是什么

把“YOLO谢韦尔钢材缺陷检测数据集”这个压缩包解压之后,你手里其实是三样东西:5000张带缺陷的钢材表面图像、同一套标注在 VOC / COCO / YOLO 三种格式下的标签文件、以及配套的划分脚本和训练教程。对做工业视觉的人来说,这个组合很友好,因为它省掉了最容易被卡住的“数据格式互相折腾”阶段,能让你直接跳到模型训练这件事上。

这个数据集里最常见的类别包括夹杂、划痕、麻点、氧化皮等几类表面缺陷,背景是纹理复杂的钢板,缺陷面积小、形态不规则,正好是你检验 YOLO 系列模型在“小目标 + 低对比度”场景下到底行不行的试金石。适合谁来用?刚接触目标检测的新手可以拿它练手,快速跑通“数据 → 模型 → 验证”的完整链路;有经验的人则可以拿它来测不同 YOLO 版本在工业场景的召回率和误检率。这篇文章就顺着这个包里的内容,从标签格式、划分脚本、训练参数一路写到排错和验收。

2. VOC、COCO、YOLO 三种标签格式:从数据组织到格式转换

2.1 三种格式的本质差异

很多人拿到“三种格式标签”时第一反应是困惑:不是有 YOLO 格式就能训练吗?为什么还要给 VOC 和 COCO?其实这三种格式分别对应了不同工具链的约定,你可以把它们理解成三种不同语言描述的同一个事实。VOC 格式以图像为索引,一张图对应一个 XML 文件,标注信息写在<object>节点里,坐标是绝对值xmin, ymin, xmax, ymax,可读性最好,但文件多且冗余。COCO 格式把整个数据集的所有标注集中到一个 JSON 里,按images、annotations、categories三张表组织,坐标同样是绝对值,格式级联,适合用来做跨模型对比或对接 Detectron2、mmdetection 这类框架。YOLO 格式则是每张图对应一个 txt 文件,每行写成class_id x_center y_center width height,其中坐标和宽高全部归一化到 0~1,模型训练时读取效率最高,也是 YOLO 系列原生的加载方式。

这里有一个容易忽略的点:COCO 的 bbox 描述的是“左上角坐标 + 宽度 + 高度”,即xywh;VOC 的 bbox 是“左上角 + 右下角”,即xyxy;YOLO 的 bbox 是“中心点 + 归一化宽高”。如果转换时没有先统一坐标系,训练出来的模型会非常不稳定,甚至从头到尾不收敛。下面我给出最常用的两段转换脚本,分别解决 VOC 转 YOLO 和 COCO 转 YOLO 的问题。实际项目中你大概率只用到其中一段,但最好都保留在工具脚本里。

2.2 用脚本把 VOC 转成 YOLO txt

当你解压后发现标签目录里只有 VOC 格式的 XML,而你想直接跑 YOLOv8 时,可以用这个脚本先把 XML 全部转成 txt。它的输入是一个 VOC 标签目录,输出是同名 txt 文件,并把类别顺序写入一个classes.txt。注意类别顺序必须和后续训练data.yaml里的names一一对应,否则训练出来的模型类别是乱的。

import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree = ET.parse(xml_path) root = tree.getroot() img_width = int(root.find('size/width').text) img_height = int(root.find('size/height').text) txt_name = os.path.splitext(os.path.basename(xml_path))[0] + '.txt' lines = [] for obj in root.iter('object'): cls = obj.find('name').text if cls not in class_names: continue cls_id = class_names.index(cls) bbox = obj.find('bndbox') xmin = float(bbox.find('xmin').text) ymin = float(bbox.find('ymin').text) xmax = float(bbox.find('xmax').text) ymax = float(bbox.find('ymax').text) # 归一化中心点、宽高 x_center = ((xmin + xmax) / 2) / img_width y_center = ((ymin + ymax) / 2) / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height # 防御性截断,防止浮点误差导致越界 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(os.path.join(out_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) # 使用方式:把下面两行换成你解压后的实际路径 class_names = ['Crazing', 'Inclusion', 'Patches', 'Pitted_Surface', 'Rolled_In_Scale', 'Scratches'] os.makedirs('labels_yolo', exist_ok=True) for xml_file in os.listdir('labels_voc'): if xml_file.endswith('.xml'): voc_to_yolo(os.path.join('labels_voc', xml_file), 'labels_yolo', class_names)

这段脚本里有个容易被忽略的细节:XML 里的类别名必须和class_names完全一致,包括大小写和下划线,否则会被continue静默跳过。另一个细节是防御性截断,因为部分标注工具生成的坐标会有微小越界,比如xmax比图像宽度大 1 像素,如果不做min/max截断,训练时 YOLO 会报“bbox out of image bounds”的警告,严重时直接丢样本。参数说明:class_names的顺序决定了模型输出的类别编号,比如索引 0 是 Crazing,训练完names: ['Crazing', ...]也要保持相同顺序。

2.3 COCO 到 YOLO 的转换与类名检查

COCO 转 YOLO 稍微复杂一点,因为需要先读 JSON 里的images、annotations、categories三张表,建立image_id → 文件名的映射,再逐条取 bbox。下面这段脚本会把 COCO 标注文件按图片拆成多个 txt。

import json import os def coco_to_yolo(coco_json, out_dir, img_dir): with open(coco_json, 'r') as f: data = json.load(f) img_id_to_name = {img['id']: img['file_name'] for img in data['images']} cat_id_to_cls = {cat['id']: cat['name'] for cat in data['categories']} # 用类别名排序来固定索引,而不是用 category_id cls_names = sorted(cat_id_to_cls.values()) os.makedirs(out_dir, exist_ok=True) anns_map = {} for ann in data['annotations']: img_id = ann['image_id'] anns_map.setdefault(img_id, []).append(ann) for img_id, anns in anns_map.items(): img_name = img_id_to_name[img_id] img_path = os.path.join(img_dir, img_name) # 如果图片是灰度图或 PNG 带透明通道,也要在这里统一处理 from PIL import Image w, h = Image.open(img_path).size txt_path = os.path.join(out_dir, os.path.splitext(img_name)[0] + '.txt') lines = [] for ann in anns: cat_name = cat_id_to_cls[ann['category_id']] cls_id = cls_names.index(cat_name) x, y, bw, bh = ann['bbox'] x_center = (x + bw / 2) / w y_center = (y + bh / 2) / h nw = bw / w nh = bh / h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}") with open(txt_path, 'w') as f: f.write('\n'.join(lines)) # 把类别列表也写出来,方便后续检查 with open(os.path.join(out_dir, 'classes.txt'), 'w') as f: f.write('\n'.join(cls_names))

这段脚本里我用sorted(cat_id_to_cls.values())来生成类别顺序,而不是直接按category_id排序。原因是很多自制 COCO 数据集的类别 id 不连续,比如类别 id 是 1、3、5,但实际只有三个类,直接用 id 当索引会让训练端类别对不上。参数说明:这段脚本假设图片文件真实存在且可以通过PIL.Image打开,如果遇到损坏的图片,训练时会在这里先暴露出来。这里的img_dir参数只需要能在转 txt 时取宽高,后续训练仍然只读图片路径。

3. 划分脚本:让 train/val/test 既均衡又可复现

3.1 划分脚本要解决的三个问题

很多入门教程会说“随机抽 80% 当训练集,20% 当验证集”,但这个做法在钢材缺陷检测里会翻车。第一个问题是类别不平衡:谢韦尔数据集中不同缺陷的出现频率差异很大,纯随机划分可能让某个类别在验证集里只出现一两次,最后的 mAP 波动大得离谱。第二个问题是图片泄漏:如果同一张钢板被切成多张局部图,并且这些局部图同时出现在训练集和验证集里,模型会通过背景记忆而不是缺陷特征来得分,指标虚高但上线就崩。第三个问题是可复现性:每次跑脚本随机数种子不一样,两次训练的数据分布不同,你很难判断模型精度的变化到底是调参带来的还是划分带来的。

所以一个合格的划分脚本至少要做三件事:按类别比例分层抽样、保证同一来源图片不跨数据集、固定随机种子。下面这段脚本是我在工业数据集上常用的写法,它按“每张图片的缺陷类别集合”来做分层划分,比按单条标注做划分更符合物理直觉。

3.2 一份带分层抽样的划分脚本

这个脚本适合 YOLO 格式的数据集。假设你的图片和标签已经整理成下面这种结构:images/放图片,labels/放 txt 标签。脚本会统计每张图片包含哪些类别,然后基于 StratifiedShuffleSplit 按类别比例分层划分 train/val/test,最后把对应文件复制到dataset/...目录下。

import os import random import shutil from collections import defaultdict random.seed(42) # 固定随机种子,保证每次划分结果一致 images_dir = 'images' # 原始图片目录 labels_dir = 'labels' # 原始 txt 标签目录 out_root = 'split_dataset' # 输出目录 train_ratio, val_ratio = 0.8, 0.1 test_ratio = 1 - train_ratio - val_ratio # 第一步:统计每张图片包含哪些类别 img_files = [f for f in os.listdir(images_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp'))] img_cls_map = {} for img in img_files: label_path = os.path.join(labels_dir, os.path.splitext(img)[0] + '.txt') classes = set() if os.path.exists(label_path): with open(label_path, 'r') as f: for line in f: if line.strip(): classes.add(int(line.strip().split()[0])) img_cls_map[img] = classes # 第二步:把样本按“类别组合向量”分组,做分层 cls_group = defaultdict(list) for img, cls_set in img_cls_map.items(): key = tuple(sorted(cls_set)) # 例如 (0, 3) 表示同时含类别0和3 cls_group[key].append(img) train_imgs, val_imgs, test_imgs = [], [], [] for key, group in cls_group.items(): random.shuffle(group) n = len(group) n_train = int(n * train_ratio) n_val = int(n * val_ratio) train_imgs.extend(group[:n_train]) val_imgs.extend(group[n_train:n_train + n_val]) test_imgs.extend(group[n_train + n_val:]) # 第三步:复制文件到目标目录 for subset, imgs in [('train', train_imgs), ('val', val_imgs), ('test', test_imgs)]: out_img_dir = os.path.join(out_root, subset, 'images') out_lbl_dir = os.path.join(out_root, subset, 'labels') os.makedirs(out_img_dir, exist_ok=True) os.makedirs(out_lbl_dir, exist_ok=True) for img in imgs: shutil.copy(os.path.join(images_dir, img), os.path.join(out_img_dir, img)) label_name = os.path.splitext(img)[0] + '.txt' src_label = os.path.join(labels_dir, label_name) if os.path.exists(src_label): shutil.copy(src_label, os.path.join(out_lbl_dir, label_name))

这个脚本的关键设计是把“类别组合”而不是“单个类别”作为分层依据。谢韦尔数据集里一张钢板可能有三种缺陷同时出现,如果只按单类别分层,划分后可能某些多缺陷组合只在训练集出现,模型永远学不到“多个缺陷同时出现”的情况。参数说明:random.seed(42)是玄学中的玄学,同一个数据集固定种子,你才能复现实验和论文里的数字;如果想做互相比较,可以把 seed 参数化,但不推荐每次跑都换种子。train_ratio, val_ratio我习惯设 0.8/0.1,测试集留 0.1,因为钢材缺陷数据集本身不大,测试集太小会让最后的评估置信度下降。

3.3 划分后必须做的检查

划分完不要立刻去训练,先花五分钟检查两件事。第一件是每个子集的类别分布,第二件是标签文件里是否有空的或异常的坐标。下面这段代码会打印每个子集中每个类别的样本数,以及标签文件的行数异常情况。

import os for subset in ['train', 'val', 'test']: label_dir = os.path.join('split_dataset', subset, 'labels') cls_count = {} empty_files = 0 for lbl in os.listdir(label_dir): if not lbl.endswith('.txt'): continue path = os.path.join(label_dir, lbl) lines = [l.strip() for l in open(path) if l.strip()] if not lines: empty_files += 1 for line in lines: parts = line.split() cls_id = int(parts[0]) cls_count[cls_id] = cls_count.get(cls_id, 0) + 1 print(f"{subset}: {cls_count}, empty_files={empty_files}")

如果某个子集的类别数明显偏离原始比例,比如验证集中类别 2 只有 3 个框,那我建议把random.seed换一个数字重新划分,或者增大val_ratio。如果出现empty_files,说明有的图片没有对应标注,这种样本要不要留完全取决于你的业务定义:但训练时 YOLO 会把它当背景图,如果背景图太多,模型会偏向预测“无缺陷”,所以在划分脚本里可以直接把空标签文件对应的图片剔除,不让它们进入任何子集。

4. 用 YOLOv8 训练自己的数据集:目录结构、yaml 与参数

4.1 按 YOLO 期望的目录结构整理数据

YOLOv8 官方默认的数据目录结构是每个子集下面分images和labels两个目录,图片和 txt 同名。很多人在这一步翻车,因为之前用 VOC/COCO 格式训练过别的框架,习惯把标签集中放在一个文件夹里。我一般解压完数据集后,会先建一个steel_dataset/根目录,然后按下面这个结构摆放:

steel_dataset/ ├── train/ │ ├── images/ │ │ ├── 00001.jpg │ │ └── ... │ └── labels/ │ ├── 00001.txt │ └── ... ├── val/ │ ├── images/ │ │ ├── 00321.jpg │ │ └── ... │ └── labels/ │ ├── 00321.txt │ └── ... └── test/ ├── images/ └── labels/

注意test目录不是必须的,YOLOv8 在训练时只会读取train和val两个子集,test子集是留给最后做评估用的。如果你用的是上一个划分脚本,它已经把文件按这个结构复制好了。我这里要强调一个“文件名一致”的问题:图片如果是00001.jpg,标签就必须是00001.txt,YOLO 只靠前缀匹配来关联图片和标签,不检查内容。要是你的原始数据有00001 (1).jpg这种带空格的文件名,最好先批量重命名,否则后续串路径时会冒出各种诡异报错。

4.2 写 data.yaml 并启动训练

在 ultralytics 框架下,训练第一步是写data.yaml。下面这份配置对谢韦尔钢材缺陷数据集是通用的:

path: /absolute/path/to/steel_dataset train: train/images val: val/images test: test/images nc: 6 names: 0: Crazing 1: Inclusion 2: Patches 3: Pitted_Surface 4: Rolled_In_Scale 5: Scratches

这里最关键的是names的顺序。如果第 2 章你用 VOC 转换脚本时把class_names写成了某个顺序,这里必须完全一致;用 COCO 转换脚本时,因为脚本里用了sorted(),这里也要按字母序写。顺序不一致的后果是:模型训练时的类别索引和标签文件对不上,损失震荡,mAP 基本为零。path建议写绝对路径,尽量别用相对路径,因为不同机器上启动目录不一样,相对路径经常导致图片加载不到。

训练命令我常用的是:

yolo train data=steel_dataset.yaml model=yolov8n.pt epochs=150 imgsz=640 batch=16 workers=8 project=runs name=steel_severstal

参数说明:model=yolov8n.pt表示加载官方预训练权重,会在 COCO 预训练的基础上做迁移学习;如果网络不好,可以先手动下载预训练模型放到weights/目录,然后用model=weights/yolov8n.pt指定。imgsz我设 640,因为原始钢板图像一般比较大,直接 1280 会占爆显存;batch=16是 24GB 显存下的经验值,如果你只有 12GB,建议降到 8 或 4。这里没有简单调大lr的必要,YOLOv8 默认的 SGD 参数在中小数据集上已经很稳。

4.3 预训练权重、训练轮数和损失函数怎么取舍

很多人纠结到底用yolov8n、yolov8s还是yolov8m。我的习惯是先用 n 和 s 各跑一轮,只看前 50 个 epoch 的验证集 mAP50 曲线。如果 s 的提升明显,再切到 s 或 m 做正式训练。因为钢材缺陷是小目标,理论上更大模型能捕获更多纹理特征,但工业场景对推理速度有要求,n 或 s 的量级在 GPU 上能跑到 30fps 以上,m 就有点悬了。

关于训练轮数,5000 张图不算多,默认的 100 epoch 通常能跑到平台期,但如果发现验证集 mAP 还在涨,可以继续用resume参数接着训。还有一点是 YOLOv8 默认使用 CIoU 损失,它对边界框形状比较敏感,对钢材这种不规则缺陷有天然优势。如果你发现模型对小缺陷召回不够,不要急着调损失函数,先去检查数据增强是不是把缺陷缩小到几个像素了;在 ultralytics 里可以把hsv_h、scale等增强参数调低,或者直接关掉 mosaic 增强(mosaic=0),因为 mosaic 会把多张图拼在一起,对于小目标缺陷可能会产生大量“裁剪后消失”的负样本。

5. 训练常见问题排查:标签、BN、显存与混淆矩阵的坑

5.1 标签文件里出现了越界坐标

现象:训练刚开始,控制台不断刷WARNING ⚠️ Box with (x, y, width, height) out of bounds,或者有些图片框的数量比实际标注少。

原因:这是标签文件里存在绝对值坐标越界或归一化坐标超出 0~1 范围。常见来源有两个:一是用 VOC 转换时没有做防御性截断,比如xmax比图像宽度大 1 像素;二是从 COCO 转 YOLO 时,某些标注的x + width大于图像宽度,而脚本直接按width / img_width计算,没有先对原坐标做修正。

解决:不要只改几个文件,直接在加载数据集前跑一次标签扫描脚本,把所有越界的坐标打印出来,并按x_center = min(max(x_center, 0.0), 1.0)的方式修正后回写。同时检查那个文件对应的原图宽度和高度是否和标注尺寸一致,因为有些数据集的 XML 里写的是size/width,但 PNG 实际尺寸不同,这是常见的“黑匣子”问题。

5.2 训练中损失变成 NaN,或 BN 崩溃

现象:训练到几十个 batch 后,loss突然变成nan,或者出现BN is running completely on zero inputs的警告,然后 mAP 直线下降。

原因:最常见原因是学习率过大和 batch size 过小导致的梯度不稳定。YOLOv8 默认初始学习率对 8 卡环境是合理的,但你单卡跑数据量小的时候,可能前 10 个 epoch 就击穿 BN 的统计量。另一个原因是 embed 数据里有极端颜色分布,比如整张图全黑或全白,激活值变化剧烈。

解决:第一步把batch降到 4 或 8,看是否恢复;第二步把初始学习率从 0.01 降到 0.001,或者直接用optimizer=SGD而不是AdamW,工业小数据集上 SGD 更稳。如果还不行,检查是不是有损坏图片或者空标签导致某类梯度一直为零,把训练集里解码失败的图删掉再训。

5.3 混淆矩阵总和不是预设类别数

现象:训练完看confusion_matrix.png,发现每一行的样本总数不等于该类别在验证集里的标注数量,甚至总和和多出来的数值都对不上。

原因:这是很多新人误以为“模型错了”的陷阱。YOLO 的混淆矩阵是按预测结果统计的,它的行表示真实类别,列表示预测类别,但最右一列是“背景”或“未检测到”,最底部一行是“预测为背景”。所以“总和和类别数不一致”是正常现象,真正该看的是每个类别的召回率沿对角线是否够高。如果你用ultralytics生成的混淆矩阵发现除对角线外有大量横向分布,说明类别相似度高,比如麻点和划痕相互误检,这种情况下先做类别合并或者数据平衡,而不是盲目加网络深度。

现象:验证集 mAP50 很高,但 mAP50-95 很低,且显存占用居高不下。

原因:钢材缺陷是小目标,mAP50-95 对边框的 IoU 阈值非常敏感,模型虽然能把目标找出来,但框的位置不够精确,所以 IoU 超过 0.6 就掉点。另一个原因是图像分辨率太低,很多缺陷只有几个像素,模型学不到精细边界。

解决:把imgsz从 640 提到 768 或 1024,显存不够就降低 batch size。同时在验证时开启rect=True或augment=True做多尺度验证,能显著提升 mAP50-95。这一步是“后悔药”,因为你训练时如果用相对低分辨率,后期加分辨率推理效果通常立竿见影。

5.4 显存不足:从 batch size、imgsz、workers 三个方向压下去

现象:启动训练不到一分钟,CUDA OOM 直接退出,或者系统内存被占满然后开始 swap,训练速度掉到蜗牛水平。

原因:显存不足的诱因多数是batch和imgsz设置得太贪心,尤其是把imgsz=1280、batch=16一起设上。另一个隐性问题是workers开太多,多个数据加载进程同时把数据灌进内存,CPU 成为瓶颈,看起来也像“卡死”。很多人的第一反应是换卡,但实际 12GB 显存也可以训练 YOLOv8n,只是要会算账。

解决:显存紧张的保守参数是batch=8 imgsz=640 workers=4。如果还 OOM,就继续降batch到 4 和imgsz到 512,同时把训练命令里的cache=True关掉,因为缓存图片到显存会额外占一部分空间。用nvidia-smi监控显存曲线,正常情况下前几个 batch 显存会小幅上涨,然后趋于稳定;如果持续上涨到 OOM,大概率是数据加载或梯度累加的逻辑异常,这时候重启训练并升级到最新版 ultralytics 往往能解决。

6. 训练完怎么验收:从混淆矩阵到部署的进阶技巧

6.1 用混淆矩阵、PR 曲线和推理图判断能否上线

训练结束后不要只盯着 best.pt 的 mAP。我会在验证集上跑一次yolo val,特意保存三个输出:confusion_matrix.png、PR_curve.png和val_batch_pred.jpg。先说混淆矩阵,如果某个真实类别的对角线只有 60%,建议去val_batch_pred.jpg里看那些漏检的图,八成是缺陷面积太小或纹理太像背景。PR 曲线则告诉你“提升召回率会牺牲多少精确率”,比如你要保证缺陷不出厂,就选择召回率 0.95 对应的置信度阈值,而不是默认的 0.25。我习惯的做法是训练完再用yolo predict对一批测试图片做可视化,观察哪些缺陷类别系统性漏检,再决定是补数据还是调增强。

6.2 导出 ONNX 并用多尺度推理提升稳定性

验证通过后,我一般会导出成 ONNX,因为后面不管是 C++ 部署还是用 ONNX Runtime 做服务,都比直接依赖 PyTorch 稳。导出命令很简单:yolo export model=best.pt format=onnx imgsz=640。导出后要检查一版输出的类别顺序,确保和你data.yaml一致。

import onnxruntime as ort from PIL import Image import numpy as np sess = ort.InferenceSession('best.onnx') input_name = sess.get_inputs()[0].name img = Image.open('test_steel.jpg').resize((640, 640)) input_data = np.array(img).astype(np.float32) / 255.0 input_data = input_data.transpose(2, 0, 1)[None, ...] outputs = sess.run(None, {input_name: input_data})

导出后还有个小技巧:对于钢材表面的微小缺陷,单尺度推理容易被漏检,可以同时用 640 和 768 两个尺寸分别推理,然后做 NMS 合并。这样会增加约 30% 推理耗时,但对“漏检就是事故”的工业场景来说通常值得。如果你把模型部署到生产环境,建议固定输入尺寸,并且在预处理时不要随意改变图像亮度,因为训练时默认的hsv增强已经让模型对颜色有一定容忍度,但过度锐化和归一化依然会改变分布。

经验上,把训练好的模型投入新的钢材产线前,至少要拿一批跟训练集不同时间段采集的图片去验证,只看公开数据集的 mAP 很容易高估线上表现。我自己吃过不少教训:训练时 mAP50 到了 0.9,上线时对现场反光强烈的钢板频繁误检,后来发现是数据增强里的翻转和旋转把缺陷的物理方向性破坏了。从那以后我只做轻微平移和尺度扰动,能做到“线上和离线指标差别不大”,这也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询