简介:这套瓷砖缺陷检测数据集面向工业质检、智能建造与机器学习研究场景,可供目标检测算法工程师、质量控制人员及从事表面缺陷研究的开发者使用。数据集覆盖边缘崩裂、破洞、裂缝等典型瓷砖缺陷,采用COCO JSON格式标注,包含清晰的类别与边界框信息,可直接用于YOLO等主流检测模型的训练与评估。压缩包共有2000个文件,其中1997张为JPG原始图像,3个为JSON标注文件,整体大小约579MB;图像均按真实拍摄场景命名,便于后续按批次或来源筛选。目前已有646人学习/下载。在实际应用中,该数据集既能辅助工厂产线自动识别缺陷瓷砖、降低废品率,也可用于零售商来货分拣、装修前质量评估和建筑竣工检测,还能作为算法团队进行模型调参、对比实验与模型迭代的基础数据,具备较强的工程落地价值。
1. 拿到7992张COCO瓷砖缺陷图:边缘崩裂、破洞、裂缝能直接训练吗
在陶瓷厂质检场景里,瓷砖缺陷检测数据集是最容易被低估的一环。你拿到手的是一个已经用COCO JSON格式标注好的数据集,一共7992张原始图,能识别边缘崩裂、破洞、裂缝三类缺陷。这意味着你不需要从零开始标注,也不用为“标注格式不统一”头痛,数据可以直接喂给目标检测模型训练。关键是看这个COCO标注到底规不规范、三类缺陷有没有被区分清楚、类别分布是不是偏得离谱。这套数据适合正在做工业视觉落地的算法工程师、质检设备集成商,以及需要快速产出检测原型的开发者。COCO JSON只是第一步,真正决定模型能不能上线的是后续的数据体检、格式转换和边界处理。
2. 读懂COCO JSON标注:三类缺陷为什么这样标、先做数据体检
2.1 边缘崩裂、破洞、裂缝的标注形态差异与COCO多边形语义
COCO JSON是目标检测和实例分割领域最常用的标注格式之一,原因不是它“洋气”,而是它的结构足够收敛:整个数据集只需一个JSON文件,里面包含images、annotations、categories三个核心字段,缩略图路径、目标框坐标、多边形分割点都挂在这三个字段下。和VOC XML一大堆文件、YOLO TXT一行一个目标相比,COCO用一整个JSON文件管理全部标注,在分发、读取、校验上都有明显优势。
瓷砖缺陷有一个特点:边缘崩裂、破洞、裂缝在形态上差别很大。边缘崩裂一般发生在瓷砖边缘,是不规则的长条或锯齿状破损;破洞是相对封闭的近似圆形或椭圆形区域;裂缝则是细长的折线段,可能贯穿整块砖。不同的形态对应不同的标注策略:破洞适合用封闭多边形去圈,裂缝如果用矩形框去框,框会特别大而目标实际面积特别小,这会让模型在训练时学到大量背景。COCO annotations里的segmentation字段正好能表达多边形,所以拿到这个数据集时,不要只看bbox,还要把segmentation的坐标点数、是否闭合、是否跨图片边界一起查一遍。
2.2 用Python解析annotations:加载、类别映射、统计数据,跑通最小体检脚本
拿到COCO JSON后的第一个动作不是训练,而是体检。我一般会写一个最短的解析脚本,把图片数量和标注数量对齐检查一遍,顺带看看这三类缺陷各自有多少目标。很多时候一个看着没问题的JSON,读出来才发现有图片没标注、有标注对不上图、类别ID错乱,这些问题在训练前发现能省好几天排错时间。
import json from collections import Counter, defaultdict coco_path = "tile_defect/annotations/instances_train.json" with open(coco_path, "r", encoding="utf-8") as f: coco = json.load(f) # images与annotations的id对齐检查 img_ids = {img["id"] for img in coco["images"]} ann_img_ids = {ann["image_id"] for ann in coco["annotations"]} missing = img_ids - ann_img_ids print("有图无标注:", len(missing)) # 类别映射 cat_map = {cat["id"]: cat["name"] for cat in coco["categories"]} print("类别ID->名称:", cat_map) # 每个类别的目标数量 cat_counter = Counter(ann["category_id"] for ann in coco["annotations"]) print("各类别目标统计:", {cat_map[k]: v for k, v in cat_counter.items()}) # 每张图的目标数分布 per_img_count = Counter() for ann in coco["annotations"]: per_img_count[ann["image_id"]] += 1 img_nums = [per_img_count[i] for i in img_ids] print("平均每图目标数:", sum(img_nums) / len(img_ids)) # 异常框检查:右下角坐标不得小于左上角 bad = 0 for ann in coco["annotations"]: x, y, w, h = ann["bbox"] if w <= 0 or h <= 0 or x < 0 or y < 0: bad += 1 print("异常框数量:", bad)这段脚本做了四件事。第一,把images里的图片ID和annotations里的image_id做一个差集,找出哪些图没被标注,防止后面转换时出现“某张图没有对应标签”。第二,把categories里的数字ID映射成中文名,避免你拿着标注文件却不知道哪个ID代表裂缝。第三,统计每类目标数量,直接看出类别是否均衡,如果边缘崩裂有1万框而裂缝只有800框,训练权重就要调整。第四,粗查bbox是否出现负坐标或宽高为0,这类异常是数据转换期最容易爆炸的定时炸弹。
这里有一个细节:这段脚本只做“粗查”,不会告诉你segmentation是否闭合、裂缝多边形是否过长,那些需要可视化抽样确认。一次跑完脚本后,如果平均每张图目标数在1到4之间,异常框数量为0,这个数据集就可以进入下一步了。如果异常框数量很多,先不要急着转换,去找原始标注软件确认坐标体系,瓷砖图像一旦经过裁剪或旋转,坐标经常整批偏移。
2.3 从体检结果判断数据集能不能用:面积分布与类别平衡表
体检不能只看数量,还要看目标面积分布。裂缝这类缺陷在7992张图里如果普遍都很小,那么后续训练必须考虑小目标检测的配置,比如在YOLO里调小anchor、增大输入分辨率、或者用P2层特征。破洞如果面积中位数很大,那模型会相对好收敛,但容易过拟合到“深色圆形区域”这种浅层特征上。边缘崩裂如果往往贴着图像边缘,转换时还要考虑坐标裁剪。
我一般会把体检结果落成一张表,作为训练前的验收记录。 | 检查项 | 判断标准 | 不合格时的处理方式 | | --- | --- | --- | | 有图无标注数量 | 0 | 联系数据方补标,或删除无标注图 | | 类别目标数占比 | 最少类不低于最多类的20% | 做类别重采样或增加loss权重 | | 异常框数量 | 0 | 修正annotations里bbox字段 | | bbox面积中位数 | 每类分别统计,最好超过全图面积1% | 增大输入分辨率或调整anchor | | 宽高比大于10的框占比 | 裂缝类允许偏高,其他类应低于5% | 回查segmentation是否被错误整包 | 这张表的价值在于让你明确“这个数据集能不能直接用”。如果边缘崩裂和破洞的标注数量均衡,但裂缝只标了零星几百个,那后面训练出来的模型对裂缝几乎等于瞎猜。相反,如果7982张图里只有几十张有异常,那这类问题属于可修复噪声,直接进入转换流程就好。
3. 把7992张COCO数据转成YOLO训练集:切分、归一化与脚本落地
3.1 为什么要从COCO转YOLO:训练框架更普及、锚框与归一化要求
现实中的落地项目更多人直接顺着“yolov8训练自己的数据集”这条路走。YOLO系列的训练接口统一、部署生态完善,从训练到TensorRT导出都是一条链路,所以即使COCO JSON是更完整的标注格式,最终训练前还是要把COCO转换成YOLO的TXT标签格式。
YOLO标签没有JSON那样的分层结构,每一个标注目标是一行纯文本,格式是“类别ID 中心点x 中心点y 宽度w 高度h”,坐标经过归一化,全部介于0到1之间。这个极简格式的好处是读取速度快、训练时不依赖额外标注引擎,坏处是丢了segmentation多边形信息,只剩矩形框。做瓷砖缺陷检测通常用矩形框就够了,边缘崩裂、破洞、裂缝这些缺陷用框来定位能覆盖绝大多数质检需求,所以这种转换不是降级,而是实用主义。
COCO的bbox是左上角坐标加宽高,YOLO需要的是中心点坐标加宽高,同时要除以图片尺寸。这里最容易翻车的不是公式,而是图片尺寸读错。有些标注者给的宽高是标注时用的尺寸,图像文件本身可能是旋转后的尺寸,两者不一致时转换出来的坐标全是错的。所以脚本里必须从图像文件本身读取宽高,不能用JSON里images字段的宽高直接当唯一依据,至少要交叉验证一遍。
3.2 转换脚本:COCO转YOLO坐标归一化与train/val切分
我自己在项目里用的转换脚本,逻辑很直白:读JSON,按图片ID分组,遍历每一张图的标注,把坐标从像素值转成归一化值,写到与图片同名的TXT文件里,同时按预设比例划分训练集和验证集。切分要按图片切,不能按目标切,否则同一张图的标注会被拆到两个集合里,造成数据泄漏。
import json import cv2 from pathlib import Path coco_path = "tile_defect/annotations/instances_train.json" image_dir = Path("tile_defect/images") output_dir = Path("tile_defect/yolo_dataset") val_ratio = 0.15 seed = 42 # 类别ID重新映射为0,1,2,顺序可自定义 class_map = {1: 0, 2: 1, 3: 2} # 原始COCO的1,2,3 -> 边缘崩裂,破洞,裂缝 with open(coco_path, "r", encoding="utf-8") as f: coco = json.load(f) img_id2info = {img["id"]: img for img in coco["images"]} anns_by_img = defaultdict(list) for ann in coco["annotations"]: anns_by_img[ann["image_id"]].append(ann) all_img_ids = list(img_id2info.keys()) rng = random.Random(seed) rng.shuffle(all_img_ids) val_count = int(len(all_img_ids) * val_ratio) val_ids = set(all_img_ids[:val_count]) # 图片文件后缀可能是.jpg/.png/.JPG,按实际文件名匹配 for img_id in all_img_ids: img_info = img_id2info[img_id] img_path = image_dir / img_info["file_name"] im = cv2.imread(str(img_path)) if im is None: print("跳过读不到的图:", img_path) continue H, W = im.shape[:2] split = "val" if img_id in val_ids else "train" label_dir = output_dir / split / "labels" image_out_dir = output_dir / split / "images" label_dir.mkdir(parents=True, exist_ok=True) image_out_dir.mkdir(parents=True, exist_ok=True) txt_lines = [] for ann in anns_by_img[img_id]: cat_id = ann["category_id"] if cat_id not in class_map: continue x, y, w, h = ann["bbox"] # 越界框先clip,后转归一化 x1 = max(0, x) y1 = max(0, y) x2 = min(W, x + w) y2 = min(H, y + h) if x2 - x1 <= 1 or y2 - y1 <= 1: continue cx = (x1 + x2) / 2 / W cy = (y1 + y2) / 2 / H nw = (x2 - x1) / W nh = (y2 - y1) / H txt_lines.append(f"{class_map[cat_id]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") if txt_lines: label_path = label_dir / (img_path.stem + ".txt") label_path.write_text("\n".join(txt_lines), encoding="utf-8") # 拷贝图片到对应split目录,保持文件命名一致 cv2.imwrite(str(image_out_dir / img_path.name), im) print("转换完成")这个脚本里比较关键的有几个地方。class_map变量把COCO原始的类别ID映射成0、1、2,因为在YOLO训练里类别ID必须连续,否则模型输出的类别数会和你的分类头对不上。坐标clip的逻辑我专门放在转换前,边缘崩裂这类目标天然贴近图像边界,标注时经常出现x为负或x+w超过图片宽度的现象,如果不强制把越界坐标拉回图像范围,归一化后的中心点和宽高就会超过1,训练时Grid分配会直接把这种目标归到背景上。凡是裁剪后宽或高不足1个像素的目标,直接丢弃,这类目标在图像上肉眼几乎看不清,留着只会增加训练噪声。
还有一个分布细节:验证集是按图片ISO分配,但如果你发现某类缺陷只集中在某几张大图上,这种随机切分会让验证集缺失这一类。更保险的做法是按“每张图是否包含裂缝”做分层,保证val里一定能见到裂缝样本,代价是代码多写十几行。如果你的任务是上线质检,建议不要省这一步。
3.3 切分策略:按图不按框,避免同图数据泄漏
很多第一次接触COCO数据集的工程师会犯一个错:用了sklearn的train_test_split,却直接拿annotations数组去切。这样同一张图的一部分标注在训练集,另一部分在验证集,模型训练时变相看到了验证答案,最终mAP虚高,上线就现原形。正确的做法始终是以image_id为最小切分单位。
对于7992张图这个规模,我给的默认配比是85%训练、15%验证。如果打算做模型选型对比,再从训练集里切一部分做测试集,比例可以是70%训练、15%验证、15%测试。切分时用固定随机种子,保证每次复现实验的结果一致。更严格的工程流程还会生成一个split.json,把每张图的归属记录下来,这样后续跑数据增强或换模型时,不用重新随机切一遍。
此外,切分之后一定要生成一个class_counts.txt,分别统计train和val里每类目标的总数。我遇到过数据切完后才发现裂缝目标数在验证集里只有个位数,这种情况下的评估指标完全没有统计意义,等于白训。
3.4 可视化校验:把YOLO格式画回原图,确认对齐
转换脚本跑完后,第一件事不是急着开训练,而是随机抽50张图,把TXT里的坐标画回原图上看一眼。这一步能发现坐标缩放错误、图片旋转方向不一致、类别ID错位等各种转换问题。我一般用OpenCV写一个极短的检查脚本,直接把标注框叠加在原图上输出到check目录。
import cv2 from pathlib import Path label_dir = Path("tile_defect/yolo_dataset/train/labels") image_dir = Path("tile_defect/yolo_dataset/train/images") out_dir = Path("tile_defect/check") out_dir.mkdir(exist_ok=True) class_colors = {0: (0, 255, 0), 1: (0, 255, 255), 2: (0, 0, 255)} class_names = {0: "chip", 1: "hole", 2: "crack"} for label_path in list(label_dir.glob("*.txt"))[:50]: img_path = image_dir / (label_path.stem + ".jpg") im = cv2.imread(str(img_path)) if im is None: continue H, W = im.shape[:2] for line in label_path.read_text(encoding="utf-8").strip().splitlines(): parts = line.strip().split() if len(parts) != 5: continue cid, cx, cy, nw, nh = map(float, parts) x1 = int((cx - nw / 2) * W) y1 = int((cy - nh / 2) * H) x2 = int((cx + nw / 2) * W) y2 = int((cy + nh / 2) * H) cv2.rectangle(im, (x1, y1), (x2, y2), class_colors[int(cid)], 2) cv2.putText(im, class_names[int(cid)], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, class_colors[int(cid)], 2) cv2.imwrite(str(out_dir / label_path.stem + ".jpg"), im) print("可视化图已输出到 check 目录")这个可视化代码本身不难,难的是“看什么”。我会重点看三类问题。第一,裂缝目标的框是不是紧紧包住裂缝线,如果框内有大面积瓷砖背景,说明标注框打宽了;第二,边缘崩裂的框是否压到图片外,如果框的边界正好和图像边界重合,没问题,但如果框明显超出且被clip,回原图核对是否漏掉了一部分裂纹;第三,同一位置是否出现两个颜色框重叠,这是常见重复标注。宽度小于2像素的框在压缩后的可视化图里可能看不清,所以抽样时要优先抽那些包含裂缝的图,源码里遍历顺序是按标签名排的,效率不高,但用于抽检足够。
4. 数据转换与标注五个高频坑:现象、原因、解决办法
4.1 裂缝目标整批被跳过:转换后训练警告“empty label”
现象:转换时没报错,但开始用YOLO训练后,日志里频繁出现“WARNING: empty label”或某个epoch的loss直接是0,一看统计,裂缝类一个目标都没进训练集。
原因:裂缝是细长目标,很多人工标注框的原始高度只有3到5个像素。转成YOLO格式后,归一化高度是5除以图片高度,比如5/1024约等于0.0049。如果之后训练预处理把图片缩放到640x640,归一化坐标还是按原始图算,但网络层下采样后裂缝的框高度可能连1个特征图网格都占不到。更直接的原因是转换脚本里“若裁剪后宽高小于1像素则跳过”这个过滤条件把裂缝全滤掉了。
解决:过滤条件不能一刀切。裂缝类要单独放宽阈值,最小保留宽度或高度可以放到0.5像素。你也可以不按像素过滤,而是按归一化后的宽高过滤,例如保留nw大于0.001或nh大于0.001的目标,小于这个值的才丢弃。与此同时,训练时把输入分辨率从640提到960或1280,细裂缝在放大后的图像上就不再是亚像素目标。
4.2 边缘崩裂的框越界:训练时出现nan坐标或loss异常
现象:可视化检查时发现,边缘崩裂的标框有一部分画在图像外面。转换后某些标签行里的cx或cy大于1,训练时输出坐标出现nan。
原因:标注工具里允许框超出画布边界,有些标注员为了快速框住边缘缺陷,会把框的起始点拖到画布外。COCO JSON里这类坐标被原样保存,直接转成YOLO格式时不做裁剪,就会产生非法坐标。
解决:在转换脚本里对每个bbox做一次clip,这一步一定不能省。先取x1=max(0,x),y1=max(0,y),x2=min(W,x+w),y2=min(H,y+h),再参与后续归一化计算。要注意,如果clip后的宽度占原宽度的比例过小,比如原框是200像素宽,clip后只剩5像素,说明这个目标本身标注就有问题,最好回原图核对,而不是无脑保留。这种越界情况在边缘崩裂类别里出现概率最高,所以转换脚本的日志里最好单独统计每一类被clip了多少个框,超过总框数10%就要回头找标注方统一修。
4.3 同一破洞被重复标注:模型训练后误检率虚高
现象:可视化时看到同一个破洞位置被两个几乎重合的框覆盖,颜色不同,但类别相同。训练结束后,测试集mAP很高,但现场一跑,同一缺陷在结果里被输出两个检测框,NMS也压不掉,因为两个框的置信度都超过阈值且中心距离较远时NMS不认为是重复框。
原因:数据集中破洞类别存在重复标注。这种情况在多人协同标注时最常见,两个人先后标注了同一张图,标注平台合并时没有去重。加上这个数据集的类别ID在原始JSON里可能没有严格规范,同一个目标被标了两次但category_id不同。
解决:写一个去重脚本,按image_id分组,对组内的目标bbox两两计算IoU,IoU大于0.7且类别相同的,保留高置信度文件里靠前的那一个,或者保留面积更大的那一个。去重之后重新跑一遍数据体检,看目标总数下降了多少。如果重复率超过5%,说明标注流程本身有疏漏,建议对全部训练结果做一次置信度分布检查,凡是ground truth没覆盖但模型频繁输出的位置,去原图里确认是不是未修复的重复标注。
4.4 images与annotations数量对不上:训练集被莫名截断
现象:数据体检脚本打印“有图无标注: 98”,转换后YOLO目录里train/images有6000张图,train/labels只有5950个txt文件。
原因:COCO JSON里部分图片没有任何标注,可能本身是负样本,也可能是标注后被删掉了还没清除image记录。YOLO格式没有“空标签”概念,一张图没有txt会被多数训练框架跳过。
解决:负样本如果是有意保留的,转换脚本里要为它们生成空txt文件,并在训练配置里把“drop empty”关掉。如果负样本是误产生的,直接把这些图片从数据集里过滤掉,比保留更省心。实际操作时最好同时导出两份清单:一份是有标注图片清单,一份是无标注图片清单,人工扫一眼无标注图片,确认它们到底是“干净的瓷砖”还是“标注遗漏”。我遇到过所谓无标注图片里肉眼能看见大面积裂缝的情况,那属于标注事故,这种图必须联系数据方补标,否则模型会学着把裂缝当背景。诊断时可以借助jq快速筛出多余的id,比如jq '[.images[].id] - [.annotations[].image_id] | unique' instances_train.json,先确认差异数量再写代码处理。
4.5 中文路径与编码问题:读取图片返回None
现象:在Windows上跑转换脚本,cv2.imread报错或者返回None,但在文件管理器里图片明明存在。换成Linux服务器后,部分图片读取依旧失败。
原因:COCO JSON里的file_name可能带中文,或图片路径含中文目录名。Windows下OpenCV的imread函数对非ASCII路径支持很差,返回None是常态。另一个来源是JSON文件编码不是UTF-8,json.load直接抛UnicodeDecodeError。
解决:读取图片使用pathlib构造路径,并考虑用cv2.imdecode代替cv2.imread:先用read_bytes读取文件,再用np.frombuffer和cv2.imdecode解码,这样可以绕开文件名编码问题。JSON文件统一用open(path, 'r', encoding='utf-8')打开,不要用系统默认编码。更进一步,数据集分发前最好把中文文件名批量改成英文或纯数字ID,这一步在数据清洗阶段做一次,之后所有脚本都少踩一类坑。此外有些图片后缀是.JPG或.jpeg,但JSON里写的是.jpg,导致路径拼接后找不到文件,转换脚本里要兼容这类后缀不一致问题。
5. 用COCO数据训练并验证检测模型:可视化、评估与补标注
5.1 先跑通“能检测”的最小闭环:YOLOv8与mmdetection两种路线
数据集经过体检、转换、可视化校验后,就可以进入训练环节。常见做法是先用YOLOv8把最小闭环跑通:yolo train data=tile_defect.yaml model=yolov8s.pt epochs=100 imgsz=960。这里的tile_defect.yaml是指向第3章生成的train和val目录的配置文件,类别名按顺序写成chip、hole、crack即可。先别急着上大模型,yolov8s在这个量级的数据集上足够验证标注质量;如果验证集的mAP在训练刚开始30个epoch内一直低于0.1,先回头看数据,不要加模型复杂度。
mmdetection路线适合需要精确控制anchor、想用Cascade R-CNN或Mask R-CNN输出分割轮廓的读者。COCO JSON本来就是mmdet的默认标注格式,改一下data_root和classes配置就可以训练。两种路线不冲突,我通常用YOLOv8做快速反馈,用mmdet做最终精度验证,特别是在缺陷形态有强旋转或细长特征时,Cascade R-CNN的回归稳定性比单阶段模型更可控。
5.2 按缺陷形态看评估:裂缝类不要迷信mAP
训练结束后不要只看一个整体mAP,要把三个类别拆开看。边缘崩裂通常面积较大,AP@0.5能达到0.9以上才算合格;破洞是相对规整的目标,重点看AP@0.75,因为高IoU下的精度才反映边界贴合程度;裂缝最特殊,宽高比极大,通用mAP会把这种目标算进“小目标”或“中目标”统计里,数值偏低是必然的,这时候要单独看验证集里裂缝的召回率,以及预测框中心点到裂缝线的距离误差,而不是mAP绝对值。
我习惯把裂缝的IoU阈值单独放宽到0.3到0.5进行验证。裂缝线的像素面积太小,IoU超过0.5几乎不可能,用0.5作为唯一门槛会得出“模型检测不了裂缝”的错误结论。实际质检中裂缝能被定位到附近几像素就算成功,所以业务指标和学术指标要分开定义。
5.3 误检回注与主动学习:把错误样本送回CVAT复标
训练完成后最容易被忽略的一步,是把现场误检样本回注成新的标注数据。7992张原始图听着不少,但在真实产线中,光照、釉面颜色、砖型变化都会让分布漂移,数据集迟早不够用。做法是把训练后的模型对一批无标签产线图做预测,筛选出置信度在0.3到0.6之间的模糊样本,导出成COCO JSON,送到CVAT或LabelImg里人工复标。这类样本比从零标数据高效得多。
如果以后想继续在这个方向上投入,建议预留至少20%的图片不参与第一轮训练,专门当作测试集和主动学习的种子池。我在多个工业检测项目里反复踩过的坑是:数据一多就全扔进训练,导致最终没有一张“干净”的测试集来评估真实性能。把一部分数据锁起来,等模型迭代到某个阶段再打开,你的评估结果才真正反映上线效果。这条习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取