简介:面向深度学习缺陷检测与YOLO目标检测入门者,这份瓷砖缺陷数据集包含已标注图片与配套txt标签,已按2类(crack裂缝、normal正常)划分,并附类别说明文件classes.txt,以及翻转、噪声等数据增强版本,可直接用于瓷砖裂缝识别模型训练与验证。包内含2000个文件,以txt标签文件为主(1765个),另有234张JPG图片及1个Python可视化脚本;图片与标签分目录存放,结构清晰,压缩包整体91.75MB。附带可视化脚本无需修改参数即可运行,随机传入一张图片便能绘制边界框并保存到当前目录,方便快速检查标注质量与模型输出。已有193人学习,适合正在做工业质检、表面缺陷检测相关课题,或希望快速获得一份规范YOLO格式小规模数据集以开展实验的读者。
1. 瓷砖裂缝检测:一个“划分好的数据集”到底想帮你少踩多少坑
质检流水线上盯瓷砖裂缝,人眼看到第两百块就麻了;换到外墙或管廊巡检,光线、釉面反光还让裂缝更难找。用 YOLO 做瓷砖裂缝识别与检测,如今是工业视觉里相当成熟的方向,但这套“瓷砖裂缝识别+检测(2类)”数据包的价值不在模型,而在它把三件最磨人的事准备好了:划分好的数据集、类别 class 文件、数据可视化脚本。很多新手拿到就开训,开训前忘了先检查数据本身对不对,翻车后才回来补课。这篇文章就围绕这三样东西展开,讲清楚怎么用、参数怎么设、坑在哪,适合做工业预研、做毕设、或者第一次完整跑通 YOLO 训练流程的人。
2. 把数据集拆开看:2类标注怎么定义、目录怎么划分、可视化脚本怎么检查
2.1 裂纹和破损的边界:为什么类别定义直接决定模型上限
标题里的“2类”,最常见的定义是裂纹(crack)与破损(chip/break)。裂纹是线状的、长宽比很高的缺陷,像头发丝一样在釉面延伸;破损是边缘崩落、缺角这类块状缺陷。两者在像素形态上差异明显,但在实际照片里经常同时出现——一条裂纹延伸到瓷砖边缘,会把釉面带崩一小块,这时同一个框里既有线状特征又有块状特征。如果你用 labelimg 或者 labelme 标注,最容易被忽略的是打标框的边界:裂纹要贴着裂缝走向画,别把灰缝、釉面纹理包进来;破损框要覆盖整个崩落区域,不要把背景裁掉一半。
为什么要强调类别定义?因为 YOLO 学的是“类别内部的共性”和“类别之间的差异”。如果标注的人把灰缝标成裂纹,另一个人把裂纹标成灰缝,训练集内部互相打架,模型被迫在特征空间里找一条折中线,结果就是 mAP 虚高、现场漏检。我一般建议拿到这种数据集后,第一件事不是数有多少张图,而是随机抽 50 张图逐一看标注框,确认 class_id 和形态对得上。这个步骤我管它叫“数据体检”,跑得快的话一刻钟,能省掉后面一整天的排查时间。
选型上,瓷砖裂缝检测为什么主要用 YOLO 而不是传统图像处理?传统方法靠 Canny 边缘检测加形态学闭运算,在单一光源、固定角度下表现尚可,但一到现场,瓷砖纹理、灰尘、水渍全变成伪边缘,阈值调到吐也调不出通用的。YOLO 这类 CNN 模型学的是语义特征,对纹理和光照有一定鲁棒性,而且从训练到部署的工具链最成熟。相比分割模型,检测模型只需要一个框,标注成本低、推理速度快,对质检场景已经够用。
2.2 train/val/test 怎么划分:目录结构与划分脚本的常规做法
YOLO 训练要求图片和标签严格对应,目录结构通常是这样的:
dataset/ ├── images/ │ ├── train/ # 训练图片,约 70%~80% │ ├── val/ # 验证图片,约 10%~20% │ └── test/ # 测试图片,可选,约 10% ├── labels/ │ ├── train/ # 与 images/train 同名的 .txt │ ├── val/ │ └── test/ ├── classes.txt # 每行一个类别名,行号就是 class_id └── data.yaml # 训练入口配置图片和标签靠“同名”关联:IMG_001.jpg必须对应labels/train/IMG_001.txt。扩展名不同没关系,YOLO 在读取时会自动替换后缀。划分的核心原则是:同一块瓷砖的多个角度照片尽量放进同一个集合。如果训练集里有一块瓷砖的正面,验证集里又有它的侧面,网络相当于见过答案再考试,val 指标会虚高,部署到新产线直接现原形。常规做法是先按文件名前缀或目录分组,再按组划分,而不是逐张随机抽。
如果你需要自己写划分脚本,一个最小实现是这样:
import random import shutil from pathlib import Path all_images = list(Path("images").glob("*.jpg")) random.Random(42).shuffle(all_images) n = len(all_images) for i, img in enumerate(all_images): split = "train" if i < n * 0.8 else "val" if i < n * 0.9 else "test" dst_img = Path("images") / split / img.name dst_lab = Path("labels") / split / (img.stem + ".txt") shutil.move(str(img), str(dst_img)) # 标签路径要先从 images 换成 labels src_lab = str(img).replace("images", "labels").rsplit(".", 1)[0] + ".txt" shutil.move(src_lab, str(dst_lab))逻辑说明:先按固定随机种子打乱,保证每次划分结果一致,便于复现。n * 0.8和n * 0.9分别切出训练集和验证集,最后 10% 进测试集。标签文件的路径不能用img.with_suffix(".txt")直接推导,因为图片在 images 目录下,标签在 labels 目录下,必须做一次字符串替换。
参数说明:Random(42)里的 42 是随机种子,换成任意整数都可以,但要保证多次运行结果一致;划分比例 0.8/0.1/0.1 是通用值,如果数据集总量很小(比如只有 200 张),建议只用 train/val 两部分,把 val 比例提到 20%,因为测试集会挤占训练样本。
2.3 class 文件和可视化脚本:开工前必须跑一遍的两个检查
class 文件是一份纯文本,每行一个类名,比如:
crack chip行号从 0 开始,所以训练标签里的0代表 crack,1代表 chip。这个顺序必须和所有标签 txt 里的数字对得上,训练时data.yaml里的names会引用它,推理时输出的也是这两个名字。如果你自己补过标注,导出时务必确认 labelimg 的 classes.txt 顺序没有变过,这是最容易出静默错误的地方。
可视化脚本的作用是“把标注画回图上”,让人眼检查。YOLO 标签格式是:
class_id x_center y_center width height全部是归一化坐标,范围 0~1,画框时要乘回原图宽高。一个最小可用的检查脚本:
import cv2 def draw_yolo_labels(image_path, label_path, class_names, save_path): img = cv2.imread(str(image_path)) h, w = img.shape[:2] with open(label_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id, x_c, y_c, bw, bh = map(float, parts) # 反归一化:归一化坐标乘回原图宽高 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 int(cls_id) == 0 else (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[int(cls_id)], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(save_path), img) # class_names 顺序必须与 classes.txt 一致 draw_yolo_labels("images/val/IMG_001.jpg", "labels/val/IMG_001.txt", ["crack", "chip"], "check_IMG_001.jpg")逻辑说明:读取图片尺寸后,x_c - bw / 2得到左上角 x 坐标,x_c + bw / 2得到右下角 x 坐标,乘上宽高后转成整数像素值。map(float, parts)把标签文本转成浮点数,避免字符串拼接出问题。颜色按 class_id 区分,crack 画红框、chip 画绿框,一眼能看出类别有没有标反。
参数说明:class_names列表顺序必须和 classes.txt 完全一致;save_path建议放进单独的check/目录,别覆盖原图;发现框超出图像边界时,多半是坐标归一化算错或标注越界,需要回看标注工具。跑完脚本后,很多问题是只有在画出来之后才看得到的:框偏移了半个身位、类别名字对不上、标签和图片根本不是同一张。这步做完,再谈训练才有意义。
3. 从 yaml 到 results:YOLOv8 跑通裂缝检测的最小路径与参数解读
3.1 安装与最小命令:先把 YOLOv8 在本地跑起来
图片检查完,class 文件确认无误,接下来就是把训练跑通。目前大家用得多的是 ultralytics 的 YOLOv8,环境配置就两步:
# 创建虚拟环境,推荐 python 3.9~3.10 conda create -n yolo-crack python=3.10 -y conda activate yolo-crack # 安装 ultralytics pip install ultralytics如果机器上有 NVIDIA 显卡,装完后用nvidia-smi确认驱动能识别卡;没有 GPU 就用 CPU 训练,慢一点但能跑通全流程。CPU 训练 yolov8n、640 输入、100 轮,大概需要几个小时到十几个小时不等,取决于核心数和内存带宽;GPU 则把这个时间压缩到半小时以内,条件允许还是建议用 GPU。
装完后,把数据集根目录绝对路径填到data.yaml里。这个数据包自带的 yaml 通常已经把 names 填好,你需要改的只有 path 和 train、val 的相对路径:
path: /home/you/datasets/tile_crack # 改成实际绝对路径 train: images/train val: images/val names: 0: crack 1: chip逻辑说明:path是数据集根目录,train和val是相对它的子路径,正斜杠结尾不要加。如果只有一个 val 目录而没分 test,不用担心,训练时 val 会同时承担测试集职责;names必须从 0 开始编号,调换顺序会导致类别错位。
最小训练命令如下:
yolo detect train \ model=yolov8n.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0逻辑说明:model=yolov8n.pt表示加载 COCO 预训练权重做迁移学习。裂缝和 COCO 的类别差很远,但底层特征(边缘、纹理、形状)是通用的,预训练权重能显著加速收敛;device=0指定第一张显卡,没有 GPU 就删掉这行,默认 CPU。启动后每个 epoch 结束会打印 box_loss、cls_loss、dfl_loss 和 mAP50、mAP50-95,看到 mAP 开始爬升、loss 在下降,说明数据和配置没有大问题。
3.2 训练参数怎么设:imgsz、epochs、batch、lr 的取舍
新手最容易在参数上纠结,其实瓷砖裂缝这种单色背景、目标结构简单的场景,ultralytics 的默认参数本身就够用。真正需要动的是下面几个:
| 参数 | 默认参考 | 裂缝场景建议 | 说明 |
|---|---|---|---|
| imgsz | 640 | 640~960 | 细裂纹用 960 能保住特征,代价是显存和训练时间翻倍 |
| epochs | 100 | 100~200 | 配合早停,别死等 300 |
| batch | 16 | 8~32 | 以显存不吃满为准,显存不足先降 batch |
| lr0 | 0.01 | 0.01 起步 | 损失曲线震荡剧烈时降到 0.005 |
| patience | 100 | 20~30 | 验证集连续 N 轮不提升就停,省时间 |
这里需要解释一下 YOLO 的损失函数构成。YOLOv8 的 loss 是三块加起来:box_loss 用的是 CIoU,综合考虑框的重叠率、中心点距离和长宽比;cls_loss 是分类用的 BCE 损失;dfl_loss 是分布焦点损失,用于让框回归更精确。终端打印的 loss 是这三者的加权和,所以 loss 一直在降不代表模型好,必须配合 val 指标看。
很多人训练时只盯 mAP,忽略了 loss 曲线的形状。训练结束后打开runs/detect/train/results.png,会看到两组曲线:train loss 一路向下,val loss 起先跟着降,到某个点开始横盘甚至抬升,这个分叉点就是过拟合开始的地方。裂缝数据集一般不大,过拟合来得快,所以patience建议从默认 100 改到 20~30——早停设置得越灵敏,越能避免把时间浪费在已经变差的训练上。
数据增强方面,ultralytics 默认开马赛克增强,对小数据集帮助很大。如果发现马赛克把裂缝切碎了导致 val loss 波动明显,把mosaic=0.5调低到 0.3,或者干脆设成mosaic=0.0关掉,通常能稳住验证集表现。对瓷砖这类纹理规则的物体,degrees=10的小角度旋转和scale=0.3的随机缩放是性价比最高的增强。
3.3 结果目录里有什么:loss 曲线、混淆矩阵和 PR 曲线的读法
训练结束后,runs/detect/train/下会生成一批文件,重点看三个。
第一个是results.png,整体曲线汇总,快速判断收敛情况。第二个是confusion_matrix.png,横轴是真实类别,纵轴是预测类别,对角线越亮越好。裂缝检测里最需要关注的是背景行——如果“crack 被预测成 background”的数量很多,说明细裂纹被漏检了,需要提高 imgsz 或检查标注框是不是把细小裂纹包得太松。第三个是PR_curve.png,每个类别的精确率-召回率曲线,曲线越贴近右上角越好;如果曲线在低召回段就往下掉,说明模型偏保守,宁愿漏检也不误报,到部署阶段调低置信度门限可以找回部分召回。
另外一组容易被忽略的指标是 mAP50 和 mAP50-95 的差值。mAP50 高但 mAP50-95 明显偏低,说明模型框的位置不够准,预测框和真实框的 IoU 只在 0.5 附近徘徊。这个现象在裂缝检测里很常见,因为裂缝是细长目标,标注框又紧贴走向,预测框稍微偏移一点 IoU 就掉得很快。要改善,优先检查标注质量,其次才考虑换更大的模型。
有朋友训练一结束就拿着 best.pt 去测单张图,我的建议是先在 val 集合上把混淆矩阵和 PR 曲线看明白,再动手推理。整体指标比单张图的目测更能反映模型能力,读这三张图花不了十分钟,信息量比盲测二十张图都大。
4. 裂缝检测避坑:五条值得写进笔记的踩坑记录
这一章写的都是常见环境里反复出现的坑,每一条都按“现象 → 原因 → 解决”来说,希望能帮你绕开。
4.1 训练时刷屏 Warning:标签文件位置对不上
现象:训练刚开始,终端不停打印标签文件找不到的告警,训练能继续,但 mAP 一直很低,val 指标完全没有参考价值。
原因:最常见的是划分脚本只分了图片,没分标签,标签还堆在原来的目录里;另一种是 labels 目录里多套了一层子目录,YOLO 按labels/train/xxx.txt相对路径找,结果文件躺在labels/train/2024/xxx.txt,路径对不上自然读不到。
解决:先对比 images 和 labels 两棵目录树,确保层数一致。再用一段简短 Python 核对:
from pathlib import Path img_dir = Path("images/train") lab_dir = Path("labels/train") missing = [p.name for p in img_dir.glob("*.jpg") if not (lab_dir / (p.stem + ".txt")).exists()] print(f"缺少标签的图片数: {len(missing)}") for name in missing[:10]: print("缺失:", name)逻辑说明:遍历训练集所有 jpg,用p.stem拿到不带扩展名的文件名,拼上.txt后去 labels 目录查是否存在。参数说明:如果打印结果为 0,说明标签齐全,问题出在其他环节;如果有缺失,对比缺失文件的分布规律,通常能反推出是划分脚本漏了标签还是标注时就没保存。
4.2 可视化脚本画出来的框全错位
现象:脚本跑完,生成的图片上框的位置和裂缝完全对不上,有的框整体偏移,有的框是镜像位置,有的框跑到图外面去了。
原因:三个典型。第一,标签坐标不是归一化的,而是像素坐标,脚本又按归一化公式乘了一遍宽高,框自然偏出去。第二,原图在标注前被缩放或裁剪过,标签坐标基于缩放前的大图,而脚本读取的是缩放后的图。第三,OpenCV 读图是 BGR 通道顺序,如果保存或显示时做了通道转换的误操作,肉眼看颜色不对,容易误判成框错位。
解决:写脚本时加一个断言,只要坐标不是 0~1 范围内就直接报错,不要让人眼去猜:
assert 0 <= x_c <= 1, f"坐标未归一化: {x_c}"如果报错,说明标签是像素坐标,先除以宽高做归一化再画。打印img.shape确认图片尺寸和标注时的基准尺寸一致,这一步能排查掉大部分错位问题。框偏移不严重但整体走位时,优先怀疑图片被重采样过,回看数据包里的原始图片和标注工具导出设置。
4.3 mAP 不低但现场漏检:问题往往出在标注标准
现象:训练集 mAP50 到 0.9,拿到新厂房的照片一测,裂纹漏检、灰缝误检成裂纹,现场根本不敢用。
原因:训练集和现场数据存在分布差异是天然的,光线、相机角度、瓷砖批次都不同;但更常见的是标注标准不统一。有人把边缘阴影、釉面纹理也画进了框,模型学到的是“纹理密度大”而不是“裂缝”,换到干净的新瓷砖上就失灵;有人画裂纹框时故意多包一圈背景,模型学到的边界比实际裂缝粗,导致细小裂纹全部漏掉。
解决:用现成数据集时,先通过可视化脚本理解原有标注风格,特别是框的松紧程度;如果是自己补数据,一定要写一份标注规范:裂纹框紧贴裂缝走向、框的宽度不超过裂缝本身的两倍;不标灰尘、不标水渍、不标釉面纹理;同一块瓷砖的多角度照片只取一张进训练集。把规范写进文档让每个人读一遍再标注,比调任何参数都管用。
4.4 train loss 贴地、val loss 高位震荡:过拟合的典型信号
现象:训练到第五十个 epoch,train loss 掉到 0.1 以下,val loss 却在 1 以上来回震荡,验证集的 mAP 不再上升。
原因:数据集小,模型把训练集背下来了。几百张图的裂缝数据包,用 yolov8s 以上的模型很容易过拟合。另一个隐蔽原因是划分时数据泄漏——同一块瓷砖的不同角度照片同时出现在 train 和 val,模型见过类似纹理,val 指标虚高,等到新数据上才暴露。
解决:先确认划分脚本按组分配,同一瓷砖的照片只进一个集合;然后把模型换成更轻的 yolov8n,减少参数量;同时加强数据增强,重点用degrees=10和scale=0.3模拟不同拍摄姿态,让模型学到的是裂缝本身而不是背景。如果 val loss 依然压不住,把 epochs 砍到 60 左右,靠早停掐断训练。
4.5 细裂纹一张都检不出来:输入尺寸与锚框的取舍
现象:粗裂纹检得不错,头发丝一样的细裂纹全漏;把单张图放大仔细看能看见裂缝,但模型就是没反应。
原因:YOLOv8 的下采样倍数决定了它能看到的最小特征尺度。输入 640 时,最小特征图是 20×20,每个格子对应原图 32×32 的区域,一条只有 2~3 像素宽的裂缝在这个格子里的响应几乎为 0。另外,训练时 imgsz 如果小于原图分辨率,resize 会把细裂纹直接磨平,相当于信息在输入端就丢了。
解决:优先把 imgsz 提到 960 甚至 1280,让裂缝在特征图上占据更多像素。显存不够时,换 yolov8n 并调低 batch,把省下来的显存投给输入尺寸;如果单张图太大,用切片训练:把大图切成若干 640×640 的小块训练,推理时用滑窗拼接。切片方案会显著增加训练时间,但对细长目标的召回提升最明显,是工业场景里常用的手段。
5. 验证模型而不是自我感动:置信度门限、单图测试与导出部署
训练结束不等于交付。我见过不少人拿 val 集测个 mAP 就宣布完成,结果在真实照片上被灰缝、水渍打得满地找牙。验证阶段要做两件事:单图目测和置信度门限调节。
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("test_01.jpg", conf=0.25, save=True)逻辑说明:加载训练产出的 best.pt,对单张图做推理,save=True会把带框结果存到runs/detect/predict/下。conf=0.25是最常用的起步门限。
参数说明:如果发现画面里到处都是灰缝误报,把 conf 提高到 0.4~0.5;如果发现裂纹漏检,降到 0.1~0.15。这个数值没有标准答案,本质是在误报和漏报之间选一个业务上更能接受的平衡点。工业现场建议宁可多出几个误报框让人工复核,也不要漏检,漏检的代价通常比误检高一个量级。
如果模型要部署到边缘设备,下一步是导出:
yolo export model=runs/detect/train/weights/best.pt format=onnx导出的 ONNX 可以用 ONNX Runtime 或 TensorRT 加速推理。导出时注意保持和训练一致的 imgsz,否则特征图尺度变了,精度会掉一截。导出后再跑一遍上面的单图测试,对比导出前后框的位置和置信度,确认没有精度损失再进入部署流程。
最后补一句个人习惯:我现在拿到任何数据集,第一件事永远是跑一遍可视化脚本,抽 30~50 张图把标注框逐一看过,确认类别顺序、坐标格式、划分逻辑三件事没有翻车,才舍得花算力去训练。这个习惯源自一次白跑了一周训练的经历,从那以后再没犯过。希望帮到你。
本文还有配套的精品资源,点击获取