ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

毛巾缺陷检测数据集实战:从XML解析到YOLOv8训练全流程

2026/9/15 1:32:42 拓冰建站 浏览量
毛巾缺陷检测数据集实战:从XML解析到YOLOv8训练全流程 简介面向毛巾表面缺陷检测任务的数据集适合高校学生用于毕业设计、课程项目或目标检测模型训练。数据集包含435张真实采集的毛巾缺陷图片每张图片均配套VOC格式xml标签与YOLO格式txt标签可直接用于Faster R-CNN、SSD、YOLO系列等主流检测模型的训练与验证省去手动标注的繁琐流程。压缩包共1307个文件其中jpg图片435个、xml标签436个、txt标签436个整体仅11.72MB下载与解压均很轻量。标签文件覆盖常见缺陷类别内容预览显示图片以que(编号)形式命名便于按编号索引与数据划分。当前已有419人浏览学习资源虽小但标注完整适合作为缺陷检测方向的入门实践与毕设基线。如需扩充样本量可自行采用翻转、旋转、色彩抖动等方式进行数据增强博主也提示可进一步索取数据增强脚本。整体来看这份资料能帮助学习者绕开数据收集与标注的重复劳动直接聚焦模型搭建、训练调参与效果评估是快速启动毛巾缺陷检测项目的高性价比选择。1. 435张毛巾缺陷检测数据集为什么足够跑通一版能用的YOLO模型435张带标注的毛巾缺陷图片放在当前动辄上万张的公开数据集面前确实不算大但对工业布面质检这类垂直场景这个量级恰恰是多数团队第一次拿到数据的真实体量。毛巾表面纹理密集破洞、污渍、褶皱、线头这类缺陷又往往是小目标检测难度不在“有没有”而在“能不能在复杂纹理里稳定框住”。这份数据集同时提供了VOC格式的xml标签和YOLO格式的txt标签意味着你可以跳过最磨人的标注转换环节直接进入模型训练。对于做算法验证、毕业设计或者产线预研的工程师来说它的价值不在于刷新mAP纪录而在于让“缺陷检测数据集→可运行的检测模型”这条链路在几小时内闭环。下面从xml解析开始把整个流程拆开讲。2. 毛巾缺陷检测数据集的xml标签VOC格式字段与解析脚本2.1 一个xml文件里记录了哪些关键信息这份数据集里的xml标签采用Pascal VOC标注风格这是工业缺陷检测里最常见的标注格式之一。一个典型的xml文件结构大致如下annotation foldertowel_defect/folder filenameIMG_001.jpg/filename size width640/width height480/height depth3/depth /size object namehole/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin132/xmin ymin87/ymin xmax159/xmax ymax114/ymax /bndbox /object /annotationfilename节点记录图片文件名size节点记录图片宽高object节点每出现一次就代表一个标注目标name是缺陷类别bndbox是左上角和右下角坐标。多数人用这份数据时只关注name和bndbox但truncated和difficult这两个字段也值得留意它们通常表示目标被截断或难以辨识在转换标签和训练时是否跳过会对模型评估产生实际影响。2.2 用ElementTree批量解析所有xml并统计类别分布拿到数据集第一件事不是急着训练而是先统计文件名、图片尺寸、类别和框数量是否与原始描述一致。解析xml用Python标准库的xml.etree.ElementTree就足够不需要额外装lxml。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter def parse_voc_xml(xml_path): 解析单个VOC xml返回标注信息字典 tree ET.parse(xml_path) root tree.getroot() size root.find(size) objects [] for obj in root.findall(object): bndbox obj.find(bndbox) objects.append({ name: obj.findtext(name), bbox: [ float(bndbox.findtext(xmin)), float(bndbox.findtext(ymin)), float(bndbox.findtext(xmax)), float(bndbox.findtext(ymax)) ], difficult: obj.findtext(difficult, 0), truncated: obj.findtext(truncated, 0) }) return { filename: root.findtext(filename), width: int(size.findtext(width)), height: int(size.findtext(height)), objects: objects } xml_dir Path(annotations) class_counter Counter() total_boxes 0 empty_xml [] for xml_file in sorted(xml_dir.glob(*.xml)): ann parse_voc_xml(xml_file) if not ann[objects]: empty_xml.append(xml_file.name) continue for obj in ann[objects]: class_counter[obj[name]] 1 total_boxes 1 print(类别分布:, dict(class_counter)) print(总框数:, total_boxes) print(无目标xml数量:, len(empty_xml))这段代码的逻辑是遍历annotations目录下所有xml逐个解析filename、size和object节点把类别名和边界框存入字典最后用Counter统计类别数量。输出无目标xml的列表很重要这类xml如果没有对应txt标签转换时容易被遗漏训练时则表现为“图片有jpg但没有label文件”。2.3 xml解析时最常见的四个坑及排查手段坑表现排查手段filename与图片实际文件名不一致训练时找不到图片解析后与images目录做一次集合比对列出缺失项bndbox越界xmax/ymax大于size宽高检查bbox是否超出width/height超出做截断类别名不统一同一种缺陷有hole和holes两个名字打印所有唯一类别名人工核对后做映射表空xml或object缺少name转换脚本直接报错解析时捕获异常并记录文件名单独人工复核实际解析时建议先跑一遍统计脚本把类别分布和文件名不一致问题暴露在前面再进入转换步骤。否则一个脏数据会让后续训练反复跳坑排查成本远高于最开始的两分钟检查。3. 把毛巾缺陷检测数据集的xml转成yolo格式坐标归一化与数据集划分3.1 yolo格式标签的规则与坐标换算YOLO系列使用的标签格式是每张图片对应一个同名txt文件每行文本表示一个目标类别id 归一化中心点x 归一化中心点y 归一化宽 归一化高。与xml里的绝对坐标不同yolo标签里的四个数值全部缩放到0到1之间换算公式为x_center ((xmin xmax) / 2) / width y_center ((ymin ymax) / 2) / height box_width (xmax - xmin) / width box_height (ymax - ymin) / height归一化的好处是不同分辨率图片可以共用同一套模型输入训练时无论原图是640乘480还是1920乘1080标签数值都在同一量纲下。转换时的关键决策是是否跳过difficult为1的目标我一般会保留difficult目标但单独记录因为毛巾缺陷样本本来就少多做一次误检分析比直接丢弃更有价值。3.2 完整的xml转txt转换脚本这份数据集同时提供了xml和yolo两种标签理论上不需要自己转换但在实际项目中经常遇到只有xml的情况转换脚本本身也是验证标签质量的重要工具。下面给出一个带边界检查和类别映射的转换脚本。import xml.etree.ElementTree as ET from pathlib import Path CLASS_MAP { hole: 0, stain: 1, wrinkle: 2, loose_thread: 3 } def voc_to_yolo(xml_path, output_dir, class_mapCLASS_MAP): tree ET.parse(xml_path) root tree.getroot() width int(root.findtext(size/width)) height int(root.findtext(size/height)) filename root.findtext(filename) lines [] for obj in root.findall(object): name obj.findtext(name) if name not in class_map: print(f跳过未映射类别: {name} in {xml_path.name}) continue bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # 越界截断防止训练时坐标出现负数或大于1 xmin max(0, min(xmin, width - 1)) xmax max(0, min(xmax, width - 1)) ymin max(0, min(ymin, height - 1)) ymax max(0, min(ymax, height - 1)) x_center ((xmin xmax) / 2) / width y_center ((ymin ymax) / 2) / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height lines.append(f{class_map[name]} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) if lines: output_path output_dir / f{Path(filename).stem}.txt output_path.write_text(\n.join(lines), encodingutf-8) xml_dir Path(annotations) output_dir Path(labels_yolo) output_dir.mkdir(exist_okTrue) for xml_file in sorted(xml_dir.glob(*.xml)): voc_to_yolo(xml_file, output_dir)脚本的核心逻辑是从size节点读取图片宽高遍历每个object节点从类别映射表获取类别id然后按公式计算归一化坐标。越界截断这步容易被忽略工业数据里标注框偶尔会稍微画出图片边缘不处理的话训练时会看到label坐标接近1.01这样的异常值导致损失函数在个别样本上异常波动。类别映射单独抽成CLASS_MAP字典方便按实际数据集的类别名称调整。3.3 数据目录结构与train/val划分转换完成后的标准目录结构如下这是ultralytics框架约定的组织方式也是yolov8训练自己的数据集的常用布局。towel_defect/ ├── images/ │ ├── train/ │ │ ├── IMG_001.jpg │ │ └── ... │ └── val/ │ ├── IMG_102.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── IMG_001.txt │ │ └── ... │ └── val/ │ └── IMG_102.txt └── dataset.yaml435张图的数据量做90比10的划分即可也就是train约390张、val约45张。划分时要注意先对全部标签按缺陷类别做分层抽样避免某个类别在val集里一个样本都没有。用sklearn的train_test_split可以快速实现分层划分。import shutil from sklearn.model_selection import train_test_split images sorted(Path(towel_images).glob(*.jpg)) labels sorted(Path(labels_yolo).glob(*.txt)) valid_pairs [(img, Path(labels_yolo) / f{img.stem}.txt) for img in images if (Path(labels_yolo) / f{img.stem}.txt).exists()] train_pairs, val_pairs train_test_split( valid_pairs, test_size0.1, random_state42, stratifylabels )如果标签里某些类别框数量极少stratify参数按文件维度做分层并不可靠这时可以按“每个文件的主导类别”做分层或者干脆手动把包含稀有类别的图片固定划入train确保训练时能看到全部类别。划分后把文件名列表保存为txt方便复现划分结果。4. 用yolov8训练这个毛巾缺陷检测数据集命令与超参数设置4.1 编写dataset.yaml配置文件YOLOv8训练自己的数据集时yaml文件是入口。data.yaml的作用是告诉ultralytics数据在哪、类别有哪些。path: ./towel_defect train: images/train val: images/val names: 0: hole 1: stain 2: wrinkle 3: loose_threadpath写数据集根目录train和val写相对于path的图片目录路径names是类别id到类别名的映射。这里有个需要注意的点类别的顺序必须和转换脚本里CLASS_MAP字典的id一致否则训练出的模型预测结果和实际类别会对不上。435张图的类别数不建议超过5类类别过多时每类的平均样本数会暴跌模型会明显偏向样本多的缺陷类型。4.2 训练命令与关键参数说明yolo detect train \ datadataset.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch16 \ patience50 \ cos_lrTrue \ optimizerAdamW \ lr00.001 \ valTrue参数推荐值作用与说明modelyolov8n.pt预训练模型权重小数据集从nano开始epochs200配合早停机制不一定要训满imgsz640毛巾原图分辨率不高时640够用目标太小可试1280batch8-16显存不够时优先降batch而不是降imgszpatience50验证集mAP连续50轮不提升就自动停止cos_lrTrue余弦退火学习率小数据集收敛更平稳训练时模型从yolov8n.pt加载的是COCO预训练权重这比从零训练效果好很多因为模型已经学会基础纹理和边缘特征毛巾纹路虽然与COCO里自然图像差异不小但底层特征仍然可以复用。如果显卡显存只有8G把batch降到8imgsz保持640基本上能跑起来。4.3 训练日志里的指标怎么看训练过程中主要关注三个指标box_loss是否持续下降、mAP50是否稳步上升、mAP50-95与mAP50的差距是否合理。毛巾缺陷检测这类任务mAP50是更重要的参考指标因为工业场景里对“框得准不准”的要求不如对“有没有检出”的要求高mAP50-95偏低是正常的尤其小目标占比高时。这里有个反直觉的点box_loss下降不代表检测效果好因为box_loss反映的是边界框回归的收敛程度真正决定你有没有检出缺陷的是cls_loss和最终的mAP。我一般会在训练结束后打开results.png看验证集mAP50曲线是否出现过拟合式的先升后降。训练集loss持续下降而val的mAP开始抖动时就是典型的过拟合信号435张小数据集上特别容易出现。5. 预测结果与xml标注双向校验低召回高误检的排查技巧5.1 用predict输出预测框并与原始标注对齐训练结束后先别急着换大模型把预测结果和原始xml标注做一次双向校验能快速定位数据质量问题。常见做法是把val集图片用训练好的权重预测一遍让模型输出带置信度的txt文件。yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcetowel_defect/images/val \ conf0.25 \ save_txtTrue \ save_confTrue预测生成的txt在runs/detect/predict/labels目录下每行格式与训练标签一致但末尾多了置信度。写个小脚本把预测框与xml里的真实框做IoU配对统计哪些真实框没有被任何预测框覆盖哪些预测框找不到匹配的真实框。前者是漏检后者是误检。430多张图里如果漏检集中在某个类别先回去看这个类别的标注框是否太小或太密集再决定是补数据还是调模型。5.2 类别不平衡与小目标缺陷的处理技巧毛巾缺陷里破洞往往只有十几个像素整图看就是一小团黑色模型容易把它和纹理阴影混在一起。处理这类小目标缺陷我一般会先调整推理参数而不是立刻换模型。把conf从0.25降到0.1观察多出来的检测框是真实缺陷还是误检。如果降阈值后召回率提升明显而误检可接受说明模型学到了特征只是置信度偏低这种情况更适合在训练时增加针对小目标的增强策略比如Mosaic增强或把imgsz提高到960。训练层面ultralytics提供的class weights参数可以按类别数量反比设置权重样本少的缺陷类别获得更大的损失惩罚相当于在损失函数层面平衡各类别贡献。5.3 一个快速验证标签质量的小技巧训练前把xml直接画回原图肉眼扫一遍所有标注框。具体方法是解析xml后把bndbox画成彩色矩形类别名称标注在框上方然后按每行4到6张图拼成大图输出。毛巾纹理本身有周期性图案一个容易被忽略的问题是标注框是否框进了纹路阴影这类伪缺陷标注会直接带偏模型。这个检查虽然土但比任何自动化校验都能更快发现系统性问题。确认标签质量没问题后再回去看训练日志和预测结果小数据集上的模型迭代基本就在这三个环节之间循环修正错误标签、调整增强策略、验证mAP变化。本文还有配套的精品资源点击获取