ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

瓷砖缺陷数据集YOLO+VOC双格式实战:888张3标签训练避坑指南

瓷砖缺陷数据集YOLO+VOC双格式实战:888张3标签训练避坑指南

简介:本资源为瓷砖缺陷目标检测数据集,面向从事工业质检、表面缺陷识别方向的算法工程师与深度学习学习者,可用于训练和验证YOLO、Faster R-CNN等目标检测模型。数据集聚焦单块瓷砖图像,标注划痕线、破洞与边缘破碎三类缺陷,标签名称分别为huahengxian、podong和bianyuanposui,共16558个矩形框,其中边缘破碎6054框、破洞6288框、划痕线4216框,类别分布相对均衡。压缩包共2000个文件,包含888张jpg图片、888个VOC格式xml标注文件、890个txt文件(含YOLO格式标签及类别说明),整体约544.31MB,图片清晰且未做数据增强,可直接用于训练。目前已有209人学习下载。读者可获得开箱即用的双格式标注数据,省去自行采集与标注成本,便于快速搭建缺陷检测基线、对比不同模型效果,并针对小目标与多类别场景开展调优实验。

1. 瓷砖缺陷数据集:888 张、3 标签的 YOLO+VOC 双格式到底怎么用

拿到一个标注好的瓷砖缺陷数据集,第一反应往往不是兴奋,而是犯嘀咕:888 张够不够训?3 个标签是哪三类?YOLO 格式和 VOC 格式同时给,到底该用哪套?我见过太多人把 zip 解压完,对着 images、labels、Annotations、JPEGImages 几个文件夹发愣,然后随手挑一个格式就开始跑,结果训练 loss 不降、mAP 卡在 0.3 上不去,回头怀疑数据集质量。其实问题多半出在格式没对齐、标签没校验、划分没做对这三件事上。这篇就围绕「瓷砖缺陷数据集 yolo+voc 格式 888 张 3 个标签」这个具体对象,把从解压到跑通第一条 baseline 的完整路径讲清楚,顺带把 YOLO 入门阶段最容易翻车的几个点提前标出来。适合刚接触工业缺陷检测、手里正好有这么一份数据、想尽快验证方案可行性的同学。

2. 先搞懂 YOLO 与 VOC 两套格式的差异和选型逻辑

2.1 同一批标注,为什么会有两种格式

VOC 格式是 Pascal VOC 时代定下来的标注规范,核心是一张图对应一个 XML 文件,缺陷框写在<object>节点里,坐标是绝对像素值xmin, ymin, xmax, ymax,类别名写在<name>里。YOLO 格式则是把每张图的标注压成一个同名.txt,每行一个目标,格式是class_id x_center y_center width height,四个坐标全部归一化到 0~1 之间,class_id 从 0 开始编号。

同一批数据同时给两套,通常是因为标注工具(LabelImg、labelme 之类)导出时勾了多种格式,或者作者想兼容不同训练框架。对使用者来说,这不是「哪个更高级」的问题,而是「你的训练代码吃哪套」的问题。Ultralytics 系的 YOLOv5/v8/v11 默认吃 YOLO txt,早期 darknet 版本也是 txt,而一些基于 torchvision、mmdetection 的老代码更习惯读 VOC XML。

2.2 选型:优先用 YOLO 格式,VOC 留作校验

我的习惯是训练直接用 YOLO 格式,VOC 格式不删,留着做交叉校验。原因有三点:第一,YOLO txt 读取快,不用解析 XML,数据加载器压力小;第二,归一化坐标天然和图像尺寸解耦,改输入分辨率时不用重算标注;第三,YOLO 训练脚本对 txt 的支持最成熟,坑最少。

但 VOC 不能扔。当你怀疑某张图标注有问题时,XML 里的绝对坐标更直观,用 LabelImg 打开能直接看到框的位置,比对着归一化小数反推像素值快得多。所以合理做法是:YOLO 格式进训练管线,VOC 格式进人工复核流程。

2.3 888 张、3 标签的规模判断

888 张图、3 个类别,平均下来每类不到 300 张的图量级。这个规模做工业缺陷检测属于「能跑通、但别指望高精度」的档位。如果 3 个类别分布均衡,每类 250~300 张,配合数据增强和迁移学习,mAP@0.5 做到 0.7 以上是有希望的;如果某一类只有几十张,那一类基本就是拖后腿的。

先做一件事:统计每类目标框数量,而不是图片数量。一张图可能有多个缺陷框,真正决定模型能不能学的是框的数量和多样性。下面这段脚本就是干这个的。

