简介:YOLO铝片表面缺陷检测数据集,包含5000张真实场景铝片表面图片,面向计算机视觉目标检测学习者、工业质检项目开发者和相关课程实践者,可直接用于YOLO系列模型训练与缺陷识别算法验证。资源包共2000个文件,以xml标签文件为主(1985个),同时提供json、txt格式标签及3个Python划分脚本、6个HTML教学文档,整体压缩包约205.89MB。三种标签分别存放于独立文件夹,便于接入VOC、COCO、YOLO生态;划分脚本支持按需生成训练集、验证集和测试集,HTML教程覆盖Windows与Linux环境搭建、YOLO训练案例修改、自定义数据集训练流程。借助配套博文中的数据集详情展示,读者可快速定位缺陷类型和使用方法。目前已有404人学习下载,适合用于铝片表面缺陷检测课题、毕业设计、竞赛备赛以及工业落地前的算法预研。
1. 铝片表面缺陷检测:一个数据包把YOLO训练从格式泥潭里拉出来
铝片表面缺陷检测,听起来是个标准的小目标检测场景,真跑到产线上做就会发现全是细节坑。质检员在灯光下用肉眼找划痕、裂纹、氧化斑和凹坑,一个班次下来漏检率随疲劳快速上升,所以很多工厂想用YOLO做自动质检。可大多数人不是卡在模型选型上,而是卡在第一步——数据格式。标注工具导出的标签五花八门,坐标有的是像素、有的是归一化值,类别ID还各说各话。这个数据包把5000张铝片表面缺陷图全部标注好,同时提供VOC、COCO、YOLO三种格式的标签,外加数据划分脚本和训练教程,相当于把从数据准备到模型训练这条链路提前铺好了路。适合正在做工业视觉、刚接触yolo目标检测,或者想用现成数据集快速跑通训练流程的从业者。
2. 三种标注格式的取舍:为什么数据集包要同时给VOC、COCO和YOLO
工业缺陷检测项目里,这个问题几乎天天有人问。YOLO训练时要的是每张图对应一个同名txt,文件里每行是一个目标,五个数字依次是class_id、归一化中心点x、归一化中心点y、归一化宽、归一化高。这种格式对训练框架最友好,读取时不需要额外信息,所以yolo系列从第一代用到最新一代,格式一直没变。VOC格式则是把每个目标写成一个XML文件里的object节点,坐标是xmin、ymin、xmax、ymax这种绝对像素值,而且XML里自带图片的width和height,所以它天然适合作为中转格式——从任意标注工具导出后,再转成其他格式都不容易丢信息。COCO格式则是把所有图片和标注汇总成一个JSON文件,用images、annotations、categories三个数组组织,支持检测也支持实例分割,是学术评估的标准格式。
2.1 YOLO、VOC、COCO三者的本质差异
三种格式不是说谁取代谁,而是各自服务于不同环节。我在实际项目里的经验是:标注工具导出VOC,训练框架吃YOLO,学术对比用COCO。下表把关键差异列出来,方便你判断自己手上的数据到底缺什么。
| 维度 | YOLO txt | VOC xml | COCO json |
|---|---|---|---|
| 坐标形式 | 归一化(cx, cy, w, h) | 像素(xmin, ymin, xmax, ymax) | 像素(xmin, ymin, w, h) |
| 每张图对应文件 | 一个同名txt | 一个同名xml | 全部图一个json |
| 是否含图像尺寸 | 否 | 是 | 是 |
| 是否含类别名 | 否,只有id | 是 | 是,单独categories数组 |
| 典型用途 | yolo训练 | 标注输出/中转 | 评估/实例分割 |
所以这个数据集包把三种格式都给了,你用的时候不一定全用得上,但能省掉最坑的转换环节。我一般只关心YOLO格式用于训练,但会留一份VOC格式作为人工复核用。原因很简单:VOC的XML用记事本就能打开,出现问题时排查成本最低;COCO的JSON文件动辄几十MB,肉眼根本没法查。
2.2 从VOC转YOLO的转换脚本:坐标归一化与越界处理
如果你的标注工具导出的是VOC,需要转成YOLO格式。下面是几乎每个项目都会用到的最小转换脚本,这段代码同样适用于把这个数据集里的VOC标签重新生成一遍YOLO标签。
import xml.etree.ElementTree as ET from pathlib import Path xml_dir = Path("Annotations") # VOC xml 所在目录 img_dir = Path("JPEGImages") # 原始图片目录 out_dir = Path("labels") # 输出 yolo txt 目录 out_dir.mkdir(exist_ok=True) classes = ["scratch", "crack", "oxidation", "dent"] def voc2yolo(xml_path): tree = ET.parse(xml_path) root = tree.getroot() img_file = root.find("filename").text size = root.find("size") img_w, img_h = int(size.find("width").text), int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in classes: continue cls_id = classes.index(name) box = obj.find("bndbox") # 防越界:标注框超出图像边界时先裁剪到合法范围 xmin = max(0, float(box.find("xmin").text)) ymin = max(0, float(box.find("ymin").text)) xmax = min(img_w, float(box.find("xmax").text)) ymax = min(img_h, float(box.find("ymax").text)) # 面积小于等于0的框直接丢掉 if xmax <= xmin or ymax <= ymin: continue cx = (xmin + xmax) / 2 / img_w cy = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out_path = out_dir / (xml_path.stem + ".txt") out_path.write_text("\n".join(lines), encoding="utf-8") for xml_path in xml_dir.glob("*.xml"): voc2yolo(xml_path)逻辑说明:这段脚本把VOC的绝对像素坐标转成YOLO需要的归一化中心点坐标和宽高。关键在防越界处理——实测很多标注工具导出的XML里,框坐标会跑到图片外一两像素,不处理的话归一化坐标会小于0或大于1,训练出来的模型在图片边缘会频繁漏检。xmax <= xmin这个判断是为了丢掉面积为0或反向的脏框。
参数说明:classes列表的顺序一旦定下来就不要改,否则之前生成的txt标签会和data.yaml里的names错位,这是新手最容易犯的错误。实际转换时,filename节点不一定可靠,有的标注工具存的是相对路径,有的存绝对路径,所以脚本里只用xml_path.stem来生成同名txt,不依赖XML里的filename字段,更稳。
2.3 COCO格式的三个处理要点
COCO格式不是必须的,但如果你想拿这个数据集跟公开结果做对比,或者用mmdetection跑一下,还是绕不开。我自己用下来有三个容易踩的细节。
第一,COCO的categories数组里,id通常从1开始,而YOLO的class_id从0开始。转换时一定记得做cls_id = coco_category_id - 1,否则数据集喂给mmdetection后,分类结果全是偏的,而且很难排查。第二,很多检测数据集只有bbox而没有segmentation字段,而COCO评估脚本计算mask相关指标时会自动忽略或报错,所以如果只做检测评估,就只用bbox的AP指标,不要去看mask AP,免得给自己制造困惑。第三,COCO的annotation坐标是[x, y, w, h]格式,即左上角坐标加宽高,而VOC是[xmin, ymin, xmax, ymax],两者转来转去时最容易把宽高算错。建议转换后立刻用可视化脚本在原图上画框验证一遍,不要直接喂给训练。
3. 用划分脚本切分铝片数据:从随机洗牌到类别均衡划分
3.1 为什么不能直接random.shuffle
很多教程会告诉你,把图片和标签放在一起,写一个random.shuffle,然后按7:2:1切开就行。小数据集上这么做没问题,但缺陷检测里必须考虑两个问题。第一个问题是图片泄漏:如果数据集里包含同一块铝片的多张连续拍摄或不同角度裁剪图,直接随机切分很容易让train和val里出现同一块铝片的不同图片。模型在验证集上的表现就会虚高,因为它在训练时已经看过这块铝片的纹理和背景了。第二个问题是类别不均衡:铝片缺陷里划痕可能占60%,裂纹占25%,氧化斑占15%,凹坑只有5%,随机切分时凹坑样本可能全部落到train集,val里一个没有,训练过程中的早停就被其他类别带偏了。
所以项目里的常见做法是:先不看图片,先读标签,按缺陷类别做分层切分。这就是这个数据集里划分脚本的核心价值所在——它把“按类别的分层划分”这件事固化成脚本,你拿到后只需要改路径和比例就能用。
3.2 一份按类别均衡的划分脚本
下面这个脚本以YOLO格式的labels目录为输入,按每个txt里出现的类别做分层划分,输出train.txt、val.txt、test.txt三个图片路径清单。
import random from collections import defaultdict from pathlib import Path random.seed(42) label_dir = Path("labels") img_dir = Path("images") train_ratio, val_ratio = 0.8, 0.1 samples = [] by_class = defaultdict(list) for txt in label_dir.glob("*.txt"): cls_set = set() for line in txt.read_text(encoding="utf-8").strip().splitlines(): if line.strip(): cls_set.add(int(line.split()[0])) if not cls_set: continue stem = txt.stem samples.append({"stem": stem, "cls": cls_set}) for c in cls_set: by_class[c].append(stem) train_set, val_set = set(), set() for c, stems in by_class.items(): random.shuffle(stems) n_train = int(len(stems) * train_ratio) n_val = int(len(stems) * val_ratio) for s in stems[:n_train]: train_set.add(s) for s in stems[n_train:n_train + n_val]: if s not in train_set: val_set.add(s) test_set = {s["stem"] for s in samples} - train_set - val_set for split, names in [("train", train_set), ("val", val_set), ("test", test_set)]: out = Path(f"{split}.txt") out.write_text( "\n".join(str(img_dir / f"{n}.jpg") for n in sorted(names)), encoding="utf-8" ) print(f"{split}: {len(names)} images")逻辑说明:先按类别循环划分,训练优先,多类别样本优先占用train名额,val取该类别在train里剩余的部分;最后test用全集减掉train和val。这种做法的核心好处是:即使凹坑只占5%,它在val和test里也能各自保留差不多的比例,训练时早停和最终评估都不会被类别不平衡带偏。
参数说明:train_ratio=0.8, val_ratio=0.1是我在5000张规模下的常用配置,如果你的样本量更小,可以考虑改成0.7/0.15/0.15;random.seed(42)必须固定,否则每次划分结果都不同,事后复现问题时会很痛苦。另外,按类别划分后train/val/test的实际比例和预设会略有偏差,因为多类别样本在不同类别里被重复计数,最后test是补集,所以test比例会略大于10%。这个偏差在实际中可接受,不要为了让比例精确到小数点后两位而引入更复杂的约束,那样代码维护成本会高很多。
3.3 划分后的五步检查
划分完不要急着训练,我一般会做五步检查,确认脚本跑的没毛病。
第一,确认三个txt文件的行数不为0,尤其test集。如果test集是空的,后面验证流程根本没数据可用。第二,抽查图片文件名与标签文件名一一对应。很多时候图片是.jpg,标签是.txt,但有些图是.pgm或.png,如果数据集混了几种后缀,脚本里应按实际后缀拼接,否则val里的图片路径不存在,训练直接报错。第三,检查同一块铝片的不同图是否被切开。方法是按文件名前缀分组,比如al_001_crop1.jpg和al_001_crop2.jpg应落在同一个集合里,如果分开了,说明数据集本身不是按原图分组采样的,你需要根据实际组织方式调整划分脚本。第四,按类别统计分布。第五,跑一次python -c "from PIL import Image; Image.open('...')"之类快速验证图片能正常打开,工业相机拍出来的图片偶尔会有损坏文件。
另外提一句:有些人喜欢把划分脚本和图片路径硬编码在训练yaml里,这样在换机器或换项目时容易踩到路径坑。我一般会把train.txt/val.txt/test.txt生成在项目根目录,data.yaml里直接引用,这样相对路径迁移时更稳。
4. 用YOLO训练缺陷检测模型:跑通最小训练命令与三个核心参数
4.1 训练环境与预训练模型选择
训练环境这块其实没有太多玄学。常见做法是用conda建一个干净的python3.9或3.10环境,装pytorch和ultralytics,一条pip install ultralytics就能搞定。第一次跑的时候,框架会自动下载yolo预训练模型权重到本地,所以网络环境要留意一下;如果公司内网限制外网,把yolov8n.pt这类权重文件单独下载后放到当前目录或ultralytics的缓存目录,避免训练时卡在权重下载阶段。
预训练模型怎么选?我做铝片缺陷检测时,一般从yolov8n起步。原因很简单:缺陷检测的类别通常只有几个,难点在小目标和反光干扰,而不是语义复杂度,yolov8n的容量够用且训练速度快。如果机器显存充足、想要更高精度,再换yolov8s或yolov8m。不要一上来就挑最大的模型,铝片缺陷没有丰富纹理需要学习,大模型在5000张这种规模上很容易过拟合。这个选择逻辑对yolov8系列、包括新出的yolov13这类版本都适用,底层的数据准备流程没有变。
4.2 用data.yaml把数据路径指对
训练前要准备好两个文件,一个是3.2划分出来的train.txt/val.txt/test.txt,另一个是data.yaml。下面这个yaml是铝片四类缺陷的最小配置。
train: ./train.txt val: ./val.txt test: ./test.txt nc: 4 names: 0: scratch 1: crack 2: oxidation 3: dentnc要和names的数量严格一致。train和val这里指向的是3.2生成的txt文件,也可以直接指向images目录,但我推荐指向txt,因为你已经用划分脚本把路径写清楚了,让yaml和划分脚本解耦,后面换数据集时不用改动训练代码。
4.3 最小训练命令与参数含义
数据准备好之后,处理数据集用于yolov8训练的流程就到收尾了,训练命令就一行。
yolo detect train data=aluminum.yaml model=yolov8n.pt \ epochs=120 batch=16 imgsz=640 patience=20 \ optimizer=AdamW lr0=0.001 \ project=runs/aluminum name=train_v1每个参数说一下。
epochs=120:铝片缺陷在5000张规模下,120轮足够收敛。如果训练到60轮还看不到val mAP上涨趋势,说明数据或参数有问题,不是轮数不够。
batch=16:batch大小和显存强相关。16在12G显存上配合yolov8n和imgsz=640比较稳;如果只有8G显存,先把batch降到8,不要动imgsz。注意,batch是全局batch,多卡训练时框架会自动分配到各卡,你不用自己算。
imgsz=640:训练尺寸。铝片表面缺陷很多是细长条,比如划痕,640不够的时候可以考虑960,但要同时把batch降下来。不要直接拉到1280,会让训练时间翻倍,而且小目标不一定变清晰,不划算。
patience=20:早停参数,如果连续20轮val mAP没有上升,训练自动停止。工业项目里我不太建议关掉早停,除非你明确知道自己要跑几轮。
optimizer=AdamW:这是我自己比较偏好的选择。老版本YOLO默认SGD,收敛曲线更陡但调参空间大,小数据集上AdamW更稳,尤其当你不想折腾学习率时。如果铁了心想用SGD,就把lr0调回0.01,并拉长epochs。
lr0=0.001:初始学习率。用预训练权重微调时,0.001是我常用的起点;如果从随机初始化开始训练,要降到0.0005左右。
提示:训练前确认train.txt里的路径在训练机上真实存在。很多翻车都发生在路径不存在但命令行不报错、最终效果极差的情况。
4.4 训练日志里要盯的三条曲线与断点续训
训练开始后,不要只盯着loss下降就开心。YOLO的yolo损失函数由三部分组成:box_loss、cls_loss、dfl_loss,分别代表框回归误差、分类误差、分布聚焦损失。这三者在tensorboard或命令行日志里都能看到。我的习惯是主要看两条:val/box_loss和val/cls_loss,它们更能反映真实泛化能力。train loss降、val loss不降,是过拟合信号,说明该调低epochs或增强数据正则;train和val一起降,才是健康状态。
另外一个容易被忽略的指标是mAP50和mAP50-95的区别。工业缺陷检测里,框的精确位置往往比类别更重要,所以mAP50比mAP50-95更有参考价值。如果两者差距过大,说明框回归精度不够,优先检查标注框边界是否准确,而不是急着调模型。
训练中断了怎么办?如果因为断电或显存不够中断,用断点续训命令从last.pt继续,不要从头再来。
yolo detect train data=aluminum.yaml model=runs/aluminum/train_v1/weights/last.pt resume=True这里的last.pt是每个epoch结束都会保存的断点文件,不要只保存best.pt就完事。best.pt是val mAP最高的checkpoint,但如果是最后一个epoch之前崩的,last.pt反而更有价值。
5. 铝片缺陷检测训练避坑实录:现象、原因与解决
5.1 loss下降很顺,mAP50却一直趴在0.6上不去
现象:train loss一路降到0.8以下,val loss也不高,但mAP50始终在0.6附近,并且召回率明显偏低。
原因:先查划分,再看标注。我第一次遇到时,发现val里混进了和train同槽的连续图片,模型在val上“背答案”,真实场景自然不行。另一个常见原因是缺陷类别之间太像,比如细划痕和轻微裂纹,人眼都容易混淆,模型会把它们预测成同一类。
解决:先用3.2的划分脚本重切数据,确认val里没有同源图。然后生成混淆矩阵,用yolo detect val data=aluminum.yaml model=runs/aluminum/train_v1/weights/best.pt --plots,重点看哪两类互相串。如果只是某个细分类互相混淆,考虑把它们合并成一个类——铝片质检场景下,划痕和细裂纹在工艺处理上往往是同一种处理方式,没必要强行分。
5.2 标签坐标越界,边缘检测大量漏检
现象:训练正常,但验证时发现图片边缘的缺陷几乎全部漏检,查看预测结果时发现很多框中心点在图像外。
原因:标注时把框拖到图片外,转换脚本没有做裁剪,导致归一化坐标里的cx或cy大于1。训练时模型学到了越界框的模式,推理时自然对边缘目标无感。
解决:在2.2的转换脚本里加上clip逻辑,对历史标签也做一次清洗。读取已有txt,把超出[0,1]的坐标裁剪回来,宽或高小于0.005的框直接删掉。我一般用20行python就能完成清洗,不需要依赖工具,手动改label太累了。
5.3 反光表面的缺陷检不到,哑光面效果却很好
现象:同一批测试数据,哑光面的缺陷检出率在0.85以上,反光面只有0.7,mAP差距超过10个点。
原因:反光让缺陷对比度变低,加上训练过程中mosaic增强很容易把细长的划痕切成四块,模型学不到完整样本。另外,数据集中反光面图片本身占比可能只有一两成,模型根本没看够。
解决:训练时关掉mosaic或调低概率,ultralytics里可以直接把mosaic设为0.5;把hsv_v从默认值提高到0.4,增强亮度变化,让模型对反光不敏感。还有一个土办法:把反光面样本在train.txt里重复一份,人为提高占比,在小样本阶段很有效。
5.4 显存不够,训练到一半中断
现象:运行时CUDA out of memory,或者训练到某轮val时突然崩掉。v100这类卡虽然显存够,但如果你同时跑了其他服务,也可能中途崩。
原因:imgsz=960、batch=16的组合,在12G以下显存的卡上很容易爆。老卡虽说显存大,但计算速度跟不上,长时间高负载也会触发其他问题。
解决:先把batch降到8,如果还崩就换yolov8n并把imgsz降到640。不要开梯度累积去硬撑,那会让训练时间变长且bn统计异常。另外把cache=True关掉,显存紧张的机器上缓存图片会额外占用大量内存。训练前用nvidia-smi看一下显存占用,确认没有其他进程残留。
5.5 换了新版本YOLO后,同样的数据结果对不上
现象:升级ultralytics版本后,使用完全相同的yaml和命令,mAP掉了两三个点。
原因:版本迭代中数据增强的默认概率、损失权重和调度策略都会变,很多是不可见的。包括新版本yolo训练,同样存在默认参数变化问题。
解决:项目根目录放一个requirements.txt,锁住ultralytics版本。pip freeze > requirements.txt是最简单的后悔药。跨版本对比模型时,至少做一次从训练到验证的全流程AB测试,不要只看权重文件大小就判断新旧版本等价。
6. 训练完怎么验证:混淆矩阵、压力测试与标注复核的三个技巧
6.1 混淆矩阵会骗人
很多人看到yolo生成的混淆矩阵,第一反应是行加起来不等于100%,以为自己代码写错了。其实这是正常现象:检测场景下有background类,且一个GT框可能与多个预测框关联,所以yolo混淆矩阵总合不唯一。看的时候不要纠结总和,只看对角线。如果某一行的非对角线元素特别亮,说明这个类别被系统性误判,优先处理误判,而不是盲目调参。我自己习惯是先看裂纹和划痕这两行,因为它们最容易串。
6.2 用产线实拍视频做压力测试
测试集图片只能反映静态效果,产线是连续运动的。我一般会录一段真实产线的短视频,在本地跑一遍推理,观察帧与帧之间框的稳定性。如果同一处缺陷在30帧里时有时无,说明置信度阈值卡得太严,把conf调低到0.25再压测一次,找到既不漏检又不误报的阈值区间。这个验证方法能提前发现很多测试集上看不出的问题,比如运动模糊和光线闪烁。
6.3 标注质量复核是最后的兜底
训练结束后,我会从val集随机抽50张图,人工比对模型的预测框和真实标注框。如果发现标注框有系统性偏差,比如所有框都偏左上方几像素,那问题不在模型,而在标注。这时候回改标注再重新训练,比调任何参数都有效。标注质量这件事,在缺陷检测里比模型结构更值得投入时间。
我现在的习惯是:每训完一版模型,先看混淆矩阵,再跑一段实拍视频,最后抽50张图复核标注。这三步做完再谈要不要调参,省掉了很多无效实验。希望帮到你。
本文还有配套的精品资源,点击获取