
简介这是一套面向目标检测与工业视觉学习者的YOLO铝片表面缺陷检测数据集包含真实场景下的铝片图像可用于铝材表面瑕疵识别等任务。标注采用labelimg完成并同时提供voc(xml)、coco(json)与yolo(txt)三种格式标签分别存放在独立文件夹中可直接接入YOLO系列模型的训练流程。压缩包共2000个文件以1985个xml标注文件为主另有6个txt清单、6个html教程页面和3个py划分脚本整体约205.89MB。附赠YOLO环境搭建与训练案例教程并提供可按需划分训练集、验证集、测试集的Python脚本方便统一管理图片与标签。目前已有404人学习适合需要快速获得带多格式标注、开箱即用型表面缺陷数据集的算法工程师与研究人员。1. YOLO铝片表面缺陷检测五千张图够不够训练一个能用的模型最近在复盘一个质检小项目用 YOLO 做铝片表面缺陷检测。铝片这东西有个特性表面上划痕、凹坑、麻点在自然光下不明显一到产线强光下就全露出来而且高反光带来的光晕会把缺陷轮廓打乱误检率比普通钢面高不少。YOLO铝片表面缺陷检测数据集含5000张图片对应voc、coco和yolo三种格式标签划分脚本训练教程.rar 这套资源就是把“从标注格式转换到训练跑通”这条链路直接打包了。它解决的核心问题很具体不用自己写标注格式转换、不用头疼数据怎么切分解压后按教程走就能进 YOLO 训练流程。适合刚接触工业缺陷检测、想拿真实数据跑通整套流程的开发者也适合手头有产线数据但缺干净标注、不想从零造轮子的工程师。2. VOC、COCO、YOLO 三种格式差异先从坐标体系说起2.1 三种标注格式的本质区别标题里说“对应voc、coco和yolo三种格式标签”这里先说清楚一件事格式不同不只是文件后缀不同而是坐标体系和存储结构完全不同。VOC 是 XML 文件COCO 是 JSON 文件YOLO 是 TXT 文件。同样是“一个缺陷框”三种格式在文件里的表达方式差得很远。VOC 的 XML 文件长这样annotation filename000001.jpg/filename size width1280/width height800/height /size object namescratch/name bndbox xmin120/xmin ymin90/ymin xmax340/xmax ymax450/ymax /bndbox /object /annotationxmin、ymin、xmax、ymax 是绝对像素坐标直接落在原图上没有做归一化。这种格式的好处是直观坏处是数据增强时要额外处理坐标变换。COCO 的标注集中在 JSON 文件里按 images、annotations、categories 三个大数组组织。annotations 里每一条记录对应一个目标bbox 字段的格式是[x, y, width, height]依然是像素坐标。它比 VOC 多出的是每个目标带独立的 id 和 image_id 关联适合做实例分割、跟踪这类任务。YOLO 的 TXT 文件最精简每一行代表一个目标五个数字分别是类别索引、归一化后的中心点 x、中心点 y、宽、高。归一化系数就是图片自身的宽和高所以所有数值都落在 0 到 1 之间。这里最容易搞混的是顺序是class x_center y_center width height不是x1 y1 x2 y2。训练 YOLO 模型时ultralytics 框架默认就是读这种 txt这也是为什么数据集必须提供 YOLO 格式。VOC 和 COCO 格式不是摆设当你后续要换成 Mask R-CNN、做分割、或者接某个标注平台时JSON 和 XML 更通用。2.2 拿到压缩包后先核对目录结构解压这套资源后我第一件事不是急着训练而是先核对目录结构是不是符合 YOLO 的训练约定。常见做法是 images 和 labels 两个根目录下分别放 train、val、test 子目录segmentation 类和实例分割类的数据集则多一个 annotations 目录。建议按下面这张表逐项核对检查项期望结果异常说明images/train/JPG 或 PNG数量与描述一致数量差太多说明压缩包不完整labels/train/与 images/train 同名 TXT每行 5 个数字TXT 缺失或空文件会导致训练时跳过该图图片尺寸所有图宽高一致或接近尺寸差异太大建议统一 resize 到 640类别顺序VOC/COCO/YOLO 三个标签里类别排列一致顺序不一致时模型训练结果全错还有一类容易忽略的问题某些图片没有任何缺陷对应 TXT 文件是空的。这种空标签文件在训练时不影响但如果你用脚本统计类别分布要注意跳过空文件否则会把它们当成某个类别。2.3 转换脚本我一般会写一个交叉校验脚本资源里既然带了转换脚本按道理解压后直接用就行。但我拿到这类数据集习惯再写一个独立脚本做交叉验证防止资源里的脚本和标注文件之间有隐性偏差。核心是核对坐标换算关系。import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt_path, class_map): tree ET.parse(xml_path) root tree.getroot() # 读取原始图片宽高坐标系都靠这两个数 w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text 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) # 防止越界标签有的标注工具会标出比图片还大的框 xmin max(0, min(xmin, w - 1)) xmax max(0, min(xmax, w - 1)) ymin max(0, min(ymin, h - 1)) ymax max(0, min(ymax, h - 1)) # 转换成 YOLO 的归一化中心点加宽高格式 x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h box_w (xmax - xmin) / w box_h (ymax - ymin) / h lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines))逻辑说明核心动作就是拿到 XML 里的绝对坐标后中心点等于(xmin xmax) / 2宽等于xmax - xmin再分别除以图片宽高完成归一化。之所以用w - 1做 clamp 而不是w是因为像素坐标的最大合法值就是w - 1直接用w会导致极少数框的右边界等于 1.0超出 YOLO 训练时的合法范围。参数说明class_map是类别名称到索引的映射字典比如{scratch: 0, dent: 1, pit: 2, stain: 3}。这个映射里类别的顺序必须和你后续在数据 YAML 里写的 names 顺序完全一致。跨格式核对的时候我一般会把 VOC 里所有的 name 去重打出来和 COCO 的 categories 字段逐一比对看名字是否对得上。3. 数据集划分脚本随机切不行要按类别分层3.1 为什么不能图省事直接取前 4000 张解压后第一反应肯定是写个简单脚本按比例把图片切到 train、val、test 三个目录。但工业缺陷数据集有个典型问题同一个铝片样品往往拍了多张照片不同角度、不同光照而且缺陷类别不是均匀分布的。如果直接从头取 4000 张做训练、中间 500 张做验证很可能导致某些类别在验证集里一个都没有或者训练集里某个类别占比畸高。正确做法有两种。第一种是纯随机加固定随机种子适合类别分布均匀的场景。第二种是按类别比例做分层采样适合缺陷类别严重不平衡的场景。铝片表面缺陷里划痕通常占比很高凹坑和污渍可能只有几十张纯随机划分很容易把这几张全分到验证集里训练集里几乎没有这个类别的正样本。3.2 分层划分脚本的写法import random, shutil from collections import defaultdict from pathlib import Path # 固定随机种子保证两次运行结果一致 random.seed(42) img_dir Path(images/all) label_dir Path(labels/all) out_dir Path(datasets/aluminum) train_ratio 0.8 val_ratio 0.1 # 1) 按每个标签第一个类别把图片归到不同类别桶里 cat_to_imgs defaultdict(list) for txt in sorted(label_dir.glob(*.txt)): if txt.stat().st_size 0: continue first_cls txt.read_text().strip().split()[0] cat_to_imgs[first_cls].append(txt.stem)这里按 TXT 第一行第一个数字记录类别归属严格说不够严谨因为一张图可能有多个缺陷类别。但铝片缺陷场景里单类别样本占多数作为快速分层可以接受。如果标签里很多多类别混合框更严谨的做法是对每个类别分别收集图片索引再做多标签分层逻辑会更复杂但结果更可靠。train_files, val_files, test_files [], [], [] for cls, imgs in cat_to_imgs.items(): random.shuffle(imgs) n len(imgs) train_files imgs[:int(n * train_ratio)] val_files imgs[int(n * train_ratio):int(n * (train_ratio val_ratio))] test_files imgs[int(n * (train_ratio val_ratio)):] # 2) 按划分结果复制图片和标签到目标目录 for split, files in zip([train, val, test], [train_files, val_files, test_files]): (out_dir / images / split).mkdir(parentsTrue, exist_okTrue) (out_dir / labels / split).mkdir(parentsTrue, exist_okTrue) for name in files: shutil.copy(img_dir / f{name}.jpg, out_dir / images / split / f{name}.jpg) shutil.copy(label_dir / f{name}.txt, out_dir / labels / split / f{name}.txt)逻辑说明先按类别分组每组内部用random.shuffle打乱顺序再按比例切成三段最终 train、val、test 里各类别占比与原始数据一致。random.seed(42)是让脚本可复现的关键跑两次结果完全一样这样你对比不同训练参数时数据集本身不会引入额外变量。参数说明train_ratio0.8、val_ratio0.1时test 比例自动为 0.1。如果你的产线打算做跨批次验证建议把 val 的采集时间窗口和 train 分开而不是简单混合切分。这是工业数据集划分里最容易忽略的坑后面避坑章节会单独展开。如果希望训练集和验证集文件路径被 ultralytics 以列表方式读取再生成一个 train.txt 和 val.txt里面存每张图片的绝对路径即可find $(pwd)/datasets/aluminum/images/train -name *.jpg datasets/aluminum/train.txt find $(pwd)/datasets/aluminum/images/val -name *.jpg datasets/aluminum/val.txt这个命令在 Linux 或 WSL 下直接执行Windows 的 CMD 里没有find的同等写法前几行提到 shell 脚本的时候说的就是这种用法。3.3 划分完必做的三项检查第一数量检查。train 图片数和标签文件数应该一致如果 labels 里 txt 总数比 images 里 jpg 总数少超过 10%说明有图片没有对应标签训练时会有一批图被白白跳过。第二类别分布打印直接统计每个 split 下的 TXT 行数for split in train val test; do echo $split awk {print $1} datasets/aluminum/labels/$split/*.txt | sort | uniq -c done三类缺陷在各 split 里的比例应该接近原始比例。第三随机抽三张图把标签框叠加在原图上保存下来人工看一眼框的位置和缺陷实际位置对不对得上。这一步脚本替代不了。我一般会顺手统计每张图的缺陷框数量如果某个 txt 里出现了 20 多个框大概率是标注重复或转换脚本有 bug。4. YOLO 训练教程从环境配置到看训练日志4.1 环境配置ultralytics 版本差异是第一个坑训练教程部分一般基于 ultralytics 框架YOLOv8 和 YOLO11 都统一在这个框架下训练。环境配置的标准动作是pip install ultralytics看似一行命令但这里有个很容易翻车的点ultralytics 升级到 8.4.x 之后部分数据集格式检查逻辑变了老教程里写的data: ./aluminum.yaml仍能跑但新版可能提示找不到 labels 目录。建议装完先固定版本pip install ultralytics8.2.0,8.4.0逻辑说明8.2 到 8.3 之间对 YOLOv8 和 YOLO11 的模型定义兼容性比较稳实测切到 8.4.x 后部分自定义数据集的缓存行为不同有时候数据增强参数会静默改掉。这不是说 8.4 不行而是在这套资源配套教程的前提下固定版本能减少变量。如果你本来就习惯最新版那跳过这条但别怪训练过程出现和教程不一致的报错。训练之前还需要预训练权重。yolo 预训练模型下载一般从 ultralytics 发布页拿官方权重yolov8n.pt大概 6MByolov8s.pt大概 22MB。训练命令里指定了权重文件框架会自动下载到当前目录。4.2 数据 YAML 的写法path: /home/user/datasets/aluminum train: images/train val: images/val test: images/test nc: 4 names: 0: scratch 1: dent 2: pit 3: stain注意 path 这里填绝对路径。很多人把 path 填成相对路径训练时当前目录对得上能跑通但 val 阶段如果换了工作目录会直接报 FileNotFoundError。names 的顺序必须和标签 txt 里的类别索引一致这个顺序在转换脚本里定好了后面到训练阶段就不要再改动。改 names 顺序等于把所有标签全部重新映射一遍。4.3 训练命令与关键参数yolo train \ modelyolov8n.pt \ dataaluminum.yaml \ epochs100 \ imgsz640 \ batch16 \ patience15 \ projectruns/aluminum \ nameexp_01参数说明参数作用这个数据集上的建议model指定预训练权重先用 yolov8n.pt 跑通再换 yolov8s 对比epochs最大训练轮数100 轮足够早停一般 50 轮左右结束imgsz训练输入分辨率640 起步显存够就试 800划痕是细长条分辨率太低了细节容易丢batch单次进模型的图片数8~16 之间低于 4 容易 BN 崩溃patience多少轮不提升就早停15 比较均衡设太大浪费时间设太小容易在平台期提前停project/name日志和权重输出路径每次跑实验换 name方便后面对比分辨率这一个参数对结果影响很大。铝片划痕可能只有几个像素宽imgsz640 时模型输入层已经把缺陷压缩得很小了更别说如果原图是 1280 的。有显存条件的话多个分辨率各跑一版对比 mAP比反复调学习率划算得多。4.4 训练日志怎么读正常训练时日志里最该盯的是三组量。第一组是train/box_loss和val/box_loss理想情况两者同步下降val 在后期走平。如果 val 的 loss 先降后涨而 train 还在降说明过拟合了回去调大增强或者加正则。第二组是metrics/precision(B)和metrics/recall(B)。铝片缺陷场景里 recall 比 precision 重要漏检比误检代价高所以如果 recall 卡在 0.8 上不去优先从数据和 imgsz 入手而不是调置信度阈值。第三组是metrics/mAP50(B)和metrics/mAP50-95(B)两者差距大说明框的定位精度不够目标能找着但边界不准。这里额外说一句关于 yolo 损失函数的事。训练日志里 box_loss 是 CIoU 损失分量cls_loss 是分类损失分量dfl_loss 是分布焦点损失。如果你看到 cls_loss 降到很低但 box_loss 还在高位大概率是框定位难而不是分类难这时候考虑提高 imgsz 比加大训练轮数更有效。训练过程中不需要每轮都盯真正要的是最后一个 epoch 的验证结果。如果是第一次训练关注跑完 100 轮要多久以及 val mAP 有没有在最后 10 轮里持续上升持续上升说明还没收敛可以追加轮数。5. 常见问题排查铝片数据集最容易翻车的五个点5.1 现象训练时报警 “corrupt label” 或提示标签越界原因VOC 转 YOLO 时边界框坐标超出图片宽高范围或者某一行 txt 里的类别索引大于 YAML 里配置的 nc又或者归一化后的宽度或高度为负值。解决训练前写一个校验脚本遍历 labels 目录下所有 txt检查每一行的五个数字是否都在合法范围内。发现问题直接定位到具体文件名修复。这一步是整个流程里成本最低、收益最大的动作别跳过。from pathlib import Path for txt in Path(datasets/aluminum/labels/train).glob(*.txt): with open(txt) as f: for line in f: parts line.strip().split() if len(parts) ! 5: print(f列数错误: {txt} - {line}) continue try: cls, xc, yc, w, h [float(v) for v in parts] except ValueError: print(f非数字标签: {txt} - {line}) continue if not (0 xc 1 and 0 yc 1 and 0 w 1 and 0 h 1): print(f坐标越界: {txt} - {line})逻辑说明脚本不做修复只负责报错。理由是越界标签单纯 clamp 到边界会掩盖真实问题比如标签框中心点落在图外这说明 XML 里的坐标本身就是错的。只有先找到是哪张图、哪一行才能决定是手动改还是重新标注。5.2 现象某个缺陷类别 mAP 特别低其他类别正常原因类别不平衡。5000 张图里 scratch 类可能有 3000 张pit 类只有 200 张模型对少数类几乎学不到稳定特征。解决先看验证输出里每个类别的 AP 和 recall。少数类 AP 低的去统计它在训练集的样本量。如果少于 500 张框做类别重采样把少数类的图片多复制几份再做平移、旋转增强放回训练集。注意不要单纯重复原图会让模型把同一张图的噪声也记住了。这种类别不平衡在工业质检数据里几乎是常态。5.3 现象训练和验证 mAP 都不错拿到新产线拍的铝片就废掉原因铝片高反光不同光照角度、不同批次铝材的表面氧化程度差异大模型学到的是当前数据集的“成像风格”而不是缺陷本身。另一方面数据增强里的色调、饱和度、明度扰动默认值偏高虽然能让模型更鲁棒但过度扰动把金属表面的真实反射特征抹掉了。解决在 ultralytics 的配置里把增强参数调保守典型值是hsv_h0.015、hsv_s0.5、hsv_v0.4再把 mosaic 的随机翻转关掉或调小。这确实会轻微降低训练集上的 mAP但换来的是一段时间后新采集数据的稳定性。工业场景里评估模型不是只看单次测试集的 mAP要看跨批次验证的稳定性。5.4 现象训练到第 20 个 epoch 突然 loss 变成 NaN原因这是典型的 yolo 训练中 bn 崩溃。常见触发条件是 batch_size 太小导致 BN 层统计量不稳定或者初始学习率太高导致梯度爆炸。我踩过的坑里batch4 配合默认 lr 出现过多次batch16 以后没有再出现。解决先把 batch 调到 16 或以上如果显存不够就降低 imgsz而不是继续降 batch。把 optimizer 换成 AdamW并且开启前三个 epoch 的 warmup能明显减少 NaN。预训练权重务必用官方发布的 yolov8n.pt不要自己从头训练。从头训练在 5000 张图的小数据集上收敛很慢而且 loss 曲线起伏大。5.5 现象验证集 mAP 很高但用视频抽帧测试处处漏检原因这是工业数据集划分里最隐蔽的坑。同一块铝片的多角度照片被随机切到了训练和验证两边模型等于“见过”了验证集里的具体样品只是没见过那个拍摄角度。验证指标自然虚高换一块新铝片就现出原形。解决按样品或按拍摄批次分组划分。一个铝片的全部图像要么全进训练集要么全进验证集保证验证集里的目标在训练阶段完全不可见。强烈建议解压这套资源后先看文件命名规则如果文件名带样品编号或批次字段按那个字段分组。如果没有编号字段就按文件名前缀聚类然后分批切。这个坑我栽过一次之后每次划分数据集都会先问一句“文件名里有没有隐含的分组信息”。6. 模型验证与部署PR 曲线比 mAP 数字更能说明问题训练完不要只盯着终端打出来的 mAP。ultralytics 会在 runs/aluminum/exp_01/ 目录下输出 confusion_matrix.png、PR_curve.png、F1_curve.png 三张图。我一般先看 PR_curve横轴召回率、纵轴精确率曲线越贴近右上角越好。工业质检场景里曲线左上角的表现远比右下角重要因为漏检比误检更致命。如果某个类别在低置信度区间里精确率掉得很快说明模型产出了大量假阳性框这时候不是降阈值能解决的要回到数据层面补样本。再看 confusion_matrix.png重点看非对角线位置的亮点这些是类别间的混淆。比如 dent 经常被认成 pit说明这两个缺陷的形状和灰度特征太接近需要回去看标注本身是否就不一致。顺带提一句yolo 混淆矩阵总合不唯一这个问题是正常的矩阵里每个单元格归一到行或者列会得到不同总合看对角线数字就够不用纠结。模型验证通过后部署前先导出 ONNXyolo export modelruns/aluminum/exp_01/weights/best.pt formatonnx imgsz640导出后至少对一张不在训练集里的图做一次完整推理确认输出框正常from ultralytics import YOLO model YOLO(runs/aluminum/exp_01/weights/best.pt) results model.predict(test_samples/real_line_01.jpg, conf0.35, imgsz640) for r in results: print(r.boxes.data) # x1, y1, x2, y2, conf, clsconf0.35 在缺陷检测里属于保守设置目的是降低漏检。具体值该怎么定看 PR_curve 上目标类别的曲线弯折位置如果 0.35 处的精确率掉得很厉害就往上提到 0.5如果召回率还不够说明模型本身没学到位调阈值只是心理安慰。那次做完整个流程我把同一个铝片样品在三个不同光照角度拍的 20 张图全放进验证集模型 mAP 从 0.92 掉到 0.81才算真正明白之前那个数字有多虚。从那以后我每拿一套数据集第一件事就是先画一张类别分布柱状图再跑一遍标签合法性检查最后确认划分方式不会把同一个样品切到两边然后才谈训练。这套流程你也会用上希望帮到你。本文还有配套的精品资源点击获取