import os from collections import Counter label_dir = "labels/train" # YOLO txt 目录 counter = Counter() img_with_obj = 0 for name in os.listdir(label_dir): if not name.endswith(".txt"): continue path = os.path.join(label_dir, name) with open(path, "r") as f: lines = [l.strip() for l in f if l.strip()] if lines: img_with_obj += 1 for line in lines: cls_id = int(line.split()[0]) counter[cls_id] += 1 print("含目标的图片数:", img_with_obj) print("各类别框数量:", dict(sorted(counter.items())))

逻辑说明:遍历 labels 目录下所有 txt,逐行取第一个字段作为类别 id 计数,同时统计非空文件数。参数说明:label_dir换成你实际的 YOLO 标签目录;如果类别名有映射表(比如classes.txt或data.yaml里的 names),把 id 换成名字再打印更直观。跑完这一步,你心里就有底了——如果某个类别框数低于 100,后面训练时要么补数据,要么在评估时单独看它的表现,别被整体 mAP 骗了。

3. 从解压到跑通第一条 YOLO baseline 的完整步骤

3.1 目录结构整理与 data.yaml 配置

解压后大概率是 images、labels、Annotations、JPEGImages 混在一起。YOLO 训练要求 images 和 labels 目录平行,且文件名一一对应(a.jpg对应a.txt)。先按下面结构归位:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml

如果原始数据只有一套 images 和一套 labels,用脚本按 8:2 划分,注意划分的是「图片+对应标签」这一对,不能只挪图片不挪标签。划分时用固定随机种子,保证可复现。

import os, random, shutil random.seed(42) img_dir = "images/all" lbl_dir = "labels/all" out_img = "images" out_lbl = "labels" files = [f for f in os.listdir(img_dir) if f.endswith((".jpg", ".png"))] random.shuffle(files) split = int(len(files) * 0.8) train_files, val_files = files[:split], files[split:] for subset, flist in [("train", train_files), ("val", val_files)]: os.makedirs(f"{out_img}/{subset}", exist_ok=True) os.makedirs(f"{out_lbl}/{subset}", exist_ok=True) for f in flist: stem = os.path.splitext(f)[0] shutil.copy(os.path.join(img_dir, f), f"{out_img}/{subset}/{f}") lbl = os.path.join(lbl_dir, stem + ".txt") if os.path.exists(lbl): shutil.copy(lbl, f"{out_lbl}/{subset}/{stem}.txt")

逻辑说明:先打乱文件列表,按 8:2 切分,再成对复制图片和标签。参数说明:random.seed(42)固定种子,换数字就换划分结果;0.8是训练集比例,888 张的话训练约 710 张、验证约 178 张,验证集偏小,评估时波动会大,心里要有数。如果某张图没有对应 txt,说明它是负样本(无缺陷),YOLO 允许空标签文件,但这里直接跳过复制,需要的话自己补一个空 txt。

data.yaml 内容:

path: ./dataset train: images/train val: images/val nc: 3 names: ["class0", "class1", "class2"]

nc是类别数,必须和实际标签里的最大 class_id+1 一致;names顺序必须和 class_id 对应,写错了模型学出来的类别就全乱。这三个名字换成数据集里真实的缺陷类别名。

3.2 用 YOLOv8 跑通训练的最小命令

环境装好后(pip install ultralytics),最小训练命令就一行:

yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16

参数说明:model=yolov8n.pt用 nano 版预训练权重,888 张的小数据集从预训练迁移比从头训收敛快得多;epochs=100是起步值,小数据集容易过拟合,配合早停(默认 patience=50)够用;imgsz=640是 YOLO 默认输入尺寸,瓷砖缺陷如果很小(比如针孔、细裂纹),可以提到 960 或 1280,但显存和速度要权衡;batch=16按显存调,爆显存就降到 8 或 4。

训练启动后重点看三个信号:box_loss 是否稳定下降、cls_loss 是否下降、验证集 mAP@0.5 是否在涨。如果 box_loss 降但 mAP 不涨,多半是过拟合或验证集划分有问题;如果 cls_loss 一直不降,检查类别是否严重不均衡。

3.3 用 VOC XML 反查标注质量

训练前花十分钟用 VOC 格式抽查几张图,能省掉后面几小时的排查。写个脚本把 XML 里的框画到图上:

import os, xml.etree.ElementTree as ET import cv2 xml_dir = "Annotations" img_dir = "JPEGImages" out_dir = "check_vis" os.makedirs(out_dir, exist_ok=True) for xml_name in os.listdir(xml_dir)[:20]: # 先抽查 20 张 tree = ET.parse(os.path.join(xml_dir, xml_name)) root = tree.getroot() img_name = root.find("filename").text img = cv2.imread(os.path.join(img_dir, img_name)) for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") x1 = int(float(bbox.find("xmin").text)) y1 = int(float(bbox.find("ymin").text)) x2 = int(float(bbox.find("xmax").text)) y2 = int(float(bbox.find("ymax").text)) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, name, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(os.path.join(out_dir, img_name), img)

