☰
YOLOv8瓷砖缺陷检测实战:从数据集标注到训练避坑全指南
2026/10/5 2:59:54 网站建设 项目流程

简介:面向工业质检与微小缺陷检测场景,这份YOLO瓷砖裂缝识别数据集覆盖“裂缝”和“正常”两类目标,使用LabelImg标注,共包含约1700张图像及对应txt标签。资源包共2000个文件,其中1765个为txt标注文件,234张为图像文件,还提供1个Python可视化脚本,压缩包整体约91.75MB;数据已按目录划分,附带的classes.txt可直接对应YOLO训练格式,且部分样本经过翻转、添加噪声等增强处理,有助于提升模型鲁棒性。除训练数据外,这个Python可视化脚本无需修改即可运行,随机传入一张图片就能自动绘制边界框并保存到当前目录,非常适合快速核对标注质量或预览检测效果。目前已有194人学习下载,对于希望快速上手瓷砖缺陷检测或基于YOLOv5做改进的开发者,这套资源能有效节省数据准备与标注检查的时间。

1. 瓷砖裂缝检测为什么难:小目标、纹理噪声和2类标签的坑

产线质检拍回来的瓷砖图,分辨率动不动到 4000×3000,上面最细的裂缝只有几个像素宽;你要做的任务是 2 类检测:一类“裂缝”,一类“崩边”。第一次跑 YOLO,mAP50 卡在 0.3 不是网络结构的问题,而是数据集本身没立住。这篇文章要拆的,就是标题里这套方案的全部内容:已经划分好的 YOLO 数据集怎么用、class 文件怎么定义、数据可视化脚本怎么帮你排查标注错误,以及你自己收集图像后,怎么把数据整理到能直接开跑 YOLOv8 训练的程度。适合谁看?手上有瓷砖缺陷图、想低成本验证深度学习检测方案,又不想在数据准备上反复翻车的工程师。

2. 先和数据集面对面:标注规范、class 文件与可视化验证

2.1 两类标签怎么定:裂缝和崩边的判定标准

瓷砖表面的缺陷类型其实很多:裂纹、崩边、针孔、色差、熔洞。但这个数据集只定了 2 类,说明在真实质检场景里,真正影响出厂判定、值得用模型去扛的就是那两个:crack(裂缝)和 edge_break(崩边)。剩下的缺陷要么靠前道工序拦截,要么出现频率太低,硬塞进来反而把类别不平衡搞得没法收拾。

我一般会在动手标注之前,先写死一份标注规范,不然多人协作时同一个缺陷今天叫 crack、明天叫 break,模型学到的特征就乱了。对瓷砖裂缝,我的判定标准这么定:

  • crack:线状或树枝状延伸的裂纹,肉眼可辨,长度不低于 5mm;瓷砖表面的网状细纹(正常纹理)不算。
  • edge_break:瓷砖边缘的崩缺,露出胚体,最短边不小于 2mm;角落的轻微掉角如果面积够大也算。

写清楚阈值之后,标注人员面对模糊样本时至少有一个可量化的判断依据。标注工具用 LabelImg 或 X-AnyLabeling 都可以,导出格式选 YOLO,工具会自动生成和每张图像同名的 txt 文件。这一步出来的东西,直接决定了后续所有工作有没有意义。

2.2 class 文件里写什么:names 顺序就是推理输出的顺序

YOLOv8 模型的类别定义写在 data.yaml 的 names 字段里,也可以单独维护一个 class 文件(比如 classes.txt 或 obj.names)。内容是一行一个类名,顺序不能乱,下标从 0 开始:

crack edge_break

这里 crack 对应 class_id=0,edge_break 对应 class_id=1。训练完成导出模型后,推理端每个 bbox 输出的第一个类别始终是 crack,第二个是 edge_break。这个文件训练中途不能换,顺序更不能调。

踩过的坑是这样的:从网上下载的数据集里,class 文件里往往不止这两个类,有人为了“精简”直接把列表里的其他类删掉,结果删除后某个类别下标变了;训练时的 class_id 和推理时对不上了,模型框出来的目标是错的,你还浑然不觉。所以拿到任何数据集的第一步,先打开 class 文件确认 names 顺序,再做可视化验证,别急着训练。

2.3 一张图配一个 txt:YOLO 归一化坐标换算公式

