YOLOv9针织品瑕疵检测实战:从数据集标记到部署
简介这份面向针织品瑕疵检测的YOLOv9标注数据集定位在工业视觉质检与目标检测教学、实验场景适合算法工程师、数据从业者及高校学生用来训练、验证和调优检测模型。包内共105个文件包括52张原始织物图像、52份一一对应的txt标注文件以及1个yaml配置文件图片与标注直接配对放入YOLOv9项目即可开始训练省去手动标注和格式转换的重复劳动压缩包整体仅5.37MB体量小巧便于快速下载、复现实验。数据源自真实拍摄的织物样本覆盖多类常见瑕疵形态既能支撑搭建小型检测基线也能帮助初学者理解YOLOv9的标注规范、目录结构与数据组织方式。目前已有836人学习下载对需要低成本验证检测方案或积累瑕疵样本的读者来说是一份实用且易上手的入门数据集可用于毕业设计、算法对比或工程预研。1. 针织品瑕疵检测数据集为什么YOLOv9标记是质检自动化的第一道门槛针织圆机的坯布以每分钟几十米的速度流过检验台质检员要在这片流动的布面上盯住破洞、漏针、油污一盯就是几小时视线离开二十秒就可能放走一截不合格的布。把这段判断交给yolov9先得有能直接喂给它的数据——针织品瑕疵检测数据集 yolov9标记.zip这类包的存在意义正是把布面图像和txt标注按YOLO格式配好解压即训省掉拍照标框和格式转换的苦活。它解决的是纺织质检自动化里最磨人的数据环节适合想落地瑕疵检测的工程师也适合拿真实数据练手YOLO闭环的新手。下面按我跑产线项目的顺序从解压讲到上线。2. 拆开yolov9标记.zip标签结构、格式转换与数据体检2.1 标注txt的真实结构类别ID与五个归一化数字所谓yolov9标记本质和yolov8、yolov5用的是同一种标注格式这也意味着这个数据集可以无缝接到yolov8训练自己的数据集的现有流程里。每张图片对应labels目录下一个同名txt文件名一致、扩展名不同这是YOLO系列能直接读取的前提。txt里每一行代表一个瑕疵目标固定五个字段0 0.5123 0.3341 0.0872 0.0654格式是类别ID 中心点x 中心点y 宽度 高度。类别ID从0开始对应yaml里names数组的下标四个坐标全部归一化到0到1之间分子是像素值分母是图片的宽和高。很多新手在这一步翻车直接把像素坐标写进txt训练时损失曲线烂成一团baseline都跑不起来。针织品瑕疵的类别命名行业里一般按疵病种类走常见的包括破洞hole、污渍stain、漏针drop_stitch、油污oil_stain、横条color_bar。解压后先打开一个txt对照图片看一遍确认类别ID与名称的对应关系这一步花五分钟能避免后续类别张冠李戴的灾难。为了直观确认标注质量我习惯先写一个十几行的预览脚本把归一化坐标还原成像素坐标画框。import cv2 img cv2.imread(images/train/001.jpg) h, w img.shape[:2] with open(labels/train/001.txt) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls, cx, cy, bw, bh parts cx, cy, bw, bh float(cx) * w, float(cy) * h, float(bw) * w, float(bh) * h x1, y1 int(cx - bw / 2), int(cy - bh / 2) x2, y2 int(cx bw / 2), int(cy bh / 2) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, cls, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1) cv2.imwrite(preview.jpg, img)这段脚本把每行标注的归一化中心坐标乘以图片实际宽高还原成像素级的左上角和右下角然后画框和类别号。运行后逐张翻看预览图主要看三点框是否贴住瑕疵边缘、框是否明显小于或大于目标、有没有漏标或者标错类别。我见过不少数据集在打包时做过缩放或压缩标注坐标全漂移了这时候直接开训等于在脏数据上炼丹纯属浪费时间。2.2 从VOC/COCO迁移到YOLOv9坐标换算脚本如果手里的数据原本是VOC的xml或COCO的json需要先转成YOLO格式再用。这种情况在纺织场景里很常见——工厂早期找测绘公司标了一批VOC格式的数据或者自己用标注工具默认导出的是JSON。转换逻辑不复杂核心是坐标系的换算。import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) if size is None: return img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_map: continue bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h bw (xmax - xmin) / img_w bh (ymax - ymin) / img_h lines.append(f{class_map[cls_name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) class_map {hole: 0, stain: 1, drop_stitch: 2} voc_to_yolo(001.xml, 001.txt, class_map)换算公式本身很简单中心点x等于左右边界的平均值再除以图片宽度宽等于右减左再除以图片宽度。但有两个细节容易踩雷。一是img_w和img_h必须取xml里记录的原始分辨率不能拿训练时要resize的目标尺寸去换算二是xml里的坐标是整数转换后要保留足够的小数位我一般保留六位或者干脆用Python原生浮点输出避免精度损失导致框变小。COCO的json转换同理只是坐标从包围盒的[x, y, w, h]变成中心点json里的w和h可能含小数换算时直接用原值归一化即可。如果原数据是未裁剪的整卷布大图标注里还可能混着超出图片边界的框这种越界框建议直接裁掉或缩进边界内否则训练时YOLO处理到这部分容易报警告损失掉一部分正样本处理数据集用于yolov8或yolov9训练时这是最容易被忽略的一环。2.3 先体检再训练类别分布、空标签与越界坐标拿到打包后的数据不管来源是哪训练前我都会跑一遍体检脚本把它当成数据的体检报告。看三个指标类别分布是否均衡、有没有空标签、坐标有没有越界。这三个问题对应三种不同的故障模式症状在第四章详细展开这里先说怎么查。import os from collections import Counter ALLOWED_CLASSES 5 # 与 yaml 里的 nc 保持一致 cls_counter Counter() empty_labels [] bad_lines [] for split in [train, val]: lab_dir flabels/{split} if not os.path.isdir(lab_dir): continue for name in os.listdir(lab_dir): if not name.endswith(.txt): continue path os.path.join(lab_dir, name) with open(path) as f: lines [ln.strip() for ln in f if ln.strip()] if not lines: empty_labels.append(f{split}/{name}) continue for ln in lines: parts ln.split() if len(parts) ! 5: bad_lines.append(f{split}/{name}: fields{len(parts)}) continue cls int(parts[0]) vals [float(v) for v in parts[1:]] cls_counter[cls] 1 if cls ALLOWED_CLASSES or any(v 0 or v 1 for v in vals): bad_lines.append(f{split}/{name}: {ln}) print(类别分布:, dict(cls_counter)) print(空标签文件数:, len(empty_labels)) print(异常行数:, len(bad_lines)) for item in bad_lines[:10]: print( , item)脚本逻辑按split遍历labels目录读取每个txt的每一行检查字段数量是否为5、类别ID是否越界、四个坐标是否都落在0到1之间。空标签在YOLO训练里会被跳过如果某个类别的全部样本都是空标签模型永远学不到这个类最终的AP必然是0越界坐标有的版本会自动clip有的直接抛异常中断训练。体检报告出来后我通常的做法是空标签直接删掉对应图片越界坐标写个三行脚本clip到边界内类别不均衡放到后面用采样或增强来缓解。3. 在YOLOv9上跑通针织品瑕疵训练配置、命令与参数调优3.1 数据集配置文件yaml路径、nc与names的顺序陷阱数据体检通过后第一步是写数据配置文件。YOLO系的yaml格式从yolov5一路到yolov9基本长一个样坑不在格式在内容。# knit_defect.yaml path: /data/knit_defect # 数据集根目录建议绝对路径 train: images/train # 训练图片目录 val: images/val # 验证图片目录 nc: 5 # 类别数 names: # 类别名顺序必须与txt里的ID一致 0: hole 1: stain 2: drop_stitch 3: oil_stain 4: color_bar三个点最容易出错。第一path建议写绝对路径相对路径在不同机器上跑会因为当前工作目录不同而找不到数据报错通常是AssertionError: train: No images found光看报错根本猜不到是路径问题。第二nc和names必须和标签txt里的类别ID一一对应names列表里的第0项就是ID为0的类别顺序错一位模型学出来就是把破洞叫成污渍排查起来极其痛苦。第三train和val写目录还是写txt文件列表都行直接写目录时Loader会在目录下递归找所有图片但目录结构必须是images和labels平级图片放在images下同名txt放在labels下放错位置训练时一张图都加载不到。3.2 训练命令与六个关键参数怎么设配置写完训练命令本身不复杂真正需要动脑的是参数。这是我最常用的起点命令python train.py \ --data knit_defect.yaml \ --cfg models/detect/yolov9-c.yaml \ --weights yolov9-c.pt \ --batch-size 16 \ --imgsz 640 \ --epochs 150 \ --patience 30 \ --device 0逐个说参数。--cfg是模型结构配置yolov9-c是常规版参数量和计算量适中瑕疵检测场景首选yolov9-e是扩展版容量更大适合小目标密集的场景但显存要求也高8G卡跑c模型做1280输入都吃力。我的经验是先从c开始不要一上来就e。--weights用COCO预训练权重初始化能显著加速收敛。如果没有对应权重训练从随机初始化开始收敛慢而且容易在最开始几个epoch就发散。--batch-size按显存调24G卡可以上328G卡降到8。batch太小BN的统计量不稳定损失曲线振荡得厉害看起来像没在学习。--imgsz 640是速度和精度的平衡点但漏针这类细长目标在640下宽度只有两三个像素特征匹配很吃亏显存够的话直接上1280。--epochs 150是我的起点值实际训练通常跑不满靠--patience 30做早停连续30个epoch验证集mAP没提升就停能省下一大半训练时间。--device指定GPU编号多卡写0,1,2,3。3.3 训练过程中看什么三条止损线与早停时机训练跑起来不是挂机等结果我习惯在前几个epoch盯紧三条止损线。第一条前5个epoch如果loss出现NaN或者跳变幅度超过一个数量级立即停车检查学习率和数据类型。常见原因是标签里有NaN坐标或者类别ID越界2.3节的体检脚本能提前拦下来。第二条训练集loss持续下降但验证集loss在某个epoch后开始反弹这是过拟合的信号。瑕疵数据集通常只有几千张图模型容量一大就背样本。看到这个苗头优先加数据增强而不是换大模型换大模型只会加速过拟合。第三条验证集mAP在某个值附近横盘超过20个epoch不动说明模型到了容量上限或者数据里有系统性噪声。这时候不是等它自己突破而是回头查标注噪声或者考虑换更大的输入尺寸。训练中判断最佳模型不能看训练集loss要以验证集mAP为准跑完后把best.pt单独拷出来改名存放避免和last.pt混淆——best是验证指标最好的权重last是最后一轮的实际部署只用best。4. 针织品瑕疵检测的五个踩坑记录从标注噪声到部署翻车4.1 破洞与深色阴影分不清标注噪声怎么过滤现象训练时loss很低验证集mAP到0.9以上但拿产线新拍的深色布测试破洞几乎全漏误报还多。原因数据集中破洞样本几乎都来自浅色布标注时标框把深色布上的组织阴影也框进去了模型实际学到的是浅色背景上的暗色斑块这个背景特征而不是经纬线断裂形成的小孔这个结构特征。解决按布料底色做分层抽样把深色布样本补到每类不低于总样本的20%同时对深色布做亮度抖动增强让模型摆脱对背景亮度的依赖。标注层面把阴影误标成破洞的那部分框删掉重标这一步只能用人力没有捷径。4.2 漏针是长条形小目标输入尺寸与特征匹配的取舍现象其他类AP都在0.8以上漏针的AP只有0.5左右损失曲线里漏针的贡献被其他类别淹没。原因漏针是竖直细长条宽度2到4像素高度能到数十像素在640×640输入下占的像素比例极小。模型经过多次下采样后它在深层特征图上可能只落在一个格子内正样本匹配数量少模型压根学不到足够的特征。解决第一优先把imgsz从640提到1280让细长目标在特征图上占据更多格子显存不够就上SAHI这类切片推理工具把原图切成512×512的patch分别检测再合并坐标模型层面可以换yolov9-e特征提取能力更强。不要指望靠多跑几个epoch解决这是输入分辨率的物理瓶颈。4.3 污渍样本占六成类别不均衡把少数类牺牲掉现象训练到中后期验证集上污渍的recall很高但破洞和漏针的AP开始掉模型对多数类过拟合。原因典型的类别不均衡。数据集里污渍样本占了六成以上每个batch里污渍的目标数远大于其他类梯度被多数类主导少数类学不充分。解决先做统计做到心里有数再选方案。用copy-paste增强把少数类目标粘贴到其他图上过采样代价最小或者按类别做采样权重让每个batch的类别比例尽量均衡。实在不行就补标数据把少数类样本数拉到多数类的三分之一以上这是最笨但最稳的办法。4.4 验证集切分不当mAP虚高掩盖真实水平现象验证集mAP报0.93上线后实际漏检率远高于验证结果客户验收不通过。原因划分train/val时按文件夹随手切的咔一刀同一个大卷布的照片被拆进两个集合。模型在验证时相当于见过同一匹布的纹理和光照属于数据泄露mAP虚高真实水平要打个七折。解决按卷号或批次划分同一卷布、同一批次的照片只能出现在一个集合里。纺织数据是按卷采集的文件名里通常有卷号字段按这个字段做分组切分。切完先验证一遍训练集和验证集的图片文件名不能有重复前缀。4.5 换车间就掉点光照与批次色差的域迁移问题现象A车间白天采的数据训练完拿到B车间晚上测试mAP直接掉10个点误报率飙升。原因两个车间光源色温不一样布匹批次不同颜色也有漂移模型把A车间的颜色分布当成了特征的一部分换到B车间就翻车。解决训练阶段把HSV增强的hue幅度从默认0.015调到0.05saturation从0.7调到1.2让模型对颜色变化更鲁棒数据层面到B车间补拍一两百张带瑕疵和无瑕疵的图加入训练集做finetune。这个补拍习惯我现在每个项目进场就拍省得后期返工。5. 验证与部署上产线前必须做的坏例回放与速度权衡5.1 逐类AP评估短板类别一眼看出数据摆平后训练收尾时我最关心的不是总mAP而是每一类的AP。总mAP被多数类拉高少数类的短板会被掩盖。用val.py跑验证参数和训练时保持一致尤其是imgsz。python val.py \ --data knit_defect.yaml \ --weights runs/train/exp/weights/best.pt \ --imgsz 640 \ --save-json跑完看结果文件里的per-class AP有--save-json会额外输出coco格式的json方便对齐标准评估指标。假设你跑完得到这样一张表类别AP0.5诊断方向hole0.92结构清晰正常stain0.85与阴影有混淆可接受drop_stitch0.61细长小目标按4.2处理oil_stain0.89正常color_bar0.78标注边界可能偏移抽样复检拿到表之后的判断逻辑AP低但recall高说明定位不准或框太大recall低说明漏检precision低说明误报。比如漏针AP0.61直接走4.2节的路径不要盲目调学习率。提示评估时imgsz必须和训练一致否则输入分辨率变了小目标AP会失真对比没有意义。5.2 坏例回放把漏检变成下一轮训练数据验证集mAP只是一个数字真正有价值的是那些模型自信满满却标错和真实目标完全没框出来的坏例。我一般跑一遍全量验证把置信度高于0.5的误报框和漏检目标分别导出成图片按类别归档。import cv2 # 按 badcases.txt 里的记录回放误报框 with open(badcases.txt) as f: for line in f: img_path, x1, y1, x2, y2, conf, cls line.strip().split(,) img cv2.imread(img_path) cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(img, f{cls} {float(conf):.2f}, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1) cv2.imwrite(fbadcase_{img_path.split(/)[-1]}, img)这段脚本按预生成的坏例清单画框存图目的不是推理而是让人眼一遍一遍看误报的框是不是撞上了阴影、水印、组织纹理漏检的位置是不是刚好在图像边缘或光照死角。三五十张坏例看完你会对数据集的盲区有直觉下一轮增加什么样本就有的放矢。把坏例里的真实目标补标进原图回灌到训练集这是所有提升手段里见效最快的比我调参数强得多。5.3 导出与推理速度fp16、engine与边沿设备取舍模型验证通过后要上线常见部署路径是导出TensorRT的engine格式在GPU上跑或者导出ONNX再转openvino在CPU上跑。YOLOv9仓库自带export.pypython export.py \ --data knit_defect.yaml \ --weights runs/train/exp/weights/best.pt \ --include engine \ --imgsz 640 \ --half--include engine表示导出TensorRT引擎--half用fp16精度推理速度能比fp32快接近一倍瑕疵检测的精度损失通常在可接受范围内。如果生产环境是Jetson这类边沿设备导出时batch-size固定为1动态batch在部分老版本上不稳定。整卷布连续过检的场景速度要求一般是每米布控制在几十毫秒内fp16 engine在常见工控机上能跑到这个量级如果还差口气再考虑把输入尺寸从1280降回640用精度换速度能不能接受由缺陷的最小尺寸决定。6. 把数据集当活资产增量迭代与归因记录的习惯模型上线不是终点数据集的维护才刚开始。我见过太多团队把模型部署完就撒手三个月后产线布种换了、光照改了模型掉到没法用才想起来重训结果训练集还是三个月前的老样本。我把数据集当活资产维护固定动作是每个迭代周期做一次增量合并和归因记录。新产线上被漏检、误报的样本先由质检员截屏归档每周抽半天时间人工标注按九比一的比例并入训练集和验证集跑一轮增量训练。关键的归因记录跟着样本走这个样本是哪个批次、哪种布色、哪个机台采的。这样当模型再次掉点时我能从归因记录里一眼看出来是哪一类变化导致的——是换了新纱线还是换了光源而不是对着黑匣子瞎猜。训练层面的动作很简单维护一个pending目录标注完跑一段合并脚本import os, random, shutil pending_img pending/imgs pending_lab pending/labels dst datasets/knit_defect for name in os.listdir(pending_img): base, ext os.path.splitext(name) split train if random.random() 0.9 else val shutil.copy(os.path.join(pending_img, name), os.path.join(dst, fimages/{split}, name)) lab f{base}.txt if os.path.exists(os.path.join(pending_lab, lab)): shutil.copy(os.path.join(pending_lab, lab), os.path.join(dst, flabels/{split}, lab))脚本按九比一随机把新样本并入train和val合并前用2.3节的体检脚本再查一遍新标注的合法性。增量训练建议用上一次的best.pt做起点而不是从COCO预训练重新来能省一半时间。每轮增量之后把训练集类别分布、验证集AP和归因记录存档形成一份可追溯的迭代日志。这条习惯是我做纺织质检项目踩过最大的跟头换来的第一版模型交付时mAP很漂亮结果客户换了批深色面料就全线崩盘根源就是数据集停在交付那天再没长过。把增量迭代当日常维护不是额外负担而是让模型活下来的唯一办法希望帮到你。本文还有配套的精品资源点击获取