简介:面向毛巾表面缺陷检测的标注数据集,适用于毕业设计、课程实训或目标检测模型验证,可帮助缺少现场采集条件的学习者直接获得规范化训练样本。资源共1307个文件,包含435张jpg缺陷图片、436个XML标签与436个TXT标签,压缩包仅11.72MB;XML采用VOC标注结构,TXT为YOLO格式,逐张命名便于划分训练集与验证集。图片覆盖多种毛巾缺陷形态,解压后可直接用于YOLO、SSD、Faster R-CNN等常见检测网络的训练与评估,也可按需进行旋转、裁剪、亮度调整等数据增强以扩充样本;原始样本保留了不同角度与背景下的真实缺陷表现,有助于检验模型泛化能力。作者支持问题留言,并会不定期更新完善,当前已有419人学习浏览。对需要完整数据集支撑毕设或算法实验的同学来说,这套资源具备标注格式标准、体量轻、上手快的优势。
1. 毛巾缺陷检测数据集:435 张双格式标注,先对齐标签再谈训练
纺织品外观缺陷检测和一般的金属零件检测不太一样,毛巾这类织物表面有周期性毛圈纹理,缺陷对比度低,断纱、油渍、破洞、起球在视觉特征上相互干扰。准备用 YOLO 做毛巾缺陷检测的人,第一步卡住的往往不是模型结构,而是数据本身:训练集和验证集怎么划分、XML 和 TXT 标签是否真的描述同一个框、增强之后坐标有没有跟着变。手上这套 435 张毛巾缺陷数据集恰好覆盖了这些基础工作。它包含了对应的 VOC 格式.xml和 YOLO 格式.txt,两类标签描述同一批图片,正好用来验证一套从标注校验、数据集切分到训练落地的完整流程。下面按实际动手顺序拆解这套数据,并给出可直接复用的脚本,适合做毕业设计,也适合拿来当作缺陷检测工程落地前的数据基线。
2. VOC XML 与 YOLO TXT 标签的结构差异与解析
2.1 两种格式的坐标体系差异
VOC 格式的 XML 标签描述的是 Pascal VOC 标准标注,核心信息集中在<size>和<object>节点里。<size>里保存图片原始宽度、高度和通道数,<object>里的<bndbox>是缺陷目标框的绝对像素坐标,也就是左上角xmin, ymin和右下角xmax, ymax。这套坐标是给人看的,也方便用 OpenCV 或 Pillow 直接画框验证。
YOLO 格式的 TXT 标签则是另一套逻辑,每一行代表一个目标框,五个数字依次是类别 id、归一化后的中心点 x、中心点 y、归一化后的宽、归一化后的高。归一化的分母是图片宽高,所以同一目标在图片分辨率变化后无需重新标注,训练时模型读取的始终是相对坐标,这对输入缩放和 mosaic 增强非常友好。
两类标注可以相互转换,但前提是图片尺寸信息一致。这个数据集里 XML 和 TXT 是成对存在的,所以合法性校验就是恢复坐标交叉验证,这个动作能发现不少下载数据常见的标签错位问题。
| 项目 | VOC XML | YOLO TXT |
|---|---|---|
| 存储结构 | 树状 XML 节点 | 纯文本逐行记录 |
| 坐标形式 | 绝对像素坐标 | 归一化相对坐标 |
| 目标信息 | object/name 与 bndbox | 类 id 与中心宽高 |
| 可读性 | 高,适合人工检查 | 低,适合训练器直接读取 |
| 文件扩展名 | .xml | .txt |
2.2 用 Python 批量解析 XML 标签
拿到数据后第一件事不是训练,而是读取所有 XML 并提取关键字段,确认类别名、图片尺寸和目标数量是否符合预期。这里用xml.etree.ElementTree解析,它属于标准库,不需要额外依赖。
import xml.etree.ElementTree as ET import glob import os def parse_voc_xml(xml_path): tree = ET.parse(xml_path) root = tree.getroot() filename = root.findtext('filename') size = root.find('size') width = int(size.findtext('width')) height = int(size.findtext('height')) objects = [] for obj in root.iter('object'): name = obj.findtext('name') bndbox = obj.find('bndbox') xmin = int(float(bndbox.findtext('xmin'))) ymin = int(float(bndbox.findtext('ymin'))) xmax = int(float(bndbox.findtext('xmax'))) ymax = int(float(bndbox.findtext('ymax'))) objects.append({ 'name': name, 'bbox': [xmin, ymin, xmax, ymax], 'width': xmax - xmin, 'height': ymax - ymin }) return {'filename': filename, 'width': width, 'height': height, 'objects': objects} xml_files = glob.glob('labels_xml/*.xml') for xml_path in xml_files[:5]: info = parse_voc_xml(xml_path) print(info['filename'], info['width'], info['height'], [(o['name'], o['bbox']) for o in info['objects']])这段脚本把 XML 转成了字典结构,需要注意findtext在 bndbox 节点缺失时会返回 None,int()之前先做类型转换是为了适配某些工具导出浮点坐标的情况。类别名在训练前必须固定成一个列表,后面配置 YOLO 数据 yaml 时names的顺序会直接影响模型输出,这一步如果乱了,训练完推理结果就是错的。解析完顺手把所有 XML 中的类别做一次去重统计,能提前发现数据集中是否存在罕见类别或标注噪声。
2.3 交叉校验 YOLO TXT 标签与 XML 坐标
YOLO TXT 标签文件内容如0 0.5185 0.4965 0.0831 0.1423,要把这些归一化坐标还原成像素坐标,与 XML 中的 bndbox 比较。还原公式是:
def yolo_to_pixel(txt_line, img_width, img_height): parts = txt_line.strip().split() cls_id = int(parts[0]) cx, cy, w, h = map(float, parts[1:]) xmin = int((cx - w / 2) * img_width) ymin = int((cy - h / 2) * img_height) xmax = int((cx + w / 2) * img_width) ymax = int((cy + h / 2) * img_height) return cls_id, [xmin, ymin, xmax, ymax] with open('labels_txt/que_1.txt', 'r') as f: for line in f: cls_id, pixel_bbox = yolo_to_pixel(line, 640, 480) print(cls_id, pixel_bbox)参数含义:cx, cy是归一化中心点坐标,w, h是目标框宽度和高度相对于图片宽高的比例,乘上img_width和img_height后得到真实像素尺寸。交叉校验时最容易发现的问题是坐标零点偏移:有一部分标注工具从 1 开始计数,而 YOLO 归一化按 0 到 1 计算,会导致整体偏移一个像素,目标很大时看不出来,目标较小时 IoU 误差明显。.txt文件里面坐标超出[0,1]区间也值得留意,这通常是标注时误画出了图片边界,训练时 YOLO 能处理部分越界框,但验证阶段可能影响 mAP 计算。435 张图全部校验一遍后,再进入目录整理和训练,能省下后面排错的精力。
3. 数据目录组织与训练集、验证集划分
3.1 适配 YOLO 训练器的目录约定
从网上下载的数据集往往图片和标签混杂在一堆目录里,直接丢给训练脚本会报找不到标签的错误。YOLOv5 和 YOLOv8 的通用目录约定是:图片放在images/train、images/val,标签放在labels/train、labels/val,且标签文件名必须与图片文件名完全一致,只差扩展名。标签目录里只保留.txt,图片目录里只保留图片文件。
这里给出一个参考目录结构,按此组织数据后面配置数据 yaml 时不用改路径:
towel_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── xml_all/ │ └── *.xml └── data.yamlxml_all目录是保留原始 VOC 标注用的,经过校验确认过不会再动它。实际训练只读取labels下的.txt,所以把 XML 另行归档或直接忽略都行,不用强行放进训练目录。这样组织的好处是数据集可以被多个训练框架直接消费,不用为某个框架单独改目录。
3.2 分层抽样划分训练集和验证集
随机划分在样本量少时可能把某个类别的缺陷全部切到测试集里。毛巾缺陷数据中一类缺陷可能只出现几十次,随机划分的方差会明显影响验证结果。这里按类别做分层划分,固定随机种子保证结果可复现。
import os import random import shutil from collections import defaultdict from pathlib import Path random.seed(42) image_dir = Path('images_all') label_dir = Path('labels_txt') train_img_dir = Path('images/train') val_img_dir = Path('images/val') train_lbl_dir = Path('labels/train') val_lbl_dir = Path('labels/val') for d in [train_img_dir, val_img_dir, train_lbl_dir, val_lbl_dir]: d.mkdir(parents=True, exist_ok=True) # 统计每个 txt 文件包含哪些类别 file_categories = {} for txt_path in label_dir.glob('*.txt'): cats = set() if txt_path.stat().st_size == 0: file_categories[txt_path.stem] = {'empty'} continue with open(txt_path, 'r') as f: for line in f: cats.add(line.split()[0]) file_categories[txt_path.stem] = cats # 按类别把样本分组 category_files = defaultdict(list) for stem, cats in file_categories.items(): for cat in cats: category_files[cat].append(stem) train_stems, val_stems = set(), set() for cat, stems in category_files.items(): random.shuffle(stems) val_count = max(1, int(len(stems) * 0.2)) val_stems.update(stems[:val_count]) train_stems.update(stems[val_count:]) # 确保不重叠 overlap = train_stems & val_stems assert len(overlap) == 0, f'overlap: {overlap}' for stem in train_stems: shutil.copy(image_dir / f'{stem}.jpg', train_img_dir / f'{stem}.jpg') shutil.copy(label_dir / f'{stem}.txt', train_lbl_dir / f'{stem}.txt') for stem in val_stems: shutil.copy(image_dir / f'{stem}.jpg', val_img_dir / f'{stem}.jpg') shutil.copy(label_dir / f'{stem}.txt', val_lbl_dir / f'{stem}.txt') print('train:', len(train_stems), 'val:', len(val_stems))这段脚本先扫描每个标签文件包含哪些类别,按类别建立文件名单,再在每个类别内部做 20% 抽样。val_count = max(1, int(len(stems) * 0.2))这行的作用是当某类样本不足 5 张时也能保证验证集至少出现一次该类样本。assert检查能拦截训练集验证集重叠的问题,一旦出现重叠,可能是在某个文件里同时写了多个类别导致的集合重复,检查标签内容即可定位。图片后缀.jpg要与实际图片统一,有些标注环境导出的是.JPG或者.png,不统一时shutil.copy会直接报文件不存在。
3.3 样本量与划分边界的选择
435 张图片在缺陷检测场景里属于小样本数据集。训练集 300 多张、验证集 80 多张是比较稳妥的比例,验证集再大训练数据量不够,模型容易欠拟合。如果只是为了验证训练流程能跑通,也可以按 9:1 划分,甚至把全部数据放在 train 目录,val 目录放几张同类图片占位,YOLOv8 允许 val 目录为空但会在验证阶段提示警告。需要注意的是验证集不能只挑简单的样本,缺陷检测的验证集应该尽量覆盖不同缺陷形态,否则训练过程中看到的 mAP 指标会虚高,早停时得到一个不能代表真实效果的模型。
4. 数据增强流水线与边界框协同变换
4.1 毛巾纹理场景下的增强策略选择
毛巾图像的毛圈纹理在 resize 后会产生摩尔纹,缺陷目标容易被纹理干扰。增强的任务不是简单复制样本,而是让模型对光照变化、模糊程度、布料形变和局部遮挡不敏感。推荐优先做亮度对比度扰动、高斯模糊模拟不同焦距、网格畸变模拟布料拉伸,水平翻转和旋转作为基础增强。色彩空间扰动在毛巾数据上幅度不能太大,白色和浅色毛巾轻微色偏就会改变检测目标的外观特征。
Mosaic 增强适合小数据集但有一个注意事项:拼接后目标被缩小,毛巾缺陷本来就小,使用时要观察小目标检测指标 mAP50 的变化。若模型对缺陷召回率低,可以把 Mosaic 概率调低,用普通的马赛克加复制粘贴增强替代。是小样本不是零样本,增强倍数控制在 3 到 5 倍之间比较合理,过度增强会让模型记住增强伪影而不是缺陷特征。
4.2 基于 albumentations 的增强流水线代码
albumentations 库对边界框增强支持完善,可以直接输出变换后的 YOLO 坐标,比手动做仿射变换安全。这个数据增强脚本需要与训练代码解耦,提前生成增强样本,也可以在线嵌入训练时使用。
import albumentations as A import cv2 train_transform = A.Compose([ A.RandomBrightnessContrast(brightness_limit=0.15, contrast_limit=0.15, p=0.6), A.GaussianBlur(blur_limit=(3, 5), p=0.4), A.HorizontalFlip(p=0.5), A.Rotate(limit=15, border_mode=cv2.BORDER_REFLECT_101, p=0.5), A.GridDistortion(num_steps=5, distort_limit=0.2, p=0.3), A.CoarseDropout(max_holes=6, max_height=40, max_width=40, fill_value=0, p=0.3), ], bbox_params=A.BboxParams( format='yolo', label_fields=['class_labels'], min_visibility=0.3 ))参数说明:format='yolo'告诉 albumentations 输入的框是归一化的中心点宽高格式;label_fields声明类别标签所在的字段名,这样增强时类别不会和坐标错位;min_visibility=0.3表示目标框被裁剪后如果可见面积低于原来的 30%,这个框会被丢弃,避免训练标签中出现几乎全被裁掉的目标。Rotate设置的border_mode在旋转时用反射填充而不是纯黑填充,因为纯黑填充会产生大量假边缘,干扰缺陷特征提取。CoarseDropout模拟油渍或遮挡,但fill_value=0是黑色填充,实际使用中可以根据毛巾颜色改成均值填充。
调用方式如下,注意需要把原始归一化坐标转成 numpy 数组,并保证传入的class_labels列表与框一一对应:
import numpy as np def apply_augmentation(image_path, label_path): image = cv2.imread(image_path) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) boxes, labels = [], [] with open(label_path, 'r') as f: for line in f: parts = line.strip().split() labels.append(parts[0]) boxes.append([float(x) for x in parts[1:]]) if len(boxes) == 0: return image, np.array([]), np.array([]) transformed = train_transform( image=image, bboxes=boxes, class_labels=labels ) return transformed['image'], transformed['bboxes'], transformed['class_labels']实际编码中有一个容易忽略的问题:boxes为空时部分版本 albumentations 调用会直接抛异常,需要先判断再走增强,否则训练过程时断时续。cv2.imread读进来是 BGR 通道顺序,如果增强流程里有颜色相关的操作,需要先转成 RGB,否则最后保存图片时颜色会偏蓝红,人眼看着别扭但模型训练时影响不大。
4.3 增强后标签的合法性自检
增强不是生成完文件就结束,变换后的坐标可能因为旋转、裁剪超出图像边界。一个合法的 YOLO 标签要求所有坐标值在 0 到 1 之间,宽高为正数。写一段快速自检脚本检查所有增强后的 txt 文件:
def validate_yolo_label(txt_path): with open(txt_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) != 5: return False, f'field count: {len(parts)}' cls_id, cx, cy, w, h = parts if not cls_id.isdigit(): return False, f'class id not int: {cls_id}' values = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in values): return False, f'coord out of range: {values}' if values[2] <= 0 or values[3] <= 0: return False, f'non-positive wh: {values}' return True, 'ok'values列表前两个是中心点坐标,后两个是宽高。宽高中任何一个等于 0 或负数,都说明增强时框被严重压缩或数据转换出现错误,必须在训练前过滤掉。自检脚本输出异常的样本编号后,可以直接定位到对应增强配置并检查是否存在过大的旋转角度或过小的min_visibility设置。这一步做到位,后面训练过程才不会莫名出现 NaN loss 或训练中断。
5. 用 YOLOv8 跑通 435 张毛巾缺陷数据训练与排错
5.1 数据配置文件 data.yaml 的编写
YOLOv8 训练时读取一个 yaml 文件描述数据位置和类别信息。路径建议写绝对路径,避免不同工作目录启动训练时找不到数据。
path: /home/user/towel_dataset train: images/train val: images/val nc: 4 names: 0: 'oil_stain' 1: 'broken_yarn' 2: 'hole' 3: 'pilling'names里的类别字符串本身不影响训练精度,但它必须与数据集中真实的类别对应,否则验证时输出的混淆矩阵会出现一张图片对应多个类别名的错乱。类别 id 在 TXT 标签中默认从 0 开始,这个 yaml 里 0 对应'oil_stain',那 TXT 中 class_id 为 0 的框就表示油渍。对这套 435 张的数据要确认 XML 中 object/name 的实际取值,不同毛巾缺陷数据集类别命名不同,可能是 B1、B2 这种代号,也可能是英文单词,配置文件照实抄即可。
5.2 训练命令与超参数选择
小样本训练不能直接套用 COCO 预训练模型的默认超参数。预训练权重用yolov8n.pt或yolov8s.pt,n 模型速度快,s 模型精度略高,在 435 张样本上建议从 n 开始跑,先验证数据链路是否正常,再升级到 s。基础命令:
yolo detect train \ model=yolov8n.pt \ data=towel_dataset/data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ project=runs/towel \ name=exp1 \ seed=42参数分解:epochs=100配合patience=20,如果在 20 个 epoch 内验证集 mAP50 没有提升就提前结束;batch=16是 8GB 显存可以跑得动的数值,显存更小就降到 8;seed=42固定随机种子保证结果可复现,对毕设中重复实验尤其重要。训练时观察 loss 曲线,YOLOv8 的日志会输出box_loss、cls_loss、dfl_loss三项,正常情况三者都逐渐下降。小数据集很容易出现过拟合:训练 loss 持续下降但验证集 mAP 不再上涨,此时不要盲目加训练轮数,应该回头检查增强强度或改用更小的模型。
5.3 数据相关的常见坑
第一种情况是标签类别 id 和 names 不对应。有人习惯把背景当类 0,于是缺陷标签从 1 开始,YOLO 默认从 0 开始,训练出来检测结果整体错一位,现象是某个缺陷类别检测不出来或者把 A 类缺陷识别成 B 类。第二种情况是空标签文件。某些 XML 转换成了空的 txt,YOLOv8 会默认该图片没有目标并正常训练,但这类图片占比过高时,模型会倾向把所有区域都预测成背景。处理方式分两种:如果空标签图确实有缺陷,回标注工具补框;如果只是验证集里多放了一些正常毛巾图,合理但不要超过验证集总量的三成。第三种情况是验证指标和实际表现不一致。训练日志里的 mAP50 是模型在验证集上的表现,如果验证集和训练集图片来自同一批布料、同一光照,指标会偏高,部署到产线换一批布料后精度明显下降。更好的实验设计是把不同批次或不同光照条件的图片单独分为一组,模拟真实场景中的分布漂移,再用这份小样本数据去验证增强策略和模型容量选择的合理性。
本文还有配套的精品资源,点击获取