YOLO 格式的标签文件不是 XML 也不是 JSON,每一行是一条检测目标,五个字段:

<class_id> <x_center> <y_center> <width> <height>

四个坐标值全部归一化到 0~1,单位是相对图像宽高的比例。举个例子,一张 1920×1080 的瓷砖图,裂缝框左上角在像素坐标 (480, 270),右下角在 (960, 540),对应标签行是:

0 0.375 0.375 0.25 0.25

换算过程:x_center 等于左右 x 坐标的平均值除以图宽,即 (480+960)/2/1920=0.375;y_center 同理;(960-480)/1920=0.25 是框宽占比,(540-270)/1080=0.25 是框高占比。

这个公式是所有转换脚本里最容易写错的地方。手工标注一般没问题,但如果你从 VOC 的 XML 或 COCO 的 JSON 转 YOLO,公式里坐标减错、忘记归一化,训练出来的模型框就会整体偏移。我自己在转完格式后,绝不直接开训练,一定先用可视化脚本抽查 50 张图,确认框的位置、大小和类别都对得上。

2.4 数据可视化脚本先跑一遍:标签错位一眼就看出来

标题里说的数据可视化脚本,核心功能就两件事:把标签框画回原图、按类别统计样本数量。画框用 OpenCV,读取 txt 的时候要注意编码,Windows 环境下有些标注工具保存的是 GBK 而不是 UTF-8,直接按 utf-8 读会抛 UnicodeDecodeError。

一个最小可用的可视化脚本可以这样写:

import cv2 from pathlib import Path def draw_labels(img_path, label_path, class_names): img = cv2.imread(str(img_path)) h, w = img.shape[:2] with open(label_path, "r", encoding="utf-8") as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: print(f"skip bad line in {label_path}: {line}") continue cls_id = int(parts[0]) if cls_id >= len(class_names): print(f"class id {cls_id} out of range in {label_path}") continue x_c, y_c, bw, bh = map(float, parts[1:]) x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) color = (0, 0, 255) if cls_id == 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) return img class_names = ["crack", "edge_break"] img_path = Path("dataset/train/images/001.jpg") label_path = Path("dataset/train/labels/001.txt") out = draw_labels(img_path, label_path, class_names) cv2.imwrite("check_001.jpg", out) print("done, check check_001.jpg")

这段逻辑很直白:读图、读 txt、把归一化坐标换算成像素坐标、按类别画不同颜色的框。两个细节是关键。第一,坐标换算必须是 (x_c - bw / 2) * w,先减后乘;反了的话框会整体偏出去半个框宽。第二,对异常行要容忍,遇到空行、多字段少字段、class_id 越界,打印出来跳过而不是直接中断,否则几百张图里混进一张坏标签,整个检查流程就停在半路上。

跑完可视化,标签错位、漏标、类别写反都会暴露。确认没问题了,再进入数据集划分阶段。

3. 划分好的数据集怎么复现:按批分组、脚本划分与 data.yaml

3.1 切分比例:按拍摄批次分组比按文件随机切可靠

标题强调“划分好的数据集”,说明划分策略本身是这套资源的核心价值之一。常见的 8:1:1 划分比例不是万能公式,前提是你得先搞清楚图像是怎么来的。产线固定相机拍的瓷砖图,同一片砖只要传送带停顿半秒,前后两张看似不同、实则高度相似,随机划分后验证集里可能混着大量训练集图像的“近亲”,mAP 虚高到 0.9,一上现场马上跌回 0.4。

我处理瓷砖数据会用按“批次”分组的方式:先把所有图像按文件名前缀或所在子目录分成 N 组,比如同一批瓷砖、同一天采集的图像归为一组,划分时以组为单位,而不是以单张图为单位。这样验证集里的缺陷形态是模型没见过的,评估结果才可信。

3.2 划分脚本:固定随机种子、按组搬运、保留标签

下面这个脚本是我处理类似项目时的常用做法,按文件名前缀分组,再从组维度随机划分成 train、val、test 三份:

