布匹缺陷检测数据集实战:VOC转YOLO格式与YOLOv8训练全流程
2026/9/15 4:13:30 网站建设 项目流程

简介:布匹缺陷检测数据集共800张工业实拍图片,聚焦“损坏”与“污渍”两类常见缺陷,面向工业质检、智能巡检及目标检测算法开发人员,可直接用于模型训练与效果验证。压缩包内图片按统一编号管理,附voc(xml)与yolo(txt)两种主流标注格式,另有jpg原图,共2401个文件,整体约988MB,标签与图像一一对应,无需额外转换即可接入常见检测框架。平台显示已有982人学习下载,数据标注精确,算法拟合表现可靠,适合作为布匹表面缺陷检测项目的训练基座。读者可获得完整图片-标签配套数据,便于开展数据划分、模型调参与缺陷识别实验;同时可结合博主提供的更多介绍链接理解采集场景与标注规范,降低实际项目落地门槛。

1. 布匹缺陷检测数据集,先解决标签格式再谈模型

布匹表面的损坏、污渍这类缺陷,在纺织产线质检里属于典型的小目标、低对比度问题。传统的机器视觉用灰度阈值、纹理滤波做,一到花纹布、深色布就失效。深度学习的检测方案能把这些缺陷当目标框出来,但前提是有一份干净、能直接喂给模型的标注数据。这题目里给的 800 张图、两种格式标答,正好踩在大多数入坑者的痛点上:不是不会跑 yolo,而是被数据格式折腾到怀疑人生。VOC 的 XML 和 YOLO 的 Txt 各有各的坑,坐标归一化、类别映射、train/val 划分,一步错全盘崩。这篇就把这套数据集从目录结构、格式转换到训练验证的完整链路拆开讲,适合已经跑通过一次检测流程、但被自定义数据集卡住的人。

2. 数据集结构与两种标签格式的核心差异

2.1 VOC 格式的目录树和 XML 解析逻辑

VOC,全称 PASCAL VOC,是目标检测领域最老牌的数据组织方式。这套布匹缺陷数据集如果按 VOC 规范组织,目录结构通常是这样的:

fabric_defect/ ├── JPEGImages/ # 原始图像,jpg格式 ├── Annotations/ # 每张图对应的xml标注文件 ├── ImageSets/ │ └── Main/ │ ├── train.txt │ ├── val.txt │ └── test.txt

每个 XML 文件的内容结构值得细看,因为后面转 YOLO 格式要从里面抽信息:

<annotation> <folder>JPEGImages</folder> <filename>defect_001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>damage</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>300</xmax> <ymax>240</ymax> </bndbox> </object> <object> <name>stain</name> <bndbox> <xmin>400</xmin> <ymin>50</ymin> <xmax>550</xmax> <ymax>160</ymax> </bndbox> </object> </annotation>

注意这里有几个关键点:一是 size 节点里记录的是原图宽高,不是缩放后的;二是每个 object 代表一个目标框,一张图可以有多个 object,也可以有不同类型的缺陷混合;三是 bndbox 四个值是像素坐标,整数。转 YOLO 格式时,xmin、ymin、xmax、ymax 必须除以 width 和 height 做归一化,很多人在这里直接写死 640 或 416,图一旦不是这个尺寸就全错了。

实际用 Python 解析时,我一般用 xml.etree.ElementTree,不用 lxml,因为标注文件不会复杂到需要 XPath 的程度,标准库足够,省得配环境:

import xml.etree.ElementTree as ET tree = ET.parse("Annotations/defect_001.xml") root = tree.getroot() img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) for obj in root.findall("object"): label = obj.find("name").text box = obj.find("bndbox") xmin = int(box.find("xmin").text) ymin = int(box.find("ymin").text) xmax = int(box.find("xmax").text) ymax = int(box.find("ymax").text) print(label, xmin, ymin, xmax, ymax)

这段代码的逻辑是按 XML 的树形结构逐层取节点,先拿图宽高,再遍历所有 object。size 节点在根元素下,bndbox 在 object 下,路径不能写错。如果发现解析出来的坐标有 0 值或者负数,大概率是原始标注框贴着图像边缘,后面训练时容易出 NaN,建议直接过滤掉这些框,或者做裁剪。

2.2 YOLO 格式的归一化坐标与类别 id 映射

YOLO 格式的目标检测标注,是每张图一个同名 txt 文件,放到 labels 目录里。每行一个目标,格式是五列:class_id、x_center、y_center、w、h。注意,这四个值是归一化后的相对坐标,不是像素值。

归一化的计算方式是:

x_center = (xmin + xmax) / 2 / img_width y_center = (ymin + ymax) / 2 / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height

这个目录结构是 YOLO 系列的通用约定:

fabric_defect_yolo/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/

800 张图分 train 和 val 时,每个 txt 文件名必须和对应 jpg 完全同名,后缀不同,否则训练时找不到标注,界面会疯狂提示 warnings。

