
简介这份水果新鲜程度检测数据集面向计算机视觉学习者与目标检测实践者聚焦水果品质分拣这一典型应用场景可用于训练和验证模型对正常与变质水果的识别能力。数据集共包含1192个文件以jpg图像、txt标签和xml标注为主其中图像约397张txt与xml分别提供YOLO与Pascal VOC两种格式的标注方便直接接入不同检测框架压缩包整体约15.19MB体积轻量便于快速下载与本地调试。目标类别覆盖apple、banana、bad apple和bad banana共4类兼顾完好与腐坏样本适合做二分类与多类别检测的对比实验。目前已有1438人学习下载说明其在教学与练手项目中具备一定参考价值。读者可借此完成数据加载、格式转换、模型训练与结果评估的完整流程并对照公开的检测结果链接复盘指标表现适合作为课程设计、毕业设计或算法入门的实战素材。1. 水果新鲜程度检测数据集4 个类别、txt 与 xml 双格式能直接喂给谁上周帮一个做生鲜分拣的朋友看模型他拿着一堆手机拍的水果照片问我有没有现成的标注数据能先跑通流程别一上来就自己标几千张。这类需求其实很典型——不是缺算法是缺一份能立刻加载、类别定义清楚、格式对得上训练框架的小规模数据集。这份水果新鲜程度检测数据集就是干这个的几百张图目标类别固定为 apple、bad banana、banana、bad apple 四类标签同时给了 txt 和 xml 两种格式分别放在两个文件夹里。txt 对应 YOLO 系列的归一化坐标xml 对应 Pascal VOC 的绝对坐标等于把两条主流训练链路都铺好了。适合谁想快速验证检测流程的算法工程师、做课程设计的学生、以及需要一个小样本集做数据增强实验的人。它不解决精度天花板但能让你在半小时内把训练脚本跑起来先看到 loss 往下掉。2. 先搞懂标注格式txt 与 xml 到底差在哪为什么两个都要留2.1 txt 格式的坐标逻辑与 YOLO 的对应关系txt 标签在 YOLO 体系里是每行一个目标格式为class_id x_center y_center width height后四个值都是相对于图像宽高的归一化数值范围 0 到 1。这份数据集里类别索引通常按 apple、bad banana、banana、bad apple 的顺序映射为 0 到 3但不同整理者可能调换顺序所以第一步永远是打开一个 txt 文件确认类别编号。归一化的好处是图像 resize 到任意尺寸都不需要重算坐标直接送进网络即可。坏处是如果你要可视化回原图必须乘回宽高否则框会缩在左上角一小块。我一般会先写个校验脚本把归一化坐标还原成像素坐标画到图上抽查十张确认没有越界或宽高为负的情况。import os import cv2 # 假设类别顺序实际以数据集内 classes.txt 或 README 为准 classes [apple, bad_banana, banana, bad_apple] def yolo_to_pixel(txt_path, img_w, img_h): boxes [] with open(txt_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cid, xc, yc, w, h map(float, parts) # 归一化转像素 x1 int((xc - w / 2) * img_w) y1 int((yc - h / 2) * img_h) x2 int((xc w / 2) * img_w) y2 int((yc h / 2) * img_h) boxes.append((classes[int(cid)], x1, y1, x2, y2)) return boxes # 抽查一张 img cv2.imread(apple_10.jpg) h, w img.shape[:2] for name, x1, y1, x2, y2 in yolo_to_pixel(apple_10.txt, w, h): cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, name, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_apple_10.jpg, img)这段代码做了三件事读 txt、把归一化坐标乘回图像宽高、画框并保存。参数上唯一需要你确认的是classes列表的顺序如果数据集里类别索引和这个不一致画出来的名字会张冠李戴。另外注意w和h是框的宽高不是右下角坐标所以计算 x1 时用的是xc - w/2这是 YOLO 格式最容易写错的地方。2.2 xml 格式的 VOC 结构与解析要点xml 走的是 Pascal VOC 路线每个图片对应一个同名 xml 文件里面包含filename、size和若干object节点。每个 object 下有name和bndboxbndbox 里是xmin、ymin、xmax、ymax四个绝对像素坐标。和 txt 相比xml 可读性更好带图像尺寸信息适合直接送进 torchvision 的检测数据集接口或做可视化标注检查。但 xml 的坑在于坐标可能超出图像边界尤其是标注时手抖或者图像被裁剪过。解析时我习惯加一层裁剪保护把坐标限制在[0, width]和[0, height]范围内避免训练时 ROI 采样报错。import xml.etree.ElementTree as ET def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) objects [] for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin max(0, int(bbox.find(xmin).text)) ymin max(0, int(bbox.find(ymin).text)) xmax min(img_w, int(bbox.find(xmax).text)) ymax min(img_h, int(bbox.find(ymax).text)) objects.append((name, xmin, ymin, xmax, ymax)) return img_w, img_h, objects这里max(0, ...)和min(img_w, ...)就是边界保护防止个别标注越界导致后续裁剪出空区域。size节点不一定所有 xml 都有如果缺失就得用 cv2 读图补上这是实际整理数据时常见的缺失项。2.3 两种格式的选型建议与转换思路选 txt 还是 xml取决于你用的框架。YOLOv5/v8/v11 系列直接吃 txt目录结构一般是images/train和labels/train平行放置文件名一一对应。MMDetection、Detectron2、torchvision 的VOCDetection则更习惯 xml。如果你拿到的数据集两个文件夹都有最省事的做法是训练时只用一个另一个留作校验——比如用 txt 训练随机抽若干张用 xml 解析出来的框做交叉比对确认两种标注一致。转换本身不难txt 转 xml 需要读图拿宽高再乘回去xml 转 txt 则除以宽高做归一化。真正容易翻车的是类别名映射xml 里写的是bad banana带空格txt 里可能是bad_banana或索引 1转换时必须统一否则类别数会对不上。3. 把数据集接进训练流程目录组织、配置修改与首轮跑通3.1 目录结构整理与文件名对齐拿到手的数据集往往图片在一个文件夹、标签在另一个文件夹文件名相同但扩展名不同。以 YOLO 为例标准结构是dataset/ images/ train/ apple_10.jpg banana_9.jpg val/ bad_apple025.jpg labels/ train/ apple_10.txt banana_9.txt val/ bad_apple025.txt注意bad-043.jpg这种带连字符的文件名对应的标签必须是bad-043.txt不能写成bad_043.txt否则训练时找不到标签会被当成背景图模型会把坏苹果学成背景这是血泪经验。划分 train/val 时小数据集建议按 8:2 随机分但要做分层抽样保证四个类别在验证集里都有出现。几百张图的规模验证集大概几十张如果某一类只有十几张验证集里可能只剩两三张指标波动会很大这时候可以适当调低验证比例或者做交叉验证。# 按 8:2 随机划分并保持类别分布简易脚本思路 python -c import os, random, shutil random.seed(42) imgs [f for f in os.listdir(raw_images) if f.endswith(.jpg)] random.shuffle(imgs) split int(len(imgs) * 0.8) for i, f in enumerate(imgs): dst dataset/images/train if i split else dataset/images/val shutil.copy(os.path.join(raw_images, f), dst) txt f.replace(.jpg, .txt) lbl_dst dst.replace(images, labels) shutil.copy(os.path.join(raw_labels, txt), os.path.join(lbl_dst, txt)) 这段脚本用固定随机种子保证可复现先打乱再切分。实际用的时候记得先建好dataset/images/train、dataset/images/val、dataset/labels/train、dataset/labels/val四个目录否则 copy 会报路径不存在。类别不平衡时这个随机划分可能让某一类在验证集里消失更稳妥的做法是按类别分组后再切分但代码会多几行先跑通再优化。3.2 YOLO 配置文件的三个必改项YOLO 系列训练需要改数据配置文件通常叫data.yaml。里面三个关键字段train和val指向图片目录nc是类别数names是类别名列表。这份数据集nc就是 4names按你的索引顺序写。常见错误是nc写了 4 但names只列了 3 个训练启动时报索引越界或者names里用了中文或空格某些版本解析会出问题建议用下划线代替空格比如bad_banana。# data.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 4 names: [apple, bad_banana, banana, bad_apple]改完配置后先别急着训几百轮。用yolo detect train datadata.yaml modelyolov8n.pt epochs1 imgsz640 batch8跑一个 epoch看三件事数据加载有没有报错、loss 是不是正常下降、验证阶段有没有输出 mAP。一个 epoch 能跑完且不报错说明格式和路径基本对了。如果 loss 是 nan多半是学习率太大或者标签坐标有负数如果 mAP 一直是 0检查类别索引是否和names对得上。3.3 小样本下的数据增强参数怎么设几百张图属于小样本数据增强是必须开的但参数不能照搬大数据集。YOLO 默认的 mosaic 增强会把四张图拼成一张对小数据集能显著增加场景多样性但这份数据集里苹果和香蕉的拍摄背景可能比较单一mosaic 比例开到 1.0 会让模型过度依赖拼接边缘。我一般把mosaic设到 0.5 到 0.8 之间mixup先关掉因为 mixup 在样本少时容易产生不真实的叠加图像。hsv_h、hsv_s、hsv_v可以适当开大一点模拟不同光照下的水果颜色变化这对新鲜度判断很关键——坏苹果和好苹果的颜色差异是重要特征增强时不能把颜色空间压得太窄。yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 \ mosaic0.7 mixup0.0 hsv_h0.02 hsv_s0.7 hsv_v0.4 \ degrees10 translate0.1 scale0.3 fliplr0.5参数说明mosaic0.7表示 70% 概率做四图拼接hsv_s0.7是饱和度增强幅度degrees10允许小角度旋转fliplr0.5水平翻转概率。注意水果检测里垂直翻转要慎用倒置的苹果在真实分拣线上几乎不出现开了反而引入噪声。这些值不是金标准先按这套跑一轮看验证集 mAP 曲线再微调。4. 避坑与排查标注、路径、类别映射里最容易翻车的五件事4.1 现象训练启动即报“No labels found”原因通常是图片目录和标签目录没有平行对应或者标签文件扩展名不是.txt。YOLO 在加载时会自动把图片路径里的images替换成labels再找同名 txt如果实际目录名是labels_txt或者标签放在annotations下就会找不到。解决方法是检查目录名是否严格为images和labels或者用path字段在 yaml 里显式指定根路径。另一个隐蔽原因是文件名大小写Apple_10.jpg和apple_10.txt在 Linux 下不匹配Windows 下可能侥幸通过换到服务器就翻车。4.2 现象验证集 mAP 正常但推理时框全偏了原因多半是 txt 标签的坐标没有归一化或者归一化时除错了基准。比如标注工具导出的是绝对坐标整理时直接当归一化用了数值大于 1训练时被截断模型学到的框位置就是错的。排查方法是随机抽几个 txt看后四个值是否都在 0 到 1 之间。如果大于 1说明是绝对坐标需要除以图像宽高重新生成。xml 转 txt 时如果忘了除也会出现同样问题。4.3 现象坏苹果被识别成好苹果混淆矩阵集中在对角线外原因通常是类别名映射不一致。xml 里写的是bad appletxt 里索引 3 对应bad_apple但你在names里写成了badapple训练时类别对不上模型把两个类当成一个学。解决方法是统一用一份classes.txt所有格式转换都从这个文件读类别顺序转换脚本里做一次断言确保每个类别的索引和名称唯一对应。另外检查数据集中是否有拼写变体比如bad_banana和badbanana混用。4.4 现象训练 loss 震荡剧烈几个 epoch 后突然变成 nan小数据集加上默认学习率容易导致梯度爆炸。YOLO 默认学习率是 0.01对几百张图来说偏大。可以先降到 0.001同时开 warmup让模型在前几个 epoch 慢慢适应。另外检查 batch size如果显存够batch 设到 16 比 8 更稳因为梯度估计方差更小。如果已经出现 nan把最后一个正常保存的权重拿回来调小学习率再继续别从头训。4.5 现象同一张图在训练集和验证集里都出现原因划分时没有去重或者数据增强后的副本被当成了独立样本。小数据集里重复图片会让验证指标虚高实际部署时性能掉得厉害。排查方法是计算图片的哈希值检查 train 和 val 之间有没有重复。另外注意bad-043.jpg和bad_043.jpg这种命名差异如果同时存在且内容相同也会造成隐性重复。解决方法是划分前先做一次全量去重按文件内容哈希而不是文件名。5. 进阶用法用这份数据做新鲜度二分类与检测联合验证这份数据集除了直接训检测器还能拆出一个二分类任务来验证特征质量。思路很简单把 apple 和 bad apple 的框裁剪出来分别打上新鲜和不新鲜标签训一个轻量分类网络看它在验证集上的准确率。如果分类准确率明显高于检测器对这两类的区分能力说明检测头没学好可能是框回归拖累了分类如果两者差不多说明特征本身对新鲜度就不够敏感需要考虑加颜色空间转换或者纹理特征。这个联合验证能帮你判断瓶颈在检测框架还是在数据本身。import cv2 import os # 从 xml 裁剪坏苹果区域生成二分类样本 def crop_bad_apple(xml_path, img_path, out_dir): img_w, img_h, objects parse_voc_xml(xml_path) img cv2.imread(img_path) for i, (name, x1, y1, x2, y2) in enumerate(objects): if name.replace( , _) bad_apple: crop img[y1:y2, x1:x2] if crop.size 0: continue cv2.imwrite(os.path.join(out_dir, fbad_{i}.jpg), crop)裁剪时注意y1:y2和x1:x2的顺序OpenCV 是先行后列。如果框坐标越界裁剪出来是空数组加个crop.size 0判断跳过。生成的正负样本可以按 1:1 配比好苹果从 apple 类里随机裁同样数量。分类网络用 mobilenet 或 resnet18 就够输入 224训 20 个 epoch 看验证准确率。这个实验花不了多少时间但能让你对数据质量有个量化判断。另一个进阶方向是测试时增强TTA。检测器对同一张图做水平翻转、多尺度缩放把多次预测的框做 NMS 融合通常能涨一两个点 mAP。这份数据集里水果姿态变化不大TTA 的收益可能有限但值得试一次。具体做法是在推理脚本里对每张图生成翻转版本分别推理后把框映射回原坐标再统一做 NMS。注意映射时水平翻转的 x 坐标要用width - x还原别直接复制。从那以后我每次拿到新数据集都强制先跑一遍格式校验和可视化抽查确认标签和图片对得上、坐标在合理范围、类别映射唯一再开始训练。这份水果新鲜程度检测数据集规模不大但四个类别覆盖了完好与腐败两种状态txt 和 xml 双格式省去了不少转换功夫适合拿来练手或者做流程验证。希望帮到你。本文还有配套的精品资源点击获取