import random import shutil from pathlib import Path random.seed(42) data_root = Path("tile_defect") images = sorted(data_root.glob("images/*.jpg")) groups = {} for img_path in images: # 假设文件名格式:batch_20240105_001.jpg,取前两段作为批次号 prefix = "_".join(img_path.stem.split("_")[:2]) groups.setdefault(prefix, []).append(img_path) group_keys = list(groups.keys()) random.shuffle(group_keys) total = len(group_keys) train_keys = group_keys[: int(total * 0.8)] val_keys = group_keys[int(total * 0.8) : int(total * 0.9)] test_keys = group_keys[int(total * 0.9) :] def copy_files(file_list, img_out, lab_out): img_out.mkdir(parents=True, exist_ok=True) lab_out.mkdir(parents=True, exist_ok=True) for img_path in file_list: lab_path = data_root / "labels" / (img_path.stem + ".txt") shutil.copy(img_path, img_out / img_path.name) if lab_path.exists(): shutil.copy(lab_path, lab_out / lab_path.name) else: print(f"missing label for {img_path}") copy_files([p for k in train_keys for p in groups[k]], Path("dataset/train/images"), Path("dataset/train/labels")) copy_files([p for k in val_keys for p in groups[k]], Path("dataset/val/images"), Path("dataset/val/labels")) copy_files([p for k in test_keys for p in groups[k]], Path("dataset/test/images"), Path("dataset/test/labels")) print(f"train groups: {len(train_keys)}, val groups: {len(val_keys)}, test groups: {len(test_keys)}")

固定随机种子为 42 是有意的:划分结果可复现。下次补了几百张新图再跑一遍,验证集不会完全变掉,你还能拿新旧模型在同一批验证图上对比。如果文件名前缀不好用,更稳妥的做法是让不同批次的原始图分别放在 images/source_a、images/source_b 子目录里,直接把二级目录名当作分组键,这样拆分逻辑和采集来源一一对应,我推荐你工程化时用这个方案。

3.3 data.yaml 配置:train/val 指向 images 目录的含义

划分完成后,YOLOv8 训练需要的 data.yaml 长这样:

path: ./dataset train: train/images val: val/images test: test/images names: 0: crack 1: edge_break

这里有个关键点:train 和 val 指向的是 images 目录而不是 labels 目录。YOLO 的训练逻辑是读取 train/images 下的图像列表,再把路径里的 images 替换成 labels 去查找同名 txt;如果你写成了直接指向 labels,训练会报数据集找不到,或者训练集图像数为 0。这个坑在往 server 上部署项目时尤其常见,因为本地目录和服务器目录结构不一致。

path字段建议写相对路径,配合项目根目录使用。用绝对路径虽然一次能跑通,但只要项目换个机器、换个盘符,就要改一处;相对路径就不存在这个问题。另外,如果 test 目录暂时没有独立数据,test 字段可以删掉,验证只需要 val。

4. 用 YOLOv8 训练自己的瓷砖裂缝模型:最小命令、关键参数和损失函数

4.1 最小训练命令:从 yolov8s.pt 做迁移学习

数据准备好了,接下来是用 yolov8 训练自己的数据集。我用的是 Ultralytics YOLOv8,模型起点选 yolov8s(small)而不是更轻的 n,原因后面讲。先装环境,再跑最小训练命令:

pip install ultralytics yolo detect train \ model=yolov8s.pt \ data=dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=20 \ project=runs/tile_train \ name=exp1

首行 pip install 装的是 ultralytics 这个包,它自带 yolo 命令行入口。训练时 model=yolov8s.pt 会自动下载预训练权重到当前目录,然后用你的瓷砖数据做迁移学习。epochs=100 是我在瓷砖缺陷数据上的经验起点:这类任务 100 轮基本收敛,再往后 mAP 提升很有限,只会徒增耗时。如果你的卡是 24G 显存但跑不满 batch=16,优先怀疑数据读取瓶颈,而不是盲目降低 batch。

4.2 必调参数:imgsz、batch、epochs、patience 怎么取舍

训练自己的数据集时,这几个参数是每次都要过的关卡:

  • imgsz:默认 640。但如果原始图是 4000 像素的产线图,我建议先用 imgsz=1280 试跑 20 轮,对比一下小裂缝的召回率。显存不够时优先降 batch,而不是降 imgsz,因为裂缝这种小目标对分辨率非常敏感。
  • batch:16 在单卡 24G 上比较稳。batch 太小,BatchNorm 层的统计量抖动大,裂缝这种纹理微弱的样本会让 loss 曲线像锯齿一样来回跳。
  • epochs:和 patience 配合使用。patience=20 的意思是连续 20 轮验证集 mAP 不提升就提前停训,这样 epochs 设 300 也不怕过拟合。
  • workers:Windows 上建议设 4,不要设 8 或更高;Windows 默认用 spawn 方式启动子进程,worker 数太高容易直接卡死。

