简介:布匹缺陷数据集面向纺织工业质检与计算机视觉方向的学习者和开发者,用于训练和评估布匹表面缺陷的自动检测与分类模型。压缩包共934个文件,以689张jpg缺陷图像和245个xml标注文件为主,图像记录孔洞、色差、污渍、线条、起球、皱褶、接缝不良等典型缺陷,xml则提供对应的目标框与类别标注,整体约927.33MB,可直接用于目标检测或分类任务。目前已有858人学习下载。数据集按训练、验证、测试划分,便于完成数据预处理、特征提取、迁移学习与模型评估的完整流程;借助标注文件,读者能快速搭建缺陷检测基线,实践VGG、ResNet等预训练模型的迁移学习,并结合翻转、旋转等数据增强提升泛化能力,为工业质检场景提供可复用的实验素材。
1. 布匹缺陷数据集:从“找得到”到“训得动”的那道坎
做布匹瑕疵检测的工程师大概都有过这种经历:算法选型讨论了两周,骨干网络换了三版,结果卡在第一步——手里没有一份标注干净、类别对齐、能直接喂给 DataLoader 的图。公开的布匹缺陷数据集要么类别定义和产线对不上,要么标注格式是 Pascal VOC 而你的训练脚本吃的是 YOLO txt,要么样本极度不均衡,断经几百张、结头只有个位数。这份dataset.rar就是冲着这个痛点来的:它把布匹表面常见的几类缺陷按目录分好,图像和标注放在一起,解压后改一个路径就能跑通训练。适合两类人:一是刚接手织物瑕疵检测项目、需要快速搭出 baseline 的算法同学;二是想把现有模型换到真实布匹数据上验证泛化性的老手。下面我按“先看清结构、再跑通训练、最后避开标注坑”的顺序拆一遍。
2. 拆包先看结构:目录、类别与标注格式怎么对齐
拿到一个压缩包,最忌讳的就是直接unzip然后train.py --data ./dataset一把梭。布匹缺陷这类工业数据集,目录层级和标注格式往往决定了你后面要不要写转换脚本。先花十分钟把结构摸清楚,能省掉后面半天的 debug。
2.1 解压后的典型目录布局
工业缺陷数据集常见的组织方式有两种:一种是按类别分文件夹(defect_type/xxx.jpg),另一种是图像统一放images/、标注统一放labels/。布匹数据集因为缺陷类别不多(通常 5 到 10 类),两种都有人用。解压后先跑一遍目录树,确认属于哪种:
# 解压到独立目录,避免污染当前工作区 mkdir -p ./fabric_defect && tar -xf dataset.rar -C ./fabric_defect # 只看两层目录,快速判断组织方式 find ./fabric_defect -maxdepth 2 -type d | sort # 统计图像数量与格式分布 find ./fabric_defect -type f \( -iname "*.jpg" -o -iname "*.png" -o -iname "*.bmp" \) | \ sed 's/.*\.//' | tr 'A-Z' 'a-z' | sort | uniq -c第一段命令把压缩包解到独立目录,避免和已有数据混在一起;第二段用-maxdepth 2只列两层,能一眼看出是“类别文件夹”还是“images/labels”结构;第三段统计图像扩展名,布匹图像常见.bmp和.jpg混用,如果混用,后面读图时要用cv2.imread统一处理,别假设全是 jpg。
提示:如果解压报错提示编码问题,多半是压缩包内文件名含中文,用
unzip -O GBK或先转码再解,别硬解出一堆乱码文件名。
2.2 类别清单与标注格式判定
结构看清后,第二步是确认类别名和标注格式。布匹缺陷的类别命名在不同数据集里差异很大,有的用英文(hole、stain、broken_end),有的用拼音或编号。类别名直接决定你data.yaml里names怎么写,写错了训练不报错但 mAP 会莫名其妙地低。
import os, glob, json root = "./fabric_defect" # 收集所有标注文件,判断格式 xml_files = glob.glob(os.path.join(root, "**", "*.xml"), recursive=True) txt_files = glob.glob(os.path.join(root, "**", "*.txt"), recursive=True) json_files = glob.glob(os.path.join(root, "**", "*.json"), recursive=True) print(f"XML(VOC): {len(xml_files)}, TXT(YOLO): {len(txt_files)}, JSON(COCO): {len(json_files)}") # 若是 YOLO txt,读第一行看类别索引范围 if txt_files: with open(txt_files[0]) as f: first = f.readline().strip() print("首行标注:", first) # 格式: class_id cx cy w h (归一化)这段脚本先按扩展名把标注文件分三类,数量最多的那种基本就是主格式。如果是.txt,读第一行确认是 YOLO 的归一化格式(5 个值,第一个是类别索引);如果是.xml,那是 Pascal VOC,需要转 YOLO;如果是.json,大概率是 COCO,用pycocotools读。布匹数据集里 VOC 和 YOLO 两种最常见,转换脚本后面会讲。
2.3 样本分布先摸底再谈训练
类别不均衡是布匹缺陷的常态。断经、破洞这类明显缺陷样本多,结头、跳纱这类细微缺陷样本少。不先统计就开训,模型会偏向多数类,少数类召回率低得没法看。
from collections import Counter import glob, os root = "./fabric_defect" counter = Counter() for txt in glob.glob(os.path.join(root, "**", "labels", "*.txt"), recursive=True): with open(txt) as f: for line in f: cid = line.split()[0] counter[cid] += 1 for cid, n in sorted(counter.items(), key=lambda x: -x[1]): print(f"class {cid}: {n} instances")统计的是“实例数”而不是“图像数”,因为一张图可能含多个缺陷。如果某个类别实例数低于总实例数的 5%,训练时要么加重采样,要么在 loss 里给类别权重,否则这个类基本学不出来。这一步的统计结果直接决定你后面data.yaml里要不要加augment和cls_pw之类的参数。
3. 从 VOC 到 YOLO:标注转换脚本与坐标校验
布匹数据集如果标注是 VOC 格式,而你想用 YOLO 系列训练,中间必须做一次格式转换。转换本身不难,难的是坐标归一化和类别映射不出错。我见过太多人转完直接训,结果框全偏了,还以为是模型问题。
3.1 VOC 转 YOLO 的完整脚本
VOC 的框是绝对像素坐标(xmin, ymin, xmax, ymax),YOLO 要的是归一化的中心点加宽高(cx, cy, w, h),且都除以图像宽高。转换时最容易翻车的是图像尺寸读错——VOC 的size字段有时和实际图像不一致,必须以实际读到的尺寸为准。
import os, glob import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_dir, out_dir, class_map): tree = ET.parse(xml_path) root = tree.getroot() # 以实际图像尺寸为准,不信任 xml 里的 size img_name = root.find("filename").text img_path = os.path.join(img_dir, img_name) with Image.open(img_path) as im: W, H = im.size lines = [] for obj in root.findall("object"): name = obj.find("name").text.strip() if name not in class_map: continue # 未登记的类别直接跳过,避免索引错位 cid = class_map[name] bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 裁剪到图像边界,防止越界框 xmin, xmax = max(0, xmin), min(W, xmax) ymin, ymax = max(0, ymin), min(H, ymax) cx = (xmin + xmax) / 2 / W cy = (ymin + ymax) / 2 / H w = (xmax - xmin) / W h = (ymax - ymin) / H lines.append(f"{cid} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] + ".txt") with open(out_path, "w") as f: f.write("\n".join(lines)) class_map = {"hole": 0, "stain": 1, "broken_end": 2, "knot": 3} for xml in glob.glob("./fabric_defect/**/*.xml", recursive=True): voc_to_yolo(xml, "./fabric_defect/images", "./fabric_defect/labels", class_map)脚本里三个关键点:一是class_map必须和data.yaml的names顺序完全一致,差一位整个类别就错位;二是坐标裁剪到[0, W]和[0, H],VOC 里偶尔有标注框超出图像边界的脏数据,不裁会导致归一化后出现负值或大于 1 的值,训练时 loss 直接 NaN;三是保留 6 位小数,精度够用且文件不会太大。
3.2 转换后的坐标校验
转完不校验等于没转。写个反向检查脚本,把 YOLO 坐标还原成像素框,和原 VOC 对比,偏差超过 1 像素就说明转换有问题。
def check_one(xml_path, txt_path, img_dir): import xml.etree.ElementTree as ET from PIL import Image root = ET.parse(xml_path).getroot() img_name = root.find("filename").text with Image.open(os.path.join(img_dir, img_name)) as im: W, H = im.size voc_boxes = [] for obj in root.findall("object"): b = obj.find("bndbox") voc_boxes.append((float(b.find("xmin").text), float(b.find("ymin").text), float(b.find("xmax").text), float(b.find("ymax").text))) with open(txt_path) as f: yolo_lines = [l.split() for l in f if l.strip()] for (xmin, ymin, xmax, ymax), y in zip(voc_boxes, yolo_lines): cx, cy, w, h = map(float, y[1:]) rxmin = (cx - w/2) * W rymin = (cy - h/2) * H rxmax = (cx + w/2) * W rymax = (cy + h/2) * H err = max(abs(rxmin-xmin), abs(rymin-ymin), abs(rxmax-xmax), abs(rymax-ymax)) assert err < 1.0, f"坐标偏差过大: {err:.2f}px in {xml_path}"这个校验函数对每张图逐框比对,误差超过 1 像素就抛异常。实际跑一遍,如果大量报错,八成是class_map顺序或图像尺寸读错了。校验通过再进训练,心里才有底。
3.3 生成 data.yaml 与路径约定
YOLO 训练靠data.yaml找数据,路径写相对还是绝对、names顺序对不对,直接决定能不能跑起来。
# data.yaml path: ./fabric_defect train: images/train val: images/val names: 0: hole 1: stain 2: broken_end 3: knotpath是数据集根目录,train和val是相对path的子路径。如果解压后没有划分 train/val,需要自己按 8:2 切分,切分时注意同一张原图增强出的样本不能跨集,否则验证集精度虚高。names的索引必须和转换脚本里的class_map一一对应,这是最容易埋雷的地方。
4. 训练与验证:参数怎么设、指标怎么看
数据准备好之后,训练本身反而是最标准的一步。但布匹缺陷有几个特殊性:缺陷目标小、背景纹理复杂、类别不均衡,参数不能照搬 COCO 那套。
4.1 训练命令与关键参数
以 YOLOv8 为例,一条能跑通的命令长这样:
yolo detect train \ data=./data.yaml \ model=yolov8s.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ augment=True \ mosaic=1.0 \ degrees=10.0 \ translate=0.1 \ scale=0.5逐个说:imgsz=640是布匹缺陷的常用输入尺寸,再小细节丢失严重,再大显存吃不消;lr0=0.01是 SGD 的初始学习率,如果用 AdamW 要降到 1e-3;patience=20表示 20 轮没提升就早停,布匹数据量通常不大,早停能防过拟合;mosaic=1.0开启马赛克增强,对小目标检测帮助明显;degrees=10.0做小角度旋转增强,布匹在产线上本来就有轻微倾斜,这个增强符合实际;scale=0.5允许 0.5 到 1.5 倍缩放,模拟不同拍摄距离。
注意:如果某个缺陷类别实例数特别少,把
mosaic开到 1.0 反而可能让少数类被淹没,可以降到 0.5 并配合copy_paste增强。
4.2 训练日志里该盯哪几个数
训练跑起来后,控制台会刷一堆指标,别只看 mAP。布匹缺陷要重点盯三个:box_loss是否稳定下降、cls_loss是否震荡、每个类别的mAP50是否均衡。
# 训练结束后用验证集单独跑一遍,输出每类指标 yolo detect val \ model=runs/detect/train/weights/best.pt \ data=./data.yaml \ imgsz=640 \ conf=0.25 \ iou=0.5conf=0.25是置信度阈值,布匹缺陷建议从 0.25 起调,太低误检多,太高漏检多;iou=0.5是 NMS 的 IoU 阈值,布匹缺陷框通常不密集,0.5 够用。验证输出里如果某一类mAP50明显低于其他类,回到第 2 章的样本统计,看是不是该类实例太少,考虑加采样或调类别权重。
4.3 推理与可视化检查
指标好看不代表实际能用。拿几张验证集外的图跑推理,把框画出来肉眼检查,这是最直接的验证。
from ultralytics import YOLO import cv2 model = YOLO("runs/detect/train/weights/best.pt") results = model.predict("./test_fabric.jpg", conf=0.25, imgsz=640) for r in results: for box in r.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) cls = int(box.cls[0]) cv2.rectangle(r.orig_img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(r.orig_img, str(cls), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite("pred_vis.jpg", r.orig_img)这段代码把预测框和类别画到原图上存成文件。重点看两类问题:一是框是否贴合缺陷边缘,偏大偏小都说明回归没学好;二是背景纹理是否被误检成缺陷,布匹的织纹和光照不均很容易触发误检,如果误检多,回去加负样本或调conf。
5. 避坑与排查:布匹缺陷数据集最容易翻车的五件事
这一章是我自己踩过的坑,按“现象 → 原因 → 解决”写,每条都对应真实场景。
现象一:训练 loss 正常下降,但验证 mAP 始终在 0.1 以下。原因:data.yaml里names的顺序和标注文件里的类别索引不一致,模型学的是错位的类别。解决:用第 3.2 节的校验脚本反向核对,或者直接打印一张图的标注和names对照,确认索引对得上。
现象二:训练中途 loss 突然变成 NaN。原因:标注里有越界框或宽高为 0 的框,归一化后出现负值或除零。解决:在转换脚本里加坐标裁剪(第 3.1 节已含),并过滤掉w或h小于 1e-6 的框。
现象三:模型在验证集上表现很好,换一批产线图就崩。原因:数据集里的图像可能来自同一批次、同一光照条件,模型过拟合了采集环境。解决:训练时加强颜色抖动和亮度对比度增强,验证集要留出不同批次的图,别随机切分。
现象四:某个缺陷类别完全检测不出来。原因:该类实例数太少,或者该类缺陷在图像里尺寸极小,被下采样丢掉了。解决:先统计实例数,少于 50 个的类别考虑过采样;小目标多的话把imgsz提到 800 或改用带 P2 层的模型结构。
现象五:推理时同一张图重复出框。原因:NMS 的iou阈值设太高,重叠框没被抑制。解决:把验证和推理的iou从 0.5 降到 0.3 到 0.4 之间试,布匹缺陷框通常不密集,低一点没关系。
6. 进阶技巧:用切片推理救回小缺陷
布匹缺陷里最头疼的是小目标——跳纱、结头这类缺陷在原图里可能只占几十个像素,直接缩到 640 训练,特征早就没了。我现在的习惯是:训练用 640 保证速度,推理时用切片(SAHI)把大图切成带重叠的小块分别检测再合并,小缺陷召回率能提一截。
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.25, device="cuda:0" ) result = get_sliced_prediction( "large_fabric.jpg", detection_model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) result.export_visuals(export_dir="./sahi_vis/")slice_height和slice_width设成和训练输入一致,overlap比例设 0.2 是为了让跨切片的缺陷至少完整出现在一个块里,太低会切断目标,太高会重复检测增加合并负担。切片推理的代价是速度慢几倍,所以适合离线抽检或对召回率要求高的场景,产线实时检测还是用整图推理。
验证切片有没有效果,别只看整体 mAP,单独统计小目标(面积小于 32×32 像素)的召回率,对比切片前后的变化。我一般会跑一个只含小缺陷的子集,切片前后各测一遍,召回率提升低于 5 个点就说明切片参数没调好,回去调overlap或切片尺寸。
从那以后我每次拿到新的布匹数据集,都强制先跑一遍样本统计和坐标校验,再动手训。这两个脚本加起来不到五十行,但能挡掉后面八成的玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取