铝片缺陷检测必看:YOLOv5数据集目录与标签格式详解
简介面向铝片表面缺陷检测场景的YOLOv5格式目标检测数据集适合工业质检、计算机视觉方向的学习者与开发者直接用于模型训练与效果验证。资源按YOLOv5标准目录清晰划分为训练集与验证集覆盖针孔、擦伤、脏污、褶皱四类缺陷训练集包含1120张图片及1120个txt标签验证集包含280张图片及280个txt标签并附4类别名称文本数据格式统一无需额外转换即可接入YOLO训练流程。压缩包共2000个文件以1401个txt标签/类别说明、598个jpg图像和1个Python可视化脚本为主整体约82MB该脚本使用简单随机传入一张图片即可绘制并保存边界框无需修改参数便于快速核对标注与预览效果。图像为640×480的RGB图片目录结构保留YOLOv5默认的images与labels层级轻量紧凑适合作为铝材表面缺陷检测的入门训练数据目前已有195人学习下载可离线直接使用。1. 铝片表面缺陷检测为什么离不开YOLOv5目录格式一个四分类数据集的真实分量铝片表面缺陷检测在工业质检里是典型的中小样本视觉任务产线逐片过检时缺陷类别不多但形态差异大用手工规则容易漏检交给目标检测模型配合面阵相机是最务实的路线。把标注好的图像整理成 YOLOv5 目录格式等于给训练流程定下一条标准流水线images 按 train/val 分目录labels 同名对应data.yaml 写清类别数和类别名之后不管是换 YOLOv8 还是做数据增强迭代迁移成本都很低。这篇笔记围绕一个四类别的铝片表面缺陷数据集展开讲目录规范、标签格式、验证划分和训练前质检适合手里有标注好的缺陷图但不知如何整理成 YOLOv5 标准格式的工程师也适合准备下载铝片缺陷数据集、打开压缩包后分不清 train 和 val 含义的初学者。2. 铝片表面缺陷四类别拆解与 YOLOv5 数据集目录格式的硬性约定2.1 四类缺陷划痕、凹坑、麻点、氧化斑的形态特征与标注差异铝片从轧制、冲压到阳极氧化每一道工序都会在表面留下不同性质的缺陷这也是做铝片缺陷检测时先要辨别缺陷类型的原因。四类别数据集的四类通常是划痕、凹坑、麻点、氧化斑这四类在形态上差异明显但在图像上又各有说头。划痕主要是轧制或搬运过程中硬质颗粒划过表面留下的线状痕迹特点是对比度低、方向性强在图像上表现为一条或多条细线灰度值比周围偏暗偶尔伴随轻微凸起。检测划痕时难点不在框定位置而在确定长度方向上的延伸范围同一个划痕在斜射光下可能断成几段容易产生重复框。标注划痕时常见做法是沿着整条线画一个能覆盖全段的矩形框如果划痕严重弯曲则拆成两到三个框避免用大框把无缺陷区域圈进来。凹坑是冲压或搬运碰撞产生的局部塑性变形在图像上呈现为边缘有阴影的小圆形或椭圆形区域中心灰度与周围接近但阴影边缘有明显灰度跳变通常是局部暗圈。凹坑的特点是尺度差异大大的凹坑可能占图像面积的十几分之一小的只有十几个像素标注时都要框到这里对标注工具的最小框尺寸限制有一定要求。麻点是材料内部气孔暴露在表面形成的点状缺陷单个体积极小且呈密集分布经常成片出现在图像上表现为密集的暗点灰度变化幅度小而频繁。麻点如果逐个标注会非常耗时不过它是四类里最容易用图像纹理特征方法区分的因此也常用来评价模型在小目标上的收敛速度。标注这一类时我一般建议按“可辨单体”标准来标注——能肉眼分辨出轮廓的才标连成一片的算作一条簇状缺陷用一个大框框住而不是把整片全标成几十个框。氧化斑则完全不同它是因为表面处理前残留水分或酸碱导致局部氧化形成的块状变色区域边界模糊灰度不均匀往往呈现从中心到边缘渐变过渡。氧化斑在目标检测里属于典型的低对比度目标它的边界没有锐利边缘所以标注规则要统一常见做法是按灰度跳变明显的外沿来框宁可小一点也不要包含过多正常表面因为背景占比过大会干扰模型对特征的学习。这四类缺陷在部署时的检出难度排序一般是麻点最难、氧化斑次之、凹坑中等、划痕看方向一致性四类之间类别不平衡的情况非常常见。一个四类数据集在 val 集里如果划痕占一半、麻点只占百分之几训练出来的模型在麻点上的 mAP 会明显偏低这也是后面第 4 章要讲验证集划分时特别注意的事情。2.2 YOLOv5 目录结构硬性规则images 与 labels 的同名对应和 train/val 分离YOLOv5 的数据集目录结构在设计上非常简单要求目录层次固定、对应关系清晰实际使用时我见过最多的问题都出在目录层级不够深或者大小写不一致。标准目录如下aluminum_defect/ ├── images/ │ ├── train/ │ │ ├── scratch_000001.jpg │ │ ├── dent_000023.jpg │ │ └── ... │ └── val/ │ ├── pit_000312.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── scratch_000001.txt │ │ ├── dent_000023.txt │ │ └── ... │ └── val/ │ ├── pit_000312.txt │ └── ... └── data.yamlimages/train 下每张图片在 labels/train 下必须有一个同名 txt 文件这个同名是严格到主名的后缀不要求一致图片可以是 .jpg、.png、.bmp标签统一是 .txt。所以 scratch_000001.jpg 对应的标签必须是 scratch_000001.txt不能是 scratch_000001_label.txt也不能放在别的目录下。把 train 和 val 分开放在两个目录下是为了避免训练时数据泄漏——凡是参与过训练的图像如果又在验证集里出现mAP 会虚高后续换到产线真实数据时性能立刻现原形。YOLOv5 本身也支持在 yaml 文件里直接写图片路径列表比如 train.txt 里逐行列图片路径但更稳妥的做法是坚持用目录结构因为 YOLOv5 的 dataloader 会递归读取 images 路径下的所有图片不再需要额外维护文件清单。标签 txt 的内容格式是 YOLO 格式每行代表一个目标框格式为class_id cx cy w hclass_id 是从 0 开始的整数对应 data.yaml 里 names 列表的下标cx、cy 是目标中心点的归一化坐标w、h 是归一化宽度和高度四者取值都在 0 到 1 之间。归一化基准是图片的原始宽度和高度不是缩放后的尺寸这一点很多人容易搞混。举例说明一张 1280x720 的图像上有一个凹坑凹坑中心在像素坐标 (640, 360)宽 80 像素高 60 像素那么标签行就是1 0.5 0.5 0.0625 0.0833。class_id 为 1 表示凹坑是第二个类别。这里比较常见的错误是把 w、h 直接填成像素值或者把 cx、cy 写成左上角坐标这两种错误模型不会报错但训练出来的检测框位置完全不对而且很难肉眼发现。data.yaml 是这个数据集的配置文件内容有五个字段train: aluminum_defect/images/train val: aluminum_defect/images/val nc: 4 names: [scratch, dent, pit, oxidation]train 和 val 写的是 images 目录路径YOLOv5 会自动去同级的 labels 目录找对应标签不需要显式指定 labels 路径。nc 是类别数必须和 names 列表长度相等且等于标签文件中最大 class_id 加一这三者任何一个对不上训练时要么报错要么类别错位。对四类铝片缺陷来说names 的顺序一旦定了就不要改。因为标签文件里只存数字 idnames 列表的顺序就是 id 到名称的映射如果训练时 names 是[scratch, dent, pit, oxidation]某个凹坑标签 id 是 1而验证时把 names 换成[dent, scratch, ...]模型仍然输出凹坑但日志和可视化里会显示成划痕这对调参阶段判读结果是致命的。3. 从原始缺陷图像到 YOLOv5 训练集与验证集标注工具、划分脚本与 yaml 配置3.1 标注工具选型与 YOLO 格式标签导出流程准备一个四类别铝片缺陷数据集如果原始数据是产线相机拍下来的大批量图像第一步是把图像按缺陷类别做粗筛这一步通常靠人工浏览缩略图完成目的是剔除无缺陷的样本和照糊的故障图。筛选完成后进入标注环节。标注工具常见的是 LabelImg 和 X-AnyLabeling也有工业界直接用 CVAT 自建标注平台的但单机环境下 LabelImg 最省事。LabelImg 支持 Pascal VOC 和 YOLO 两种导出格式保存时选择 YOLO 格式每次保存一张图片就会在指定目录生成一个与图片同名的 txt 文件。需要提醒的是 LabelImg 默认保存路径是图片所在目录如果图片目录和标签目录混在一起后面还要再整理所以建议标注前在工具里预设好标签输出目录。在开始标注之前先建好类别列表文件 classes.txt并按第 2.1 节确定的类别顺序写入四类名称。YOLO 格式标签里存的是类别 id顺序错乱后面极难纠正。标注过程中一个小技巧是让两个工程师分别标注同一批图然后用脚本比较两个版本的框差异对 IoU 低于 0.7 的样本进行人工复审这种方法能显著减少漏标和错标代价是标注时间翻倍但考虑到一个四类数据集可能要用大半年前期多花一两天校准标准是值得的。标注完成后把所有图片按顺序重命名常见做法是统一为六位数字并保留类别前缀比如 scratch_000001.jpg、dent_000023.jpg 这种命名方式。它带来的好处是在训练日志里看到某个样本出问题时能立即知道它属于哪一类排查效率高很多。随后用脚本按比例划分 train 和 val并让相同编号的图片和 txt 同步进对应目录。3.2 目录生成与 train/val 划分一段可复用的 Python 脚本手工在文件管理器里拖动几百个文件分层建目录不是不能做但非常容易漏掉对应关系我建议用一段脚本完成全部文件操作。下面是实际建四类铝片缺陷数据集时常用的划分脚本。import os import random import shutil from pathlib import Path # 原始标注输出目录图片和txt在同一层 src_dir Path(raw_annotated) # 数据集目标根目录 dst_root Path(aluminum_defect) # 固定随机种子保证每次划分结果一致 random.seed(42) # 定义训练集比例 train_ratio 0.8 # 创建目标目录结构 for split in [train, val]: (dst_root / images / split).mkdir(parentsTrue, exist_okTrue) (dst_root / labels / split).mkdir(parentsTrue, exist_okTrue) # 收集所有jpg图片并确认每张都有同名txt images sorted(src_dir.glob(*.jpg)) valid_pairs [] missing_label 0 for img in images: txt img.with_suffix(.txt) if txt.exists(): valid_pairs.append((img, txt)) else: # 没标签的图片直接丢弃不混进数据集 missing_label 1 print(f共找到 {len(images)} 张图片缺少标签的 {missing_label} 张有效样本 {len(valid_pairs)} 张) # 按图片名排序后打乱再按比例切分 random.shuffle(valid_pairs) train_count int(len(valid_pairs) * train_ratio) train_pairs valid_pairs[:train_count] val_pairs valid_pairs[train_count:] # 复制到目标目录 for split, pairs in [(train, train_pairs), (val, val_pairs)]: for img_path, txt_path in pairs: shutil.copy2(img_path, dst_root / images / split / img_path.name) shutil.copy2(txt_path, dst_root / labels / split / txt_path.name) print(f训练集 {len(train_pairs)} 张验证集 {len(val_pairs)} 张)脚本逻辑分四段第一段创建 images 和 labels 下的 train、val 四个目录第二段收集所有 jpg 并校验同名 txt 是否存在缺标签的图片直接排除这比在训练时遇到缺标签报错才回头清理要省事得多第三段用随机种子打乱顺序后按固定比例切分保证训练集和验证集在类别分布上尽量接近第四段把图片和标签同步复制进目标目录。执行时注意原始目录只放待处理的一批图片不要把已经切割好的图片重新扫进来否则同一个文件可能同时落到 train 和 val。划分比例上四类别铝片缺陷数据集的规模通常在几千张我一般按 8:2 切分如果标注框总数不足 1 万可以提升到 9:1因为验证集的主要作用是评估模型是否收敛不需要太多样本。每类的框数量在 val 中至少保留 30 个以上否则评估结果不稳定。3.3 data.yaml 配置要点与路径写法数据集目录建好后下一步是写 data.yaml这是 YOLOv5 训练的入口文件配置不对训练根本起不来。实际配置如下# 铝片表面缺陷四类数据集配置文件 train: aluminum_defect/images/train val: aluminum_defect/images/val nc: 4 names: 0: scratch 1: dent 2: pit 3: oxidationtrain 字段使用相对路径时YOLOv5 会相对当前工作目录解析。如果训练命令是从项目根目录执行的相对路径最稳妥如果打算把数据集挪到别的机器上训练建议写成绝对路径或者把 train 直接写成包含完整路径的文本文件列表。两种方式都有效但我个人偏好目录路径加相对路径的组合因为数据集如果跟着项目仓库一起分发其他人在任何机器上 clone 下来就能直接训练不用改 yaml。names 列表的写法有两种一种是用逗号分隔的单行字符串列表另一种是上面这种带下标的 dict 写法两者效果等价dict 写法更直观。nc 必须等于 names 的元素个数多一个少一个都会导致训练时类别数读取错误。这里还有一个容易犯的错有人会把背景也算成一个类别nc 写成 5names 里加一行 background。这是目标检测分类任务里最常见的概念混淆YOLO 格式的标签里不存在背景类背景由没有目标的区域隐式表达nc 必须只是需要检测的类别数。配置文件的路径可以直接传到 train.py 的 --data 参数上YOLOv5 会读取并缓存其中的数据配置之后每次执行 train.py 时如果发现 yaml 被修改过会重新加载这一点对频繁调整数据集的阶段很重要改完 yaml 不用清缓存直接再跑训练就行。4. 训练前的数据质检与常见避坑记录让铝片缺陷模型翻车的 5 个细节4.1 标签类别号错位训练不报错推理全乱套现象是训练损失正常下降验证集 mAP 看起来也不错但推理时对凹坑图像输出划痕标签而且这种错位是系统性的某个类全部映射到另一个类不是单张图的偶发问题。原因是在标注阶段 classes.txt 里的顺序与 data.yaml 里 names 的顺序不一致或者两个标注工具之间导出时类别 id 发生了偏移。举例说明LabelImg 里 classes.txt 写的是 scratch、dent、pit、oxidation但导出后某个第三方脚本按字母序重新排序了类别列表此时原本 id 为 1 的 dent 变成了 id 为 2 的 pit之后训练时虽然整体流程正常但标签含义已经不匹配了。解决方法是训练前写一个校验脚本随机抽取每个类别几张图片用 OpenCV 把标签框画在图像上人工检查框与实际缺陷是否对应。这一步是数据集质检的核心动作也是拿到任何外部数据集之后要做的第一件事。画框脚本的代码可以参考import cv2 def draw_boxes(img_path, txt_path, names): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path, r) as f: for line in f: parts line.strip().split() cls_id, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 归一化坐标转像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, names[cls_id], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return img names [scratch, dent, pit, oxidation] img draw_boxes(aluminum_defect/images/train/scratch_000001.jpg, aluminum_defect/labels/train/scratch_000001.txt, names) cv2.imwrite(check.jpg, img)校验时重点看三个点框有没有套住目标、框是否明显偏向一侧、类别名是否对得上。只要发现一张图里的框与对应类别不符就要立即检查整个数据集的类别映射而不是单独改这一张图。4.2 标签文件缺失或为空被静默忽略的训练样本现象是训练到某个 epoch 时程序报错提示信息指向某个丢失文件更隐蔽的是 dataloader 直接把缺标签的图片静默忽略掉导致实际参与训练的样本比预期少了一截但没有显式报错。原因有三类标注时漏保存、复制脚本里对缺失 txt 的校验不严格、某些缺陷类别全部被当作背景而没有标任何框。其中第三类最难发现因为文件存在、内容为空不会触发任何报错但训练出来的模型恰好在某个类别上完全没有检出能力。解决方法是训练前统计每个 txt 文件的行数把空文件单独列出来再用人工看一下这些图片是不是确实没有缺陷如果是背景样本建议直接移到另一个目录不要混在带目标的样本里。统计脚本核心逻辑如下from pathlib import Path label_dir Path(aluminum_defect/labels/train) empty_files [] total_boxes 0 for txt in sorted(label_dir.glob(*.txt)): lines [line.strip() for line in txt.read_text().splitlines() if line.strip()] if not lines: empty_files.append(txt.name) total_boxes len(lines) print(f空标签文件 {len(empty_files)} 个{empty_files[:10]}) print(f总标注框数 {total_boxes})还要顺带统计每类的框数量分布用 collections.Counter 按每行首列类别 id 累计快速判断是否存在类别极端不平衡。如果某一类的框数只有训练集的百分之一要提前想好后续是加过采样还是改 loss 权重而不是等训练完再看。注意空标签文件不都是垃圾。如果图片确实是干净表面把它单独归入 background 目录训练时可以作为负样本参与 loss 计算帮助模型减少假阳性。但不要把它们和带目标的图片混在同一目录下。4.3 验证集缺类或样本太少mAP 失真的边缘情况现象是训练集上加了很多数据增强loss 已经降到很低但 val 的 mAP 一直在某个值附近抖动不上升仔细看各类别的 PR 曲线后发现有类别的 AP 直接是 0。原因很大程度上是验证集里这一类别的样本数量过少或为零。比如麻点类在原始数据里占比只有 2%按 8:2 切分后验证集里可能只有十几张且这十几张的拍摄角度都集中在同一批次模型在训练里学到的是“该类在特定光照下”的特征一换图片就失效AP 自然很惨。解决方法是切分验证集时按类别分层采样保证每一类在 val 中都有足够样本。实现思路是在第 3.2 节脚本基础上增加分层逻辑先把有效样本按框的类别归到四个桶里再分别从每个桶中拿出 20% 作为验证样本剩下的作为训练样本。如果某个桶的样本总数就很少可以考虑把这个类别全部放进训练集验证时改用 K 折交叉验证或单独准备一组固定测试图。分层划分对四类缺陷检测来说是必需品而不是可选项因为产线拍回来的图片天然存在类别频率差异氧化斑和划痕出现概率远高于麻点不给验证集做分层保底你每次跑出来的 mAP 都可能因切分随机性而大幅度波动根本无法判断修改参数到底有没有效果。4.4 图像尺寸不统一letterbox 缩放后的坐标偏移现象是训练时不报任何错误但验证集里某些图片的预测框整体偏上一段距离尤其在图像边缘附近特别明显而训练集里的图片检测正常。原因是数据集里混入了不同分辨率的图片YOLOv5 训练时用 letterbox 将所有图缩放统一到 640x640这个缩放过程会把原图按长边等比缩放并在剩余区域填充灰色边。如果某张图本身是 4:3 而模型输入是 1:1letterbox 会给上下填充不同边距标签里的归一化坐标不受分辨率变化影响理论上标签和图像的变换是同步的。出问题的场景往往是一部分图片被提前裁剪或 padding 成了 1:1 再进数据集标签却没有同步做 padding 处理导致框的位置在原始图上对得上、在缩放后图像上偏移了 padding 的宽度。解决方法是检查数据集中图片的宽高比分布用脚本输出所有图片的尺寸和宽高比把明显异常的图片挑出来处理。如果确定要对非正方形图做预处理务必与标注步骤同步进行也就是先统一尺寸再标注或者在脚本里对图像和标签做同样的 padding 变换。4.5 越界框与过小目标清洗脚本的参数边界现象是训练一段时间后模型在验证集上的召回率偏低尤其在小目标上特别差检查标签发现部分 txt 里的框坐标明显超出图像范围比如 w 接近 1.5或者出现了负坐标。原因是标注工具在编辑过程中允许框拖出图像边缘导出时会把越界部分也写进 txt另外标注规范里如果规定了框的最小像素尺寸一些工具在缩小时不会拦截导致生成了低于阈值的细小框。YOLOv5 官方 dataloader 在加载数据时对越界坐标做了 clip 处理但如果坐标偏差太大裁剪后的框严重偏离真实目标仍会引入噪声。清洗时用脚本把每个框的归一化宽高换算成像素尺寸筛出小于 8x8 像素的框同时检查并 clip 所有越界坐标。参考代码def clean_label(txt_path, image_w, image_h, min_size8): lines [] with open(txt_path, r) as f: for line in f: cls_id, cx, cy, bw, bh map(float, line.split()) if bw * image_w min_size or bh * image_h min_size: continue # 确保中心点和宽高都落在 [0,1] 范围内 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 1e-6), 2 * min(cx, 1 - cx)) bh min(max(bh, 1e-6), 2 * min(cy, 1 - cy)) lines.append(f{int(cls_id)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) return lines这里的关键是把框宽的上限定为2 * min(cx, 1 - cx)这样裁剪后的框不会越过图像左右边界同时保持中心点不动。对麻点这类小目标min_size 设 8x8 像素是底线低于这个尺寸的框即使标注正确在 640 输入下也几乎只剩几个像素对训练没有正向作用。5. 用 YOLOv5 在四类铝片缺陷数据集上完成训练与验收5.1 最小训练命令与关键超参数的取舍数据集和 data.yaml 准备到位后最小训练命令如下python train.py --data aluminum_defect/data.yaml --weights yolov5s.pt --img 640 --epochs 100 --batch-size 16 --device 0--img 640 对应铝片缺陷里麻点这类小目标的常用输入分辨率分辨率不是越高越好高分辨率同时抬高显存和推理耗时。--epochs 100 对千张级四类数据集足够收敛--batch-size 16 在单卡上通常不爆显存报错就降到 8 并把 --workers 调到 4 以下。yolov5s.pt 是 COCO 预训练权重虽然原任务是 80 类但迁移到四类缺陷检测上依然能提供底层特征提取能力比从零训练收敛快得多。想要训练加速就在命令里加 --cache让数据缓存在内存中。5.2 mAP 判读与预测结果可视化验收训练完成后用验证命令得到指标python val.py --data aluminum_defect/data.yaml --weights runs/train/exp/weights/best.pt --img 640 --conf-thres 0.25 --iou-thres 0.5--conf-thres 0.25 是置信度阈值低于阈值的框被过滤掉--iou-thres 0.5 代表 IoU 超过 0.5 的预测框视为命中这是缺陷检测里比较贴合实际使用场景的判读标准。判读时不要只看整体 mAP要逐个类别看 AP整体 mAP 是四个类别 AP 的均值麻点类如果只有 0.4 而其它类 0.9整体看着 0.7 不错但产线里漏掉的恰好是麻点。然后把验证集图像喂给 detect.py逐张翻看预测画框结果重点找两类问题一类是不该有框的位置出现了假阳框比如氧化斑边缘被识别成划痕另一类是同一目标上两个高度重叠的框打了不同类别标签这种情况说明类别特征混淆严重。把最终验证指标和画框图固定存到同一份检查记录里作为这个版本数据集的基线以后改数据、改增强、改模型结构都拿同一组基线对比调参才有意义。我踩过最深的坑是第一次拿到别人给的四类铝片缺陷数据集没做任何质检就直接开训跑了一整天损失曲线看起来很漂亮结果拿到现场试跑时才发现某一类别的标签全部对不上等于从头再标了一遍。后来凡是拿到新的缺陷数据集不管是谁给的先花半小时做一轮标签可视化抽检再花十分钟统计类别数量和空标签文件这两步做完再决定要不要动 train.py。这个习惯救了我好几次。希望帮到你。本文还有配套的精品资源点击获取