类别映射也需要提前定好,比如:

类别 id类别名中文含义
0damage布匹损坏
1stain污渍

这套映射表在转格式脚本里要写死,在 data.yaml 里也要写死,两边不一致是训练时 mAP 为 0 的最常见原因,没有之一。

3. 把 VOC 转成 YOLO 的完整脚本与三个校验步骤

3.1 最大化复用:一个脚本跑通格式转换和数据集划分

把 VOC 转 YOLO,核心逻辑就是遍历 JPEGImages 里所有图片,去找同名 XML,提取坐标做归一化,然后写入 labels 目录。这里给一份可以直接复制使用的脚本版本,带 train/val 划分:

import os import random import xml.etree.ElementTree as ET # 配置 voc_root = "fabric_defect" yolo_root = "fabric_defect_yolo" class_names = ["damage", "stain"] train_ratio = 0.8 random.seed(42) # 创建目录结构 for split in ["train", "val"]: os.makedirs(f"{yolo_root}/images/{split}", exist_ok=True) os.makedirs(f"{yolo_root}/labels/{split}", exist_ok=True) # 收集所有标注文件 ann_dir = os.path.join(voc_root, "Annotations") all_anns = [f for f in os.listdir(ann_dir) if f.endswith(".xml")] random.shuffle(all_anns) split_idx = int(len(all_anns) * train_ratio) train_anns = all_anns[:split_idx] val_anns = all_anns[split_idx:] print(f"train: {len(train_anns)}, val: {len(val_anns)}") def convert(ann_file, split): tree = ET.parse(os.path.join(ann_dir, ann_file)) root = tree.getroot() img_name = root.find("filename").text img_w = int(root.find("size/width").text) img_h = int(root.find("size/height").text) import shutil src_img = os.path.join(voc_root, "JPEGImages", img_name) dst_img = os.path.join(yolo_root, "images", split, img_name) shutil.copy(src_img, dst_img) yolo_lines = [] for obj in root.findall("object"): label = obj.find("name").text if label not in class_names: continue class_id = class_names.index(label) box = obj.find("bndbox") xmin = float(box.find("xmin").text) ymin = float(box.find("ymin").text) xmax = float(box.find("xmax").text) ymax = float(box.find("ymax").text) # 坐标过滤 if xmax <= xmin or ymax <= ymin: print(f"skip invalid box: {ann_file}, {label}") continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h yolo_lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_name = img_name.rsplit(".", 1)[0] + ".txt" with open(os.path.join(yolo_root, "labels", split, txt_name), "w") as f: f.write("\n".join(yolo_lines)) for split, anns in [("train", train_anns), ("val", val_anns)]: for ann in anns: convert(ann, split) print("done")

这个脚本看起来长,但每个环节都有对应场景。img_w 和 img_h 必须从 XML 里读,不能假设所有图一样大;坐标过滤这一段是在防止后续训练时 loss 变成 nan,框倒置或者面积为 0 的标注,喂给任何检测头都会出问题;写 yolo_lines 时统一保留 6 位小数,避免浮点精度不足导致框偏移明显。

划分比例按 0.8 给了 train、0.2 给 val,800 张图分出来就是 640 和 160。如果缺陷类别分布不均,需要在划分前按类别做分层抽样,否则可能 val 里某类一个都没有。分层抽样也很简单:先统计每个类别有多少张图,然后按比例从每类里抽。

3.2 转换后的三件事:统计、复查、喂训练

脚本跑完别急着开训。第一件该做的事是统计两个 split 的类别数量,确认没有由于 XML 里类别名写错而被过滤掉导致某个 split 数据缺失。第二件是随机抽几张图,把标注框画上去和原图对比,看坐标是否对齐。这一步能用 Opencv 直接画矩形框,或者用 LabelImg 打开图片和标签看。第三才是进入训练环节。

这 800 张图里如果出现了某个类别只有 50 张,而且其中 40 张都在 train、val 只有 10 张,那么 val 的 mAP 波动会非常大。此时要么改划分比例,要么做离线增强补齐稀缺类。离线增强可以是对原图做随机裁剪、旋转、亮度和对比度扰动。但增强后的图片必须重新生成标注框坐标,不是简单复制一份图就行,裁剪会改坐标、翻转也会改坐标,这一块不能偷懒。

