ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLO瓷砖裂缝检测数据集准备与可视化避坑指南

YOLO瓷砖裂缝检测数据集准备与可视化避坑指南 简介面向瓷砖制造与质检场景的YOLO目标检测数据集包含1700余张已标注瓷砖图像共“裂缝”“正常”两个类别适合初学者或工程师直接用于裂缝检测模型的训练与验证。数据均采用LabelImg标注标签以txt格式保存并与图片一一对应同时也提供类别配置文件。资源共2000个文件以1765个标注txt为主搭配234张jpg原图与1个可直接运行的数据可视化py脚本压缩包大小约91.75MB。可视化脚本无需修改输入任意一张图片即可绘制边界框并保存结果方便快速检查标注质量。目前已有193人学习下载。对部分数据进行了翻转、添加噪声等增强处理可提升模型泛化能力目录划分清晰便于按YOLOv5等框架直接组织训练集与验证集是一份开箱即用的瓷砖缺陷检测入门资料。1. 瓷砖裂缝识别2 类数据集才是产线检测的常见起步配置瓷砖裂缝检测是质检场景里最典型的目标检测需求缺陷目标小、背景纹理杂、同一条裂缝在不同光照下长相差很远。很多人拿到 YOLO 就直接拿开源通用数据集训练结果在自家产线上一测漏检率、误检率高到没法用——问题往往不是网络结构不够新而是数据集的类别定义和划分方式从一开始就埋了雷。这套方案解决的是数据侧的事2 类缺陷裂缝和崩瓷的标注规范、划分好的 train/val/test 目录、类别 class 配置文件data.yaml以及训练前用来检查数据质量的可视化脚本。它适合两类人一是刚接触 YOLO、不想在数据格式上反复折腾的初学者二是已经在产线上跑过一版模型、但效果不稳定、怀疑是数据问题的一线工程师。先把数据做扎实后面搬模型才有意义。2. 裂缝检测 2 类的定义与数据准备class 文件、标注格式与来源2.1 两类缺陷怎么定crack 和 chip别把背景硬凑成第二类标题里写的是 2 类。常见做法是分成crack裂缝和chip崩瓷/边角破损两类。为什么不是只做裂缝一类因为 YOLO 训练时负样本来自图像背景本身如果标注里只有裂缝模型会把瓷砖表面的纹理、色差、水墨晕染都当成可疑目标误检率会非常高。把崩瓷这类高频次生缺陷加进来模型被迫去区分“线状裂纹”和“块状崩落”特征学习反而更稳。这里有个容易翻车的点不要把“无缺陷瓷砖”标成第二类。目标检测里一个图像区域如果没有目标就不产生正样本你硬框一个“正常区域”模型学到的是“这个位置有东西”只会让训练更混乱。2 类指的就是两种缺陷类别背景靠没有标注的区域自然形成。类别编号从 0 开始这是 YOLO 系列统一约定。数据可视化脚本和训练框架都依赖这个顺序后面 class 文件里 names 的排列顺序必须和 txt 标注里第一列的整数一一对应。2.2 数据来源与采集量现场产线、实验室、公开数据集怎么取舍做瓷砖裂缝数据的渠道无非三种。现场产线相机拍摄最理想光照、角度、灰尘都真实但往往涉及产线改造初期拿不到太多负样本实验室里自己拿瓷砖样品掰、砸、划能快速积累缺陷样本缺点是背景太干净泛化会差一点公开数据集比如 Roboflow、Kaggle 上的瓷砖缺陷集起步最快但瓷砖纹理差异极大抛光砖和仿古砖的裂缝形态完全不是一回事外来的数据只能当预训练补充不能直接当验收数据。采集量上我一般建议每类起步至少 6001000 个标注实例。这里的“实例”指框的数量不是图片数量。一条裂缝在 2448×2048 的大图里可能只占很小一块按整图存的话一张图只算一个实例样本量很容易虚高如果后面做切图一张大图切成 640×640 的 12 个子图裂缝可能出现在多个子图里实例数就实打实上来了。另外注意一点数据集下载后先看它图片尺寸。很多公开的瓷砖数据是 512×512 的缩略图用来练手可以但产线相机出图通常是 2000 万像素级别直接拿小图训练出来的模型搬到大图上会明显吃力这就是标题里数据可视化脚本要帮你提前暴露的问题之一。2.3 用 labelimg 打标 YOLO 格式参数与容易漏掉的细节打标工具最常用的是 labelimg因为轻量、格式切换直接。打开后要手动把保存格式从 PascalVOC 切到 YOLO这个选项在工具栏里很多人默认用 VOC 打完才发现输出的是 XML还得再转一次。YOLO 格式的标注文件是 .txt和图片同名同目录或放在并行 labels 目录下。每行 5 个数字0 0.521484 0.438477 0.031250 0.014648第一个数字是类别 id后面四个是归一化后的框中心 x、中心 y、宽 w、高 h取值范围 01。归一化是相对整图尺寸算的不是像素值。打标时有两个细节值得留意。一是裂缝这类细长目标框要尽量贴紧目标不要把裂缝周围一大片纹理都包进去否则模型学到的特征是“纹理块”而不是“裂纹线”二是对跨两张图的裂缝按实际可见部分分别框不要跨图标注图外的像素模型看不到标了反而引入错误坐标。打标完成后的验证方式很简单随便打开一个 txt确认每一行的第一列只出现 0 和 1坐标值都在 01 之间。如果出现 1.2 这种越界数字训练时 loss 会异常跳变这是最常见的数据层翻车原因之一。2.4 class 文件类别配置文件怎么写data.yaml 逐字段说明YOLOv5 到 YOLOv11 都认一个 YAML 格式的类别配置文件通常叫 data.yaml 或数据集名.yaml。它负责告诉训练程序三件事数据在哪、有几类、每一类叫什么。train: ./images/train val: ./images/val test: ./images/test nc: 2 names: 0: crack 1: chiptrain、val、test这三级路径是相对 data.yaml 所在目录的相对路径。常见的错误是写绝对路径换一台机器就要改一遍相对路径则可以随数据集目录整体拷贝迁移。test字段在训练时用不到但推理评估时会读建议保留。nc: 2是类别数量必须和names里的条目数一致多一个少一个都会在训练启动时报错。names的顺序就是类别 id 的顺序这里crack对应 id 0chip对应 id 1。很多人在这个文件里把名字换了个排序忘了改标注 txt 里的数字训练出来的模型预测类别就全乱了——这个问题后面避坑章专门讲。3. 划分好的数据集长什么样目录结构、划分逻辑与验证3.1 YOLO 数据集的目录结构train / val / test 谁对应什么一套划分好的 YOLO 数据集标准目录结构长这样tile_defect/ ├── data.yaml ├── images/ │ ├── train/ │ │ ├── tile_0001.jpg │ │ ├── tile_0002.jpg │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── tile_0001.txt │ │ ├── tile_0002.txt │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... └── scripts/ └── visualize_dataset.pyimages 和 labels 下是三个平行子集。训练时 YOLO 通过前缀匹配找标签一张图片tile_0001.jpg的标签必须是tile_0001.txt放在 labels 下对应的同一子集目录里。图片和标签的文件名不含扩展名必须完全一致大小写也要一致。val 集不是摆设。训练过程中每轮结束都会在 val 上计算 mAP它负责告诉你模型在没见过的数据上表现如何。test 集是最终验收用的只跑一次用来模拟部署后的效果。如果数据量很小少于 600 张可以把 test 省掉把 val 当验收集但至少要保证 val 和 train 没有重叠。3.2 划分脚本先随机、后复制保证类别和场景平衡我自己写划分脚本时会遵循三个原则按文件 stem 匹配而不是按目录直接搬先随机打乱再切分最后按类别校验划分比例。import random import shutil from pathlib import Path random.seed(42) # 固定随机种子保证每次划分结果一致 source_images Path(tile_raw/images) source_labels Path(tile_raw/labels) dest_root Path(tile_defect) image_files sorted(source_images.glob(*.jpg)) random.shuffle(image_files) n len(image_files) n_train int(n * 0.8) n_val int(n * 0.1) splits { train: image_files[:n_train], val: image_files[n_train:n_train n_val], test: image_files[n_train n_val:], } for split_name, files in splits.items(): img_dst dest_root / images / split_name lbl_dst dest_root / labels / split_name img_dst.mkdir(parentsTrue, exist_okTrue) lbl_dst.mkdir(parentsTrue, exist_okTrue) for img_path in files: label_path source_labels / (img_path.stem .txt) if not label_path.exists(): print(f[warn] 缺少标签: {label_path}) continue shutil.copy2(img_path, img_dst / img_path.name) shutil.copy2(label_path, lbl_dst / label_path.name) print(ftrain: {len(splits[train])}, val: {len(splits[val])}, test: {len(splits[test])})每张图片都在标签目录里做一次存在性检查找不到对应 txt 就直接跳过并打印警告而不是让它带着错误进训练集。使用shutil.copy2复制而不是移动保留一份原始标注后续删改有后悔药。随机种子固定为 42使得每次重新运行脚本得到完全一样的划分方便复现实验。划分比例上数据量 1000 张以上用 8:1:1 比较稳数据量只有 300 张左右时可以把 test 省掉按 8:2 切val 承担最终评估。不要用 6:2:2训练集太少小目标检测本身就吃数据。3.3 划分完必须做的三项自检图片可读、标签一一对应、类别 id 有效划分脚本跑完不等于数据集就合格。我会做三项快速自检每一项都有对应命令。第一项统计图片和标签数量是否严格吻合find images/train -name *.jpg | wc -l find labels/train -name *.txt | wc -l两边数量不一致说明存在没有标签的图片或者打标时漏存了文件。第二项抽查 txt 内容格式。每条记录必须有 5 列前 4 个浮点数在 01 之间awk { if (NF ! 5) print 列数错误:, FILENAME, NR; for (i2; i5; i) if ($i 0 || $i 1) print 越界:, FILENAME, $i } labels/train/*.txt越界坐标会导致损失函数里出现 nan训练出来的模型直接废掉。第三项类别 id 是否都在nc范围内。拿 data.yaml 里的 nc 去对如果 nc2标注里出现2或-1说明打标的时候类别选错或漏改。这类错误在训练启动时往往不报错但模型预测会持续输出异常结果。可视化脚本配合这三项自检基本能把数据层 90% 的问题挡在训练之前。4. 数据可视化脚本训练之前先看这四张图4.1 标注框回画把 YOLO 坐标画回原图训练前最该做的一件事是把 YOLO 格式的归一化坐标还原到原图上亲眼看一遍框的位置和大小是否贴合缺陷。很多数据问题不是看数字能发现的——比如框中心偏了半个身位、一条裂缝被框成两个叠在一起的矩形、框里其实有一半是背景纹理。可视化脚本的核心就是干这个。import cv2 import numpy as np from pathlib import Path CLASS_NAMES {0: crack, 1: chip} COLORS {0: (0, 0, 255), 1: (0, 255, 255)} def draw_yolo_boxes(image_path: Path, label_path: Path, save_path: Path): img cv2.imread(str(image_path)) h, w img.shape[:2] with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id int(parts[0]) xc, yc, bw, bh map(float, parts[1:]) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color COLORS.get(cls_id, (255, 255, 255)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label CLASS_NAMES.get(cls_id, str(cls_id)) cv2.putText(img, label, (x1, max(0, y1 - 6)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) cv2.imwrite(str(save_path), img)脚本的核心逻辑是把归一化坐标乘以图片宽高还原成像素坐标。注意(xc - bw / 2) * w算出来是浮点数转 int 时会截断对画框来说误差在 1 像素以内不影响判断。把一张大图直接 resize 成缩略图后用这个脚本把框画上去放在一张白板上批量扫一遍比逐张打开原图高效得多。跑完后重点看两类样本一类是框明显大于缺陷区域说明标注时框松了另一类是框和缺陷错位超过框面积的三分之一说明打标时坐标填错了或者标签和图片不是同一个文件。4.2 单类数量与图像数统计定位不平衡裂缝检测场景天然存在类别不平衡一条长裂纹可能被拆成 3 个框而一个崩瓷往往只有 1 个框。所以统计要分两个维度图片张数和标注实例数。from pathlib import Path from collections import Counter label_root Path(labels/train) image_root Path(images/train) total_boxes Counter() images_per_cls Counter() for img_path in image_root.glob(*.jpg): label_path label_root / (img_path.stem .txt) if not label_path.exists(): continue seen set() for line in label_path.read_text(encodingutf-8).strip().splitlines(): cls_id int(line.split()[0]) total_boxes[cls_id] 1 seen.add(cls_id) for cls_id in seen: images_per_cls[cls_id] 1 print(实例数:, dict(total_boxes)) print(包含该类的图片数:, dict(images_per_cls))只看实例数会骗人。假设 crack 有 2000 个实例但只分布在 150 张图上说明数据高度集中模型相当于反复看同样几张图泛化会很差。我通常要求每个类别至少分布在 300 张不同图片上少于这个数就要回去补数据或者用数据增强把单图的裁剪变体做出来。还要统计每张图的框数量分布。正常瓷砖缺陷密度下单图 25 个框很常见如果出现大量单图 20 个框的样本大概率是同一张图被切图脚本重复生成了训练时这类样本的梯度贡献会被放大模型会偏科。4.3 框的宽高比 / 面积分布发现过小目标与误标裂缝的形态决定了框的长宽比极其悬殊。一条横向裂纹的框宽可能是高的 20 倍。这个分布要可视化出来因为它直接影响 YOLO 的 anchor 配置。import numpy as np ratios [] areas [] label_root Path(labels/train) for label_path in label_root.glob(*.txt): for line in label_path.read_text(encodingutf-8).strip().splitlines(): parts line.split() if len(parts) ! 5: continue bw, bh float(parts[3]), float(parts[4]) if bh 0: continue ratios.append(bw / bh) areas.append(bw * bh) ratios np.array(ratios) areas np.array(areas) print(宽高比 中位数:, np.median(ratios)) print(框面积 中位数:, np.median(areas)) print(面积 0.001 的框占比:, (areas 0.001).mean())面积阈值按归一化处理0.001表示框面积占整张图的千分之一。以 640×640 输入来算这个框大约只有 20×20 像素——对检测器来说已经开始吃力了。如果这个占比超过 15%训练时就应该提高输入分辨率或者先把大图切小再训练。宽高比分布异常通常意味着误标如果出现大量接近 1:1 的裂缝框说明标注时框选太方把纹理块当成裂缝了。正常裂缝的长宽比分布应该明显偏向细长中位数一般落在 38 之间。4.4 亮度与对比度统计瓷砖纹理会干扰训练的信号这一步很多人忽略。瓷砖表面有抛光、哑光、仿古纹理在同一批数据里如果亮度方差很大模型会不自觉地把亮度当成缺陷线索训练出来的模型一换光照就崩。可视化脚本里我习惯加一个全局亮度统计。import cv2 import numpy as np from pathlib import Path brightness [] for img_path in sorted(Path(images/train).glob(*.jpg))[:200]: img cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) if img is None: continue brightness.append(img.mean()) brightness np.array(brightness) print(f亮度均值: {brightness.mean():.1f}, 标准差: {brightness.std():.1f}) print(f最暗 5%: {np.percentile(brightness, 5):.1f}, 最亮 5%: {np.percentile(brightness, 95):.1f})抽 200 张已经能看出趋势。标准差大于 40说明数据里同时存在暗光和强光样本这不是坏事但训练时最好开 mosaic 和 HSV 增强让模型见过更多亮度组合如果标准差小于 15说明数据太单一部署到产线不同工位大概率翻车。对比度也有类似作用裂缝本质上是局部灰度突变如果样本里对比度分布过窄模型会把“模糊的纹理线”和“清晰的裂缝”混为一谈。这个分布不建议硬调看到了放在心里之后拿验证集结果对照着看。5. 瓷砖裂缝数据集的常见问题排查训练前必查的 5 个坑5.1 YOLO loss 不降或直接变 nan先查标签坐标越界现象训练启动正常但前几个 epoch 的 box_loss 和 cls_loss 剧烈跳动甚至出现 nan训练几轮后 mAP 始终是 0。原因最普遍的是标签文件里坐标不在 01 范围内。打标时把图片尺寸改过或者从 VOC 转 YOLO 时除错了宽高导致归一化坐标出现 1.3、-0.2 这类越界值。越界坐标在计算 IoU 时会出现无效区域梯度直接爆掉。解决不要逐个翻 txt一条命令把所有标签的越界项筛出来awk { for (i2; i5; i) if ($i 0 || $i 1) print FILENAME, $0 } labels/train/*.txt有输出就把对应图片和标签成对删掉或者回到 labelimg 里重新标。修完重跑loss 曲线马上恢复正常。5.2 训练正常但预测结果类别全错乱class 文件 names 顺序和标注不一致现象模型能框出裂缝但输出的类别名是 chip置信度还不低或者所有目标都预测成同一类。原因data.yaml 里names的排列顺序和标注 txt 里的 id 对应不上。比如标注文件里写的是0你本意是 crack但 data.yaml 里names: {0: chip, 1: crack}框架按顺序把 0 解析成 chip模型从头到尾学的是错位标签。解决写一个小脚本把每个类别 id 对应的实例数和名字打出来逐项核对python -c from pathlib import Path labels Path(labels/train).glob(*.txt) cnt {} for p in labels: for line in p.read_text().strip().splitlines(): if line: cls line.split()[0] cnt[cls] cnt.get(cls, 0) 1 print(cnt) 输出结果应该和 data.yaml 里nc、names的顺序一一对上。如果只有{0: ..., 1: ...}名字顺序反了就直接改 data.yaml如果出现2说明打标时选漏了类别要回去重新标。5.3 大图上小裂缝全部漏检直接 resize 到 640 是元凶现象训练时 mAP 看着有 0.8一到产线 2448×2048 原图上跑小裂缝几乎全漏只剩几条大的。原因训练输入默认 640×6402448 宽的图等比缩放后一条实际 30 像素宽的裂缝只剩 8 个像素特征完全消失。检测器在小目标上的能力本来就弱数据预处理再一压缩等于直接放弃小目标。解决第一选择是切图把大图切成 640×640 带重叠的子图再训练推理时也切图后拼接结果第二选择是提高训练尺寸imgsz1280但显存占用会翻倍。最省事的折中是把原图先缩放到 1280 再训练小裂缝保留程度比直接压到 640 好不少。5.4 val 集 mAP 虚高部署后暴跌划分时没有按场景隔离现象训练时 val 集 mAP 稳定在 0.9 以上换个车间拍一组新图来测mAP 直接掉到 0.4。原因划分数据集时用的是全局随机同一块瓷砖的连拍图、同一条产线的相邻帧被同时分进了 train 和 val模型相当于开卷考试。它记住的是这批瓷砖的纹理细节不是裂缝的通用特征。解决按采集批次或场景划分而不是按单张图随机分。产线来的数据按拍摄时间分块前 80% 进 train、后 20% 进 val不同光照条件的数据尽量保证 train 和 val 里都有但同一场景的连续帧不要跨集合。5.5 可视化脚本 cv2.imread 返回 None中文路径和特殊字符的锅现象标注回画脚本跑一半某几张图画不出来控制台报[ WARN:] ... imread_(...): cant open/read file。原因OpenCV 的imread底层用的是 C 的文件接口对中文路径、中文文件名支持很差Windows 上尤其明显。数据集目录只要有一层叫“瓷砖数据”整个脚本就会间歇性失灵。解决换用先读字节再解码的方式绕开imread的路径解析逻辑def imread_unicode(path): data np.fromfile(str(path), dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)这个函数可以直接替换脚本里的cv2.imread。另外还要注意标签文件里有没有 BOM 头utf-8-sig编码读取最保险否则第一行的类别 id 会被解析成\ufeff0int() 转换直接报错。6. 从数据集到模型用 YOLO 训练、评估完再补一刀切图数据集就绪后训练命令很直接。我一般用 ultralytics 的 CLI 启动最小命令是这样yolo detect train \ datatile_defect/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience20patience20表示验证集指标连续 20 轮不涨就提前停止省时间。小数据集上我习惯用yolov8n起步跑通流程后再换yolov8s或yolov8m对比效果不要一上来就上最大模型裂缝没那么复杂大模型只是过拟合得更快。训练完别急着部署先跑一批自己的验证图yolo predict \ modelruns/detect/train/weights/best.pt \ sourcetile_defect/images/test \ saveTrue \ conf0.25 \ iou0.5conf0.25是置信度门限产线上想少漏检就降到 0.15想少误检就抬到 0.4没有统一最优值拿自己的数据试。iou0.5是 NMS 阈值两个重叠框 IoU 超过它就被合并——裂缝这种细长目标如果两条裂纹挨得近阈值调低到 0.3 反而更稳。如果整张大图直接预测效果还是不行就上切图推理python -c from ultralytics import YOLO from PIL import Image model YOLO(runs/detect/train/weights/best.pt) img Image.open(sample.jpg) w, h img.size size 640 overlap 64 for y in range(0, h, size - overlap): for x in range(0, w, size - overlap): crop img.crop((x, y, min(xsize, w), min(ysize, h))) results model.predict(crop, conf0.2, imgsz640) # 这里把 results 的框坐标加上 (x, y) 偏移再合并到大图坐标 切图推理的关键是最后把子图坐标偏移回原图坐标同时处理边界处同一裂缝被切两半、出现两个框的问题——用 NMS 把重叠度高的框合并。我自己做瓷砖检测项目有个习惯每次训练前先跑一遍数据可视化脚本和标签自检训练完保留当时的 data.yaml 和标注统计结果方便后续排查问题。数据是黑匣子你不动它它就会在模型上线那天突然咬你一口。希望这些方法能帮你在裂缝检测这条路上少踩几个坑把精力留给真正难的推理优化和产线适配。本文还有配套的精品资源点击获取
返回列表