简介:布匹缺陷数据集面向纺织工业质检与计算机视觉方向的学习者和开发者,用于训练和评估布匹表面缺陷的自动检测与分类模型。包内共934个文件,以689张jpg缺陷图像和245个xml标注文件为主,图像记录孔洞、色差、污渍、线条、起球、皱褶、接缝不良等典型缺陷,xml则提供对应的目标框与类别标注,压缩包整体约927.33MB,可直接用于目标检测或分类任务。目前已有858人学习下载。数据集按训练、验证、测试划分,便于模型训练、调参与性能评估;读者可据此完成图像预处理、CNN特征提取、迁移学习与数据增强等完整流程,并借助标注文件快速搭建检测基线,理解工业质检场景下的泛化能力与部署思路,为智能制造相关项目提供可复用的实践素材。
1. 布匹缺陷数据集到底能解决什么问题:从验布工的眼睛到模型的输入
做纺织质检的同行大概率都经历过这个场景:产线上验布机跑得飞快,老师傅盯着布面看,一天下来眼睛发花,漏检率蹭蹭往上涨。你想上一套视觉检测方案,算法选型倒不难,难的是手里没有一批标注好的布匹缺陷图片。dataset这个词在搜索里热度一直不低,但真正落到布匹这个细分场景,公开可用的资源其实很有限。布匹缺陷数据集-dataset.rar这个标题指向的就是这么一类资源包:里面装的是按缺陷类别整理好的布面图像,常见类别包括断经、断纬、破洞、污渍、竹节、跳纱等。它能帮你跳过最耗时的数据采集和标注阶段,直接进入模型训练和验证。适合两类人:一是刚接触工业质检视觉方向、想先跑通一个完整 pipeline 的工程师;二是已经在做布匹检测、需要补充数据做对比实验或预训练的在岗人员。这一章先把数据集的结构、类别逻辑和选型判断讲清楚,后面几章再落到具体怎么用。
2. 拆开 dataset.rar 之前:布匹缺陷的类别体系与标注格式
2.1 布匹缺陷的常见分类逻辑
布匹缺陷的分类不像通用目标检测那样随意,它跟织造工艺强相关。常见做法是按成因分三大类:经向缺陷(断经、经缩、吊经)、纬向缺陷(断纬、纬缩、百脚)、以及局部性缺陷(破洞、污渍、竹节、跳纱、棉结)。这个分类逻辑直接影响你后面做检测时的策略——经向缺陷往往是细长条,纬向缺陷是横向条带,局部缺陷才是接近方形或不规则形状的目标。如果你用同一个 anchor 尺度去覆盖所有类别,细长条的经向缺陷很容易被漏掉。
我一般拿到一个布匹数据集,第一件事不是急着写 dataloader,而是先统计每个类别的样本数量和标注框的宽高比分布。这一步能帮你判断:这个数据集是不是类别极度不平衡,以及你需不需要针对细长目标单独调 anchor。
import os import xml.etree.ElementTree as ET from collections import Counter # 统计各类别样本数和标注框宽高比 def analyze_dataset(anno_dir): class_count = Counter() aspect_ratios = [] for xml_file in os.listdir(anno_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() for obj in root.findall('object'): name = obj.find('name').text class_count[name] += 1 bbox = obj.find('bndbox') w = float(bbox.find('xmax').text) - float(bbox.find('xmin').text) h = float(bbox.find('ymax').text) - float(bbox.find('ymin').text) if h > 0: aspect_ratios.append(round(w / h, 2)) print("类别分布:", dict(class_count)) print("宽高比样本(前20):", aspect_ratios[:20]) return class_count analyze_dataset('./annotations')这段代码做两件事:统计每个类别的标注数量,以及计算标注框的宽高比。anno_dir指向你解压后存放 XML 标注的目录。跑完之后你会得到一份类别分布,如果某个类别只有几十个样本,而最多的类别有上千个,那训练时就得考虑重采样或者 focal loss 之类的策略。宽高比数据则告诉你需不需要在 anchor 配置里加极端比例。
2.2 标注格式的识别与转换准备
布匹缺陷数据集常见的标注格式有三种:Pascal VOC 的 XML、YOLO 的 txt、以及 COCO 的 json。dataset.rar里具体是哪种,解压后看目录结构就能判断——有Annotations文件夹和JPEGImages文件夹的基本是 VOC 格式;全是.txt且每行是class x_center y_center w h的是 YOLO 格式;有annotations.json且带images、categories、annotations三个顶层键的是 COCO 格式。
判断清楚格式之后,别急着转。先检查两件事:一是图片和标注文件是否一一对应,有没有孤儿标注或孤儿图片;二是标注框有没有越界(xmin 小于 0 或 xmax 大于图片宽度)。这两个问题在公开数据集里出现的频率比你想象的高,不提前处理,训练时 loss 会莫名其妙变成 NaN。
from PIL import Image import xml.etree.ElementTree as ET import os # 检查标注框越界和图片-标注对应关系 def validate_voc_dataset(img_dir, anno_dir): issues = [] img_files = set(os.path.splitext(f)[0] for f in os.listdir(img_dir)) anno_files = set(os.path.splitext(f)[0] for f in os.listdir(anno_dir)) # 孤儿文件检查 only_img = img_files - anno_files only_anno = anno_files - img_files if only_img: issues.append(f"有图无标注: {len(only_img)} 个, 示例: {list(only_img)[:3]}") if only_anno: issues.append(f"有标注无图: {len(only_anno)} 个, 示例: {list(only_anno)[:3]}") # 越界检查 for anno_file in os.listdir(anno_dir): if not anno_file.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, anno_file)) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) for obj in root.findall('object'): 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) if xmin < 0 or ymin < 0 or xmax > img_w or ymax > img_h: issues.append(f"{anno_file} 标注越界: ({xmin},{ymin},{xmax},{ymax}) vs ({img_w},{img_h})") for issue in issues[:10]: print(issue) print(f"共发现 {len(issues)} 个问题") return issues validate_voc_dataset('./JPEGImages', './Annotations')img_dir和anno_dir分别指向图片和标注目录。这段脚本会输出孤儿文件列表和越界标注列表。越界标注的处理方式通常是裁剪到图片边界内,但如果越界严重(比如超过图片尺寸的 20%),我建议直接丢弃这条标注,因为它的真实性存疑。
3. 从 rar 到可训练格式:VOC 转 YOLO 的完整脚本与参数说明
3.1 转换脚本的编写与目录组织
VOC 转 YOLO 是布匹缺陷数据集落地时最常走的一步。YOLO 格式要求每张图对应一个同名.txt文件,每行是class_id x_center y_center width height,所有坐标都归一化到 0 到 1 之间。转换本身不复杂,但有几个细节容易翻车:类别名到 id 的映射必须固定且一致,不能这次跑是 0 下次跑变成 1;归一化时要用图片的实际宽高,不能用标注文件里写的 size,因为有些数据集的 size 字段是错的。
import os import xml.etree.ElementTree as ET from PIL import Image # 固定类别映射,顺序一旦确定不要改 CLASS_MAP = { 'broken_end': 0, # 断经 'broken_pick': 1, # 断纬 'hole': 2, # 破洞 'stain': 3, # 污渍 'slub': 4, # 竹节 'float': 5, # 跳纱 } def voc_to_yolo(img_dir, anno_dir, out_dir): os.makedirs(out_dir, exist_ok=True) skipped = 0 for xml_file in os.listdir(anno_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(anno_dir, xml_file)) root = tree.getroot() # 用 PIL 读实际图片尺寸,不用 XML 里的 size img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): skipped += 1 continue with Image.open(img_path) as im: img_w, img_h = im.size lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in CLASS_MAP: continue cls_id = 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 = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) x_center = (xmin + xmax) / 2.0 / img_w y_center = (ymin + ymax) / 2.0 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_name = os.path.splitext(xml_file)[0] + '.txt' with open(os.path.join(out_dir, out_name), 'w') as f: f.write('\n'.join(lines)) print(f"转换完成,跳过 {skipped} 个缺失图片的标注") voc_to_yolo('./JPEGImages', './Annotations', './labels')CLASS_MAP是整条链路里最需要锁死的东西。我一般会把它单独存成一个classes.txt,训练和推理都读同一个文件。out_dir是 YOLO 标签的输出目录,转换完成后每个.txt和原图同名。注意代码里用 PIL 读实际尺寸而不是 XML 里的 size,这个细节能避免一批因为标注工具写错尺寸导致的坐标偏移。
3.2 训练集验证集划分的坑
转换完之后要划分训练集和验证集。常见做法是 8:2 或 7:3,但布匹缺陷数据集有个特殊之处:同一匹布上裁下来的图片可能高度相似。如果你随机划分,验证集里很可能出现和训练集几乎一样的图,导致验证指标虚高。我一般会先按布匹编号或采集批次分组,再在组级别做划分,确保同一匹布的图不会同时出现在训练集和验证集里。
import os import random from collections import defaultdict # 按文件名前缀分组,假设文件名格式为 "clothID_xxx.jpg" def split_by_group(img_dir, out_txt, train_ratio=0.8): groups = defaultdict(list) for f in os.listdir(img_dir): if not f.lower().endswith(('.jpg', '.png', '.bmp')): continue group_id = f.split('_')[0] groups[group_id].append(f) group_ids = list(groups.keys()) random.seed(42) random.shuffle(group_ids) split_idx = int(len(group_ids) * train_ratio) train_groups = group_ids[:split_idx] val_groups = group_ids[split_idx:] with open(out_txt, 'w') as f: for g in train_groups: for img in groups[g]: f.write(f"train/{img}\n") for g in val_groups: for img in groups[g]: f.write(f"val/{img}\n") print(f"训练组 {len(train_groups)} 个,验证组 {len(val_groups)} 个") split_by_group('./JPEGImages', './split.txt')group_id的提取方式取决于你的文件名规则,如果文件名里没有布匹编号,那就得靠采集时间或文件夹名来分组。random.seed(42)是为了让划分可复现,团队协作时每个人跑出来的划分结果一致。这个细节在调参对比实验里很重要,否则你没法判断指标变化是模型改动的效果还是数据划分变了。
4. 用 YOLOv8 跑通布匹缺陷检测的最小训练命令
4.1 数据配置文件与目录结构
YOLOv8 要求一个 YAML 格式的数据配置文件,里面指定训练集、验证集路径和类别名。目录结构我一般组织成这样:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml的内容:
path: /home/user/dataset train: images/train val: images/val nc: 6 names: 0: broken_end 1: broken_pick 2: hole 3: stain 4: slub 5: floatnc是类别数,必须和names里的条目数一致。path写绝对路径最稳妥,相对路径在不同工作目录下跑容易出问题。
4.2 训练命令与关键参数
最小训练命令就一行:
yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 patience=20model=yolov8n.pt用的是 nano 版本,适合先跑通流程。imgsz=640是输入尺寸,布匹缺陷里细长条目标多的话可以试 1024,但显存占用会翻倍。patience=20表示 20 个 epoch 验证指标不提升就早停,这个参数能帮你省不少时间。batch=16在 8G 显存上跑 640 尺寸基本没问题,如果 OOM 就降到 8。
跑通之后看runs/detect/train/下的results.csv,重点关注metrics/mAP50-95和每个类别的 AP。如果某个类别 AP 特别低,先回去看那个类别的样本数和标注质量,别急着调模型。
4.3 推理与可视化验证
训练完拿一张图跑推理:
yolo detect predict model=runs/detect/train/weights/best.pt source=test_image.jpg conf=0.25 save=Trueconf=0.25是置信度阈值,布匹缺陷检测里这个值可以适当调低到 0.15 到 0.2,因为漏检的代价通常比误检高。save=True会把带框的图存下来,方便你肉眼检查。我一般会抽 20 张验证集图片跑一遍,把漏检和误检的图单独挑出来看,判断是标注问题还是模型问题。
5. 布匹缺陷检测的避坑与排查:5 个血泪教训
5.1 现象:训练 loss 正常下降但 mAP 一直上不去
原因:最常见的是标注框和实际缺陷位置对不齐。布匹缺陷里有些缺陷边界模糊,不同标注人员画的框差异很大。另一个可能是类别映射在转换时搞错了,比如把断经和断纬的 id 弄反了。
解决:抽 10 张图把标注框画出来和原图叠在一起看,肉眼确认对齐情况。类别映射问题就重新检查CLASS_MAP和data.yaml里的names是否一致。如果标注本身质量差,考虑用带噪声的标注训练,或者只保留标注一致性高的样本。
5.2 现象:模型对细长条缺陷几乎全部漏检
原因:默认 anchor 尺度偏向方形目标,细长条的经向缺陷在特征图上响应弱。另外如果输入尺寸是 640,一条横跨整幅图的断经在缩放后可能只剩几个像素宽。
解决:把imgsz提到 1024 或 1280,同时检查数据增强里的缩放策略。YOLOv8 默认的 anchor 是自适应的,但你可以通过调整anchors参数或改用分割模型来改善。我一般会先试提高输入尺寸,效果不够再考虑换模型结构。
5.3 现象:验证集指标很好但产线上跑效果差
原因:训练数据和实际产线数据分布不一致。公开数据集里的布匹缺陷往往是精选过的,光照、角度、背景都比较干净,产线上有振动、光照变化、布面褶皱。
解决:在产线上采集一批真实图片,哪怕只有几十张,加进验证集里看指标。如果差距大,考虑用产线数据做微调,或者在训练时加更强的数据增强(亮度抖动、高斯噪声、随机裁剪)。这个坑几乎每个做工业质检的人都会踩一次。
5.4 现象:推理速度达不到产线要求
原因:模型太大或者输入尺寸太高。产线验布机速度通常在 30 到 60 米每分钟,留给每帧的处理时间可能只有几十毫秒。
解决:先测一下yolov8n在目标硬件上的推理耗时。如果不够,考虑量化(ONNX Runtime 或 TensorRT),或者换更小的输入尺寸。布匹缺陷检测里有个取舍:输入尺寸降了细长条漏检率会上升,得根据实际缺陷的最小尺寸来定。
5.5 现象:同一张图多次推理结果不一致
原因:如果用了数据增强的 TTA(测试时增强),结果会有波动。另外 GPU 非确定性算子也会导致微小差异。
解决:产线部署时关掉 TTA,用固定输入尺寸和确定性推理。如果对稳定性要求极高,可以在后处理里加 NMS 的置信度阈值和 IoU 阈值固定,避免因为阈值浮动导致框的数量变化。
6. 把布匹缺陷数据集用出复利:预训练、增量与产线对齐
数据集的价值不止于跑通一次训练。我一般会把它当成一个预训练底座来用:先在dataset.rar这份数据上训一个基础模型,然后拿产线上采集的新数据做增量微调。这样做的好处是产线数据哪怕只有几百张,也能在预训练权重的基础上快速收敛,比从 COCO 预训练权重起步更贴近布匹纹理。
具体做法是分两阶段:第一阶段用完整数据集训 100 到 150 个 epoch,学习率用默认的 0.01,得到base.pt。第二阶段用产线数据微调,学习率降到 0.001,epoch 设 30 到 50,冻结 backbone 的前几层。微调时验证集必须包含产线数据,否则你没法判断模型是不是真的适应了新场景。
# 第一阶段:基础训练 yolo detect train data=dataset/data.yaml model=yolov8n.pt epochs=150 imgsz=1024 batch=8 lr0=0.01 # 第二阶段:产线数据微调 yolo detect train data=prod_data.yaml model=runs/detect/train/weights/best.pt epochs=50 imgsz=1024 batch=8 lr0=0.001 freeze=5freeze=5表示冻结前 5 层,这个数字可以根据你的产线数据量调整——数据越少冻结越多。prod_data.yaml里验证集要混入一部分原始数据集的图片,防止模型在微调时遗忘基础类别。
还有一个我踩过的坑:增量训练时如果产线数据里出现了新类别(比如原数据集没有的“纬缩”),必须把nc和names都更新,并且新类别的样本数不能太少,否则模型会把新类别和旧类别混淆。我一般要求新类别至少 100 个标注框才加进去,不够就先归到“其他”类里。
最后说一个验证方法:把模型在验证集上的预测结果按类别导出成 CSV,每行是图片名、真实类别、预测类别、置信度。然后按置信度从低到高排序,优先看低置信度的样本。这些样本里藏着模型最不确定的情况,往往就是产线上最容易翻车的地方。这个习惯帮我省了很多次返工,希望帮到你。
本文还有配套的精品资源,点击获取