import cv2 img = cv2.imread("fabric_defect_yolo/images/train/defect_001.jpg") h, w = img.shape[:2] with open("fabric_defect_yolo/labels/train/defect_001.txt") as f: for line in f: cls, xc, yc, bw, bh = map(float, line.strip().split()) x1 = int((xc - bw/2) * w) y1 = int((yc - bh/2) * h) x2 = int((xc + bw/2) * w) y2 = int((yc + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check_visual.jpg", img)

验证时注意:YOLO 的 txt 存的是归一化中心点坐标和宽高,画框之前要先换算回像素坐标,中心点减宽高一半得到左上角,加一半得到右下角。换算错了画出来框全偏到一边。

4. 用 YOLOv8 起训练:配置、命令与参数实测建议

4.1 数据描述文件的写法:路径必须和实际目录一致

YOLO 系列用 YAML 文件描述数据集,v5、v8、v11 写法一致:

path: /home/user/fabric_defect_yolo train: images/train val: images/val nc: 2 names: ['damage', 'stain']

这里的 path 是绝对路径,train 和 val 相对于 path。最容易犯的错是写成:

train: /home/user/fabric_defect_yolo/images/train

然后 path 也写了完整路径,两个拼到一起直接找不到文件。train 和 val 字段写相对路径,这是 YOLO 的默认解析逻辑。names 里的顺序和类别 id 必须和转换脚本里的 class_names 一致,一个叫 ["damage", "stain"],另一个写成了 ["stain", "damage"],训练不会报错,但模型输出的类别含义完全反了。

800 张图的数据量不算大,这个规模下直接选择 YOLOv8s 或者 YOLOv8m 比较合适,不要一上来就 YOLOv8x 或者更大的模型,小图数量下大模型容易过拟合,val 的 mAP 反而往下走。官方 ataset 是 COCO 预训练权重,自己的小数据集在它基础上微调,收敛速度和效果都比从零训练好很多。

4.2 常用命令和关键训练参数

训练入口命令如下:

yolo detect train data=fabric_defect.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0 project=runs_fabric name=defect_exp

跑起来之后看输出日志,关注四个指标。Box Loss 在第一个 epoch 结束应该在 1.5 以下,不然就是学习率太大或者数据格式有问题;Cls Loss 和 DFL Loss 会稳步下降,正常情况不会出现剧烈震荡;mAP50 在数据量小的时候可能前几个 epoch 都是 0,这正常,等第 20 个 epoch 左右再看趋势。

我一般习惯把 Patience 参数留着,它是早停策略,val 的 mAP 连续 N 个 epoch 不涨就自动停。比如patience=20,如果 20 个 epoch 都没有提升,说明模型已经收敛,继续训练只会过拟合。

参数名推荐值说明
imgsz640输入分辨率,缺陷是小目标可以试试 960
batch8-32看显存,8G 显存用 8,16G 用 16
lr00.01预训练权重微调不用动
mosaic1.0数据增强,小数据集建议开启
augmentTrue不关闭默认增强,800 张图不增强很难训

小目标缺陷和布纹纹理干扰是这个场景的两个主要难点。imgsz 从 640 提到 960 会显著增加对小目标的召回,但显存占用和训练时间也上去了。如果损伤和污渍这类框的面积普遍小于 32x32 像素,Imgsz 调到 960 的价值很大,值得试一次对比。

4.3 一次训练的最小验证回路

训练结束后先别急着看 mAP,直接跑一次 predict 能更快发现问题所在:

yolo detect predict model=runs_fabric/defect_exp/weights/best.pt source=test_images/ imgsz=640 conf=0.25 save=True

source 可以是单张图、一个目录或者一段视频。如果 val 的 mAP50 很高但 predict 出来的框乱飘,大概率是训练样本里长宽比极端的图太多,模型泛化性差。这时候回看数据集:布匹图片是不是都是同一朝向、同一光照?如果是,需要补充不同光线、不同角度和不同折痕背景的图片和对应标注。800 张图里如果有 100 张加入这些变化,对模型鲁棒性的帮助远超再训 500 张同分布的数据。

5. 从 800 张小数据集出发,先调数据再调模型

布匹缺陷检测这类实际产线场景,最缺的往往是带标注数据本身。800 张图虽然听起来不大,但配合预训练权重微调已经能跑出一个可用版本,关键是先把这 800 张的利用率拉满。数据增强顺序上有讲究:先做几何增强(翻转、旋转、裁剪),再做色彩增强(亮度、对比度、饱和度)。如果缺陷本身是颜色差异很小的污渍,色彩增强的幅度别拉太大,容易把正样本变没。一张布的缺陷区域往往占整幅图不到 5% 的面积,mosaic 增强在这个场景里效果很好,它会把四张图拼在一起,强制模型在小感受野上学会缺陷和纹理的区分。

调参之外,一个容易被忽略的点是多尺度训练。在 YOLOv8 里开启scale=0.5,训练时每张图会随机缩放到原图的一半到 1.5 倍,这等价于隐式增加了不同密度的布面纹理样本。800 张原图经过多尺度、mosaic 和翻转叠加,实际喂给模型的样本方差会扩大数倍。

如果你手头有 800 张之外更多的布匹图片但没标注,上一轮训完的模型可以直接拿来预标注一半,人工修正后再混合训练,两个 epochs 就能再把 mAP 往上拉几个点。这个滚动式数据扩展,比换大模型更经济,也更符合布匹缺陷这类长尾分布数据的迭代方式。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询