逻辑说明:解析每个 XML,取 filename 找到对应图,遍历 object 画框和类别名。参数说明:[:20]控制抽查数量,先看 20 张,没问题再全量;坐标用float再转int,因为有些标注工具会写小数。重点看三件事:框是否贴合缺陷、类别名是否写错、有没有漏标。漏标是工业数据集最隐蔽的坑,模型学不会它没见过的框。

4. 瓷砖缺陷检测训练中的避坑与排查清单

4.1 坑一:标签类别 id 从 1 开始,训练全乱

现象:训练能跑,但 mAP 极低,预测出来的类别名和实际对不上。

原因:VOC XML 里类别是字符串,转 YOLO txt 时如果脚本按「从 1 开始」编号,而 data.yaml 的 names 从 0 开始,整体错位一位。

解决:转换后立刻统计 txt 里出现过的 class_id 最小值和最大值,确认是 0 到 nc-1。发现从 1 开始,要么改转换脚本,要么在 data.yaml 里补一个占位类别,但后者是下策,容易留隐患。

4.2 坑二:图片和标签文件名不一致

现象:训练日志里出现大量「image not found」或某类样本数为 0。

原因:图片是defect_001.jpg,标签是defect_001.JPG.txt或defect-001.txt,大小写、分隔符不统一。

解决:写脚本统一 stem,把标签重命名为和图片同名。批量重命名前先备份,血泪经验是重命名脚本写错一次,标签全废。

4.3 坑三:验证集里没有某个类别

现象:训练时某一类的 mAP 始终为 0 或 nan。

原因:8:2 随机划分时,稀有类别恰好全被分到训练集,验证集里一个都没有。

解决:划分时按类别分层抽样,保证每个类别在验证集里至少有若干框。888 张的小数据集尤其要注意,随机划分的玄学在这里体现得淋漓尽致。

4.4 坑四:imgsz 和标注尺寸不匹配导致小目标丢失

现象:大缺陷能检出,细裂纹、针孔类缺陷全漏。

原因:原图分辨率高,缩到 640 后小缺陷只剩几个像素,特征在下采样中丢没了。

解决:把 imgsz 提到 960 或 1280,或者对图像切块训练。切块要注意切完的块要重新生成对应标签,别直接复用原图标签。

4.5 坑五:把 VOC 和 YOLO 标签混在同一个目录

现象:训练报格式错误,或 loss 异常。

原因:labels 目录里混进了 XML 文件,数据加载器按 txt 解析直接崩。

解决:images 和 labels 严格分离,labels 目录下只放 txt。用find labels -name "*.xml"检查一遍,有就清掉。

5. 小数据集提效:增强策略与验证技巧

888 张的规模,数据增强不是可选项,是必选项。但增强不能乱加,工业缺陷检测里有些增强会破坏缺陷的物理特征。我的习惯是分两层:几何增强放心用,颜色增强要克制。

几何增强里,水平翻转、垂直翻转、小角度旋转(±10°)、随机缩放、平移,这些对瓷砖缺陷基本安全,因为缺陷的形态不依赖绝对方向。但要注意:如果缺陷有明确的方向语义(比如划痕方向是判定依据),翻转就要谨慎。Mosaic 和 MixUp 是 YOLO 自带的强增强,小数据集上能显著提升泛化,但 MixUp 在缺陷检测里可能把两个缺陷叠在一起,产生不存在的形态,建议先开 Mosaic,MixUp 观察效果再决定。

颜色增强里,亮度、对比度微调可以,色调大幅偏移要避免。瓷砖缺陷很多时候靠颜色差异区分(比如色差、污渍),HSV 空间里 H 通道大改会让模型学到错误的颜色先验。

验证阶段,除了看 mAP@0.5,一定要单独看每个类别的 PR 曲线。整体 mAP 会被数量多的类别拉高,掩盖稀有类别的糟糕表现。另外,888 张的验证集只有 170 多张,单次评估波动大,建议用交叉验证或者至少换两三个随机种子划分,看 mAP 的稳定性。如果不同划分下 mAP 差 0.1 以上,说明数据分布不均,得回头处理。

最后一个具体技巧:把训练好的模型在验证集上跑一遍,导出预测结果,和 VOC 格式的原始标注叠在一起看。哪些是漏检、哪些是误检、哪些是框不准,一目了然。这一步比盯着 loss 曲线有用得多。我自己做瓷砖缺陷项目时,就是靠这个对比发现有一类缺陷的标注框普遍偏大,模型学出来的框也跟着偏大,IoU 上不去。改完标注重新训,mAP 直接涨了 0.08。数据集的质量永远比模型结构更值得花时间,希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进