这四个参数里,imgsz 和 batch 对结果的影响最直接。我见过有人为了把 batch 从 16 提到 32,把 imgsz 从 1280 降到 640,结果裂缝检出率掉了十几个点,这就是典型的本末倒置。

4.3 训练过程监控:loss 曲线和 mAP50 哪个更该信

训练结束后,runs/tile_train/exp1/ 目录下会生成一堆结果文件。我按重要性排序:results.png(包含 train/val 的 box_loss、cls_loss、dfl_loss、mAP50、mAP50-95 曲线)、confusion_matrix.png、labels.jpg。

看 results.png 时,最需要警惕的是 val/box_loss 跌到某个点后开始回升,这个转折点就是过拟合开始的信号。此时 mAP50 可能还在涨,别高兴太早,继续训下去验证集的 mAP 很快就会掉头。瓷砖产线质检任务,我一般要求模型 mAP50 至少到 0.85 才敢上现场测试。mAP50-95 这个指标反而不用太苛求,因为裂缝框特别细长,在高 IoU 阈值下天然吃亏,mAP50-95 低不代表模型不能用。

另外,confusion_matrix 里如果 crack 那行有大量被预测成背景的样本,说明裂缝漏检集中在某些特定形态上,优先回去看漏检图像长什么样,而不是继续调参。

4.4 YOLO 损失函数与裂缝细长框的矛盾

这里补一个选型层面的判断,很多人容易忽略。YOLOv8 的损失由三块组成:分类损失(BCE)、边界框回归损失(CIoU)、以及 DFL 分布损失。其中边界框回归对宽高比非常敏感,瓷砖裂缝的标签框通常极其细长,宽高比 1:10 甚至 1:20 都很常见。CIoU 对长宽比的一致性是带惩罚的,但裂缝框本身就是极端长宽比,模型训练时学习到的先验框和它匹配不上,box_loss 就会比普通目标检测数据高一个量级。

我在实际项目中遇到的现象是:mAP50 卡在 0.6 左右,查 loss 发现 box_loss 始终降不到公开数据集那种水平。这时候别急着堆改进公式,先回去看标注:标注框是不是把裂缝框成了接近正方形的外接框,框内塞了大量背景。如果标注确实紧贴裂纹,那细长框难收敛就是天然的,能够做的是把训练分辨率提到 1280,或者在长宽比极端时考虑 NWD 这类对小目标更友好的损失变体——也就是网上常说的 nwd 改进 yolo 那条路线。但对一个 2 类的瓷砖检测任务,先把标注做紧、把贴图分辨率提上来,比一上来就换损失函数性价比高得多。

5. 避坑指南:瓷砖裂缝数据集的常见问题与排查

5.1 现象:mAP 指标正常,现场却漏检细裂缝

原因:原始图像分辨率太高,YOLO 在 640×640 下缩放后,细裂缝只有一两个像素宽,特征在卷积下采样过程中直接丢了。

解决:把大图做滑窗切块,4000×3000 的图切成 4×4 共 16 个 1000×1000 的 patch,再重新标注和训练;推理阶段也用同样切法,把 block 结果的框坐标换算回原图坐标系。这个方案是我在高分辨率瓷砖缺陷检测里最常用、最稳的一条路径。

5.2 现象:训练 loss 很低,推理却几乎没输出

原因:数据里大量瓷砖图完全没有缺陷,负样本占比过高,模型学到的策略是“输出空结果”也能把 loss 压得很低。瓷砖表面纹理复杂,大片干净区域让模型误以为没有目标就是最优解。

解决:把完全没有缺陷的图从训练集里剔除,只保留一小部分(比如 10%)作为难负样本参与训练;如果缺陷本来就稀少,考虑用采样策略让每个 batch 里都保证至少出现部分正样本,而不是纯靠文件列表顺序去碰运气。

