简介:这份资源是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_ok=True) 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_size=640, overlap=128, conf=0.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:y+slice_size, x:x+slice_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, conf=conf) 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_thr=0.5)逻辑说明:stride = slice_size - overlap保证相邻块有重叠区,避免缺陷正好落在切缝上被切断;坐标回映射时四个角都要加偏移,只加左上角是常见错误;最后做全局 NMS,因为同一个缺陷可能被相邻两块各检一次。参数上,overlap建议不小于最大缺陷尺寸的一半,太小会漏跨块目标,太大推理耗时上升;conf在切片场景可以适当调低到 0.2,靠后续 NMS 和业务规则过滤,因为切片后单块上下文变少,置信度整体偏低。
验证环节我有个习惯:不看总 mAP,而是按缺陷类别分别算 recall 和误检率,再折算成产线上的漏检率和过杀率。布匹质检里过杀率往往比漏检率更影响产线接受度——工人被频繁误报烦到直接关掉系统,这事我见过不止一次。所以调阈值时宁可 recall 稍低,也要把过杀压住,具体阈值在验证集上按每类的 PR 曲线找拐点,而不是全局一个 0.5 了事。
最后说个我自己的教训:早期做这类项目,我总想着把模型精度再刷高两个点,结果在产线验证时发现瓶颈根本不在模型,而在切片策略和阈值标定。后来养成的习惯是,baseline 训完立刻搭一个最小可用的切片推理加可视化脚本,拿真实大图跑一遍,看漏检和误检到底长什么样,再回头决定是改模型还是改后处理。数据、模型、后处理这三段,任何一段没对齐,指标都是自欺欺人。希望帮到你。
本文还有配套的精品资源,点击获取