拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

行李箱缺陷检测数据集:650张双格式标注与YOLO训练指南

行李箱缺陷检测数据集:650张双格式标注与YOLO训练指南 简介面向行李箱外观缺陷检测与工业质检场景这份数据集提供650张清晰jpg图像采用VOC与YOLO双格式标注适用于训练YOLO系列、Faster R-CNN等目标检测模型。压缩包内JPEGImages、Annotations、labels三个文件夹一一对应包含653个txt、650个xml、650个jpg共1953个文件整体大小25.16MB。标签共2类damaged矩形框199个、good_condition框737个总计936个标注框图像未做增强保持真实采集状态适合用于模型训练、验证与算法对比。目前已有48人学习下载目录结构清晰免去自行标注与格式转换的繁琐工作可直接用于行李箱表面划痕、破损等缺陷检测项目的数据准备与模型迭代。1. 行李箱缺陷检测数据集650 张双格式标注先别急着训练做行李箱出厂质检的人都懂一个道理缺陷检测模型的瓶颈从来不是算法而是数据。一个划痕换个角度拍就是另一种光照特征一道裂纹在拉链齿上可能被当作正常纹理。这份行李箱缺陷检测数据集一共 650 张 JPEG 图片带 2 类矩形框标注标签是 damaged 和 good_conditionVOC 和 YOLO 两种格式都给你备好了解压就能用。damaged 一共 199 个框good_condition 737 个框总框数 936图片清晰度没有问题关键是没有做过数据增强——这意味着你拿到手的是原始分布后续怎么扩怎么调完全自己做主。适合两类人一是想快速把 YOLO 检测流程跑通、验证一下行李箱缺陷到底可不可分的人二是做工业质检预研、需要一份标注格式干净又能直接喂给训练脚本的数据集的人。2. 数据集结构JPEGImages 与 Annotations、labels 的对应关系和坐标换算2.1 三文件夹如何对齐同名文件与 bndbox 的来源这份数据压缩包解开之后是三个文件夹JPEGImages 放图片、Annotations 放 XML、labels 放 TXT。三者的命名完全一致例如一张图叫 xyxr_image626.jpg那么同目录下对应 Annotations/xyxr_image626.xml 和 labels/xyxr_image626.txt。这一点对训练脚本特别重要因为 YOLO 的 dataset 加载逻辑就是按「同名不同后缀」去找配对文件的任何一张图缺了 txt 或者 XML训练时都会直接跳过或报错。XML 是 VOC 格式的标准结构核心信息在 object 节点里name 存标签名bndbox 存矩形框坐标左上角 xmin/ymin右下角 xmax/ymax。这份数据的标签只有两个damaged 代表有缺陷good_condition 代表表面正常。注意 good_condition 不是背景它也是带框的——这在质检场景里很常见正常区域也要框出来否则模型无法区分「缺陷」和「正常区域」两个类别之间的边界。我拿到任何数据集的第一件事不是直接训练而是写一个校验脚本确认图片数量、XML 数量、TXT 数量三者完全一致并且每张图的 XML 里的 object 数量和 TXT 里的行数也对得上。import os from pathlib import Path import xml.etree.ElementTree as ET base Path(./行李箱缺陷检测数据集) img_dir base / JPEGImages xml_dir base / Annotations txt_dir base / labels imgs sorted(img_dir.glob(*.jpg)) xmls sorted(xml_dir.glob(*.xml)) txts sorted(txt_dir.glob(*.txt)) # 1. 三个文件夹文件数一致 print(len(imgs), len(xmls), len(txts)) # 2. 逐个检查 xml 里的 object 数量 是否等于 txt 里的框数量 bad 0 for xml_f in xmls: txt_f txt_dir / (xml_f.stem .txt) if not txt_f.exists(): bad 1 continue tree ET.parse(xml_f) n_obj len(tree.findall(object)) n_txt len(txt_f.read_text().strip().splitlines()) if n_obj ! n_txt: bad 1 print(f{xml_f.name}: xml{n_obj} txt{n_txt}) print(不一致文件数:, bad)这段脚本的逻辑很简单但非常管用先统计三个子目录的文件个数是否都等于 650再比对每一对 XML 与 TXT 中的目标数量。n_obj是 XML 里 object 节点的个数n_txt是 TXT 非空行数两者相等才说明标注完整。常见的问题是某张图漏标了负样本或者标完图又被替换过这种文件不一致会在训练时报「AssertionError: label not found」提前跑一遍能省掉一个小时的排查时间。2.2 VOC 坐标转 YOLO 归一化坐标一次换算与三个边界注意虽然这份数据集已经同时给了 VOC 和 YOLO 两种格式但你必须看懂两种格式之间的换算逻辑因为后续你自己标注、增广或者换模型的时候大概率需要再写一次转换脚本。VOC 存的是像素绝对坐标YOLO 存的是归一化后的相对坐标每行五个数class_id、x_center、y_center、width、height后四个都是 0~1 之间的小数。换算公式就是小学数学x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h我通常会用一段独立脚本验证包里的 TXT 和 XML 是否真的彼此吻合顺便检查有没有坐标越界。import cv2 import xml.etree.ElementTree as ET from pathlib import Path base Path(./行李箱缺陷检测数据集) img_dir base / JPEGImages xml_dir base / Annotations txt_dir base / labels def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h return x_center, y_center, w, h for xml_f in sorted(xml_dir.glob(*.xml))[:5]: txt_f txt_dir / (xml_f.stem .txt) img cv2.imread(str(img_dir / (xml_f.stem .jpg))) img_h, img_w img.shape[:2] tree ET.parse(xml_f) with open(txt_f) as f: lines f.read().strip().splitlines() for obj, line in zip(tree.findall(object), lines): name obj.find(name).text 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) vals [float(x) for x in line.split()] yolo voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h) print(f{xml_f.stem} | {name} | voc{xmin:.0f},{ymin:.0f},{xmax:.0f},{ymax:.0f} | yolo{vals[1:]} | 转换{yolo})逻辑说明从 XML 读出绝对坐标和图片的宽高换算成 YOLO 坐标后与 TXT 里的数值逐个对比。这里特别注意两点bndbox里的值拿到后必须用float()而不是int()因为坐标可能带小数点强转会丢精度另外 TXT 行的第一个值class_id是从 0 开始的damaged 对应 0good_condition 对应 1不要和标签名的先后顺序弄混。边界注意第一YOLO 的 x_center、y_center、width、height 必须落在 [0,1] 区间任何一个大于 1 或者小于 0 都说明标注框越界了训练时会出 warning 甚至直接过滤掉这个框第二width 和 height 不能为 0一旦某个目标被标成一条线换算出来是 0YOLO 会把这个框当作无效框丢掉第三xmin/xmax 的顺序不能反标注工具偶尔会把左上角和右下角写反导致 width 是负值这类错误靠肉眼几乎看不到只能靠批量脚本查。2.3 txt 与 xml 不一致时以谁为准验证集划分前的最后一道闸拿到库存数据最怕的就是「两份标注文件对不上」。现实里常见的翻车场景是数据从别的项目拷贝过来XML 是原始标注TXT 是某个半成品脚本生成的中间漏了几个框或者类别编号错位。这时候如果你不管三七二十一直接训练YOLO 读的是 TXT模型会在不知不觉中把 some 错误的框当作 ground truth 去学训练过程不报错但 mAP 就是上不去。我的习惯是以 XML 为准做一次全量校验如果发现不一致优先重新生成 TXT而不是手工改 XML。理由很简单XML 结构更完整、可读性更好而且还有 object name 这种语义信息改错能看出来TXT 是纯数字矩阵错一个数你根本察觉不到。上面 2.1 的脚本里如果发现不一致文件数大于 0下一步就是做一次转换回写而不是硬着头皮训练。这类校验脚本值得沉淀成自己的工具函数因为后面你会遇到各种「半残」数据集不只是行李箱任何工业缺陷检测项目都可能碰上标注文件和图片不同步的问题。校验、转换、再校验三个步骤走完数据才敢进训练管线。3. 直接跑通 YOLO从划分数据集到得到 best.pt3.1 划分 train/val用脚本固定随机种子别让验证集泄漏650 张图对目标检测来说属于小数据集划分策略比数量更重要。最常见的问题是把同一行李箱的连续帧照片同时丢进 train 和 val——这不算严格意义的泄漏但会让验证集指标虚高因为模型见过了几乎一样的背景和纹理。由于这份数据没有给出拍摄序列信息我一般按文件名字母序打散后用固定的随机种子做 8:2 划分。import random from pathlib import Path import shutil base Path(./行李箱缺陷检测数据集) img_dir base / JPEGImages xml_dir base / Annotations txt_dir base / labels out Path(./dataset_split) random.seed(42) # 固定种子保证每次划分结果一致 imgs sorted(img_dir.glob(*.jpg)) random.shuffle(imgs) val_ratio 0.2 n_val int(len(imgs) * val_ratio) val_imgs set(imgs[:n_val]) for split in [train, val]: (out / images / split).mkdir(parentsTrue, exist_okTrue) (out / labels / split).mkdir(parentsTrue, exist_okTrue) for img in imgs: split val if img in val_imgs else train txt txt_dir / (img.stem .txt) if not txt.exists(): continue # 缺少标签的图直接跳过不放进训练集 target_img out / images / split / img.name target_txt out / labels / split / (img.stem .txt) shutil.copy(img, target_img) shutil.copy(txt, target_txt) print(train:, len(list((out/images/train).glob(*.jpg)))) print(val:, len(list((out/images/val).glob(*.jpg))))逻辑说明按 8:2 把图片分成 train 和 val图片和 txt 同步拷贝到 YOLO 习惯的 images/labels 目录结构下。random.seed(42)保证你跑十次划分结果都一样这是最基本的可复现要求。val_imgs用 set 存放是为了后续in判断更快。缺失 txt 的图直接跳过宁可少一张也不要把无标签图送进训练。这里必须强调固定随机种子的价值。650 张的小数据集每次划分不同训练出来的 best.pt 可能差 3~5 个点 mAP然后你会在调参上浪费大量时间其实只是数据划分在漂。固定 seed 相当于给整个实验上了后悔药任何一次参数调整都能在同等数据条件下做对比。3.2 写 data.yaml 并启动训练关键参数取值YOLO 训练需要一份 data.yaml 告诉框架数据在哪、有几类、叫什么名字。这份数据集的类别顺序是[damaged, good_condition]class_id 分别是 0 和 1写 yaml 时 names 的顺序必须和 labels 文件夹里的 txt 第一个数字对应否则类别就会错位。# dataset.yaml path: ./dataset_split train: images/train val: images/val nc: 2 names: 0: damaged 1: good_condition参数说明path是项目根目录相对路径train和val指向划分出来的图片目录框架会自动在同级目录找 labels。nc是类别数这里固定 2。names的索引从 0 开始与 txt 中的 class_id 完全一致。训练命令我一般会在 YOLOv8 的 CLI 上直接跑数据量小不搞分布式一个显卡就够yolo train modelyolov8n.pt datadataset.yaml epochs100 imgsz640 batch16 patience20 device0参数说明yolov8n.pt是 nano 版本预训练权重650 张图的数据量用 n 或者 s 级别足够上 m 或 l 大概率过拟合。imgsz640是输入尺寸图片会按比例缩放填充到 640×640不是改动原始图片文件。batch16要看显存8G 显存跑 16 没问题显存小就调成 8。patience20表示验证集指标连续 20 个 epoch 不提升就提前结束省时间。epochs 设 100 是因为小数据集收敛快实际可能 40 轮左右就停了。这里有一个容易踩的坑直接把原图以原始分辨率训练。这份数据的 JPEG 图片分辨率不高但也可能接近 2000×2000直接喂进去显存瞬间爆掉而且小目标检测精度并不会因为输入变大而提升太多。先按 640 跑一轮实验记录各项指标后面再对比 1280 的差异。另外注意device0指第一张显卡没有 GPU 环境就把这个参数去掉用 CPU 硬顶不过 100 个 epoch 会跑到天荒地老建议至少用一张入门级显卡。3.3 训练输出怎么看best.pt 与混淆矩阵的一一对应训练结束后runs/detect/train目录下会生成大量文件最关键的三个是weights/best.pt验证集指标最好的权重、weights/last.pt最后一轮权重、confusion_matrix.png混淆矩阵图。对小数据集而言best.pt 不一定等于 last.pt因为最后几轮可能已经过拟合了。验证集指标不能只看 mAP50对于二分类缺陷检测我更关注 per-class 的 Precision 和 Recall。damaged 是少数类只有 199 个框它的 Recall 低往往意味着缺陷漏检——在质检场景里漏检比误检严重得多漏一个坏箱子到产线损失的是整批货的信誉。yolo val modelruns/detect/train/weights/best.pt datadataset.yaml这条命令会输出一个详细的指标表包括每个类别的 P、R、mAP50、mAP50-95。如果你的 damaged Recall 明显低于 good_condition说明模型在牺牲少数类去迁就多数类需要回到第 4 章提到的类别不平衡处理。val 过程还会重新生成一张 confusion_matrix.png可以直接看 damaged 有多少被分成了 good_condition这个数字比任何指标都直观。4. 避坑标签不平衡、BN 崩溃与验证集泄漏——五个翻车点4.1 damaged 只有 199 框模型默认学成「全都正常」现象训练出来的模型在验证集上 mAP50 有 0.85 以上看起来还行但打开混淆矩阵一看damaged 的 Recall 只有 0.4 左右一半的缺陷被漏检了模型把所有带框区域都猜成 good_condition。原因两个类别的框数量差距太大damaged 199 框对 good_condition 737 框比例接近 1:3.7。YOLO 的默认损失函数里分类损失是等权重的模型发现把一切预测成 good_condition 就能把整体 loss 压得很低于是选择躺平。解决先做类别权重调整把受损类别的损失权重提上去也可以做数据增强对含 damaged 的图片做马赛克和翻转扩样最直接的办法是在损失函数层面给 damaged 一个更大的权重YOLOv8 可以通过修改 loss 配置或使用带权重的采样器实现。我的习惯是先做过采样把 damaged 的图复制一部分到训练集简单粗暴但有效。4.2 训练中途 loss 变成 nanBN 层震荡现象训练跑了几十个 epochloss 突然变成 nan或者从某个 epoch 开始 mAP 掉到 0terminal 里飘红字。原因小数据集上最常见的原因是学习率偏大 batch size 太小导致 BN 层的统计量在少数几张图上抖动最终方差爆炸。另外一个常见触发点是验证集里出现了某张异常图片比如全黑或者全白BN 在推理时碰到了训练时没见过的分布。解决先把学习率降到默认值的 1/10比如 0.001 改成 0.0001batch 尽量保持 16 以上。还不行的话可以冻结 backbone 先跑 10 个 epoch让检测头先收敛再解冻全模型微调。做法是在训练脚本里设置freeze10冻结前 10 层。不要急着换模型bn 震荡多半不是网络结构的问题是训练配置的问题。4.3 划分数据集时没固定种子调了个寂寞现象同一份代码、同一个超参数昨天训练 mAP 0.82今天重跑变成 0.78你以为是随机性其实大部分是数据划分变了。原因如果 train/val 划分没有固定随机种子每次运行 shuffle 结果都不一样验证集里哪些图参与评测完全变了。小数据集上划分扰动对指标的影响能超过超参数调整本身导致你在调参时做出错误判断。解决把划分脚本里的random.seed(42)写死并且把划分出的文件清单保存一份 txt每次实验都用同一份划分。我一般会把 seed 作为文件名后缀比如train_val_split_seed42.txt这样每次终点检查时都能确认自己用的是同一份数据。这在数据集只有几百张时特别重要。4.4 直接拿原图 2000×2000 训练显存直接爆现象运行训练脚本后几秒钟显存溢出报CUDA out of memory有的人一着急就调小 batch 到 2结果模型精度崩了。原因数据集的 JPEG 图片本身分辨率高如果不设置 imgsz框架或者你自己的脚本按原图尺寸送进网络计算量和显存占用随分辨率平方增长。batch2 时 BN 统计量又极不稳定loss 曲线抖动剧烈。解决imgsz640 起步这是 YOLO 系列的默认输入尺寸对绝大多数工业缺陷检测场景够用。显存不够也不要只降 batch可以同时降 imgsz 到 512损失一点精度换训练稳定性。如果真需要高分辨率检测细小裂纹建议用 imgsz640 训练完再在 imgsz1280 上做微调而不是一来就上大图。4.5 误把「无缺陷」当成背景导致类别概念混乱现象有的同学拿到这份数据后人为把所有 good_condition 框删掉想着「focus 只检测缺陷」结果模型把行李箱上正常区域全部当成背景或误检成 damaged准确率大跌。原因good_condition 框在这个数据集里不是背景它是一个独立的类别。模型需要同时学习「什么样的区域算 good_condition」和「什么样的区域算 damaged」才有区分能力。直接删掉一个类别相当于把二分类变成单分类检测头的分类分支失去对比信号。解决不要改动标签维持 nc2 训练。如果你的业务只需要检测缺陷那也应该保留 good_condition 作为负样本类别而不是删掉框。训练完成后推理时只取 class_id0 的输出作为缺陷告警即可后处理过滤的事交给业务逻辑。5. 进阶用混淆矩阵验证两类缺陷的可分性阈值调优与漏检权衡5.1 读混淆矩阵判断两个类别是「可分的」还是「互咬的」训练结束后runs/detect/train/confusion_matrix.png和 val 生成的混淆矩阵能回答一个核心问题damaged 和 good_condition 到底能不能被模型分开。如果矩阵里 damaged 那一行有大量预测落到 good_condition说明两类在特征空间上重叠严重单纯调阈值解决不了需要回看标注质量——是不是有些「轻微划痕」被标成了 good_condition或者有些「正常纹理」被标成了 damaged。用一个脚本把混淆矩阵的数据读出来比肉眼看图更精确import torch from pathlib import Path # 加载训练时保存的 confusion matrix 数据YOLOv8 会在 val 时输出 results torch.load(runs/detect/train/confusion_matrix.pt, map_locationcpu) print(results) # 打印原始矩阵数据行真实列预测逻辑说明这个矩阵的行是 ground truth 类别列是预测类别对角线越亮代表分类越准。重点看 non-diagonal 的数字分布假如 damaged 被误分为 good_condition 的比例超过 30%说明这两类的视觉差异没有被模型充分学到。优先怀疑标注标准是否统一再看是否需要给模型加更多 damaged 样本的特征多样性。5.2 阈值调优默认 0.25 只是起点不是真理YOLO 推理时默认置信度阈值是 0.25但这个阈值在高漏检代价的场景里太激进了。对于行李箱缺陷检测我认为漏检的代价远大于误检——一个 damaged 被判成 good_condition 流入下一道工序比十个 good_condition 被拦下来返检要棘手得多。因此实际部署时我会把 confidence 阈值调低到 0.1 到 0.15宁可多报几个 false positive 让人工复查也不要漏掉真缺陷。推理时调整阈值的方法yolo predict modelruns/detect/train/weights/best.pt source./test_imgs conf0.1参数说明conf0.1把置信度阈值降到 0.1模型会输出更多候选框其中包含大量低置信度的 damaged 框。代价是 good_condition 类别也会跟着多报后续需要加一个业务过滤规则例如只保留 damaged 类别且置信度大于 0.1 的输出good_condition 的框直接忽略。这里有个值得知道的细节类别之间的阈值可以不一样落地时我会给 damaged 用低阈值给 good_condition 用高阈值。做法是对输出按 class_id 分别设置置信门限damaged 取 0.1good_condition 取 0.4这样既保住召回率又避免无意义的大量正常框干扰产线。阈值调完还要在 val 集上跑一遍 F1 曲线找到 Precision 和 Recall 的平衡点——现在很多人把 0.5 当作默认值用那真是把调参当成了玄学。我自己的习惯是从拿到这份数据集开始划分、训练、val、读混淆矩阵整套流程固定成一套脚本每次换参数都跑同一个验证命令。从那以后我再也没有靠感觉调过阈值也再没出现过训练完忘记记录数据划分这种事。希望帮到你。本文还有配套的精品资源点击获取
返回列表