5.3 现象:同一块瓷砖换个光照角度,推理结果完全相反

原因:数据集里光照多样性不够。瓷砖表面是镜面材质,反光区裂缝和阴影区裂缝看起来是两个物种,模型只见过其中一种。

解决:训练时除了 YOLO 默认的 HSV 增强,额外加 CLAHE 均衡和随机亮度偏移,模拟不同光照下的成像差异。更踏实的是在采集阶段就覆盖三种光照条件:正面光、侧光、暗场,三种各拍一遍进数据集。这种“笨办法”对镜面材质的稳定性贡献,比任何网络结构改进都大。

5.4 现象:class 文件顺序改过之后,重训模型推理结果全乱

原因:names 列表的下标被调整过,比如把 crack 从第 0 位挪到了第 1 位,但标注文件里的 class_id 没有同步改,训练和推理的类别映射发生错位。

解决:训练前写一个小脚本,遍历 labels 目录,统计出现的 class_id 集合,确认最大 ID 不超过 names 长度减一;同时把 names 打印出来和标注文件里的 ID 一一核对。这个检查我每次都做,成本几秒钟,省掉的是推理结果莫名错乱的排查时间。

5.5 现象:可视化脚本读标签报 UnicodeDecodeError

原因:Windows 下部分标注工具保存的 txt 是 ANSI/GBK 编码,不是 UTF-8。

解决:读取时做编码回退。代码里这样写:

try: lines = open(label_path, encoding="utf-8").readlines() except UnicodeDecodeError: lines = open(label_path, encoding="gbk").readlines()

这个小补丁看起来粗暴,但在国内产线环境下非常实用,标注工具来源不统一时能少翻一次车。

5.6 现象:训练集和验证集来自同一批瓷砖,模型一换相机就废

原因:划分时按单张图随机切分,同一片瓷砖的不同拍摄帧同时进了训练集和验证集,模型本质上是在“背题”。

解决:严格按采集批次或编号分组划分,见第 3 章的脚本思路。更严格的做法是把验证集从项目建立第一天就单独锁定,任何一次迭代都不允许验证集图像回流到训练集,给它留出一份“清白”的评估集。

6. 验证阶段的可视化脚本:跑通预测后必查的 3 件事

6.1 在验证集上批量 predict 并输出带框图片

训练完拿到 best.pt,第一步是在验证集上做批量预测,并保存可视化结果:

from ultralytics import YOLO model = YOLO("runs/tile_train/exp1/weights/best.pt") results = model.predict( source="dataset/val/images", save=True, conf=0.35, line_width=2, project="runs/val_vis", name="tile_check", )

conf=0.35 是我在瓷砖产线场景下习惯的起点。裂缝检测里,置信度阈值设太低,纹理干扰会被当成目标;设太高,细裂缝漏检直接拉高。0.35 这个值先用着,后面根据 F1 曲线再调,别迷信默认的 0.25。

6.2 按类别输出 PR 曲线,别被平均 mAP 骗了

mAP 是平均过的,它会把 crack 的短板藏到 edge_break 后面。训练输出目录里已经有 PR_curve.png,但你还要自己看一眼每个类别的曲线端点。如果 crack 的 recall 端断崖下跌,说明细裂缝大量漏检,优先补长裂缝样本、提高 imgsz;如果 edge_break 的 precision 端上不去,说明误检多,优先增加负样本或收紧标注规范。这一步不用写代码,直接打开曲线文件就能判断。

6.3 把 badcase 挑出来人工翻一遍

这是我最坚持的一个步骤:从验证集预测结果里,把置信度低于 0.5 的真实正例(漏检)和置信度高于 0.5 的误检框各挑 20 张,单独存一个目录翻一遍。漏检的 20 张如果全是细裂缝,就是分辨率或标注的问题;误检的 20 张如果全是瓷砖纹理边界,就是标签定义需要补充干扰样本。这个习惯帮我排掉的雷比任何自动化评估指标都多,因为指标只能告诉你“不行”,只有翻图才能告诉你“为什么不行”。

数据可视化脚本的价值不只是训练前检查,验证阶段同样要靠它把模型行为翻译成人能看懂的画面。每次训练完,我雷打不动地跑一遍预测可视化、翻一遍 badcase,这套流程走完才会安心把模型交给产线同事去试跑,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询