布匹缺陷数据集实战:从VOC转YOLO到训练避坑全指南
简介布匹缺陷数据集面向纺织工业质检与计算机视觉方向的学习者和开发者用于训练和评估布匹表面缺陷的自动检测与分类模型。压缩包共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), recursiveTrue) txt_files glob.glob(os.path.join(root, **, *.txt), recursiveTrue) json_files glob.glob(os.path.join(root, **, *.json), recursiveTrue) print(fXML(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), recursiveTrue): with open(txt) as f: for line in f: cid line.split()[0] counter[cid] 1 for cid, n in sorted(counter.items(), keylambda x: -x[1]): print(fclass {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, recursiveTrue): 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 \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ augmentTrue \ mosaic1.0 \ degrees10.0 \ translate0.1 \ scale0.5逐个说imgsz640是布匹缺陷的常用输入尺寸再小细节丢失严重再大显存吃不消lr00.01是 SGD 的初始学习率如果用 AdamW 要降到 1e-3patience20表示 20 轮没提升就早停布匹数据量通常不大早停能防过拟合mosaic1.0开启马赛克增强对小目标检测帮助明显degrees10.0做小角度旋转增强布匹在产线上本来就有轻微倾斜这个增强符合实际scale0.5允许 0.5 到 1.5 倍缩放模拟不同拍摄距离。注意如果某个缺陷类别实例数特别少把mosaic开到 1.0 反而可能让少数类被淹没可以降到 0.5 并配合copy_paste增强。4.2 训练日志里该盯哪几个数训练跑起来后控制台会刷一堆指标别只看 mAP。布匹缺陷要重点盯三个box_loss是否稳定下降、cls_loss是否震荡、每个类别的mAP50是否均衡。# 训练结束后用验证集单独跑一遍输出每类指标 yolo detect val \ modelruns/detect/train/weights/best.pt \ data./data.yaml \ imgsz640 \ conf0.25 \ iou0.5conf0.25是置信度阈值布匹缺陷建议从 0.25 起调太低误检多太高漏检多iou0.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, conf0.25, imgsz640) 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_typeyolov8, model_pathruns/detect/train/weights/best.pt, confidence_threshold0.25, devicecuda:0 ) result get_sliced_prediction( large_fabric.jpg, detection_model, slice_height640, slice_width640, overlap_height_ratio0.2, overlap_width_ratio0.2 ) result.export_visuals(export_dir./sahi_vis/)slice_height和slice_width设成和训练输入一致overlap比例设 0.2 是为了让跨切片的缺陷至少完整出现在一个块里太低会切断目标太高会重复检测增加合并负担。切片推理的代价是速度慢几倍所以适合离线抽检或对召回率要求高的场景产线实时检测还是用整图推理。验证切片有没有效果别只看整体 mAP单独统计小目标面积小于 32×32 像素的召回率对比切片前后的变化。我一般会跑一个只含小缺陷的子集切片前后各测一遍召回率提升低于 5 个点就说明切片参数没调好回去调overlap或切片尺寸。从那以后我每次拿到新的布匹数据集都强制先跑一遍样本统计和坐标校验再动手训。这两个脚本加起来不到五十行但能挡掉后面八成的玄学问题。希望帮到你。本文还有配套的精品资源点击获取