ARTICLE DETAIL

资讯详情

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

苹果缺陷检测YOLO数据集:1000张图搞定VOC/COCO/YOLO训练

苹果缺陷检测YOLO数据集:1000张图搞定VOC/COCO/YOLO训练 简介面向目标检测学习者和农产品质检开发者这套YOLO苹果缺陷目标检测数据集包含1000张真实场景苹果图像覆盖不同光照、角度与果面状态使用LabelImg标注且框体质量高可直接用于YOLO系列缺陷识别模型训练节省数据准备时间。压缩包共2000个文件、约14.2MB主要包含xml和txt两类标签文件以及yaml配置、py划分脚本、html环境搭建与训练教程VOC、COCO、YOLO三种格式标签目录已预设结构清晰便于接入常见检测框架。目前已有869人学习下载适合课堂实训、课程设计或缺陷检测预研。随包附赠Linux和Windows双版本YOLO环境搭建及训练案例教程并提供训练集、验证集、测试集划分脚本可自行划分图片与标签快速完成从环境配置、数据预处理到模型训练的全流程。1. 苹果缺陷检测为什么值得用YOLO数据集起步1000张图能撑起一条分拣原型线做过农业视觉的人都有体会从采摘到打包苹果表面的碰伤、腐烂、果锈全靠人工眼挑一天下来眼睛又酸又累漏检率还不低。把YOLO目标检测模型接到分拣线上用摄像头实时框出缺陷位置是中小型果厂最想落地的一步。但这类项目最缺的不是模型是带标注的数据。这个数据包把1000张苹果缺陷图片一次性配齐了VOC、COCO和YOLO三种标签格式还附了划分脚本和训练教程拿到的第一反应是终于不用自己从零画框了。适合刚接触目标检测的工程师、农业方向的开发者以及想用最小成本验证苹果缺陷到底能不能用视觉分拣的人。下面我按拿到这个rar包后的实际处理顺序把格式转换、脚本使用和训练参数全部拆开讲。2. 数据集先验检查1000张苹果缺陷图先看清格式差异和标注细节2.1 苹果缺陷检测的难点为什么不能拿通用检测模型直接硬套苹果缺陷检测在目标检测任务里属于看着简单、做起来磨人的类型。核心难点有三个第一缺陷与正常果皮之间是渐变的碰伤初期只是颜色微微发暗边缘没有明显轮廓标注时不同的人框出来的边界可能差出十几个像素这直接变成训练噪声。第二苹果是高光物体分拣线上的光源稍不均匀高光区就会把浅层纹理整片吃掉模型很容易把高光当成缺陷或者漏掉被高光遮挡的真缺陷。第三缺陷形态高度不规则腐烂斑块可能连成一片也可能碎成几块标注规范稍微不统一模型学到的东西就飘。这三个难点决定了这个项目里数据质量的优先级高于模型结构。拿到数据集后不要急着训练先做一次彻底的标注可视化检查。我一般会抽20张左右把标注框直接画在图上逐张看框是否贴合缺陷边界、有没有漏框、有没有把两个缺陷框成一个。这种检查在开始阶段花掉半小时能帮后续训练省下好几轮debug时间。2.2 VOC、COCO、YOLO三种标签格式到底差在哪这个数据包一个比较省心的地方是把同一批图片导出了三种主流标注格式。很多人第一次接触会误以为只是文件后缀不同实际上三者的坐标体系、存储方式和适用生态完全不同。我整理了一张对比表格式存储形态坐标含义单位典型工具VOC每张图一个.xmlbndbox里的xmin、ymin、xmax、ymax原始像素LabelImgCOCO整个数据集一个.jsonbbox为左上角x、y、宽度w、高度h原始像素LabelStudio、Detectron2YOLO每张图一个.txtclass x_center、y_center、width、height归一化到0-1ultralytics, YOLOv5/v8VOC格式的好处是xml里除了框坐标还有图片尺寸、物体名称等结构化信息人工可读性强断点续标方便适合作为母版长期维护。COCO格式的优势在于生态完整官方的评估脚本、可视化工具都是围绕COCO JSON设计的如果后续要跑mmdetection或者对比多种模型COCO是通用语言。YOLO格式则最贴近训练本身txt一行一个目标训练时几乎零解析开销但它有一个隐患如果脱离目录结构和类别清单单看一个txt文件完全看不出框画得对不对。目标检测常用标注工具里我最常用的组合是LabelImg标VOC做母版再写脚本转YOLO和COCO。LabelStudio功能更全能直接导出COCO格式但在标注框的微调手感上不如LabelImg顺手。无论用什么工具都建议保留一份VOC母版后面改类别、查错、补充标注都用它。2.3 1000张图片能做什么、不能做什么先说实话1000张图对于目标检测来说属于小数据集边界。单类缺陷算下来每类也就几百张能支撑起一条原型验证线但直接拿去部署到不同光照、不同品种的苹果分拣现场很容易翻车。不过这个数据包的思路是把base线先跑起来1000张作为底座配合预训练权重和强的数据增强室内稳定光照下得到可用的mAP50成绩是完全可以的生产部署前再补采现场数据迭代到2000到3000张就稳了。另外一个不能忽视的细节是类别设计。苹果缺陷检测里正常苹果通常不需要单独成类模型只需要框出哪里有毛病没有框的区域就是正常。常见标注规范是缺陷类别分为碰伤、腐烂、果锈疤痕三类正常果不画框作为背景存在。这样做的好处是类别少、样本集中模型把精力放在缺陷特征上不用额外学习什么是苹果。3. 把VOC标签转换成YOLO和COCO转换脚本与划分逻辑3.1 数据集的目录规划与划分脚本train/val/test无论数据包里原来是什么结构我拿到手第一件事是重新组织目录统一成pytorch和ultralytics都认的标准结构。图片和YOLO标签放在同名目录下train、val、test三个子集分别放好。这样的好处是后期换模型框架时不用再动目录。划分训练集、验证集、测试集看似简单实际有个容易踩的坑很多人直接按文件顺序切前80%当train后20%当val结果是同一批次采集的图片大概率都堆在一起验证集和训练集光照风格相似mAP虚高。正确做法是先随机洗牌再按比例切分并且固定随机种子让每次划分结果一致。下面这段脚本可以直接拿去用import os import glob import random import shutil SRC_IMAGES images # 原图目录 SRC_LABELS labels # YOLO格式txt目录 DST apple_defect # 输出根目录 TRAIN, VAL 0.8, 0.15 # test 取剩余 0.05 SEED 42 # 固定种子才能复现划分结果 # 兼容 jpg/png避免后缀写死导致漏图 imgs glob.glob(os.path.join(SRC_IMAGES, *.jpg)) \ glob.glob(os.path.join(SRC_IMAGES, *.png)) names [os.path.splitext(os.path.basename(p))[0] for p in imgs] random.seed(SEED) random.shuffle(names) # 先洗牌再按比例切分 n_train int(len(names) * TRAIN) n_val int(len(names) * VAL) splits { train: names[:n_train], val: names[n_train:n_train n_val], test: names[n_train n_val:], } for split, subset in splits.items(): img_dir os.path.join(DST, images, split) lbl_dir os.path.join(DST, labels, split) os.makedirs(img_dir, exist_okTrue) os.makedirs(lbl_dir, exist_okTrue) for name in subset: # 用 glob 匹配实际后缀避免源文件里 jpg/png 混用时报错 src_img glob.glob(os.path.join(SRC_IMAGES, name .*))[0] src_lbl os.path.join(SRC_LABELS, name .txt) shutil.copy(src_img, img_dir) shutil.copy(src_lbl, lbl_dir) print(ftrain{n_train} val{n_val} test{len(names) - n_train - n_val})这段脚本有两个参数需要按实际情况调TRAIN和VAL的比例。1000张小数据集我建议给test留5%到10%50到100张图片足够评估最终模型了留多了训练样本更紧张。SEED建议固定下来后面每次重新划分都从同一个初始状态出发模型训练的可复现性才有保证。3.2 VOC转YOLO归一化坐标转换脚本数据包里如果给的是VOC母版xml转了YOLO格式才能直接喂给YOLO训练。转换的核心逻辑是从xml里读出目标的绝对像素坐标再分别除以图片宽高做归一化。注意YOLO要求的不是左上角和右下角坐标而是中心点x、中心点y、框宽、框高四个值都归一化到0到1区间。import os import glob import xml.etree.ElementTree as ET # 类别名与索引的映射顺序必须和训练时的data.yaml保持一致 CLASS_MAP {bruise: 0, rot: 1, scar: 2} def voc_to_yolo(xml_file, out_dir): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) txt_path os.path.join( out_dir, os.path.basename(xml_file).replace(.xml, .txt) ) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: print(f跳过未定义类别: {name}) # 防止类别索引越界 continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 左上角右下角 - 中心点宽高 - 归一化 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append( f{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f} ) with open(txt_path, w) as f: f.write(\n.join(lines)) # 批量转换 for xml_file in glob.glob(voc_xmls/*.xml): voc_to_yolo(xml_file, yolo_labels)为什么YOLO用中心点加宽高而不是左上右下因为YOLO的检测头输出就是预测目标中心点相对于网格的偏移量加上宽高相对于先验框的缩放系数标注格式直接对齐输出头训练时就少一步坐标变换误差也更小。转换完成后务必抽查几个txt文件看看归一化数值是否都在0到1内——如果出现1.05或者-0.01这种值通常是标注框越界或者图片尺寸读错了。3.3 把VOC汇总成单个COCO JSON文件COCO格式的特点是把整个数据集的标注塞进一个JSON文件用annotations数组把所有目标列出来每一条通过image_id关联到图片。bolb框用左上角x、y、宽w、高h表示单位是像素不需要归一化。写转换脚本时有一件事特别容易漏area字段。COCO的mAP评估会按目标尺寸分成small、medium、large三组area就是分组依据不写的话评估脚本直接报错或者结果失真。import json import glob import os import xml.etree.ElementTree as ET from PIL import Image CLASS_MAP {bruise: 0, rot: 1, scar: 2} CATEGORIES [ {id: 0, name: bruise}, {id: 1, name: rot}, {id: 2, name: scar}, ] def voc_folder_to_coco(xml_dir, img_dir, out_json): images, annotations [], [] ann_id 0 for img_id, xml_file in enumerate(sorted(glob.glob(os.path.join(xml_dir, *.xml)))): tree ET.parse(xml_file) root tree.getroot() img_filename root.find(filename).text # 用PIL直接读真实尺寸避免xml里size字段写错 width, height Image.open(os.path.join(img_dir, img_filename)).size images.append({ id: img_id, file_name: img_filename, width: width, height: height, }) for obj in root.iter(object): name obj.find(name).text if name not in CLASS_MAP: continue bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) w xmax - xmin h ymax - ymin annotations.append({ id: ann_id, image_id: img_id, category_id: CLASS_MAP[name], bbox: [xmin, ymin, w, h], area: w * h, # mAP按尺寸分组的依据必须给 iscrowd: 0, # 苹果缺陷不用群体标注固定0 }) ann_id 1 coco_data { images: images, annotations: annotations, categories: CATEGORIES, } with open(out_json, w) as f: json.dump(coco_data, f) voc_folder_to_coco(voc_xmls, images, annotations/defect_coco.json)如果你后续要跑mmdetection这类框架COCO JSON里还可能需要补充license和info字段但基础训练和评估用上面这三个字段就足够。转换完成后可用程序做一次反向校验把COCO里的bbox还原回原图上叠加显示确认坐标没有错位再存为正式版本。数据包里的VOC母版保留一份COCO和YOLO都看成派生格式后面改类别或补充标注只改VOC母版然后重新跑转换脚本。4. 用YOLOv8把苹果缺陷数据集跑成可用的检测模型4.1 组织data.yaml并对齐路径ultralytics的YOLOv8训练自己的数据集时只需要一个yaml文件告诉它数据在哪、有几类、叫什么名字。这个文件是全部配置的入口路径写错后面全白搭。我一般把路径写成绝对路径避免相对路径在不同工作目录下解析出问题。# 数据集根目录改成你自己的绝对路径 path: /data/apple_defect train: images/train val: images/val test: images/test # 类别数必须和VOC转YOLO时的CLASS_MAP一致 nc: 3 names: 0: bruise 1: rot 2: scar这里有个非常隐蔽的坑data.yaml里的类别顺序必须和训练标签txt里的数字索引严格对应。如果VOC转YOLO时CLASS_MAP写的bruise0、rot1、scar2而data.yaml里names顺序写成了rot、bruise、scar模型不会报错但会把碰伤当成腐烂来学训练过程一切正常验证结果却一塌糊涂。我见过不止一个人在这种地方耗掉一整天最后拿同一张图人工对比才发现的。建议转换脚本和data.yaml的类别清单维护在同一个源里避免各写各的。4.2 训练命令与关键参数epochs、imgsz、batch、预训练权重目录和yaml准备好之后训练本身其实就一条命令。这里最关键的决策是下载预训练权重而不是从随机初始化开始训。YOLOv8的预训练权重在官方release里可以下载yolov8n.pt是在COCO上预训练过的权重对苹果缺陷这种小数据集能起到非常大的迁移学习作用。为什么有效COCO里有大量物体边缘、纹理、颜色特征这些底层特征在苹果缺陷上同样适用模型只需要在预训练基础上微调高层语义几百张图就能收敛得不错从零开始训的话1000张图远远不够。yolo detect train \ data/data/apple_defect/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ patience15 \ seed42 \ projectruns/apple_defect \ namedefect_v0参数选择上我按实际踩过的经验逐条说。epochs给100对于小数据集不是必须跑满因为patience15意味着验证集mAP连续15轮不涨就自动停。imgsz我推荐640苹果缺陷属于中小目标碰伤斑块往往只有几十个像素用320的话这些细节直接被下采样吃掉模型根本没机会学到缺陷纹理显存够的话甚至可以上768。batch的底线是16后面避坑章会细说batch太小对BN层的危害。seed固定42保证每次训练结果可复现调参时才能公平对比。YOLOv8还提供n、s、m、l、x五档模型规模数据只有1000张时用yolov8n或者yolov8s就够了。用m以上的模型在小数据集上几乎必定过拟合训练时间翻倍mAP反而更低。分拣线部署还得考虑推理速度n模型在CPU上也能跑到接近实时的水平性价比最高。4.3 训练输出怎么看从train_loss到混淆矩阵哪些指标真要盯训练过程中屏幕上会滚动打印一堆指标新手很容易被刷屏搞蒙。其实核心就四个数box_loss、cls_loss、dfl_loss和验证集的mAP50。前三个是损失值正常趋势是整体下降然后变平mAP50是当IoU阈值取0.5时的平均精度均值对苹果缺陷检测来说这个值比mAP50-95更能反映工业场景的真实体验因为分拣线只要大致框出缺陷位置不需要像素级精准。训练结束后runs/apple_defect/defect_v0目录下会生成weights/best.pt和weights/last.pt以及一堆曲线图。我重点看的是confusion_matrix.png和PR_curve.png。混淆矩阵的行是真实类别列是预测类别从对角线能一眼看出哪类缺陷最容易混淆。苹果缺陷项目里最常见的现象是碰伤和正常背景之间误检多——因为碰伤早期颜色接近正常果皮模型会把暗色高光当成碰伤。如果PR曲线上各类别的曲线都比较靠近右上角说明模型状态健康如果某类曲线离右下角近说明这类样本确实难需要补数据而不是继续调参。5. 苹果缺陷数据集训练的五个常见翻车现场与排查5.1 现象训练日志一闪而过No labels found或验证全0训练一启动就提示找不到标签文件或者整个训练过程mAP一直是0。原因九成是目录结构对不上ultralytics在yaml里同时给了path、train、val它会把这三个路径拼接成absolute path train images路径去找图片标签则默认找同级labels目录。如果划分脚本把图片放到了images/train标签却放到了labels/train之外的杂目录就会匹配不上。解决方法是打开训练生成的一个验证图看标签是否真的叠加在图片上再对目录树做一次文件数统计find apple_defect -name *.txt | wc -l find apple_defect -name *.jpg | wc -l正常情况两者数量应该几乎相等。对不上就回到划分脚本检查labels目录的命名和层级是否与images完全镜像。另外一个不易察觉的原因txt文件名里有多余的空格或者中文ultralytics对文件名空格容忍度低建议统一转成英文短名。5.2 现象训练从第一个epoch起loss就是nan这个现象几乎是新手必踩。原因一般是两类一类是标注数据有问题比如某个目标的xmax比xmin还小或者归一化后的中心点坐标落在0到1区间之外这些异常值让损失函数算出来的梯度直接爆炸另一类是类别索引越界txt里写了class5但data.yaml里nc3模型取类别权重时越界产生非法值。解决分两步。第一步写个脚本扫所有txt把每行5个数都做合法性检查过滤掉越界和非法行。第二步在data.yaml里把nc改小重新启动训练。下面这个检查脚本是我每次训练前的固定动作import glob bad_files [] for txt in glob.glob(apple_defect/labels/**/*.txt, recursiveTrue): for line in open(txt): parts line.strip().split() if len(parts) ! 5: bad_files.append((txt, 字段数不为5)) else: cls, x, y, w, h map(float, parts) if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad_files.append((txt, 归一化越界)) if cls 0 or cls 3: bad_files.append((txt, 类别索引越界)) for f, reason in bad_files: print(f, reason)5.3 现象loss下降缓慢训练集mAP高、验证集掉得厉害1000张小数据集最常见的过拟合信号训练到几十轮时train损失降得很低但val的mAP却停滞甚至回跌。说白了就是模型把训练图背下来了光照、角度稍微一变就认不出。解决思路有两条路。第一条是加大数据增强这是小数据集最便宜有效的扩容方式对苹果这个圆形物体水平翻转是安全的色调偏移要克制因为分拣线总不能让模型把偏红的苹果当腐烂。第二条是缩小模型从yolov8s退回yolov8n减少模型容量让它没有那么多参数去记住训练集。我通常把两条路一起走增强拉到一个中等强度模型直接用n档。5.4 现象训练中BN层疯狂抖动loss曲线像锯齿一样用小batch训练时loss曲线可能在几轮之间来回跳验证指标也忽高忽低这是目标检测里典型的BN崩溃。原因在于BatchNorm层的统计量依赖当前batch的均值和方差batch太小时估计出的统计量噪声大训练不稳定。苹果缺陷数据集的图片分辨率高一张640的图塞进显卡就会吃掉大量显存很多人为了塞进模型拼命降batch结果降到4以下BN就开始摆烂。解决方法是保持batch不低于16在不超过batch上限的前提下用梯度累积模拟更大的batchyolo detect train \ modelyolov8n.pt \ dataapple_defect/data.yaml \ batch8 \ # ultralytics暂未直接暴露累积参数用大batch优先如果显存实在不够两个备选方案一是把imgsz从640降到512减少显存占用保住batch二是冻结部分网络参数只微调最后几层也能缓解BN的不稳定。注意不要一上来就怀疑数据先看batch再谈其他。5.5 现象两次训练结果差异巨大不确定哪次可信这是个很隐蔽但会让人抓狂的问题。苹果缺陷模型严谨复现时两次完全相同的训练命令mAP却差了4到5个百分点。原因是没有固定随机种子数据加载顺序、增强的随机变化、模型权重初始化都会引入噪声。而划分脚本如果每次运行都重新生成随机序列train/val的图片分配也全变了两次结果没有可比性。解决就是在训练命令里加seed42同时划分脚本里固定random.seed(SEED)。我用了一个习惯每次训练前把当前代码、划分脚本、data.yaml一起拷贝到一个以时间为名的目录里这样半年后回来看还能完整复现当时的实验。这个习惯在调参过程中救过我很多次。6. 验证与落地进阶PR曲线、混淆矩阵和一条能持续迭代的数据闭环训练完成后第一步是先用验证集跑一遍正式评估拿到精确率、召回率和PR曲线。用data.yaml里划分好的test子集评估得到的是没有参与训练和验证的独立样本表现这才代表模型在没见过的新苹果上的真实水平yolo detect val \ modelruns/apple_defect/defect_v0/weights/best.pt \ data/data/apple_defect/data.yaml \ splittest \ plotsTrue生成的混淆矩阵图有一个细节必须注意很多人看到矩阵里各行的和不是100%以为模型出了问题其实ultralytics的混淆矩阵是逐行归一化的每行代表该真实类别的样本被分到了每个预测类别的比例行和为1整张表相加不等于1是正常现象。看这个矩阵时重点盯两条对角线之外的误检模式碰伤被识别成腐烂的比例高不高疤痕是否频繁漏检这对下一步数据采集的方向很有指导意义。如果想要部署到分拣线模型导出成ONNX或者TensorRT是关键一步。YOLOv8的导出方式很简单yolo export modelbest.pt formatonnx imgsz640导出后用onnxruntime做推理在工业电脑上跑CPU速度通常能达到每帧30到50毫秒基本满足皮带滚动下的实时检测需求。不过我多说一句室内验证效果好不代表现场效果好分拣线光照、苹果品种、输送带运动模糊都会引入新的分布偏移。所以我给自己定的数据迭代路径是已标注的1000张作为种子数据模型跑起来后把它部署到现场采集模型不确定的样本——置信度在0.4到0.7之间那种模棱两可的帧——隔天人工标注一轮每周积累几百张新图重新训练。这样走完三四轮后模型对真实环境的适应性会有一个质的飞跃。这个低成本数据飞轮的起点就是先把这套YOLO苹果缺陷检测流程完整跑通拿到第一条可信的基线。希望帮到你。本文还有配套的精品资源点击获取
返回列表