
简介面向瓷砖表面缺陷检测任务这个zip压缩包提供了一套可直接用于目标检测模型训练的数据集。图像均为单块瓷砖特写标注了边缘破碎、划痕线、破洞三类缺陷总计16558个矩形标注框同时整理为VOC与YOLO两种格式内置JPEGImages、Annotations、labels三个目录便于按需导入常见检测框架。压缩包共2000个文件以jpg图片、xml标注和txt标签为主整体大小约544MB图片清晰且未做增强适合用于算法对比、模型调优或教学演示。目前已有209人学习下载数据标注准确合理可作为瓷砖质检场景的基准数据集或实验素材。1. 瓷砖缺陷数据集888张双格式为什么目标检测入门选它最顺自己做瓷砖外观检测项目时找数据集是最耗时间的一道坎这份瓷砖缺陷数据集刚好能省掉这部分麻烦。它包含888张真实瓷砖表面图片同时提供YOLO和VOC两种标注格式标签共3类crack裂纹、pinhole针孔、chip崩角压缩包解压后马上能进训练流程。对刚接触YOLOv8的人双格式意味着不用四处找转换脚本对做工业质检预研的工程师888张图规模适中能快速验证算法可行性。下面我按自己拆包的习惯从目录结构、格式互转、训练配置到踩坑记录完整走一遍。2. 拆开压缩包VOC XML和YOLO TXT的目录结构、字段对照与自检脚本2.1 解压后先看目录三类文件长什么样解压后建议先用tree扫一眼目录结构常见布局是这样注意目录名在不同打包版本里可能略有出入但核心路径逃不出这三类# 用 tree 命令查看目录结构Linux/macOS 自带Windows 可用 tree /F tree -L 2 瓷砖缺陷数据集yolovoc格式888张3个标签输出大致为├── images │ ├── 0001.jpg │ ├── 0002.jpg │ └── ... ├── Annotations │ ├── 0001.xml │ ├── 0002.xml │ └── ... ├── labels │ ├── 0001.txt │ ├── 0002.txt │ └── ... └── classes.txt这段命令只是确认布局。images下是原始图Annotations是VOC格式的XML标注labels是对应YOLO格式的TXT标注classes.txt里按行写明三个类别的名字。有些打包者会把Annotations改成xml、labels改成yolo名字不一样结构一样别被目录名带偏。VOC格式的标注落在XML里典型内容是这样annotation folderimages/folder filename0001.jpg/filename size width1280/width height800/height depth3/depth /size object namecrack/name bndbox xmin152/xmin ymin96/ymin xmax378/xmax ymax412/ymax /bndbox /object /annotationsize里的width和height是原图真实像素宽高YOLO转换时全靠它做分母object里name是类别名bndbox四个值是目标框的绝对像素坐标。对应的YOLO TXT文件内容大概是这样0 0.207031 0.317500 0.176562 0.395000一行5个数分别是类别id、归一化中心x、归一化中心y、归一化宽、归一化高。拿上面的XML验证中心x (152 378) / 2 / 1280 0.207031框宽 (378 - 152) / 1280 0.176562数学关系完全吻合。这也是拿双格式数据集练手最有价值的地方——两种格式互相印证能直接定位标注问题。补充一句不同打包者对三个类别的命名不完全一样crack也可能写成“裂纹”pinhole写成“针孔”chip写成“崩角”。命名差异不影响坐标换算但会影响训练时类别名的可读性建议统一成英文或拼音避免终端输出乱码。2.2 两种坐标体系如何对应从像素边缘坐标到归一化中心坐标VOC记录的是xmin、ymin、xmax、ymax这种直角坐标单位是像素YOLO记录的是归一化后的中心点坐标和宽高范围落在0到1之间。换算公式其实只有四个x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height w (xmax - xmin) / image_width h (ymax - ymin) / image_height这里要特别强调分母必须是原图宽高不是标注框的宽高。我见过新手把分母写成xmax - xmin出来的坐标全在0到1之外训练时框直接飘到图外还反过来怀疑数据集标注错了。我用表格整理两种格式的对应关系方便你后面写转换脚本时直接对照排查转换后坐标对不上的问题。物理含义VOC字段YOLO字段换算关系框位置xmin, yminx_center, y_center(xminxmax)/(2×W)(yminymax)/(2×H)框大小xmax-xmin, ymax-yminw, h(xmax-xmin)/W(ymax-ymin)/H浮点误差在这个换算里是常态。比如标注框正好顶到图右边缘xmax image_width时理论归一化宽度是1.0但除法算出来可能是0.99999。这类误差不影响训练真正要注意的是w算出来大于1后面5.3节会专门讲这个坑。2.3 写一个统计脚本先摸清三个标签的分布再动手转换之前先统计是我拆数据集的习惯。先摸清每个类别有多少框后面做数据增强和参数调整才有依据import os from pathlib import Path def analyze_labels(labels_dir: str, class_file: str) - None: 统计每个类别出现的框数量以及包含该类的图片数。 classes [c.strip() for c in open(class_file, encodingutf-8)] box_count {c: 0 for c in classes} img_count {c: 0 for c in classes} total_boxes 0 for txt_path in Path(labels_dir).glob(*.txt): with open(txt_path, r, encodingutf-8) as f: lines f.readlines() seen set() for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) if cls_id len(classes): print(f越界类别: {txt_path.name} - {cls_id}) continue name classes[cls_id] box_count[name] 1 total_boxes 1 seen.add(name) for name in seen: img_count[name] 1 print(f总框数: {total_boxes}) for name in classes: print(f{name}: 框数 {box_count[name]}, 含该类图片数 {img_count[name]}) if __name__ __main__: analyze_labels(labels, classes.txt)代码逻辑不复杂遍历labels下所有TXT每行拆成类别id和坐标len(parts) 5是硬性校验防止空行或异常行混进来。cls_id len(classes)表示标签文件里出现了classes.txt之外的类别这种文件必须人工检查。统计结果里如果某一类框数明显少后面训练就要考虑类别权重。还有一个更值得看的指标每张图的平均框数。如果888张图里只有300张带标注说明大量正常样本混在里面不是坏事但划分验证集时应该按文件名单采样避免test全落在有缺陷的图片上。提示标注文件里出现空TXT是正常的表示这张图没有缺陷。统计脚本里len(parts) ! 5直接continue不会把空文件当异常。3. VOC转YOLO的转换脚本坐标换算公式与三种校验手段3.1 解析XML拿到目标框先处理边界值先说一个血泪经验拿到双格式数据集别急着删掉其中一种。两种格式都留着后续做数据清洗时能互相当参照。以VOC转YOLO为例写转换脚本只需要Python标准库import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path: Path, class_list: list) - list: 把单个VOC XML转成YOLO格式的标注行。 tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_list: continue cls_id class_list.index(name) 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) # 顶到边界的框裁剪到图内避免归一化后超出[0,1] xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return linesxmin max(0, xmin)这几行是防呆工业数据集的XML里偶尔会出现标注框比图还大一圈的情况直接换算会让YOLO格式的w大于1训练时检测头会学歪。class_list的顺序必须和训练时dataset.yaml里的names完全一致否则类别全部错位。如果XML里同一个图有多个objectfor obj in root.findall(object)会自然遍历所有框遇到类别名不在class_list里的我选择直接跳过避免把脏数据带进训练。3.2 批量转换并写出文件名对齐是隐形地雷批量写出需要特别注意文件名对齐。常见做法是按XML的stem生成同名TXTfrom pathlib import Path def convert_all(xml_dir: Path, txt_dir: Path, class_file: Path) - None: 把整个xml目录转换成yolo txt目录。 class_list [c.strip() for c in class_file.read_text(encodingutf-8).splitlines() if c.strip()] txt_dir.mkdir(exist_okTrue) for xml_path in sorted(xml_dir.glob(*.xml)): lines voc_to_yolo(xml_path, class_list) txt_path txt_dir / (xml_path.stem .txt) txt_path.write_text(\n.join(lines), encodingutf-8) if not lines: # 留空文件方便后续按名字对齐图片 txt_path.write_text(, encodingutf-8)这里必须用xml_path.stem .txt保证和图片同名。一个典型的翻车情况是某些标注工具输出的XML文件名和图片名对不上比如图片是IMG_1001.JPGXML是1001.xml直接按stem对应会漏掉一批标注。建议转换完成后对比images目录和txt_dir的文件名集合缺哪个补哪个。要检查转换结果一个快速判断是对比两个目录的文件数量。前面说过888张图应对应888个TXT如果列表里只有880个说明有8个XML解析失败或者文件名没对齐。用集合差集快速找出缺失名单比肉眼一个个看强得多。3.3 校验方式一回读坐标算与原标注的IoU转换完不是结束还要做回读校验。把新生成的TXT解析成像素框和原XML算IoU看有没有偏差def compute_iou(box_a, box_b): 计算两个像素框的IoUbox格式为(xmin, ymin, xmax, ymax)。 x1 max(box_a[0], box_b[0]) y1 max(box_a[1], box_b[1]) x2 min(box_a[2], box_b[2]) y2 min(box_a[3], box_b[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area_a (box_a[2] - box_a[0]) * (box_a[3] - box_a[1]) area_b (box_b[2] - box_b[0]) * (box_b[3] - box_b[1]) return inter / (area_a area_b - inter 1e-6)这个IoU正常情况下应该接近1.0。只要换算过程没改坐标结果就是1.0如果发现某张图IoU低多半是读取XML时把xmin和ymin写反了或者除错了分母。我习惯在分母加1e-6是防止两个空框相遇时除零报错。3.4 校验方式二在原图直接画框用眼睛复查数字校验之后我还会做一次可视化抽检。随机抽20张图把两种格式的框画上去对比import cv2 def draw_boxes(image_path, boxes, color, thickness2): 在图上画出像素坐标框boxes为[(xmin, ymin, xmax, ymax)]。 img cv2.imread(str(image_path)) for box in boxes: x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness) return imgcv2.rectangle的坐标必须是整数所以先用int(v)转换。画完把两种格式的图并排对比肉眼看框有没有偏移、类别颜色对不对。这一步不能省程序只能保证数学一致不能保证语义一致标签和框错位这种事只有人眼能发现。4. 用YOLOv8跑通训练数据集划分、yaml配置、训练参数与指标解读4.1 先切分数据888张按7:2:1划成train/val/test拿到数据后不要直接开训我一般按7:2:1拆成训练集、验证集、测试集。这里的关键是图片和标签要一起移动还要保持YOLO要求的目录结构import random from pathlib import Path random.seed(42) images_dir Path(images) labels_dir Path(labels) image_files sorted(images_dir.glob(*.jpg)) random.shuffle(image_files) n_train int(len(image_files) * 0.7) n_val int(len(image_files) * 0.2) splits { train: image_files[:n_train], val: image_files[n_train:n_train n_val], test: image_files[n_train n_val:], } for split, files in splits.items(): (Path(yolo_dataset/images) / split).mkdir(parentsTrue, exist_okTrue) (Path(yolo_dataset/labels) / split).mkdir(parentsTrue, exist_okTrue) for img in files: # 图片和同名标签一起软链到对应目录 (Path(yolo_dataset/images) / split / img.name).symlink_to(img.resolve()) txt labels_dir / (img.stem .txt) if txt.exists(): (Path(yolo_dataset/labels) / split / txt.name).symlink_to(txt.resolve()) else: print(f警告: {img.name} 没有对应标签)random.seed(42)固定随机种子别人复现结果更容易自己做对比实验也更公平。symlink_to是软链不复制实体文件省磁盘空间缺点是换机器后链接失效需要重新执行一次脚本如果不介意磁盘占用直接改成shutil.copy2更省心。YOLOv8会自动在images同级的labels目录里找同名TXT所以目录名必须是这两个。如果切分出来的验证集里正常瓷砖图片占比太高会导致验证时精确率虚高建议切完后看一眼每个split里带标注的图片数量保证三个子集的有缺陷样本比例接近。4.2 写好dataset.yaml并确认类别顺序# dataset.yaml path: ./yolo_dataset train: images/train val: images/val test: images/test names: 0: crack 1: pinhole 2: chip类别顺序从classes.txt里读取不能靠感觉排。如果classes.txt里是chip在前这里写成了crack在前训练出来的类别可信度全部错位。path用相对路径配合train: images/train这种写法YOLOv8会自动拼接成./yolo_dataset/images/train。4.3 训练命令与关键参数imgsz、epochs、batch、patience# 用YOLOv8n做基线图片尺寸640训练100轮 yolo train datadataset.yaml modelyolov8n.pt epochs100 imgsz640 batch16 patience20 device0imgsz640是速度和精度的折中。瓷砖表面crack这类缺陷细长如果显存够优先试imgsz1280小目标会显著改善代价是训练时间变成四倍。patience20表示连续20轮验证集指标不涨就早停避免无效空转。batch16在显存不足时先降到8保证能跑起来最重要。yolov8n.pt是官方预训练权重默认从GitHub下载网络慢就手动下好后放到项目目录再把model参数改成model./yolov8n.pt。如果是双卡或单卡大显存可以加device0,1做数据并行但小数据集没太大收益单卡反而少很多麻烦。4.4 看结果时先抓四个指标P、R、mAP50、mAP50-95训练完直接看runs/detect/train下的results.csv重点关注四列它们分别对应检测的精确率、召回率和两种IoU阈值下的综合精度。指标关注点翻车信号P精确率检出的框里有多少是对的P升高R掉说明模型变保守R召回率真实框里有多少被检出来R上不去多半是小目标漏检mAP50常规IoU阈值下的综合精度低于0.6说明数据或参数有明显问题mAP50-95更严格阈值下的综合表现和mAP50差距过大说明框定位不准瓷砖缺陷里crack是长条形和COCO里常见的方形目标差别很大mAP50-95会比自然图像低一截这是正常现象不用急着调参。如果训练途中loss曲线突然变成NaN网上常叫“BN崩溃”先不要怀疑数据集优先查学习率是不是太大、batch是不是太小把默认学习率从0.01降到0.001再试一轮。训练目录下还有一张val_batch0_labels.jpg和val_batch0_pred.jpg分别表示第一轮验证时人工标注和模型预测的对比这是最直观的质量反馈。如果验证集里出现训练时见过的图指标会虚高所以要确认划分脚本的随机种子确实生效。5. 瓷砖缺陷数据集的实战避坑五个踩过的坑与处理方案5.1 类别不平衡pinhole样本少P和R出现虚高或虚低现象训练出的模型对crack很敏感对pinhole几乎不输出框验证时crack的R约0.85pinhole的R只有0.4。原因888张图里三个标签数量分布不均pinhole可能只有一百来个框模型在训练时学到的大多是crack特征对pinhole的响应很弱。这不是数据集本身的问题是工业缺陷数据的常态。解决先用2.3的统计脚本算出每类框数如果某个类别框数明显少优先做离线增强把包含该类样本的图做45度旋转和亮度扰动扩充到与其他类别接近的比例。也可以训练时给少数类更高的loss权重YOLOv8里可以通过class_weights传入但效果不如直接扩充样本直观。5.2 小目标在resize后丢失细裂纹缩成一条线现象原图分辨率在1280x800左右训练时imgsz640直接等比缩放宽度只有2到3像素的细裂纹缩放后只剩1像素推理时基本消失。原因YOLOv8下采样到P5层时特征图是输入的1/32640/3220一个2像素宽的目标在20x20的特征图上连半个格子都填不满。解决先统计目标框的短边分布如果大量框的短边小于10像素直接上imgsz1280训练。瓷砖数据里的crack标注框往往是从裂纹起点到终点框很大但缺陷本身细这种情况不能只看框大小下结论要多抽几张图可视化特征响应判断模型感受野到底覆盖到没有。如果坚持用640可以开启scale0.5增强让模型对小尺度更鲁棒。5.3 XML标注框越界归一化后w大于1训练loss正常但结果飘现象转换后某个TXT里出现0 0.830078 0.501250 1.034500 0.398000这种w大于1的数据训练不报错但推理时这个框会超出图像边缘。原因XML里的xmax标得比图片宽度还大常见于标注工具自动标注后手工修正但没有回填原图尺寸。解决转换脚本里强制裁剪到图内如上文3.1的xmin max(0, xmin)。更严谨的做法是回读所有TXT检查是否存在w 1.0 1e-6的行有就把对应图片和XML挑出来人工复核。不要指望数据增强帮你把越界框裁正它会直接丢弃这部分监督信号。5.4 增强策略不能照搬COCO马赛克让纹理缺陷失真现象用YOLOv8默认的马赛克增强验证集loss在60轮后反而上升抽检发现部分crack被马赛克的色块拼接连成完整裂纹模型学到了错误的纹理组合。原因Mosaic是把四张图拼成一张本质适合COCO这类自然图像瓷砖缺陷是精细纹理差异拼接处常产生假边缘和假纹理对缺陷检测干扰极大。解决训练时显式关闭马赛克加mosaic0.0。同时把hsv_h、hsv_s调小瓷砖颜色变化小颜色抖动过多会让模型把色差当成特征。最后保留轻度translate0.1上下翻转flipud0.0关掉瓷砖纹理正反不对称翻转会引入错误样本。5.5 双格式转换后坐标错位回读数字对不上先查图片尺寸现象同一张图VOC框和YOLO框画出来差几个像素肉眼几乎看不出但IoU只有0.97测试指标始终上不去。原因两种格式来源是同一标注但某个环节里XML的size宽度和图片真实宽度不一致。比如标注时用重命名前的图XML里记的是旧尺寸或者图片被EXIF方向旋转过宽高互换了。解决校验阶段别只算IoU还要直接取图片的实际宽高和XML里的size比对不一致立即重建标注。经验是出这种问题的图往往集中在某个批次因为在标注工具版本升级后尺寸回写逻辑可能改变。6. 用预训练权重和冻结策略做微调一次收敛更快的完整验证6.1 迁移学习起步yolov8n.pt搭配freeze参数先说为什么用迁移学习。888张图说多不多从零训练容易过拟合用官方预训练权重可以借到COCO上学过的纹理和边缘特征。实际操作是把yolov8n.pt放到项目根目录加一个freeze参数# 冻结前10层backbone只训练neck和head学习率调小 yolo train datadataset.yaml modelyolov8n.pt epochs80 imgsz1280 batch8 freeze10 lr00.001freeze10表示前10层参数不更新训练速度更快主要用来快速判断数据质量。如果冻结训练后mAP50能到0.7说明数据本身没问题再解冻全部层用lr00.0001继续微调50轮通常还能涨1到3个点这个增量来自backbone对瓷砖纹理的进一步适配。6.2 批量预测加混淆矩阵验证收尾的两板斧训练结束后我习惯先看runs/detect/train里的confusion_matrix.png判断crack有没有被误判成chip。如果出现大面积跨类别混淆说明两个类的外观相似度太高需要回头检查标注边界而不是继续加增强。最后用测试集跑一遍完整推理# 用最佳权重跑测试集保存结果和置信度 yolo predict modelruns/detect/train/weights/best.pt sourcetest save_txtTrue save_confTruesave_txtTrue输出带置信度的TXT结果save_confTrue在图片上标出置信度。检查结果时重点看置信度0.3到0.6之间的框它们多半是边界模糊的样本决定最终阈值该设在0.25还是0.5。从那以后我每次拿到新数据集都强制走一遍这套流程统计标签分布、双格式互检、小参数训练、看混淆矩阵全部通过才谈调优。希望帮到你。本文还有配套的精品资源点击获取