简介:这是一份面向目标检测入门与实战的YOLOv5格式水果检测数据集,涵盖苹果、橘子、梨三个类别,已划分好训练集与验证集,解压后即可直接用于模型训练。资源共2000个文件,以1397个txt标注标签和602张jpg图像为主体,另附1个Python可视化脚本,压缩包约226.87MB;图像统一为1080×810分辨率,每张包含多个目标,边界框标注完整,标签与图片一一对应。数据集按YOLOv5目录规范组织,训练集1117张、验证集279张,附带3类类别txt文件,免去自行整理数据的麻烦。可视化脚本无需修改即可运行,随机传入一张图片即可绘制边界框并保存,方便快速检查标注质量,适合小型目标检测项目的快速验证。目前已有518人学习下载,对想练习水果检测或构建YOLO训练流程的读者是实用资源。
1. 苹果橘子梨检测数据集:先看懂 YOLOv5 的“目录格式”再动手
做目标检测的人,最难的不是看懂网络结构,而是手里没有一份能直接跑起来的干净数据集。这个苹果、橘子、梨三类水果的目标检测数据集,最大的价值是把图片、标签、训练集、验证集全部按 YOLOv5 的目录格式排好了,下载后不用改目录、不用写脚本重新划分,把 data.yaml 里的路径指对就能开始训练。对刚接触 YOLOv5 的人来说,它能让你把注意力放在训练参数、检测效果和数据增强上,而不是浪费在“为什么训练时报标签错误”这种格式问题上。适合正在做毕业设计、农业分拣 demo、或是第一次跑 YOLOv5 的开发者。接下来我把数据集的结构、标签格式、训练方法和踩坑点一次讲清楚。
2. 目录结构、标签文件与 data.yaml:三类水果数据集的规范拆解
2.1 images 与 labels 的对应关系:图片和标签怎么配对
YOLOv5 的数据集不像 VOC 那样用一个 XML 文件包住所有标注信息,它把图片和标注分开放。这个数据集根目录下通常是images/train、images/val、labels/train、labels/val四个文件夹,同名图片和同名标签通过“不带扩展名的文件名”配对。例如images/train/apple_001.jpg对应labels/train/apple_001.txt。
配对规则是严格的:标签文件名必须和图片文件名完全一致,只有后缀不同。图片可以是.jpg、.png,标签必须是.txt。如果某张图片没有对应的 txt,训练时 YOLOv5 会把它当作背景图处理;反过来,如果 txt 存在但没有图片,训练时会在加载阶段跳过,且日志里会提示。很多新手把数据集拷到别的机器后,发现文件名被系统改了大小写或空格,配对就会失效。
# 检查 images/train 和 labels/train 的文件名是否一一对应 cd 数据集根目录 for img in images/train/*.jpg; do base=$(basename "$img" .jpg) if [ ! -f "labels/train/${base}.txt" ]; then echo "缺少标签: $img" fi done这个脚本的逻辑很简单:遍历所有图片,取出不带扩展名的文件名,再去 labels 目录里找同名 txt,找不到就打印出来。建议在训练前跑一遍,正常情况不应该有任何输出。如果输出很多,说明这份数据集在你这边被改动过,需要重新补齐或修正文件名。
2.2 标签 txt 的内容与类别编号:一行五个数是什么意思
打开任何一张水果图片对应的 txt,你会看到每行五个数字,例如:
0 0.523437 0.482143 0.176562 0.278571 1 0.351562 0.519643 0.142188 0.194048 2 0.687500 0.507143 0.160938 0.273810五个数字依次是:类别编号(class_id)、归一化后的中心点 x 坐标、中心点 y 坐标、归一化后的宽度 w、归一化后的高度 h。归一化意味着所有坐标值都在 0 到 1 之间,用像素值除以图片的原始宽高得到。类别编号从 0 开始,这份数据集里 0 代表苹果,1 代表橘子,2 代表梨。
这里有个反直觉的地方:标签里并不直接写类别名,只写数字编号。所以类别顺序完全由data.yaml里的names列表决定。如果训练时你把 names 写成了['apple', 'pear', 'orange'],那么标签里编号 1 的橘子会被当成梨,训练出来的模型会把橘子识别成梨。这种错误不会报错,只会影响精度。查看某个标签的统计信息,可以用下面这段 Python 代码:
from collections import Counter counter = Counter() for txt_path in ['labels/train']: with open(txt_path + '/apple_001.txt') as f: for line in f: cls = int(line.split()[0]) counter[cls] += 1 print(counter) # 例如 Counter({0: 12, 1: 15, 2: 10})这段代码统计单个标签文件里各类别的出现次数,主要用来确认类别编号有没有明显异常。如果出现cls=3,说明这份标签里混进了第四类,或者标签文件被污染了,需要立刻检查。
2.3 data.yaml 配置:train/val 路径与类别名怎么填
YOLOv5 训练前必须有一份数据集配置文件,通常叫data.yaml。这个文件告诉训练脚本:训练集图片在哪、验证集图片在哪、类别有几类、类别名是什么。写法如下:
# data.yaml train: ./datasets/fruit/images/train val: ./datasets/fruit/images/val nc: 3 names: ['apple', 'orange', 'pear']注意train和val都指向图片目录,而不是标签目录。YOLOv5 会自动根据图片路径推导标签路径,默认把images替换成labels。例如./datasets/fruit/images/train/apple_001.jpg对应的标签会被自动寻找为./datasets/fruit/labels/train/apple_001.txt。如果目录结构是datasets/fruit/train/images这种,路径就要写成./datasets/fruit/train/images,YOLOv5 会把images替换为labels,找到./datasets/fruit/train/labels。
nc必须等于标签里的最大类别编号加一。这里三个类别编号是 0、1、2,所以nc=3。写错nc会导致模型输出维度不对,训练直接报错。names的顺序必须与标签中的编号一一对应,顺序错了模型不会报错,但评估结果完全不可信。
2.4 验证集划分:为什么这份数据集的划分方式更适合训练
很多公开数据集只给一张总的标注表,需要自己划分,而这份数据集的训练集和验证集已经按目录分开。划分的逻辑通常是:验证集里包含各种光线条件、多种摆放角度、不同距离的水果图片,而不是简单随机切分。这样做的好处是验证集能真实反映模型遇到新图片时的表现。
实际训练中,如果随机切分,同一棵树上拍的十几张图片很可能同时出现在训练集和验证集里,造成“信息泄露”。验证集 mAP 会虚高,但模型拿到真实场景里的新照片表现很差。这份数据集的划分方式更像实际部署场景,值得沿用。我自己遇到新数据集时,也会用sklearn的train_test_split按文件名分组,而不是直接打散所有图片。
3. 训练前校验:图片损坏、标注越界与类别顺序检查
3.1 用 Python 脚本扫一遍数据集
不校验就训练,风险很大。图片文件虽然能正常打开,但可能像素值全黑、分辨率过低;标签可能越界、宽度或高度为负、中心点不在图片范围内。YOLOv5 训练时对部分问题有容错,但越界过大的标注会严重干扰回归分支的学习。我一般训练前都会跑一遍完整的数据集体检脚本:
import os from PIL import Image base_dir = 'datasets/fruit' for subset in ['train', 'val']: img_dir = os.path.join(base_dir, f'images/{subset}') lbl_dir = os.path.join(base_dir, f'labels/{subset}') for img_name in os.listdir(img_dir): img_path = os.path.join(img_dir, img_name) base = os.path.splitext(img_name)[0] txt_path = os.path.join(lbl_dir, base + '.txt') # 1. 检查图片能否打开 try: with Image.open(img_path) as im: w, h = im.size except Exception as e: print(f'图片损坏: {img_path} -> {e}') continue # 2. 检查标签是否存在 if not os.path.exists(txt_path): print(f'缺少标签: {txt_path}') continue # 3. 检查标签内容 for line in open(txt_path): parts = line.strip().split() if len(parts) != 5: print(f'格式错误: {txt_path}: {line}') continue cls, cx, cy, bw, bh = map(float, parts) if cls < 0 or cls >= 3: print(f'类别编号越界: {txt_path}: {line}') if not (0 < cx < 1 and 0 < cy < 1): print(f'中心点越界: {txt_path}: {line}')这段代码做了三重检查:图片能不能正常解码、标签文件是否存在、标签里的坐标是否符合归一化规则。特别说明一下0 < cx < 1这个条件,虽然边界情况下中心点等于 0 或 1 理论上可能,但实际上目标不可能紧贴图片边缘,越界标注通常是转换脚本算错了。另外,这里用map(float, parts)直接把字符串转成浮点数,如果某一行混入非数字字符,Python 会抛异常,这一行也会被触发。
3.2 可视化验证:把标注框画回图片
数值检查只能排除格式错误,不能排除“标错位置”。最有效的验证方式是把每个标注框画到原图上,人工抽查二十张左右。YOLOv5 官方仓库里带了detect.py,但训练前更快的做法是用OpenCV自己画框:
import cv2 import os img = cv2.imread('datasets/fruit/images/train/apple_001.jpg') h, w = img.shape[:2] for line in open('datasets/fruit/labels/train/apple_001.txt'): cls, cx, cy, bw, bh = map(float, line.split()) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cls)), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite('check.jpg', img)这里要注意,从归一化坐标还原为像素坐标时,中心点减去半宽得到左上角 x,中心点加上半宽得到右下角 x,宽度和高度分别乘上图片宽和高。画出来的框如果明显偏离水果位置、框住大片背景,或者多个框几乎重叠,都说明标注质量有问题。这种可视化检查我建议做两轮:第一轮随机抽 10 张训练集图片,第二轮抽 10 张验证集图片。
3.3 常见误用:直接把 VOC 格式硬改成 YOLO
网上很多教程教人把 XML 转成 txt,但转换脚本写不对时,最容易出现两类问题:一个是忘记归一化,直接把像素坐标写进 txt;另一个是把类别编号和类别名顺序搞混。像素坐标没归一化时,YOLOv5 训练也能跑,但 loss 会在一个高位震荡,收敛极慢,最终模型几乎不学习。
正确转换时,中心点 x = (xmin + xmax) / 2 / image_width,宽度 = (xmax - xmin) / image_width。很多脚本把image_width和image_height写反,结果就是所有框变成纵向拉伸。如果这份数据集是从 VOC 或者 Roboflow 导出的,建议转换后随便挑一张图,算一下框的宽高比是否合理。水果目标基本是近似方形或椭圆,宽高比应在 0.5 到 2.0 之间,超出这个范围的框大概率是转换 bug。
4. YOLOv5 训练实战:预训练权重、超参数与命令行
4.1 环境准备与依赖安装
YOLOv5 官方仓库基于 PyTorch,训练前需要把环境准备好。我常用的环境组合是 Python 3.9、PyTorch 2.0、CUDA 11.8。如果你用 GPU,建议先确认 nvidia-smi 显示的驱动版本能支持对应的 CUDA;没有 GPU 也能训练,但速度会慢很多,1000 张图片、50 个 epoch 可能要好几个小时。
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里requirements.txt里包含torch、opencv-python、matplotlib、pandas等几十个包。如果机器上已经装了 PyTorch,建议先单独检查版本,避免requirements.txt把 PyTorch 重新装成 CPU 版。我遇到过几次这种情况:之前 GPU 版本好好的,clone 仓库后重新 install,结果 PyTorch 被降级成 CPU 版,训练时 GPU 占用率为零。
4.2 训练命令与参数解释
训练入口是train.py,最基础的命令是:
python train.py \ --data ../fruit.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 80 \ --name fruit_run--data指向刚写好的data.yaml,--weights是预训练权重,--img是输入图片的尺寸,--batch是批大小,--epochs是训练轮数,--name是本次训练的结果保存目录名。第一次跑建议用yolov5s.pt,模型小、显存占用低,三分类水果任务完全足够。
参数里最需要根据电脑配置调整的是--batch。显存不够时会报CUDA out of memory,这时把 batch 减半,例如从 16 降到 8。不要盲目增加--img,水果这种目标不算特别小,640 已经足够,推到 1280 只会让训练时间翻四倍,精度提升不大。
4.3 超参数调整:针对三种水果的优化方向
数据集的标签框质量决定了下限,超参数则决定能否充分逼近这个下限。YOLOv5 默认超参数文件是data/hyps/hyp.scratch-low.yaml,里面有几个关键值值得手动改:
lr0: 0.01 # 初始学习率 lrf: 0.01 # 最终学习率系数 momentum: 0.937 weight_decay: 0.0005 warmup_epochs: 3.0训练三类水果,我通常会关掉或调低mosaic增强。水果边缘干净、背景相对单一,mosaic 增强虽然能提升泛化性,但会让小目标更容易漏检。在hyp.scratch-low.yaml里把mosaic: 1.0改成0.5,或者直接改train.py里mosaic参数。另外hsv_h、hsv_s可以适当调高,因为水果颜色对检测很重要,适当改变色相和饱和度能让模型学到更鲁棒的颜色特征,而不是死记特定光照下的颜色。
还有一个容易忽略的点:--cache参数。如果内存足够大,建议加上--cache ram,把图片提前加载到内存,训练时不需要每次从磁盘读图,能缩短约 30% 的训练时间。很多人在小数据集上忽略了这一步,白白等了很久。
5. 常见问题排查:标签错位、类别冲突与训练崩溃
5.1 训练时报错 “imgsize=None” 或找不到图片标签
现象:训练刚开始,终端输出大量WARNING: label file not found,或者直接报错退出。
原因:最常见的是路径层级不对。YOLOv5 的data.yaml里train路径写到了images/train,但实际的目录是fruit/train/images,导致自动推导标签路径失败。另一种情况是图片后缀不是.jpg,而是.JPG,Windows 和 Linux 大小写敏感,在 Linux 上找不到文件。
解决:先按第 2.1 节里的 bash 脚本检查配对情况。路径不对就改data.yaml,大小写不对就统一重命名图片。不建议在训练脚本里写绝对路径,因为换机器后绝对路径会失效,最好用相对路径并保证在数据集根目录下运行训练命令。
5.2 验证集 mAP 很高,但实际检测不准
现象:训练结束,验证集 mAP@0.5 达到 0.98,但用摄像头拍一张水果照片,模型要么漏检,要么框偏了。
原因:验证集图片和训练集图片拍摄场景太接近,产生了数据泄露。比如训练集里包含苹果在白色背景下的图,验证集里也有同一批白色背景图,模型记住了背景,而不是学到水果本身。如果这是数据集本身的问题,那说明划分时没有按场景隔离。
解决:下载数据集后不要直接训练,先手动拆分一个额外的“测试集”,选择与训练集完全不同的场景图片。具体做法是把验证集中的图片按光线、背景、拍摄距离分组,至少留出 20 张非训练场景的图片。训练结束后单独验证这批图片,用真实场景 mAP 决定模型是否合格。从那以后我每次拿到新数据集都会先做这一步。
5.3 训练过程中 Loss 变成 NaN
现象:训练到第 10 个 epoch 左右,loss 突然变成nan,之后每个 epoch 的指标全是nan。
原因:最常见的是学习率过大或 batch size 不匹配。YOLOv5 默认学习率 0.01 对大多数任务没问题,但如果数据集类数少且图片尺寸大,bn 层的统计量可能爆炸。另一个原因是标签里出现了 0 宽或 0 高的框,回归分支计算 IoU 时除以 0。
解决:先检查标签文件,用第 3.1 节的脚本把bw <= 0 or bh <= 0的数据剔除。如果标签没问题,再把--lr0从 0.01 改成 0.005,并加--warmup-epochs 5。如果还是 NaN,那就需要检查图片里是否有全黑或全白的损坏图,这种图会让特征值全为 0,导致 BN 层分母异常。
5.4 自己补了几张图片后,验证集指标反而下降
现象:看到模型漏检了某张图片,手动标注后加进训练集,重新训练,验证集 mAP 反而下降了 2 个百分点。
原因:新补充的图片没有严格按归一化格式标注。比如你用某个标注工具导出的格式不是 YOLOv5 的类别编号,而是apple这样的字符串,或者坐标值没有归一化。YOLOv5 会把异常标签当作背景处理,反而增强了背景的负样本特征。
解决:补图时必须让图片和标签放进正确的目录,并且重新运行第 3 章的校验脚本。另外,新图片要检查类别编号是否与原始数据集一致。我自己出现过一次这种情况:标注工具默认类别名是apple、orange、pear,导出时没有映射为 0、1、2,结果整个训练集里混入了三种“字符串类别”,训练直接报错。最后还是把所有标签重新生成才救回来。
6. 验证与导出:推理前必须做的一项检查
6.1 用训练好的权重跑一次验证集
训练完成后,runs/train/fruit_run/weights/best.pt是验证集上 mAP 最高的权重。先用它跑一次验证集,确认指标可复现:
python val.py \ --data ../fruit.yaml \ --weights runs/train/fruit_run/weights/best.pt \ --img 640 \ --batch 16val.py会输出每个类别的 P、R、mAP@0.5 和 mAP@0.5:0.95。对三分类水果任务,mAP@0.5 在 0.95 以上才算理想。重点看一下每个类别的单独 AP,如果某个类明显低于其他类,说明该类图片数量不足,或者该类样本和背景的区分度不够。这时再去补数据,而不是盲目加 epoch。
6.2 导出为 ONNX 时的尺寸与 NMS 设置
如果要把模型部署到手机或嵌入式设备,通常需要把 PyTorch 权重导出为 ONNX:
python export.py \ --weights runs/train/fruit_run/weights/best.pt \ --include onnx \ --img 640导出后的best.onnx可以用onnxruntime验证推理结果。这里有一个关键坑:导出时--img必须与训练时一致,否则模型输入尺寸变了,检测框坐标缩放会出现偏差。另一个坑是 YOLOv5 导出的 ONNX 里自带 NMS 逻辑,有些推理框架要求关闭模型内置 NMS,自己在外部做非极大值抑制。如果你发现 ONNX 在手机上输出的框有大量重复,就去检查 NMS 的 IoU 阈值,一般设为 0.45。
6.3 我的最后一道习惯性检查
模型导出后,我会用十张完全没见过的水果照片做一次“黑匣子测试”,而不是直接看验证集指标。具体方法是拍十张不同角度、不同背景的水果照片,分别放在白纸背景、木桌背景、手拿水果三种场景中,用detect.py跑一遍:
python detect.py \ --weights best.onnx \ --source ./test_images \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45--conf-thres 0.25是置信度阈值,低于 0.25 的框会被过滤;--iou-thres 0.45是 NMS 的 IoU 阈值,两个框重叠超过 0.45 就会保留置信度高的那个。这一检查主要是验证真实场景里的漏检率和误检率。如果十张图里出现了两三张漏检,那说明整个数据集的多样性不够,需要补充更多光照和角度样本,而不是调整阈值来掩盖问题。
从那以后我每次拿到目标检测数据集,都强制走一遍“校验标签 → 可视化抽查 → 独立场景测试”的流程,哪怕数据集描述里已经写明是标准格式。因为工具和脚本在传输过程中可能被改动,唯一能信任的是自己亲手跑出来的结果。这份苹果橘子梨数据集本身是可靠的,但不同机器上的环境差异还是会带来各种意外,希望这篇笔记能帮你绕开我踩过的坑。希望帮到你。
本文还有配套的精品资源,点击获取