简介:面向瓷砖表面缺陷检测任务,这套YOLO格式数据集包含裂缝与正常两个类别,约一千七百余张经过标注的瓷砖图像,并已划分好训练目录,附带说明两个类别的classes.txt文件和Python可视化脚本,适合工业质检、目标检测入门或算法快速验证。压缩包采用7z格式,共两千个文件,其中一千七百多个文本标签记录边界框坐标,两百多张实际图像用于训练,另有一个无须修改即可运行的绘图脚本,整体约九十一点七五MB。目前已有约两百人学习。数据对部分样本做翻转、加噪等增强,可帮助提升模型的泛化能力;可视化脚本随机传入一张图片即可绘制边界框并保存到当前目录,方便直接核对标注质量,节省人工检查与重复开发的时间,拿到后便能接入YOLOv5等主流框架进行训练。
1. 先看明白:这份瓷砖裂缝数据集到底能解决什么问题
真正把新手卡在YOLO门外的,常常不是网络结构,而是那份“拿到手就能直接跑”的数据集。瓷砖表面裂缝就是一类典型的细长小型缺陷,它在釉面纹理里只有几个像素宽,颜色与底色接近,人工标注费眼,模型训练起来又特别容易学偏,所以一套带完整标签、划分好训练与验证集的裂缝数据,价值往往比一打理论PPT高得多。这份资源收录了大约1700张瓷砖表面图像,PNG格式,用labelImg按YOLO格式标注,一共2类——裂缝和正常,标签文件与图片分目录存放,classes.txt已写清类别顺序,还附带一个可视化脚本,随机传一张图就能画出边界框并存到当前目录,不需要改任何路径。想做工业缺陷检测的算法验证,或者想入门YOLO训练全流程,这份资源可以作为第一个能直接跑的样本项目。
2. 拆解数据集结构:标签格式、目录划分与增强边界
2.1 先读懂标签文件:YOLO格式没你想的那么玄学
标签是txt文本,每一行代表一个目标框,格式固定为class_id x_center y_center width height,坐标都是归一化后的相对值,也就是真实像素坐标除以图片宽高。labelImg导出时默认就输出这种格式。数据集文件名里藏着信息:N217、N188这类前缀对应normal类别(正常),C209、C39这类前缀对应crack类别(裂缝),原始图片在标注后统一转成PNG保存。刚上手的人最容易把归一化坐标当成像素坐标来画框,画出来全部偏到图外面,这类问题在后面的避坑章节会展开讲。
在一头扎进训练之前,先写一小段脚本把标签读明白。下面这段可以视为可视化脚本的最小核心,能帮你把任意一张图和它的txt还原成带框图片:
import cv2 def draw_from_txt(img_path, txt_path, output_path): img = cv2.imread(img_path) h, w = img.shape[:2] # 原图尺寸,归一化坐标要乘回来 with open(txt_path, "r", encoding="utf-8") as f: for line in f.readlines(): line = line.strip() if not line: continue parts = line.split() cls_id = int(parts[0]) # 类别ID x_center, y_center = float(parts[1]), float(parts[2]) bw, bh = float(parts[3]), float(parts[4]) # 归一化 -> 像素坐标 x1 = int((x_center - bw / 2) * w) y1 = int((y_center - bh / 2) * h) x2 = int((x_center + bw / 2) * w) y2 = int((y_center + 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, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imwrite(output_path, img)这段逻辑是整个可视化脚本的地基。x_center、y_center是0到1之间的相对值,必须乘以原图宽高才能还原成像素坐标,width、height同理。cls_id为0时画红色框,对应裂缝;为1时画绿色框,对应正常,这个颜色习惯在工业缺陷可视化里最常用。注意cv2.imread按文件内容解码而不是按后缀判断,所以就算图片后缀是.jpg但实际编码是PNG,OpenCV也能正确读出来——这一点在后面的避坑章节还会专门提。顺带说一句,这套标签格式不止瓷砖能用,轴承表面缺陷检测、齿轮损伤检测这类工业缺陷任务,标注格式和读取逻辑几乎一模一样。
2.2 目录结构与class文件:YOLO能直接吃的标准组织方式
labelImg标注完成后,图片和txt标签分别保存在不同目录中,这是YOLO系列训练脚本默认接受的布局。拿到资源后建议先按下面这个结构核对一遍:
dataset/ ├── images/ │ ├── train/ │ │ ├── C209_jpg.rf.b672214ed5ee6f2384b9f8db39305224.jpg │ │ └── N217_jpg.rf.cc836bba4d8c9d066097b32b5915fd3f.jpg │ └── val/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── C209_jpg.rf.b672214ed5ee6f2384b9f8db39305224.txt │ │ └── N217_jpg.rf.cc836bba4d8c9d066097b32b5915fd3f.txt │ └── val/ │ └── ... └── classes.txtYOLO训练时要求同名的图片和标签分别放在images/与labels/下,靠文件名一一对应,只有前缀不同、后缀不同(.jpg对.txt)。所以拿到数据集第一件事,是检查有没有图片文件找不到对应txt、或者txt是空文件的情况,这类问题训练时不会直接报错,但会表现为验证集精度莫名其妙很低。
classes.txt这里只有两行,内容是:
| 类别ID | 类名 | 说明 |
|---|---|---|
| 0 | crack | 裂缝 |
| 1 | normal | 正常 |
类别顺序就是模型输出的ID映射。第一行crack对应class_id=0,第二行normal对应class_id=1。这个顺序一旦和训练配置里的names不一致,检测结果就会把裂缝全标成正常,属于最隐蔽的翻车方式。目录各角色在训练中的作用可以这样对应:
| 目录/文件 | 作用 | 在YOLO训练中的角色 |
|---|---|---|
| images/train | 训练图片集 | train路径 |
| images/val | 验证图片集 | val路径 |
| labels/train | 训练标注文本 | 读取标签目标 |
| labels/val | 验证标注文本 | 评估时读取 |
| classes.txt | 类别名列表 | 与data.yaml中的names对应 |
2.3 为什么做翻转和噪声增强,而不是旋转和颜色抖动
这份数据集的说明里提到“对部分数据集进行翻转、添加噪声点数据增强”,这两个操作不是随手选的。瓷砖裂缝的特点是走向随机,有的横、有的竖,水平翻转和垂直翻转能让模型对裂缝方向不敏感,本质上是免费扩充样本的方向多样性。而噪声点模拟的则是产线低照度环境下相机传感器的热噪点、瓷砖表面的粉尘颗粒,让模型学会在干扰中定位裂缝,而不是靠“图片很干净”这种捷径。
相比之下,随机旋转、随机缩放、颜色抖动这类通用增强在裂缝检测里容易出事。裂缝只有几个像素宽,旋转一定角度就变成断线,缩放倍数稍大就会抹掉细缝细节;颜色抖动在浅色瓷砖上会让裂缝对比度进一步丢失,模型学到的特征直接失效。翻转和加噪声是相对安全的两类增强,这也是这个数据集选择它们的原因。
做翻转增强时有一个必须同步处理的环节——标签。图片翻转了,框的坐标不跟着变,训练就是在学错位映射:
import cv2 def flip_image_and_label(img, txt_path, flip_code=1): # flip_code: 0垂直翻转, 1水平翻转, -1水平+垂直 img_flip = cv2.flip(img, flip_code) with open(txt_path, "r") as f: lines = [ln.strip() for ln in f if ln.strip()] new_lines = [] for ln in lines: parts = ln.split() cls_id, cx, cy, bw, bh = parts[0], float(parts[1]), float(parts[2]), \ float(parts[3]), float(parts[4]) if flip_code == 0: # 垂直翻转,Y方向映射 cy = 1.0 - cy elif flip_code == 1: # 水平翻转,X方向映射 cx = 1.0 - cx else: # 两者都翻 cx, cy = 1.0 - cx, 1.0 - cy new_lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return img_flip, new_lines增强的核心原则是“图片变了,标签必须跟着变”。水平翻转时x_center变成1 - x_center,垂直翻转时y_center变成1 - y_center,否则框就跑到错误位置。椒盐噪声的比例一般控制在0.01左右,太高会把浅色砖面搞得像花斑,模型反而去学噪声;太低又起不到抗干扰的作用。1700张的规模对单个工业场景来说不算大,但配合翻转和噪声增强,用来做迁移学习预训练或者验证检测流程已经够用。真正需要注意的是类别不平衡——正常样本数通常远大于裂缝样本数,增强应该优先补裂缝这一侧,而不是盲刷正常样本。
3. 可视化脚本实战:不改路径跑通、看出标签问题
3.1 脚本核心逻辑:读图片、解析txt、画框、存当前目录
数据集自带的可视化脚本,逻辑上是上一章draw_from_txt函数的完整封装,做了三件事:读取图片和它的同名txt;把归一化坐标还原成像素坐标画框;把结果写到当前目录。脚本开头定义类别颜色映射,中间通过glob找到图片对应的txt文件,末尾用cv2.imwrite保存为vis_前缀的新文件。默认情况下脚本自动从图片目录里随机挑一张,传参时则画指定图片,所以“无需更改路径直接运行”这件事靠的是目录约定。
贴近自带脚本行为的简化版长这样:
import cv2 import glob import random import os CLASS_COLORS = { 0: (0, 0, 255), # crack 裂缝,红色 1: (0, 255, 0), # normal 正常,绿色 } def visualize_one(img_path): img = cv2.imread(img_path) h, w = img.shape[:2] # 按目录约定找到同名标签:images -> labels,后缀 .jpg -> .txt txt_path = img_path.replace("/images/", "/labels/").rsplit(".", 1)[0] + ".txt" if not os.path.exists(txt_path): print(f"找不到标签: {txt_path}") return with open(txt_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if len(line) == 0: continue parts = line.split() cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:]) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) color = CLASS_COLORS.get(cls_id, (255, 255, 255)) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, str(cls_id), (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) output = "vis_" + os.path.basename(img_path) cv2.imwrite(output, img) print(f"已保存: {output}") if __name__ == "__main__": # 不带参数随机挑一张,带参数画指定图片 candidates = glob.glob("images/train/*.jpg") + glob.glob("images/val/*.jpg") path = candidates[random.randrange(len(candidates))] visualize_one(path)这一版在自带脚本思路上简化到只剩核心,正好能看到可视化脚本在黑匣子里做的事。replace("/images/", "/labels/")是把图片路径切到标签路径,前提是目录结构符合2.2节的约定;rsplit(".", 1)[0] + ".txt"是把后缀换成txt。CLASS_COLORS里红绿区分两个类别,如果画出来的颜色和预期不一致,第一反应就应该是class_id读错了。脚本默认随机选图,适合快速浏览;想画指定图片,把路径传给visualize_one即可。自带脚本默认“不用改路径就能跑”,靠的就是这套约定优于配置的目录假设。
3.2 随机选图跑一遍:三条判断标准
拿到资源后的第一步建议是直接运行可视化脚本:
python visualize.py # 无参数:随机抽取一张图,画框后保存为 vis_xxx.jpg 到当前目录 python visualize.py images/val/N244_jpg.rf.4c37c002bb2014efd44707997c9985c8.jpg # 带参数:指定要检查的图片,输出同样保存到当前目录第一次跑的时候不带参数,连抽十几张,观察不同图片的画框结果。重点看三处:裂缝框是否贴合裂缝两端,而不是把大片空白区域包进去;正常样本是不是也画了框;文件名前缀不同的图片,crack和normal的框颜色是否符合CLASS_COLORS定义。这个动作很快,但能暴露大部分数据集质量问题,也是训练前性价比最高的检查手段。
3.3 用可视化结果反向检查标注质量
跑完可视化后,可以按下面这个对照表快速定位问题:
| 可视化现象 | 可能的标注问题 | 建议处理 |
|---|---|---|
| 框明显比裂缝大一圈 | 标注框未收紧,包进了太多砖面纹理 | 用标注工具打开,沿裂缝端点重新画 |
| 同一张裂缝图没显示任何框 | 标签文件与图片文件命名不匹配 | 检查txt是否与jpg同名、是否在同一目录层级 |
| 两张正常样本的颜色标签不一致 | 类别ID映射与classes顺序不一致 | 以classes.txt顺序为准,检查映射代码 |
| 框大量偏移、一半在图外 | 归一化坐标被当成像素坐标直接使用 | 按2.1节的转换公式重新计算 |
这里想多说一句“正常”类别的标注策略。正常样本的框有两种标法:一种是把整块砖面框出来,另一种是干脆不标任何框。这两种策略YOLO都能训练,因为未标注区域会被当作背景负样本;但如果标了整砖,normal类学到的就是“全图框”,检测时告诉你“这块砖没问题”,这类训练在验证时mAP会很高,实际表现却一般。关键不在于哪种策略更好,而在于train和val必须保持同一个标注口径,否则评估指标会非常奇怪——可视化脚本能帮你一眼看出两边的标注口径是否一致。
4. 避坑:瓷砖裂缝数据集实战中最容易翻车的5个坑
4.1 标注与目录结构相关的坑
坑一:图片实际是PNG,文件名后缀却是.jpg。
现象:训练脚本跑起来不报错,但偶尔有图片读取后颜色通道错乱,或者某些按后缀解析的库直接解码失败。
原因:很多数据集在采集时原始文件是jpg,后期经过增强、转码后存成PNG,文件名后缀却没同步改。OpenCV的imread按文件内容探测编码,所以没问题;但PIL的Image.open、部分数据加载库按后缀判断解码方式,就会花屏或报错。
解决:训练前统一跑一遍格式检查,用cv2.imread读入后校验img.shape和img.dtype,或者干脆把所有图片统一转成PNG并同步改名。我一般会写一个三行的遍历脚本,把不满足要求的文件单独列出来,避免训练中途才暴露。
坑二:classes.txt类别顺序被改动,导致标签错配。
现象:训练时loss下降正常,但跑出来的检测结果把裂缝全识别成normal,可视化时颜色也全部反掉。
原因:classes.txt的顺序就是模型输出的ID映射,一旦和训练配置文件里的names不一致,标签与类别名就全错位了。
解决:永远以classes.txt为准。训练前打印一次names,对照确认第一项是crack、第二项是normal,不要用自己手写的类别名单替换。
坑三:标签文件里混入空行、或一行只有class_id没有坐标。
现象:训练时偶尔报错,或者YOLO解析标签时静默跳过该文件,导致某张图的标签缺失,精度莫名下降。
原因:labelImg保存过程中误操作、人为删改、传输损坏都会产生坏标签文件。空行会被continue跳过还好,只有一列或四列的残缺行会让解析器直接翻车。
解决:写脚本遍历所有标签文件,过滤掉空行和字段数少于5的记录,输出坏文件名的清单。人工核对清单后,再考虑是重新标注还是删掉这张图。
4.2 数据增强与数据划分相关的坑
坑四:翻转图片后,标签没有同步翻转。
现象:增强后的训练图里裂缝在左边,但框画在右边,训练时模型学的是“左边裂缝配右边框”的错位映射,收敛慢且检测偏移明显。
原因:很多人只对图片做cv2.flip,忘了对归一化坐标做对应修正。这是数据增强里最常见的翻车点,没有之一。
解决:水平翻转必须执行cx = 1.0 - cx,垂直翻转必须执行cy = 1.0 - cy,2.3节的代码可以直接复用。做完增强后,再跑一遍可视化脚本抽查十几张,确认框和裂缝重合。
坑五:随机划分训练集和验证集,导致评估指标虚高。
现象:验证集mAP轻松到0.95以上,一上实际检测线就漏检严重,黑匣子怎么调都救不回来。
原因:同一批次瓷砖的图像高度相似——光照、纹理、拍摄角度几乎一致。随机划分会把同批相似图片同时放进train和val,模型相当于在考场上见过“相似答案”,验证分数自然漂亮。
解决:按文件名前缀分组划分。这份数据集里N和C开头的图片分别对应不同状态,建议按前缀或者按采集批次切片,而不是随机打散。更稳妥的做法是先跑一遍可视化,把同一块砖的多个视角找出来,成组划分到同一侧。
这类问题不是这份数据集特有,几乎是所有工业缺陷数据集共有的通病。拿到任何带标注的检测资源,第一步都应该是先跑检查脚本,再谈训练。
5. 收尾技巧:训练前强制过一遍可视化,再核对data.yaml两处路径
我拿到这份瓷砖裂缝数据集后的固定流程是两件事。第一件,全量跑一遍可视化脚本,按3.3的对照表抽样检查二十张以上,确认标注口径统一。第二件,写data.yaml时严格核对路径与类别名。
YOLOv5和YOLOv8通用的配置长这样:
path: ./dataset # 数据集根目录 train: images/train # 相对path的训练图片目录 val: images/val # 相对path的验证图片目录 nc: 2 # 类别数 names: ["crack", "normal"] # 与classes.txt保持一致path字段定义根目录,train和val用相对路径。很多训练报错都出在这个配置上——path写错成绝对路径、names和classes.txt顺序不一致、val指向了空目录,可视化脚本能暴露前两类问题,但val目录为空只能靠ls检查。顺手看一眼images/val里的文件数量,确认不是0再启动训练。
训练过程中还有一个小技巧值得养成习惯:看box_loss和cls_loss两条曲线的下降节奏。box_loss在降、cls_loss不动,优先怀疑类别ID错位或类别不平衡;两条都在降但验证集mAP上不去,再回头看数据划分是否泄漏。YOLO的损失函数看起来复杂,但训练日志里最值得盯的就是这两个分量。
从那以后,我每次拿到一份新的检测数据集,都强制自己先跑一遍可视化脚本再碰训练参数,这个习惯帮我躲掉过至少三次因为标签错位、白训几小时的尴尬。希望帮到你。
本文还有配套的精品资源,点击获取