
简介本资源是面向计算机视觉初学者与农业智能化开发者的一套苹果瑕疵检测专用数据集专为YOLO系列目标检测模型训练与验证设计解决水果品质自动化分级中瑕疵定位与分类的实际问题。压缩包共786个文件包含393张带瑕疵的苹果实拍JPG图像及对应393份PASCAL VOC格式XML标注文件完整覆盖霉点、划痕、色斑等典型缺陷类型标注信息含边界框坐标与类别标签可直接用于YOLOv5/v8等框架的数据加载与训练。资源大小30.24MB结构规整、开箱即用适配主流CV开发流程。目前已有79人学习下载配套数据已通过基础质量校验图像清晰度统一、标注一致性高且涵盖不同光照与摆放角度样本有助于提升模型泛化能力与小瑕疵识别鲁棒性。 做目标检测的朋友应该都经历过这种尴尬网上开源数据集一大堆COCO、VOC、Cityscapes应有尽有但一到自己项目里要检测“苹果有没有烂、有没有磕碰”这种细粒度瑕疵时几乎找不到能直接用的数据。就算找到相关数据集不是背景纯净得像影棚摆拍就是标注格式乱七八糟。所以我看到“YOLO目标检测-苹果瑕疵检测数据集图片xml格式标签”这套资源时第一反应是挺高兴的因为它的核心价值很明确带VOC格式的xml标注可以直接进入YOLO训练流程省去了自己采集、标注苹果瑕疵图的巨大工作量。这篇文章就围绕这套数据聊点实在的包括数据集结构怎么看、xml标签里到底存了什么、为什么训练YOLO前必须转成txt格式、以及实际训练和调优过程中最容易踩的坑。内容同时覆盖新手和老手的需求如果你正准备做果蔬品质检测、农产品智能分拣或者果园无人巡检这应该能帮你少走不少弯路。1. 数据集结构与核心价值拆解1.1 苹果瑕疵检测的应用场景和模型选型先说应用场景。苹果瑕疵检测在产线上其实已经很常见了传统方案多是用工业相机配合重量传感器、红外光谱或者光电分选机靠颜色阈值和大小来分级。但这种方案有个硬伤对形状不规则、颜色不均匀的瑕疵很难稳定识别例如早期腐烂导致的小块褐变、机械损伤后的果肉凹陷、虫蛀针孔等颜色和正常果皮差别可能很小传统视觉算法根本扛不住。这正是深度学习目标检测切入的空间。用YOLO系列做这类任务的优势在于模型本身就是一阶段检测器速度和精度能同时兼顾。在流水线上如果每秒要过好几个果子YOLOv5s甚至更轻量的nano版本可以在嵌入式设备上跑到几十甚至上百帧而Faster R-CNN这类两阶段模型虽然精度可能略高但速度很难满足实时分拣。所以“YOLO目标检测”和“苹果瑕疵检测”这两个词绑定在一起基本就是冲着轻量化落地方案去的。这也是这套数据集最有价值的地方——它假设你已经选定YOLO作为检测框架数据直接为这个目标服务。1.2 解压后你应该看到什么图片与xml标注的对应关系拿到压缩包之后正常的目录结构一般长这样apple_defect_dataset/ ├── images/ # 原始图片 │ ├── apple_001.jpg │ ├── apple_002.jpg │ ├── ... │ └── apple_1138.jpg ├── annotations/ # xml格式标签 │ ├── apple_001.xml │ ├── apple_002.xml │ ├── ... │ └── apple_1138.xml └── classes.txt # 类别列表部分版本提供图片和xml文件名一一对应这是VOC系列数据集最常见的组织方式也是脚本处理时默认的依赖条件。压缩包里大概是1300张左右的高清图片对应1300个xml标注文件。如果是你自己从零做一个数据集拍1300张带瑕疵的苹果照片每一张都要手工框出所有缺陷区域这个工作量少的也要两三天多则一周。所以这类现成数据集对起步阶段帮助非常大。有一点要提醒解压之后第一时间别急着训练先随机翻三五十张图片对照xml里的标注框看一遍。重点检查两类问题第一有没有明显标注遗漏比如图上有个一眼能看出来的烂斑但是xml里没框第二有没有框和实际瑕疵偏差太大的情况比如框子只框住了瑕疵的一半或者把阴影也框进去了。这些标注噪声会直接影响训练效果。我见过有人拿开源数据直接训完测试时发现模型对苹果的阴影特别敏感查了半天最后发现是训练集里大量把阴影标成了瑕疵所以前期花十分钟检查数据绝对值得。2. 标注格式拆解xml文件里到底藏了什么信息2.1 VOC格式标注的字段解读打开一个xml标注文件内容通常是这样的annotation folderimages/folder filenameapple_037.jpg/filename path/home/user/apple_defect_dataset/images/apple_037.jpg/path size width1280/width height720/height depth3/depth /size object namebruise/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin436/xmin ymin188/ymin xmax512/xmax ymax245/ymax /bndbox /object object namerot/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin790/xmin ymin421/ymin xmax854/xmax ymax502/ymax /bndbox /object /annotation几个关键字段说明一下。size里记录了图片宽高和通道数YOLO格式转换时归一化坐标必须依赖这个值。object是每一个被标注目标name是类别名bndbox里的xmin/ymin/xmax/ymax是目标左上角和右下角的像素坐标。truncated表示目标是否被图像边界截断difficult表示目标是否难以识别这两个字段在很多转换脚本里默认忽略但在严格评测时会起作用。我见过有人直接拿xml里的像素坐标当YOLO输入格式去训练结果损失一直降不下去这就是因为没搞懂格式机制。YOLO系列需要的标签不是绝对像素坐标而是相对图片宽高的归一化中心点坐标和宽高。像素坐标和归一化坐标之间的转换如果写错一个地方训练过程就会非常难受。后面第三节我会给出完整的转换脚本。2.2 为什么会用xml而不是txt标注这里顺便聊聊格式选择的逻辑。VOC格式用xml做标注最大的好处是可读性好而且信息维度比YOLO的txt格式丰富得多。xml里可以存每个目标的truncated、difficult状态这些信息在决定是否过滤某些难例时很有用。LabelImg这类标注工具默认输出的就是VOC格式生态成熟很多公开数据集包括Pascal VOC本身都用这个格式所以拿到手就能直接用。但YOLO系列训练时需要的txt格式非常“精简”每一行只有5个数类别索引加归一化的中心点x、中心点y、宽度、高度。这种设计是为了模型读取时的高效减少IO解析成本。一个文件里如果有一百个目标就有一百行每个目标的含义完全由行号和前5个数决定简单粗暴。所以两者之间需要一个转换环节这也是处理所有VOC格式YOLO数据集的必经之路。网上虽然有很多现成的“voc2yolo”转换脚本但不同数据集在细节上会有差异比如类别顺序的拍法、坐标是否全部落在图片范围内这些细节处理不好就会出问题。我的做法是自己写一个通用转换脚本每次拿到新数据先跑一遍然后统计转换后的txt文件里有没有越界坐标、有没有空标签避免坏数据直接进入训练流程。3. 训练前的核心准备标签格式转换与数据划分3.1 从xml到txtYOLO标准标签格式转换实操这里写一个我自己一直在用的转换脚本。完整功能包括读取每个xml文件、解析图片宽高、归一化坐标、按classes.txt的类别顺序映射到类别编号、最后输出YOLO格式的txt文件。import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_dir, txt_dir, classes_file): # 读取类别列表顺序就是YOLO格式里的类别索引 with open(classes_file, r) as f: classes [line.strip() for line in f.readlines()] class_to_id {name: idx for idx, name in enumerate(classes)} if not os.path.exists(txt_dir): os.makedirs(txt_dir) xml_files [f for f in os.listdir(xml_dir) if f.endswith(.xml)] for xml_file in xml_files: tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) txt_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_to_id: print(f[跳过] {xml_file} 中有未定义的类别: {name}) continue box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 防止出现坐标越界或负值 xmin max(0, min(xmin, width)) xmax max(0, min(xmax, width)) ymin max(0, min(ymin, height)) ymax max(0, min(ymax, height)) # 必须保证xmax xminymax ymin否则就是无效框 if xmax xmin or ymax ymin: print(f[警告] {xml_file} 中存在无效框已跳过) continue # 归一化中心坐标和宽高都要除以图片宽高 center_x (xmin xmax) / 2 / width center_y (ymin ymax) / 2 / height box_width (xmax - xmin) / width box_height (ymax - ymin) / height # 防止归一化后超出[0,1]范围 center_x max(0, min(center_x, 1)) center_y max(0, min(center_y, 1)) box_width max(0, min(box_width, 1)) box_height max(0, min(box_height, 1)) txt_lines.append(f{class_to_id[name]} {center_x:.6f} {center_y:.6f} {box_width:.6f} {box_height:.6f}) txt_name os.path.splitext(xml_file)[0] .txt with open(os.path.join(txt_dir, txt_name), w) as f: f.write(\n.join(txt_lines)) print(f[完成] {xml_file} - {txt_name}) if __name__ __main__: voc_to_yolo(annotations, labels, classes.txt)写的时候有几个细节值得注意。一是类别顺序问题classes.txt里的顺序必须和你最终训练时的data.yaml保持一致因为YOLO标签只存编号不存名字编号错位模型就会把碰伤当成腐烂来学。二是深拷贝的问题转换时如果同时处理多个数据集建议每次处理前先确认classes.txt是当前数据集对应的版本防止类别列表混用。三是转换完以后要随机抽几个txt文件用脚本把归一化坐标还原成像素坐标画在原图上看看框的位置对不对。这一步看着麻烦但能提前发现坐标错位隐患。3.2 数据划分与目录组织YOLO官方推荐结构转换完标签之后接下来是目录组织。YOLOv5和YOLOv8都推荐将全部数据按比例划分为训练集、验证集、测试集目录结构大概是这样的apple_defect/ ├── images/ │ ├── train/ │ │ ├── apple_001.jpg │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... ├── labels/ │ ├── train/ │ │ ├── apple_001.txt │ │ └── ... │ ├── val/ │ │ └── ... │ └── test/ │ └── ... └── apple_defect.yaml图片和标签的文件名要保持完全一致只是扩展名不同。划分比例一般按8:1:1或者8:2训练集太少会过拟合验证集太少则模型真正效果评估不准。如果你打算做模型最终效果汇报建议固定一个test集训练过程中不看test集的结果只在最后统一评测一次。划分时要注意随机性最好是按文件夹级别随机划分而不是按图片名称排序划分防止某个批次的数据出现在同一张图片的不同副本里。如果图片是按拍摄时间排序的直接按前80%分训练集、后20%分验证集容易造成训练集和验证集分布不一致模型训练完发现val精度很好但实际效果差原因就是数据泄漏或分布偏移。apple_defect.yaml的内容也简单# 数据集根目录绝对路径和相对路径都可以 path: /home/user/apple_defect train: images/train val: images/val test: images/test # 类别数和类别名数量一定和classes.txt一致 nc: 5 names: [bruise, rot, wormhole, crack, spot]这里有个小坑YOLOv8官方定义里path字段如果写了train/val/test一般写成相对于path子路径但如果你的图片和标签都在同一层级下直接写train: images/train就是对的。如果发现训练时数据加载为0先检查yaml里的路径是不是能正确拼出完整目录。4. 完整训练流程与关键参数调整策略4.1 用YOLOv8训练苹果瑕疵模型的完整流程到了正式训练这一步直接用Ultralytics YOLOv8是目前最省事的方案。我下面的命令都基于YOLOv8因为它的API稳定、配置简单训练、验证、导出一步到位。安装依赖略过假设你已经装好ultralytics且能正常运行。训练命令如下yolo detect train \ modelyolov8s.pt \ dataapple_defect.yaml \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ optimizerSGD \ patience20 \ seed42 \ projectrun/apple_defect \ nameyolov8s_baseline几个关键参数的选择逻辑说一下。model选择yolov8s.pt而不是nano是因为苹果瑕疵本身是小目标居多模型容量太小容易漏检s版本在精度和速度之间比较平衡。如果后续要部署到Jetson等嵌入式设备上再考虑从s蒸馏或直接用nano调参。imgsz使用640是YOLO系列的标准输入如果你的图片本身是4K高清直接用640训练虽然速度高但可能会把很多小瑕疵细节丢掉可以考虑用960甚至1280训练但显存会明显上升所以需要根据显卡实际容量来权衡。epochs设到150配合patience20做早停。目标检测数据集1300张图片不算多150轮基本能收敛如果到120轮mAP还在增长说明没学够可以继续加。如果50轮就稳定不再升了那就要考虑是不是学习率设置有问题或者数据量不太够。训练过程中可以打开loss曲线实时观察重点看box_loss和cls_loss的下降趋势。batch大小按显存来。比如12GB显存yolov8s640分辨率16batch是安全的如果是24GB显存可以直接32。batch太小比如2或者4会导致BN层统计不稳定训练曲线会剧烈震荡。实在显存不够优先考虑降低imgsz而不是疯狂减小batch。4.2 数据增强与不平衡样本处理苹果瑕疵检测还有一个很实际的问题就是类别不均衡。比如“碰伤”这类瑕疵因为发生频率高可能有四五百个目标样本而“虫眼”因为少见可能只有几十个目标样本。如果直接训练模型会倾向于把所有瑕疵都预测成高频类别低频类别recall很低。解决不均衡有几个常用手段。第一是Mosaic和数据增强策略调整YOLOv8默认开启Mosaic增强它能在一张图里拼多个图片对小目标检测有不小的帮助。如果发现训练后期loss震荡厉害可以考虑在最后10个epoch关掉Mosaic让模型在更接近真实分布的数据上微调yolo detect train \ modelyolov8s.pt \ dataapple_defect.yaml \ epochs150 \ imgsz640 \ batch16 \ mosaic0.5 \ close_mosaic10第二是按类别频率做样本重加权。如果某个类别目标太少可以单独复制该类别样本图片或者在计算损失时对低频类别施加更大权重。YOLOv8默认的损失函数中类别权重是均匀的但训练出的类别损失曲线如果出现严重不平衡就需要手动处理。第三是考虑用带预训练权重的模型继续微调而不是从零训练这也是我强烈建议的一种做法。yolov8s.pt这个权重是在COCO上训练的模型前几层已经学会了通用的边缘、纹理、颜色特征针对苹果图像微调时收敛速度会快得多而且因为特征提取器已经比较强即使样本不多也能学到有效特征。我曾经在只有五六百张标注图的番茄病害数据集上微调过YOLOv8smAP在50轮之内就到了0.78但要随机初始化训练可能150轮也到不了这个数。还有一点如果你的瑕疵目标尺寸特别小可以在训练前考虑把原始图片切块成两个640x640的patch分别训练或者使用SAHI这类切片推理工具在推理阶段把大图切块检测完再拼接。对于高分辨率流水线图片这个技巧非常实用。5. 验证集评估与常见bug排查实录5.1 评估指标怎么读mAP、PR曲线和混淆矩阵训练结束后模型会在验证集上自动产生一组指标最常见的是mAP50和mAP50-95。mAP50指的是IOU阈值大于0.5时算检对mAP50-95是在从0.5到0.95之间每隔0.05取一个阈值计算mAP后求平均。前一个相对宽松考验模型大致框得准不准后一个严格得多框位置只要稍微偏一点分数就会掉。对于苹果瑕疵检测我一般会两个指标一起看。如果mAP50有0.85、mAP50-95只有0.55说明模型能定位到瑕疵但框的边界位置不够精细。这种情况要调整的话可以适当加一些边界框回归的损失权重或者在验证时手动调整conf_threshold和iou_threshold来看实际效果。如果mAP50-95也到了0.7以上说明模型框的位置已经相当精准了实际部署时可以直接应用。confusion_matrix.png这个文件也不能忽略。它显示的是每个真实类别被预测成哪些类别的比例。我经常发现苹果瑕疵检测里腐烂和碰伤这两个类别很容易混淆因为早期腐烂区域颜色和碰伤区域都是褐色的视觉上差别很小。如果训练集里这两类的标注尺度、形状没有明显区分混淆矩阵里会出现明显非对角线元素。这时可以考虑把这两个类别合并成一个大类“damage”或者增加更多细粒度标注让模型学到更细致的差异。5.2 实际训练中遇到的典型问题与排查方法我把自己和同行在类似项目里踩过的坑整理成一张速查表希望你有问题的时候能第一时间想到现象可能原因解决办法训练loss一开始就很大且不下降标签归一化错误、类别编号错位、学习率过大检查txt标签把归一化坐标还原成像素坐标画图对比降低lr0到0.001训练过程中突然出现NaN学习率过高、batch内出现坏数据、梯度爆炸降低lr0检查数据里是否有全黑图片或异常标注开启amp或关闭amp试一下验证集精度很低但训练集精度高过拟合、验证集分布和训练集差异大增加数据增强、增大数据集、检查数据划分是否随机漏检小瑕疵图片里烂斑没框出来模型输入分辨率不足、小目标正样本不足提高imgsz、图片切块训练、使用SAHI切片推理把正常果皮检测成瑕疵标注噪声、阴影未被排除、背景干扰检查训练集里有没有阴影被标成瑕疵增加负样本或难例挖掘这里多说一句“坏数据”的问题。训练前最好写个小脚本批量检查图片能不能正常解码因为网络下载的数据集偶尔会混入损坏的图片。OpenCV的imread读不出内容时会返回None这些图片如果不排除训练到中途可能会突然报错而且很难定位是哪张图出了问题。我一般会写个循环把所有图片读一遍同时校验对应的txt标签文件是否都存在确保每个训练样本完整可用。另一个很容易被忽略的问题是图片颜色空间的差异。有些数据集里的图片是RGBA四通道或16位深度的PNG图YOLO训练时可能会因为通道数不一致报错。处理办法是在预处理脚本里统一转换到RGB三通道8位深度再存成jpg格式。别小看这一步几千张图片如果混了几个特殊格式训练到一半就会卡住。6. 从数据到落地模型部署与产线集成要点6.1 导出与推理训练完成后YOLOv8可以很方便导出成ONNX、TensorRT等格式。导出命令如下yolo export modelrun/apple_defect/yolov8s_baseline/weights/best.pt formatonnx imgsz640导出的ONNX文件可以直接用ONNX Runtime推理也可以在TensorRT上进一步加速。如果目标是部署在GPU服务器上建议把导出格式设置为engineTensorRT优化后的推理速度往往是ONNX Runtime的好几倍。如果是要部署到边缘设备比如Jetson Nano可以根据设备算力选择TensorRT engine或者更轻量的nano模型导出的engine。推理阶段的置信度阈值需要单独调。训练时默认的conf0.25在通用场景下问题不大但瑕疵检测实际部署时漏检导致的损失可能比误检大得多所以生产环境中我往往会把conf调到0.1甚至更低配合后处理规则再做一次过滤。比如检测到某个区域是腐烂但面积太小且置信度偏低可以认为是噪声丢弃而置信度中等但面积较大的区域则保留。这种规则和后处理逻辑往往比单纯调模型参数更能适应具体产线。6.2 边缘场景与持续迭代部署模型只是第一步真正让模型在产线上稳定运行还要注意几个非模型层面的问题。第一个是光照一致性。苹果表面是曲面光照影响极大。同一颗苹果在暖光、冷光、逆光、侧光条件下拍出来的图模型推理结果可能差异非常明显。如果你在数据集里看到的图片和现场环境不一样部署前务必做充分的domain adaptation最简单的是采集一部分现场图片加入训练集做finetune或者在拍摄端做光照标准化。第二个是相机畸变和安装角度。如果用等焦镜头图片边缘畸变不明显但如果用广角镜头边缘苹果会被拉伸变形检测框的精度会受波及。建议在图像预处理里加入去畸变步骤或者直接用现场相机多拍一部分样张加入训练集。第三个是模型迭代闭环。产线每天可能产生大量新的苹果图像建议设计一个“难例回流”机制每天把模型置信度低于某个阈值或者人工复核发现错误的图片保存下来每周统一筛选一批加入训练集重新微调模型。这样做一两周之后模型在现场的精度会有肉眼可见的提升。如果你打算把这套数据练出来的模型直接应用在农产品分拣机器人或者果园巡检无人机上还要记得考虑算力的端侧适配问题。不同硬件的推理框架、量化精度、动态尺寸支持能力都不同最好在选型阶段就确定部署平台免得训练费了大力气最后发现模型在目标设备上跑不起来。7. 扩展方向与个人实操心得这套苹果瑕疵检测数据集目前解决了从零到一的问题但它绝不是终点。在实际项目中你可以考虑几个自然的扩展方向。第一是多类别果蔬适配。苹果的瑕疵检测框架完全可以迁移到梨、桃子、番茄等果蔬上。核心思路是保留模型在苹果样本上学到的纹理特征和瑕疵形态用新的果蔬数据做迁移学习通常几十张到几百张标注图就能取得不错效果。因为不同水果的碰伤、腐烂、斑点在视觉模式上有大量共同点模型的前几层特征是完全通用的。第二是多模态融合。如果现场环境允许可以用深度相机获取苹果的3D信息结合RGB图像判断瑕疵深度。比如碰伤在3D点云上也会有一个凹陷腐烂区域则会有软塌变形。多模态融合可以显著降低误检率是目前农产品检测赛道的热点方向之一。第三是实例分割。如果果园要求更精细的判断比如“腐烂面积是否超过苹果表面积的20%”来判定等级那检测框就不好使了需要用YOLOv8-seg这类实例分割模型逐像素圈出瑕疵区域再算面积占比。这套数据集的框标注没法直接用于分割需要额外标注多边形mask工作量会大不少但产线精度和等级判断能力会明显提升。最后分享一点我的个人体会。做目标检测项目很多人一上来就想调模型结构、改损失函数、试各种SOTA方法但实际经验告诉我数据质量永远是第一位的。拿到任何数据集先花时间理解它、清理它、可视化它比盲目训十轮模型都更有价值。这套苹果瑕疵数据集整体质量不错但你也别指望它覆盖所有真实场景。最好在训练前就想清楚你的目标部署环境根据目标环境适当补充一些现场数据哪怕只有两三百张对模型的泛化能力都会有很大帮助。用YOLO做苹果瑕疵检测这件事说难不难说简单也不简单。难在数据、光照、瑕疵形态都变数太多简单在有成熟的开源框架和训练流程普通人几天之内就能跑通全链路抓到第一版模型。拿到这套数据集的朋友建议按照我上面说的步骤走一遍先检查标注、再转格式、按比例划分、微调训练、分析指标、部署验证。走完这一圈你对整个目标检测项目的落地流程应该就有完整的体感了。这套流程跑通之后再遇到类似的项目比如其他水果的瑕疵检测、工业零部件表面缺陷检测、甚至纺织品瑕疵检测基本都可以借鉴同一套方法论。目标检测的模型架构大同小异真正的核心竞争力始终是数据理解能力和对业务场景的把控。希望这篇分享能帮你绕开我踩过的那些坑让你的苹果瑕疵检测项目尽快落地。本文还有配套的精品资源点击获取