简介:面向目标检测入门与进阶学习者,这是一份专用于YOLO模型训练的苹果目标检测数据集。整体包含1000张真实场景下的高清苹果图像,样本覆盖不同光照与背景,由LabelImg逐框标注,标注框质量高;标签数据同时整理为VOC(XML)、COCO(JSON)、YOLO(TXT)三种格式,分文件夹存放,可直接接入YOLOv5、YOLOv8等主流训练流程。压缩包共2000个文件,其中1000个xml、990个txt构成核心标注,另配6个超文本教程文档、3个Python划分脚本与1个yaml训练配置,整体约94.46MB。随包附赠Windows和Linux双版本环境搭建教程、Ubuntu系统安装教程、训练案例教程,以及训练集/验证集/测试集划分脚本,可自动切分图片与标签并生成ImageSets下的txt列表;教程从零开始讲解环境配置和案例修改,读者只需替换数据路径即可训练自己的苹果检测模型。目前已有1396人学习下载,适合需要在真实数据集上快速上手YOLO系列检测任务的研究者、学生或开发者。
1. 1000张苹果图,为什么值得拿来练YOLO目标检测
YOLO苹果目标检测数据集(含1000张图片)+对应VOC、COCO和YOLO三种格式标签+划分脚本+训练教程,是目标检测入门里少见的“全配齐”型资源:图片、标注、三种常见标签格式、划分脚本和训练指引一次性打包好,拿到手不用再做数据预处理,直接进训练流程。对要做果园分拣Demo、毕业设计和采摘机器人视觉基线的人来说,这套资源的价值在于把采集、标注、格式转换这些最耗精力的杂活替你做完了。有一点Python基础就能接住,按下面流程走,半天内可以看到第一个能框住苹果的检测结果。
2. 包内数据结构与三种标签格式的核心差异
拿到这种资源包,我的习惯是先看目录结构和标签内容,而不是急着打开图片。很多人在YOLO训练上翻车,不是模型参数问题,而是数据包里的标签格式没看明白:VOC的框坐标是“左上、右下”两个点,COCO的bbox是“左上角加宽高”,YOLO标签是归一化的中心点加宽高。三套坐标定义完全不同,不理解差异直接用错转换方式,训练结果就是模型看起来在收敛,实际框全跑偏。
2.1 VOC、COCO、YOLO三种标签的文件形态对比
这类数据包最常见的目录布局是这样:
apple_dataset/ ├── images/ # 1000张jpg图片,通常按apple_0001.jpg编号 ├── VOC/ # 每张图片对应一个xml文件 │ └── annotations/ ├── COCO/ │ └── annotations.json # 整个数据集合并成一个json ├── YOLO/ │ └── labels/ # 每张图片对应一个txt文件 ├── split_dataset.py # 划分脚本 └── train_tutorial.md # 训练教程说明不同打包者目录命名会有差异(可能叫Annotations、labels、json),但三套标签的“容器形态”是固定的。VOC格式的标签是一个XML文件,最核心的信息是bndbox节点里的四个像素坐标:
<annotation> <filename>apple_0001.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>apple</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>330</xmax> <ymax>350</ymax> </bndbox> </object> </annotation>VOC坐标是像素级的,xmin/ymin是左上角,xmax/ymax是右下角。这套坐标最接近人的标注习惯,也最容易做可视化检查,所以很多标注工具的默认导出格式就是它。
COCO格式把整批标注合成一个JSON文件,大意是:
{ "images": [ {"id": 1, "file_name": "apple_0001.jpg", "width": 640, "height": 480} ], "annotations": [ {"id": 1, "image_id": 1, "category_id": 1, "bbox": [250.0, 180.0, 160.0, 120.0], "area": 19200, "iscrowd": 0} ], "categories": [ {"id": 1, "name": "apple"} ] }注意COCO的bbox是[x, y, width, height],不是右下角;category_id从1开始。这两点如果理解不到位,后面转YOLO格式时最容易出问题。
YOLO格式则是每张图片对应一个txt文件,每行代表一个目标:
0 0.3516 0.4479 0.3281 0.5625这行字段依次是:类别编号、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。归一化就是把像素坐标除以图片宽高,所以所有值都在0到1之间。类别编号从0开始,苹果这类单类数据集里就是0。
三种格式的关系一句话概括:VOC和COCO描述像素坐标,YOLO描述归一化坐标;VOC用对角两点,COCO和YOLO用左上角或中心点加宽高。
2.2 同一套标注换格式时最容易丢的四个信息
从VOC或COCO转YOLO时,有四类信息最容易被转丢或转错。
第一是类别编号的偏移。COCO的category_id从1开始,YOLO的类别编号从0开始。如果直接拿COCO的id当YOLO类别编号,单类数据集看起来没事,因为1减1才是0;一旦数据集超过一类,转出来的标签整体错一位,相当于模型一直在学习错误的类别映射。
第二是坐标换算公式。VOC的xmin/ymin/xmax/ymax转YOLO时,要先换算成中心点和宽高:
cx = (xmin + xmax) / 2 / width cy = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height见过不少人在这一步踩坑:中心点取成xmin + width / 2没错,但归一化的分母用成了目标框的宽高而不是图片宽高,结果所有坐标被放大,YOLO解析到超过1的坐标直接忽略,表现为“模型训练正常,推理时一个目标都不检测”。
第三是浮点精度。YOLO标签文件里坐标习惯保留6位小数,对640x480的图片来说,6位小数对应的误差不到1个像素,基本无感。但有些转换脚本保留4位甚至2位小数,框的位置会明显偏移,mAP掉一到两个百分点是常态。检查转换脚本时务必留意输出精度。
第四是iscrowd和分割掩码。COCO标注有时带segmentation多边形和iscrowd字段,只做目标检测时这两个字段不影响训练。但当COCO再转回VOC或YOLO时,如果忽略了iscrowd=1的密集目标,这些目标会因为“该出现却没出现在标签里”干扰训练。苹果数据集很少出现这种情况,但你后续把数据集扩充到密集果实场景时就要小心。
2.3 校验标签与图像对应关系的快速脚本
比格式理解更让人头痛的是训练开始之后才发现标签和图片对不上。我拿到数据包后会先跑一个校验脚本,把三类问题一次性识别出来:缺标签、缺图片、越界坐标。
from pathlib import Path import cv2 img_dir = Path('images') label_dir = Path('YOLO/labels') imgs = {p.stem for p in img_dir.glob('*.jpg')} labels = {p.stem for p in label_dir.glob('*.txt')} print('图像数:', len(imgs), '标签数:', len(labels)) print('缺标签:', len(imgs - labels), '缺图片:', len(labels - imgs)) # 逐行检查归一化坐标是否越界 for lp in label_dir.glob('*.txt'): img_path = img_dir / (lp.stem + '.jpg') h, w = cv2.imread(str(img_path)).shape[:2] for line in open(lp): parts = line.split() if len(parts) < 5: print('字段缺失:', lp, line.strip()) continue cls, cx, cy, bw, bh = map(float, parts[:5]) # 归一化值应该严格落在(0, 1]区间 if not (0 < cx < 1 and 0 < cy < 1 and bw <= 1 and bh <= 1): print('坐标越界:', lp, line.strip())这段脚本做的事很直接:先把jpg文件名和txt文件名分别放进集合,做差集就能找出“有图没标”和“有标没图”的文件;再打开每个txt,把五个字段解析出来,检查中心点、宽度、高度有没有超出归一化范围。多数标注工具不会生成越界标签,出现越界通常都是转换脚本里cx - w / 2这类公式漏了括号,导致坐标为负数。
提示:文件名后缀不一致也会让差集非空。如果图片是
.jpeg、.png,或者标签文件被命名成apple_0001.txt.txt,要么改脚本里的后缀,要么批量重命名,别手动去数张数。
这套脚本跑一遍,比看十篇格式对比文章都管用。数据没问题,后面训练才会稳。
3. 划分脚本的用法:用train/val/test拆分把训练跑稳
苹果检测数据集包里几乎都会带一个划分脚本,作用是把1000张图按比例拆成训练集、验证集和测试集。很多人觉得划分只是复制文件,不值得花时间,实际上一旦把划分和标签格式绑在一起操作,问题就多了:YOLO格式要保证图片和txt成对移动,VOC要考虑XML跟着走,COCO则要重写JSON关联关系。这章把划分讲透,并给出一个可以沿用到其他数据集的验证习惯。
3.1 跑通自带划分脚本的最小命令
假设解压后根目录下有一个split_dataset.py,通常它接受的参数类似这样:
python split_dataset.py \ --images images \ --labels YOLO/labels \ --format yolo \ --ratio 0.7 0.2 0.1 \ --seed 42 \ --out dataset_split参数含义依次是:--images指定图片目录,--labels指定YOLO标签目录;--format yolo告诉脚本直接按文件移动,不用做坐标换算;--ratio依次是训练、验证、测试比例,三个数加起来等于1;--seed固定随机数种子;--out指定输出目录。
跑完后应看到如下结构:
dataset_split/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ ├── test/ │ ├── images/ │ └── labels/ └── train.txt如果你的包内脚本不叫这个名字,看看是不是split_train_val.py之类的名称,这类脚本入参大同小异。核心逻辑十有八九是:用random.shuffle或np.random.choice打乱文件名列表,再按ratio切片。
固定种子这一点非常关键。没有固定种子的划分会让两次运行产生不同的train/val组合,别人复现你的实验时mAP差出一截,很多时候就是这个原因。写脚本时建议加上random.seed(42),让结果可复现。
3.2 三种标签格式下的划分差异与COCO重写
VOC格式的划分和YOLO基本一样,就是图片加XML成对移动。VOC目录命名习惯是图片目录叫JPEGImages、标签目录叫Annotations,两边stem一致,把对应的.jpg和.xml复制到同一对目录下就行。
COCO的划分不是复制文件,而是重写JSON。COCO把所有标注集中在annotations.json一个文件里,划分时必须按图片ID把相关条目筛出来,重新生成train.json和val.json:
import json import random data = json.load(open('COCO/annotations.json')) # 拿到全部图片ID,按8:2切成train/val image_ids = [img['id'] for img in data['images']] random.seed(42) random.shuffle(image_ids) cut = int(len(image_ids) * 0.8) val_ids = set(image_ids[cut:]) train_ids = set(image_ids) - val_ids # 保留原字段结构,只过滤对应子集 def subset(save_name, keep_ids): subset_data = { 'images': [img for img in data['images'] if img['id'] in keep_ids], 'annotations': [a for a in data['annotations'] if a['image_id'] in keep_ids], 'categories': data['categories'] } json.dump(subset_data, open(save_name, 'w'), indent=1) subset('COCO/train.json', train_ids) subset('COCO/val.json', val_ids)这段脚本可行是因为COCO的annotations数组里每个元素都带image_id,用集合判断就能把属于val图的框全部摘出来。categories字段一定要完整复制,训练时要用它建立类别映射。
COCO划分比YOLO容易踩坑的点在于:很多人只改images数组,忘了同步过滤annotations,结果是val.json里图片只剩200张,标注框却还是1000张图的框,模型验证时看到许多“框对应不到图”的条目,指标计算直接乱掉。按上面的方式把两个数组同时过滤就没事了。
3.3 划分后的三个验证习惯
第一个习惯是检查文件是否严格成对。YOLO格式训练时对应关系靠文件名映射,某张图没有对应txt,会被当成“无目标背景图”;某txt没有jpg,训练日志会出现warning。建议回到2.3节的校验脚本,对dataset_split下的train、val、test各跑一遍。
第二个习惯是看各类别的框数分布。苹果数据集单类还好,多类时就怕验证集里某一类只有几个框,mAP对那类完全没有参考意义。统计方式很简单:
from collections import Counter from pathlib import Path base = Path('dataset_split') for split in ('train', 'val', 'test'): counter = Counter() label_dir = base / split / 'labels' for txt in label_dir.glob('*.txt'): for line in open(txt): cls = int(line.split()[0]) counter[cls] += 1 print(split, dict(counter))如果某一类在val里只有个位数框,建议做分层划分:先把图片按类别分组,再按比例从各组里取。虽然单类苹果用不上,但这个习惯能直接复用到其他数据集。
第三个习惯是确认train和val的图片集合没有交集。数据泄漏会让val的mAP虚高到0.99,看着漂亮,一换新果园就崩。用文件名集合求交集,打印重叠数量,目标检测里验证集与训练集零重叠是底线。
4. 把YOLO苹果检测的训练跑通:配置、命令与参数调节
数据划分完成后,剩下的就是训练。当前实操层面,YOLOv8是绝对主流选择,基于ultralytics库,文档齐、预训练权重好找、回滚也方便。这章以YOLOv8为例,把训练命令和参数调节逻辑写清楚。
4.1 环境准备与预训练权重怎么选
先建一个干净的Python环境,然后安装ultralytics:
pip install ultralytics python -c "from ultralytics import YOLO; print(YOLO.__name__)"安装时会自动带上匹配的PyTorch版本。如果机器没显卡,CPU也能训练1000张图的小任务,只是慢一些;有NVIDIA显卡就先用nvidia-smi确认驱动正常。
预训练权重用yolov8n.pt还是yolov8s.pt,需要根据硬件和精度目标决定。我的经验是:1000张单类苹果图,yolov8n在4GB以下显存的小GPU上最稳,训练快且和s的精度差距通常不超过两个点;显存6GB以上可以直接上yolov8s.pt,容量更大,不容易欠拟合。首次训练指定model=yolov8s.pt时,ultralytics会自动从官方源下载权重;下载失败就手动放置到项目根目录,再指定本地路径。预训练权重相当于模型已经见过大量通用视觉特征,苹果这类形体和纹理特征都能靠它迁移过来,完全从零训练既慢又难收敛,没这个必要。
4.2 编写data.yaml与类别名称的细节
训练前要把数据路径和类别信息写进一个YAML文件。这一步看着简单,实际坑位不少。我一般把它放在数据集根目录,命名apple.yaml:
path: /absolute/path/to/apple_dataset train: images/train val: images/val test: images/test nc: 1 names: 0: apple几个值得注意的地方:path写绝对路径,不建议写相对路径,因为训练时当前工作目录很可能不在数据集目录;train和val是相对path的目录,路径分隔符用/;names可以用列表形式['apple'],也可以写成字典指定编号,推荐字典写法,多类场景下列表顺序一变就全错。
nc: 1类别数必须和names的条目数一致。如果数据集里除了苹果图还混了纯背景图,背景不属于目标类,names里不要写它,YOLO会自动把无目标的图片当负样本处理。
4.3 训练命令与epoch/batch/imgsz的调节逻辑
把1000张图喂给YOLOv8,实际训练命令通常这样写:
yolo detect train \ model=yolov8s.pt \ data=apple.yaml \ epochs=80 \ imgsz=640 \ batch=16 \ lr0=0.01 \ close_mosaic=10 \ patience=20 \ device=0各参数的调参逻辑如下。
epochs设置在60到100之间比较合适。1000张图单类目标,一般80轮就已经收敛;patience=20表示连续20轮验证集没提升就提前停止,能省掉大半个晚上的训练时间。
batch受显存约束,16到32都常见。显存不够报OOM时,先把batch降到8或4,再不行就换device=cpu配合更小的imgsz。验证集指标基本不受批量大小影响,实验环境定了之后batch不要频繁改。
imgsz选640性价比最高。苹果目标通常占画面比例较大,640足够;测试集小苹果多时提高到960有帮助,但训练时间几乎翻倍。还有一种常见做法是训练用640、推理用960,通过输入分辨率差补一部分小目标能力,但这个差异只能缓和小目标漏检,不能根治。
close_mosaic=10的意思是最后10轮关掉Mosaic增强。Mosaic能提升目标多样性,但训练末期会干扰模型精细收敛,尤其目标只占小区域时更明显。YOLOv8默认在最后10轮关闭,保持默认即可,不要为了“增强更多”把它改成负数或更大的值,收益很小。
lr0=0.01是常规初始学习率。训练日志里观察loss变化,如果loss持续走高,把lr0降到0.001重试;如果一开始暴跌到某个值后几乎不动,也不必惊慌,YOLO的loss由box、cls、dfl三部分叠加,绝对值低到一定程度后本来就是缓慢下降。
训练过程中,终端里每轮会打印box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95这些指标。前几个epoch的precision和recall可能是0,因为置信度阈值没校准好,这很正常,等到第20轮左右再看趋势。
4.4 训练产物与第一次验证推理
训练结束后结果都在runs/detect/train/目录下。
runs/detect/train/ ├── weights/ │ ├── best.pt # 验证集上最优的权重 │ └── last.pt # 最后一轮权重 ├── results.csv # 每轮的metric记录 ├── confusion_matrix.png ├── F1_curve.png └── PR_curve.png第一次推理用best.pt对测试图跑:
yolo predict \ model=runs/detect/train/weights/best.pt \ source=images/test/apple_0100.jpg \ conf=0.25 \ save=Trueconf=0.25表示置信度阈值,低于该值的框会被丢弃。苹果检测通常设0.25到0.45之间,调低一点召回更高,调高一点误检更少,看场景容忍度。save=True会把标注完的图片存到runs/detect/predict/。
用best.pt而不是last.pt做推理是省时间的好习惯。两者在训练后期会接近,但best.pt代表验证集上的最优状态,泛化性一般更好。训练完急着拿last.pt跑,等于白丢一次选择最优权重的机会。
5. 五个常见坑:漏检、误检和“loss不降”的排查记录
苹果检测训练中最常见的五类问题,按“现象、原因、解决”三步记录如下。这些问题我在不同数据集上都遇到过,不一定每次都出在同一个环节,但排查顺序几乎一致:先看标签,再看数据划分,最后调训练参数。
5.1 mAP和loss趋势里的“假动作”怎么读
现象一:训练时loss下降很快,到第20轮附近就不再明显下降,你以为模型坏了。原因很简单:YOLOv8的loss是box_loss + cls_loss + dfl_loss三部分的加权和,绝对值降到3到6之间后本来就变得很平缓。解决办法是别死盯loss绝对数值,转去看results.csv里的metrics/precision(B)和metrics/recall(B)曲线。只要precision和recall还在稳定上升,loss不降是正常趋势,不需要干预。
现象二:train_loss一路向下,val mAP50却开始回落。这是过拟合的典型特征,在单类数据集上出现频率很高,尤其当你用了较大的模型或较长的epoch。解决方法是把epochs从80降到50,或者借助patience早停;如果还想保留更多训练时间,打开augment、加大hsv_h增强,用随机光照变化缓解过拟合。苹果表面反光强烈,这个增强对这类目标效果明显。
现象三:一个更隐蔽的现象是训练指标正常,但推理时检测框忽大忽小、位置不稳定。原因多半是imgsz在训练和推理时不一致,模型在低分辨率上见过的目标纹理与高分辨率推理时的特征有偏差。解决方法是固定训练和推理都用640,或至少保证推理分辨率是训练分辨率的整数倍。目标检测对尺度敏感,这类玄学问题多半出在预处理上,先统一尺寸再排查其他可能性。
5.2 漏检、误检和标签错位的三类血泪问题
第一类:树冠小苹果漏检。现象是地面单果识别精准,果树冠层密集的小苹果一个都框不出来。原因有两个方向:目标框缩放到640后只剩十几个像素,特征太弱;或者训练数据里大苹果占多数,模型对小目标欠拟合。解决方法从数据切入最直接:先把imgsz提到960试一轮,若提升有限,用裁剪方式把含小苹果的区域单独做成训练图,让“小框数”在总量里的比例明显上来。
第二类:把树叶、防草布背景误框成苹果。现象是precision看着不错,但推理结果里总有零散背景框。原因一般是负样本不足:训练集中每张图至少有一个框,模型几乎没有见过“该图无苹果”的样例。最有效的解决手段是加背景图:从原数据集里挑几十张不含苹果的果园图放进images/train,不配标签文件,让YOLO在无目标图上也能学会“不输出框”。实测几十张背景图就能把误检率打下一半。
第三类:标签错位导致的“假收敛”。现象是val mAP很高,但把预测框可视化到图片上,框的位置比苹果实际区域偏上或偏下十几个像素。最常见的原因是前面提到的转换脚本把归一化坐标算错,数值没越界但偏移了约一个框大小。这类问题最难排查,因为指标看起来正常。我排查时最常用的办法是把best.pt的预测结果和原标签画在同一张图上:如果预测框和标签框系统性偏移,先重跑格式转换,别动训练参数。标签问题引发的症状,训练调参根本救不回来,这是一条典型的血泪经验。
注意:如果发现val mAP奇高但推理不丝滑,先可视化对比标签框,不要急着调模型。标签错位时loss会正常下降,指标会看起来正常,只有可视化能看清真面目。
第四类:显存OOM。现象是训练刚开始就报CUDA out of memory。原因是batch或imgsz超出显存容量。解决方法是先把batch降到8或4;如果还不行,把device=0换成空闲卡,或者用workers=4减少数据加载阶段的内存占用。单卡用户最简单的方式是让ultralytics自动寻找batch大小,命令行加batch=-1,它会根据剩余显存自动回退。
6. 从best.pt到验证指标复算和ONNX导出
训练结束不是项目的终点,验证和部署才是判断这套流程值不值得继续投入的关键。下面三个做法是我在后续项目里复用率最高的:用val集复算指标、看懂混淆矩阵、导出ONNX给边缘设备推理。
6.1 用验证集复算mAP与逐类指标
训练日志里已经带mAP,但很多人不清楚数据切分,推荐用脚本独立再算一遍。手上有best.pt和另一份权重时,用同一份val跑预测做对比:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') metrics = model.val(data='apple.yaml', split='val', imgsz=640) print('mAP50:', metrics.box.map50) print('mAP50-95:', metrics.box.map) print('precision:', metrics.box.mp) print('recall:', metrics.box.mr)split='val'明确指定只评估验证子集,避免把训练集混进来。map50是IoU阈值0.5下的平均精度,map50-95是0.5到0.95每隔0.05取一个阈值再做平均,后者更严苛、更反映框的定位质量。苹果检测里树冠密集场景的map50和map95差距会明显大于单果场景,因为果与果之间框重叠度高,IoU要求越高越难精确覆盖单个果实。
6.2 看混淆矩阵而不是只盯着mAP
confusion_matrix.png能看到模型把哪些目标错分成了背景。对单类模型来说,矩阵主要信息集中在“真实苹果被漏判为背景”的比例上。我见过不少只看曲线不看矩阵的人,结果是mAP高但漏检率也高,两个指标互相矛盾。正确做法是训练中每隔几轮打开混淆矩阵看趋势:如果训练到40轮后漏到背景的比例仍然高于5%,说明漏检主要来自小目标或遮挡,需要回数据层面想办法;如果低于2%,可以放心去调置信度阈值和NMS参数。
6.3 导出ONNX并在边缘设备上跑推理
导出ONNX是最常见的部署前步骤,ONNX Runtime在Jetson、RK3588、树莓派这类设备上支持广,还能避免锁定单一推理框架:
yolo export model=runs/detect/train/weights/best.pt \ format=onnx \ dynamic=True \ opset=12 \ simplify=True导出成功后,用ONNX Runtime加载权重做推理,是下一步代码量不大但对工程能力提升明显的事。苹果采摘、果园巡检这类任务的现场设备往往没有GPU,量化到FP16或INT8再上板,是比继续调训练参数更见效的优化手段。
我的习惯是训练完成后再花一晚上做一次“验证三件套”:标签校验脚本跑一遍测试集、混淆矩阵认真看一遍、ONNX导出加一次本地推理。这三件事做完,这个数据包对当前项目才算真正闭环。以后换到自采数据或扩展多类别场景,这套流程都能直接搬过去用。这些踩坑换来的经验,希望也能帮到你少走两步弯路。
本文还有配套的精品资源,点击获取