简介:一套完整的YOLO铝片表面缺陷检测数据集与训练配套方案,面向工业质检、表面缺陷识别等方向的初学者和算法工程师。数据集中含1000张真实产线场景的高清铝片图像,缺陷类型多样,场景光照与角度变化丰富;所有图像均使用LabelImg人工精标,标注框质量高,并统一输出VOC(xml)、COCO(json)、YOLO(txt)三种标签格式,可直接用于YOLO系列模型训练与验证。资源包共2000个文件,除样本图片及标签外,还附带数据集划分脚本和详细训练教程,教程覆盖Linux与Windows环境搭建、案例修改、训练集/验证集/测试集自动划分等内容,可减少从零配置环境的成本。压缩包整体约60.69MB,目录结构清晰,便于按需取用。目前已有933人学习下载,是快速上手铝片缺陷检测任务的高性价比资料。
1. 铝片表面缺陷检测真正卡住的,不是模型,是数据能不能直接开训
很多做铝片表面缺陷检测的团队,第一步就倒在数据准备上。工厂里合格品随手就是几千张,但划痕、压伤、氧化斑这类缺陷样本本身就难凑,凑齐了还要转成 YOLO 能读的标签格式,中间任何一个环节出错,后面几百个 epoch 全白跑。这个标题里的数据集压缩包,给的就是一条现成的路径:1000 张图片、VOC/COCO/YOLO 三种格式标签、一个划分脚本、一份训练教程,拿到手可以顺着它把整个流程走通。它适合两类人:一类是刚接触 YOLO 的工程师,想通过一个能跑通的完整项目把训练、验证、推理串起来;另一类是在评估铝片表面缺陷检测可行性的方案选型人员,先用现成数据验证平台和参数,再决定自己的产线数据该怎么投入。
2. 先别急着训练:把 1000 张图和三种格式标签看清楚
2.1 1000 张图够不够,取决于缺陷分布而不是总数
先说一个容易被忽视的事实:缺陷检测数据集的价值不在于总张数,而在于“缺陷目标”的分布。铝片表面的缺陷通常是小目标,一张 1920×1080 的图里可能只有一小块划痕区域。如果 1000 张图里每一类缺陷只有二三十个标注框,那训练出来的模型大概率只能记住背景,学不会区分缺陷类型。反过来,如果一张图里有多处缺陷,1000 张图对应着 3000 多个标注实例,这个规模是完全够跑通一个基础方案的。
和轴承缺陷检测、硅片缺陷检测类似,铝片表面缺陷数据天然是长尾分布:某种缺陷占了 80%,其他几种各占一点。拿到数据集后第一件事不是解压就跑训练,而是把所有标签文件解析一遍,统计每个类别出现的次数。
提示:至少保证每个参与训练的缺陷类别有 30 个以上的标注框,低于这个量级,测试集里几乎不可能出现该类缺陷的有效评估。
如果某个类别只有零星几个框,宁可先只训练数据量足够的三个主类,把冷门类别留到第二版增量数据里再加,也不要硬着头皮让模型去学一个只有 8 个样本的类别。
2.2 VOC、COCO、YOLO 三种标签格式的差异
同一个标注结果用三种格式表达,最容易踩的坑就是坐标含义不一致。表格里一眼能看明白:
| 格式 | 文件形式 | 坐标描述 | 类别表示 | 典型读取方式 |
|---|---|---|---|---|
| VOC | .xml | xmin、ymin、xmax、ymax,像素绝对值 | 字符串 name | 直接用 xml 解析 |
| COCO | .json | bbox 为 [x, y, width, height],像素绝对值 | category_id 整数 | pycocotools 或 json 解析 |
| YOLO | .txt | x_center、y_center、width、height,全部归一化到 0~1 | 整数序号(从 0 开始) | 按行读取,每行一个目标 |
VOC 的 xml 里<size>节点还同时存了图片原始宽高,转换脚本依赖这个宽高做归一化;COCO 的 json 里每张图片对象同样带 width 和 height。YOLO 的 txt 不存图片尺寸,因为所有值已经除过宽高,所以如果转换时把图片尺寸取错了,训练时模型读到的是完全错位的坐标,还很难一眼发现。
COCO 还有一个隐藏坑:bbox是[x, y, width, height],不是[xmin, ymin, xmax, ymax]。很多转换脚本写错就错在这一行,后面会单独讲。
2.3 解压之后先做一次四件事核对
我知道拿到压缩包的人都会急着解压开箱,但这里建议先花十分钟做一次数据体检。解压 .rar 用 7-Zip 或 WinRAR,解压不完整会直接导致后续训练报“图片解码失败”。
第一件事:看文件数量是否匹配。用一段简单的 bash 命令在数据集根目录统计:
cd 铝片数据集目录 for ext in jpg jpeg png xml json txt; do echo "$ext: $(find . -name "*.${ext}" | wc -l)" done理想情况下,图片数量与 txt 数量一致,xml 和 json 数量也应与图片一致。只要数量对不上,说明要么解压不完整,要么原始数据本身就是缺标签的,这时去找缺失原因比直接训练更紧迫。
第二件事:抽 3 到 5 个 xml,看<object>节点里的 name 是否都在同一个类别集合内;抽 3 到 5 个 json,看 categories 列表里有多少类。这一步能避免后面训练时报“类别超界”或“标签文件存在但内容为空”。
第三件事:检查 YOLO txt 里的坐标值是否都在 0~1 之间。如果出现 2.5 这种数字,说明转换时没有按图片宽高归一化,或者图片本身有 EXIF 旋转导致宽高错位。
第四件事:随机挑两张图,把标注框画上去看一眼。用 OpenCV 就能干,这个可视化检查也是后面所有转换脚本的第一道验收标准。传统 OpenCV 缺陷检测靠阈值分割和滤波,对光照敏感,所以现在大多数铝片表面缺陷方案都转向 YOLO 这类数据驱动模型,前提就是标注格式别错。
3. 格式转换与划分脚本:把数据整理成 YOLO 能直接吃的样子
3.1 一套完整目录应该长什么样
不管原始压缩包里给了多少种格式,训练前最终都要整理成 YOLO 的标准目录结构。常规做法是建一个独立数据集目录:
aluminum_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── classes.txt图片放 images,同名 txt 标签放 labels,train/val/test 三个子目录两边一一对应。YOLOv5 和 YOLOv8 都认这个结构。
如果压缩包里只有 VOC 和 COCO 格式的标签,那就需要先转成 YOLO txt 再做划分。下面这部分转换脚本是固定的套路,可以直接抄。
3.2 VOC 转 YOLO:解析 xml 并归一化坐标
import os import xml.etree.ElementTree as ET CLASSES = ["scratch", "dent", "stain"] # 按你的数据集实际类别调整顺序 def voc2yolo(xml_path, out_dir): tree = ET.parse(xml_path) root = tree.getroot() # 图片尺寸必须从xml里读真实值,不能写死 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASSES: continue # 跳过标注里不在类别表中的物体 box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # VOC是左上/右下角点,YOLO要中心点+宽高,且全部除以图片宽高 cx = (xmin + xmax) / 2.0 / img_w cy = (ymin + ymax) / 2.0 / img_h bw = (xmax - xmin) / img_w bh = (ymax - ymin) / img_h lines.append(f"{CLASSES.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") base = os.path.splitext(os.path.basename(xml_path))[0] out_path = os.path.join(out_dir, base + ".txt") with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 批量转换 xml_dir = "annotations/voc" txt_dir = "labels" os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith(".xml"): voc2yolo(os.path.join(xml_dir, xml_file), txt_dir)这段脚本值得注意的点有三处:一是CLASSES列表的顺序就是最终 txt 里类别编号的映射表,一旦按这个顺序转换并开始训练,就不要中途改动顺序,否则整个标签体系全乱;二是坐标换算公式里分母必须用 xml 里记录的原始宽高,不能用cv2.imread读出来的宽高,因为某些标注软件会修改图片分辨率导致两者不一致;三是如果 xml 里存在difficult标记的目标,建议直接跳过,它的标注本身质量就不可靠。
3.3 COCO 转 YOLO:避开的那个 [x,y,w,h] 坑
import json import os def coco2yolo(annotation_file, label_dir): with open(annotation_file, encoding="utf-8") as f: coco = json.load(f) images = {img["id"]: img for img in coco["images"]} categories = coco["categories"] # 关键:COCO的category_id不一定是连续的,要重新映射成0开始的序号 id2cls = {} cat_names = [] for idx, cat in enumerate(categories): id2cls[cat["id"]] = idx cat_names.append(cat["name"]) # 按图片id聚合标注 anno_map = {} for ann in coco["annotations"]: anno_map.setdefault(ann["image_id"], []).append(ann) for img_id, anns in anno_map.items(): img = images[img_id] img_w, img_h = img["width"], img["height"] base = os.path.splitext(img["file_name"])[0] lines = [] for ann in anns: if ann.get("iscrowd", 0): continue # 群目标不适合常规检测任务 cls_idx = id2cls[ann["category_id"]] x, y, w, h = ann["bbox"] # 注意COCO格式是左上角x,y加宽高 cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h bw = w / img_w bh = h / img_h lines.append(f"{cls_idx} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") with open(os.path.join(label_dir, base + ".txt"), "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 调用示例 coco2yolo("annotations/instances_aluminum.json", "labels")这里最容易翻车的就是ann["bbox"]那行。COCO 的 bbox 语义是[左上角x, 左上角y, 宽, 高],如果按 VOC 的[xmin, ymin, xmax, ymax]去理解,中心点坐标会偏到右下角,宽高会变成两个角点坐标差值之后再除以图片尺寸,结果整个框全部错位。同时,COCO 的category_id不一定是从 0 开始连续的,必须通过id2cls重新映射,直接用原始 id 当 YOLO 类别号会让类别数对不上。
3.4 划分脚本:训练、验证、测试怎么切才不出事
标签转换完成后,下一步做数据集划分。常见做法是 70% 训练、15% 验证、15% 测试。如果数据集总量不大,验证集和测试集各 15% 就是每边 150 张图,够评估用了。
import os import random import shutil from collections import Counter random.seed(2024) image_dir = "images_all" # 原始全量图片 label_dir = "labels_all" # 原始全量标签 train_dirs = ["images/train", "labels/train"] val_dirs = ["images/val", "labels/val"] test_dirs = ["images/test", "labels/test"] for path in train_dirs + val_dirs + test_dirs: os.makedirs(path, exist_ok=True) # 只保留有配对标签的图片 files = os.listdir(image_dir) paired = [] for f in files: base = os.path.splitext(f)[0] if os.path.exists(os.path.join(label_dir, base + ".txt")): paired.append(f) random.shuffle(paired) n = len(paired) n_val = int(n * 0.15) n_test = int(n * 0.15) val_files = set(paired[:n_val]) test_files = set(paired[n_val:n_val + n_test]) for f in paired: base = os.path.splitext(f)[0] img_src = os.path.join(image_dir, f) label_src = os.path.join(label_dir, base + ".txt") if f in val_files: shutil.copy2(img_src, os.path.join("images/val", f)) shutil.copy2(label_src, os.path.join("labels/val", base + ".txt")) elif f in test_files: shutil.copy2(img_src, os.path.join("images/test", f)) shutil.copy2(label_src, os.path.join("labels/test", base + ".txt")) else: shutil.copy2(img_src, os.path.join("images/train", f)) shutil.copy2(label_src, os.path.join("labels/train", base + ".txt")) # 校验:打印训练集里每个类别的样本数量 def count_cls(label_root): counter = Counter() for txt in os.listdir(label_root): with open(os.path.join(label_root, txt)) as f: for line in f: if line.strip(): counter[line.split()[0]] += 1 return counter print("train classes:", count_cls("labels/train")) print("val classes:", count_cls("labels/val"))这段脚本在划分后立刻打印每个类别在训练集和验证集里的样本数。这一步很关键:如果某个冷门缺陷在验证集里一个样本都没有,那训练出来的模型对这个缺陷的 mAP 根本无从评估,后面看到召回率偏低时,先怀疑是数据划分问题,而不是模型问题。
3.5 写 data.yaml 并跑通第一次训练
数据整理完毕后,在数据集根目录创建 data.yaml:
# data.yaml path: /home/user/aluminum_dataset # 改成你的数据集绝对路径 train: images/train val: images/val test: images/test nc: 3 names: ['scratch', 'dent', 'stain']path必须是绝对路径,或者保持相对路径时训练命令要在数据集根目录下执行。nc是类别总数,names的顺序必须和转换脚本里的CLASSES完全一致,这是整个流程里最容易被忽略但影响最严重的一处一致性问题。
跑通第一次训练,直接用预训练权重起步:
# YOLOv5 方式 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt python train.py --data ../aluminum_dataset/data.yaml \ --weights yolov5s.pt \ --epochs 200 \ --batch-size 16 \ --img 640 \ --cache ram \ --name aluminum_v1如果用的是 YOLOv8,训练命令更简洁:
pip install ultralytics yolo detect train data=../aluminum_dataset/data.yaml \ model=yolov8s.pt \ epochs=200 \ imgsz=640 \ batch=16 \ cache=True这两个命令的语义相同:加载 COCO 预训练权重,在铝片数据上微调。等第一次训练跑起来,再谈参数调优。
4. 跑通训练:环境配置、参数调优、预训练权重和损失曲线怎么看
4.1 预训练权重怎么选,为什么不要从零训练
标题里的“训练教程”通常默认走 yolov5 或 yolov8 的预训练微调路线。先在 COCO 上训练过的yolov5s.pt或yolov8s.pt权重,已经学会了通用的边缘、纹理、形状特征,铝片缺陷里的划痕和压伤跟 COCO 里的物体边缘模式有可迁移的部分,所以从预训练权重继续训,几百个 epoch 就能收敛;从零训练同一个数据集,常常要两三倍的时间,效果还不一定好。
YOLOv8 的模型文件在首次执行yolo detect train时会自动下载,YOLOv5 需要手动下载yolov5s.pt放到仓库根目录。如果网络受限,可以把官方 release 里对应的 .pt 文件提前下好放进来。
选择模型规格的参考很直接:s 模型在 1080Ti 上基本够跑,m 和 l 对显存的要求逐级上升。缺陷检测目标通常较小,模型容量对最终 mAP 的影响不如输入分辨率和标签质量大,所以先上 s 模型把流程跑通,再看瓶颈在哪。
4.2 三个最影响缺陷检测效果的训练参数
第一个是imgsz。640 是 YOLOv5/v8 的默认值,但铝片原图如果是 2000×2000 的大图,划痕可能只有几十像素宽,直接缩到 640 会让缺陷缩成几个像素,模型等于在学“猜位置”。常见做法是先看原图里缺陷框占整图的比例,如果面积比例小于 5%,建议把训练尺寸提到 1280,或者把大图切片成 640×640 的 patches 再训练。切片方案后期会增加推理拼接的复杂度,但往往比盲目提升imgsz换显存更划算。
第二个是batch-size和cache的配合。batch 太小会让 BN 统计量抖动,训练曲线像锯齿;batch 太大又撑爆显存。V100 这类大显存卡可以开到 64 甚至 128,消费级显卡 16 通常是安全值。--cache ram在显存富余时也能缓解数据读取瓶颈,让 GPU 不至于饿着等图。
第三个是数据增强。YOLOv8 默认开启 mosaic、mixup、scale、fliplr。小缺陷数据集上 mosaic 有奇效,因为它把四张图拼成一张,让模型看到更多缺陷出现在不同背景下的组合。但到了训练后期,mosaic 会持续干扰 BN 统计,导致验证精度上不去。YOLOv8 可以直接在训练命令里加close_mosaic=10,意思是最后 10 个 epoch 关闭 mosaic 让模型精调,这个参数值得一试。
4.3 yolo 损失函数的曲线怎么读
训练日志里 YOLOv8 会输出box_loss、cls_loss、dfl_loss三个分量,YOLOv5 则合成了train/box_loss、train/cls_loss等。很多人只盯着总 loss,其实应该分别看。
box_loss衡量预测框和真实框的重合度,它下降得越快,说明定位学习越顺利。cls_loss衡量类别分错的程度,缺陷类别相近时这个值下降会比较慢。比如划痕和擦伤本身视觉边界模糊,cls_loss半天不降是正常的。dfl_loss是 YOLOv8 里分布式焦点损失,服务于框的回归精度。
读曲线的节奏一般是:前 10 个 epoch 在 warmup,下降慢是正常的;20 到 50 个 epoch 之间 mAP 开始快速爬升;之后进入平台期。如果训练集 loss 还在下降但验证集 mAP 连续 30 个 epoch 不涨,说明过拟合已经开始,继续加大数据增强不如回头补数据。
4.4 训练中 BN 崩溃的现场和补救
BN 崩溃是 YOLO 训练里比较典型的翻车现场。表现为 loss 在某个 epoch 突然跳到 NaN,或者训练集 mAP 和验证集 mAP 差距大到离谱。
原因集中在三处:学习率初始值过高导致 BN 层的统计量被极端激活值破坏;batch-size 太小使得每个 batch 的均值和方差都不可靠;梯度在深层网络里爆炸后无法自愈。
常规做法是先保住训练进度,把lr0调到 0.0001,batch 至少不低于 8,再检查标签 txt 里有没有超过 [0,1] 范围的坐标。BN 崩溃不是一个纯玄学问题,只要标签坐标合法、学习率合理、batch 不过小,它出现的概率极低。
5. 避坑:做缺陷检测标注和训练时最常见的五处翻车
5.1 类别 id 错位,模型在推理时把划痕认成压伤
- 现象:训练时 loss 正常下降,mAP 曲线也正常,但推理阶段发现类别跟真实缺陷对不上,比如把划痕全部识别成压伤。
- 原因:转换脚本里
CLASSES列表的顺序,和第一次训练时 data.yaml 里names的顺序不是同一套。txt 里的数字 0 在训练时被解释成了names[0],而标注阶段 0 代表的可能是另一个类别。 - 解决:训练前把
classes.txt和 data.yaml 的names逐行对齐,固定后用脚本校验所有 txt 里出现过的类别序号最大值是否小于nc。建议把类别顺序表作为配置文件放在数据集根目录,后续任何人重新训练都先读这个文件。
5.2 划分后训练报 No labels,检查发现标签没同步
- 现象:训练一开始就报
AssertionError: train: No labels in .../images/train,但 labels 目录里明明有内容。 - 原因:划分脚本只复制了图片到
images/train,标签没有按同名对应关系复制到labels/train。YOLO 按图片文件名去找标签文件,找不到就不给这张图配 label,数量少了就整体报错。 - 解决:划分脚本里保证图片和标签用同一个
base文件名同步处理,并在划分结束后统计images/train和labels/train的文件数做断言:
ls images/train | wc -l ls labels/train | wc -l两边数量相等再开始训练。
5.3 稀有缺陷类别在验证集里消失,模型对它们直接失明
- 现象:训练结果里总的 mAP 达到 0.8 以上,但人工抽检发现某种冷门缺陷全部漏检。
- 原因:按图片随机划分时,稀有缺陷本来就只出现在十几张图里,随机抽样后验证集可能一张都没分到。模型即便学到了该类特征,也无法在评估指标里体现,因为验证集里压根没有它的样本。
- 解决:划分时做分层抽样,先按图片包含的类别集合把数据分组,再在每组内部按比例切分。最简单的实现是遍历每张图的标签,统计图片对应的缺陷类别,然后按类别集合分层。如果某个类别样本实在太少,可以在训练时额外做该类别图片的过采样,或先剔除该类别,等数据补齐再训第二版。
5.4 COCO 的 bbox 被当成角点坐标,边界框全部画到右下角
- 现象:转换后抽查可视化,框的左上角几乎都贴着真实目标的右下边缘,看起来像是框整体偏移。
- 原因:COCO 的
bbox是[x, y, width, height],转换脚本里按[xmin, ymin, xmax, ymax]算了中心点。 - 解决:转换代码里用
cx = x + w / 2而不是(x + xmax) / 2。所有转换脚本完成后,随机抽取 20 张图做可视化检查,把框画在原图上逐张看过再进训练。这一步虽然费几分钟,但能省下后面重新转换全数据集的时间成本。
5.5 训练中途 loss 变 NaN,日志里一片红灯
- 现象:训练在 30 到 60 个 epoch 之间突然
loss: nan,并且之后的验证全部失效。 - 原因:学习率设置偏高,在缺陷数据这种样本量小的数据集上,梯度更新幅度过大,BN 统计量被推入数值不稳定区域;另一类常见原因是标签坐标超过 [0,1],比如归一化后出现 2.3 这样的值,反向传播直接产生 NaN。
- 解决:先把
lr0降到 0.0001,batch-size 提到 16 以上,再用脚本扫描所有 txt 坐标,过滤出数值范围超界的标签文件,修正源头。注意不要通过降低训练周期数来掩盖这个问题,它的根因在数据或超参,不在训练代数。
6. 验证模型:混淆矩阵、PR 曲线与产线现场复核的边界
训练完成后,用最佳权重对测试集做验证:
yolo detect val data=../aluminum_dataset/data.yaml \ model=runs/detect/aluminum_v1/weights/best.pt运行时生成的confusion_matrix.png和PR_curve.png才是真正要细看的文件。很多人在这一步会问:为什么混淆矩阵里每个格子的数字加起来,不是测试集的目标总数?这是一个很正常的现象。YOLOv5/v8 的混淆矩阵除了各个类别之间互相错分的目标,还包含背景被误检成缺陷的列、以及缺陷被漏检后计入背景的行,所以总数天然比目标总数多。不要盯着这个数字算准确率,要看归一化后每行每列的比例,重点确认哪两类缺陷容易被互相混淆。
PR 曲线决定的是置信度阈值的选择。产线缺陷检测的诉求通常是“宁错杀、不漏过”,因为漏检一块缺陷铝片的损失比多检几块片子重得多。实际操作里把置信度阈值设到 0.15 到 0.25 之间,换取更高的召回率,后续再用人工或复检工位排除误检,比把阈值拉高追求精确率更符合现场逻辑。
最后一步是 badcase 人工复核。把验证集里置信度低于阈值但真实存在缺陷的图、以及置信度很高但检测框明显对不上的图全部导出,逐张看一遍。我个人的习惯是每次都把坏案例截图存到一个单独目录,对比上一版模型,确认新增的漏检是数据分布问题还是模型退化问题,再决定下一步是补数据还是调参数。这套流程走下来,一个 YOLO 铝片缺陷检测方案能不能投入现场,心里就有底了,而不是只看最后打印出来的 mAP 数字。希望帮到你。
本文还有配套的精品资源,点击获取