简介这份资源是2019年天池布匹瑕疵检测赛题相关的图像数据集面向从事工业质检、缺陷检测算法实践的开发者与学习者可用于分类、目标检测等任务的训练与验证。压缩包共935个文件以689张jpg瑕疵图像和245个xml标注文件为主另附1个txt说明文件整体约927.26MB图像与标注基本配套便于直接构建监督学习数据。数据集覆盖32种布匹表面瑕疵类别但各类别样本数量差异明显部分类别图片较多、部分偏少作者也坦言整体质量不算高更适合作为入门练手或数据增强、类别不平衡处理的实验素材。目前已有2919人学习下载读者可从中获得多类别瑕疵样本、现成的标注文件以及贴近真实产线的图像分布用于快速搭建检测流程、验证模型鲁棒性并积累工业视觉项目经验。1. 布匹瑕疵数据集-2019天池一张让检测模型从能跑到能用的入场券第一次把布匹瑕疵检测模型推到产线边上最扎心的不是模型精度不够而是根本不知道该拿什么数据去训。工业质检这行有个公开的秘密算法开源的一大把能直接拿来练手的标注数据却少得可怜。布匹瑕疵数据集-2019天池这类比赛数据集恰好填的就是这个空档——它把真实织造场景下的断经、破洞、油污、结头等缺陷按图像加标注的形式固化下来让做表面缺陷检测的人有个能复现的起点。它解决的不是有没有模型的问题而是模型在真实瑕疵分布上到底靠不靠谱的问题。适合谁做工业视觉的算法工程师、想从通用目标检测转垂直场景的开发者以及需要快速验证一套质检 pipeline 是否值得投入的技术负责人。下面我按自己踩过的路子把这份数据从拿到手到跑出可用结果的全过程拆开讲。2. 先搞清楚这份数据长什么样类别、分布与标注格式拿到一份瑕疵数据集最忌讳上来就train.py一把梭。布匹瑕疵有个特点缺陷尺度差异极大一个针尖大的结头和一条贯穿整幅的断经在同一个输入分辨率下根本没法兼顾。所以第一步必须把数据的类别体系、样本分布和标注格式摸清楚这决定了后面模型选型、输入尺寸和增强策略怎么定。2.1 瑕疵类别体系与工业含义布匹瑕疵的类别命名通常直接对应织造工艺里的缺陷成因常见的有断经、断纬、破洞、油污、结头、跳纱、色差这几类。理解每一类的物理形态很关键断经是经向纱线断裂形成的细长条状缺陷长宽比可能到 20:1破洞是局部组织缺失边界不规则油污是低对比度的块状污染和背景灰度接近结头是纱线打结形成的小目标面积可能只占图像的千分之几。这些形态差异直接决定了检测难点——细长目标对 anchor 比例敏感低对比度目标对光照和归一化敏感小目标对下采样倍数敏感。我一般会先做一张类别-形态-难点的对照表作为后续调参的依据类别典型形态尺度特征主要检测难点断经细长条状长宽比极大anchor 比例失配、感受野覆盖不足破洞不规则块状中等偏大边界模糊、与背景对比度不稳定油污低对比块状中等光照敏感、易与正常污渍混淆结头小目标极小下采样后特征丢失、正样本不足跳纱线状细长与正常纹理混淆这张表不是摆设后面设 anchor、选输入尺寸、配数据增强全要回来查它。2.2 标注格式识别与统计脚本比赛数据集常见的标注格式有两种VOC 风格的 XML每个图一个文件含 bbox 坐标和 COCO 风格的单个 JSON。先写个脚本把目录结构、类别分布、每类样本数、bbox 尺寸分布统计出来别靠肉眼翻文件夹。import os import json from collections import Counter, defaultdict import xml.etree.ElementTree as ET def scan_voc(anno_dir): 统计 VOC 格式标注类别数、每类框数、宽高分布 cls_counter Counter() size_records defaultdict(list) # 类别 - [(w, h), ...] for fname in os.listdir(anno_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(anno_dir, fname)) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() 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) cls_counter[name] 1 size_records[name].append((w, h)) return cls_counter, size_records def report(cls_counter, size_records): total sum(cls_counter.values()) print(f总标注框数: {total}) for cls, cnt in cls_counter.most_common(): ws [s[0] for s in size_records[cls]] hs [s[1] for s in size_records[cls]] print(f{cls}: {cnt} 框 ({cnt/total*100:.1f}%), f宽均值 {sum(ws)/len(ws):.1f}, 高均值 {sum(hs)/len(hs):.1f}, f最小面积 {min(w*h for w,h in size_records[cls]):.0f}) if __name__ __main__: cls_counter, size_records scan_voc(./annotations) report(cls_counter, size_records)这段脚本干三件事遍历 XML 拿类别名和 bbox 坐标、按类别累计框数、算每类宽高均值和最小面积。跑完你会得到两个关键信号——如果某类框数占比低于 5%后面必须做重采样或 focal loss 补偿如果最小面积只有几十像素输入尺寸就不能压到 416 以下否则小目标在特征图上直接消失。参数上xmax-xmin的减法顺序别写反VOC 坐标是左上右下写反了宽高全负统计结果直接失真。2.3 从原始标注到训练格式的转换统计清楚后把标注统一转成训练框架吃的格式。以 YOLO 系列常用的 txt 格式为例每行是class_id cx cy w h全部归一化到 0~1。转换时最容易翻车的是类别 id 映射和坐标归一化基准——归一化要用图像真实宽高不是标注文件里写的size节点有些数据集这个节点是错的血泪经验。import os import xml.etree.ElementTree as ET from PIL import Image CLASS_MAP {断经: 0, 断纬: 1, 破洞: 2, 油污: 3, 结头: 4, 跳纱: 5} def voc_to_yolo(anno_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(anno_dir): if not fname.endswith(.xml): continue tree ET.parse(os.path.join(anno_dir, fname)) root tree.getroot() img_name root.find(filename).text # 用真实图像尺寸不用 XML 里的 size 节点 with Image.open(os.path.join(img_dir, img_name)) as im: iw, ih im.size lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) cx (x1 x2) / 2 / iw cy (y1 y2) / 2 / ih w (x2 - x1) / iw h (y2 - y1) / ih lines.append(f{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(os.path.join(out_dir, fname.replace(.xml, .txt)), w) as f: f.write(\n.join(lines))逻辑说明CLASS_MAP把中文类别名映射成连续整数 id训练框架只认整数坐标先算中心点再除以真实宽高保留 6 位小数足够。参数上CLASS_MAP的 id 必须从 0 连续中间断号会让损失函数索引越界如果某张图没有有效标注写空文件而不是跳过否则训练时样本和标签对不上。转换完随手抽 5 张用可视化脚本画框核对这一步省不得。3. 检测模型怎么选、怎么训从 baseline 到能上产线的配置数据摸清之后才轮到模型。布匹瑕疵检测本质是表面缺陷检测surface defect detection的一个子问题和通用 COCO 检测的区别在于背景高度同质都是布面纹理、缺陷尺度跨度大、正负样本极度不均衡。这三点决定了不能直接套通用检测的默认配置。3.1 模型选型的三个判断依据我一般按三条线选第一缺陷最小尺度决定输入分辨率和 backbone 的下采样策略结头这类小目标多的场景backbone 总下采样倍数不宜超过 16否则 P3 特征图上目标只剩几个像素第二细长缺陷决定 anchor 或回归方式断经这种 20:1 的长条要么加极端长宽比 anchor要么用无 anchor 的 FCOS/ATSS 类方法绕开比例设计第三产线推理速度决定模型体量单张图推理要压到几十毫秒内YOLO 系列的中小模型通常比两阶段检测器更现实。常见做法是先用 YOLOv5s 或 YOLOv8s 跑一个 baseline看每类 AP 分布再针对性换 backbone 或加注意力。别一上来就上大模型布匹数据量往往撑不住过拟合比欠拟合更常见。3.2 训练配置与关键参数baseline 训练配置我一般这么定输入 640×640兼顾小目标和显存batch size 按显存拉到 16 或 32初始学习率 0.01 配 cosine 衰减warmup 3 个 epoch数据增强里 Mosaic 开但关掉随机裁剪的极端比例因为布面纹理被裁碎后模型学到的是伪影不是缺陷。# data.yaml 关键字段 path: ./fabric_dataset train: images/train val: images/val nc: 6 names: [断经, 断纬, 破洞, 油污, 结头, 跳纱]# 训练命令关键参数逐个说明 python train.py \ --img 640 \ --batch 16 \ --epochs 100 \ --data data.yaml \ --weights yolov8s.pt \ --hyp hyp.fabric.yaml \ --cache参数说明--img 640是输入边长小目标多可提到 800 但显存翻倍--batch 16是显存和梯度的折中显存够就往上加--cache把图像缓存进内存加速读取数据集不大时强烈建议开--hyp指向自定义超参文件里面重点调mosaic建议 0.5 而非默认 1.0、scale缩放到 0.5 而非 0.9避免小目标被缩没、fl_gamma低对比度油污可开 1.0 做 focal 补偿。训练过程中盯val/box_loss和每类 mAP如果某一类 mAP 长期为 0先回去查标注转换有没有漏类而不是急着改模型。3.3 针对小目标和细长缺陷的增强策略通用增强在布匹场景会帮倒忙。随机旋转 90 度对布面纹理是合理的织造方向本身有经纬两个方向但任意角度旋转会让细长缺陷的形态失真色彩抖动幅度要收窄因为油污和色差本身就是颜色差异抖过头模型分不清是缺陷还是增强噪声。我的做法是分阶段增强前 50 个 epoch 开 Mosaic 和适度缩放让模型见足够多的上下文组合后 50 个 epoch 关掉 Mosaic用接近真实分布的单图训练收尾这一步对最终 mAP 通常有 1~2 个点的提升。另外针对结头这类小目标单独做一个过采样副本把含小目标的图在训练集里重复 2~3 次比调 loss 权重更直接。4. 避坑与排查这份数据集上最容易翻车的五个地方4.1 类别不均衡导致模型只认大类现象训练完 mAP 看着还行但一查每类断经破洞 recall 很高结头 recall 接近 0。原因结头样本占比可能不到 3%标准交叉熵下模型倾向于全预测成背景或大类。解决先统计每类框数对低于 5% 的类做过采样同时在 loss 里加类别权重或换 focal loss验证时单独看小类的 PR 曲线而不是只看总 mAP。4.2 标注框越界导致训练报错或静默丢样本现象训练日志里样本数比预期少或者归一化后出现大于 1 的坐标。原因原始标注的 bbox 坐标超出图像边界转换脚本没做裁剪。解决转换时对 x1/y1/x2/y2 做max(0, min(coord, size))裁剪裁剪后宽高小于 2 像素的框直接丢弃并在日志里记录丢弃数量别让它静默消失。4.3 输入分辨率压缩把小目标压没了现象验证集上大缺陷检测正常小缺陷全漏。原因原图可能是 2000 像素以上直接 resize 到 640结头从 30 像素变成不到 10 像素特征图上几乎不可见。解决要么提高输入尺寸到 800~1024要么用切片推理把大图切成带重叠的小块分别检测再合并后者在产线高分辨率场景更常用。4.4 背景纹理被当成缺陷现象模型在验证集上误检率高检出的框落在正常布面纹理上。原因布面本身有织纹、反光、褶皱和某些低对比度缺陷油污特征接近负样本不够多样。解决在训练集里显式加入纯背景图作为负样本YOLO 支持空标签图比例控制在 10% 左右同时收窄色彩抖动避免增强制造出假缺陷。4.5 验证集和训练集同源导致指标虚高现象本地验证 mAP 0.85换一批新图直接掉到 0.5。原因验证集是从同一批拍摄条件下随机切的光照、布种、相机参数都一样模型学到的是这批数据的特定分布。解决按布种或拍摄批次划分训练/验证集而不是随机切如果数据里有多批次信息务必按批次做 group split这是工业场景最容易被忽略的一刀。5. 把模型推到能用切片推理与产线验证的具体做法训练指标好看只是及格线布匹瑕疵检测真正难的是高分辨率大图上的落地。产线相机拍出来的图动辄 4000×3000直接 resize 喂模型等于自废小目标检测能力。我一般用切片推理把大图按固定窗口切块块间留重叠逐块检测后把框映射回原图坐标再用 NMS 合并跨块重复框。import numpy as np def slice_inference(model, image, slice_size640, overlap128, conf0.25): 大图切片推理切块 - 检测 - 坐标回映射 - 全局 NMS H, W image.shape[:2] stride slice_size - overlap all_boxes, all_scores, all_cls [], [], [] for y in range(0, H, stride): for x in range(0, W, stride): patch image[y:yslice_size, x:xslice_size] if patch.shape[0] slice_size or patch.shape[1] slice_size: patch np.pad(patch, ((0, slice_size-patch.shape[0]), (0, slice_size-patch.shape[1]), (0, 0))) results model(patch, confconf) for box in results[0].boxes: xyxy box.xyxy[0].cpu().numpy() # 关键加上切片偏移映射回原图坐标 xyxy[0] x; xyxy[1] y xyxy[2] x; xyxy[3] y all_boxes.append(xyxy) all_scores.append(float(box.conf)) all_cls.append(int(box.cls)) return global_nms(all_boxes, all_scores, all_cls, iou_thr0.5)逻辑说明stride slice_size - overlap保证相邻块有重叠区避免缺陷正好落在切缝上被切断坐标回映射时四个角都要加偏移只加左上角是常见错误最后做全局 NMS因为同一个缺陷可能被相邻两块各检一次。参数上overlap建议不小于最大缺陷尺寸的一半太小会漏跨块目标太大推理耗时上升conf在切片场景可以适当调低到 0.2靠后续 NMS 和业务规则过滤因为切片后单块上下文变少置信度整体偏低。验证环节我有个习惯不看总 mAP而是按缺陷类别分别算 recall 和误检率再折算成产线上的漏检率和过杀率。布匹质检里过杀率往往比漏检率更影响产线接受度——工人被频繁误报烦到直接关掉系统这事我见过不止一次。所以调阈值时宁可 recall 稍低也要把过杀压住具体阈值在验证集上按每类的 PR 曲线找拐点而不是全局一个 0.5 了事。最后说个我自己的教训早期做这类项目我总想着把模型精度再刷高两个点结果在产线验证时发现瓶颈根本不在模型而在切片策略和阈值标定。后来养成的习惯是baseline 训完立刻搭一个最小可用的切片推理加可视化脚本拿真实大图跑一遍看漏检和误检到底长什么样再回头决定是改模型还是改后处理。数据、模型、后处理这三段任何一段没对齐指标都是自欺欺人。希望帮到你。本文还有配套的精品资源点击获取