ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

苹果检测实战:YOLOv5与YOLOv8目标检测全流程解析

苹果检测实战:YOLOv5与YOLOv8目标检测全流程解析

做了几年目标检测相关的项目,苹果检测算是农业视觉里很典型的一个需求。采摘机器人要识别果实位置,果园巡检无人机要统计挂果量,分选线要判断大小品相,说到底第一步都是“先用模型把苹果一个个框出来”。这篇就把我在整理苹果数据集、跑通YOLOv5和YOLOv8模型过程中的完整经验写出来,从数据准备到训练调参,再到部署排查,都按实操顺序来,适合正在做农业视觉、或者想用目标检测做果实识别但还没形成完整流程的朋友参考。

1. 苹果检测任务:从需求到技术选型

1.1 果园场景下的检测难点

苹果检测看着是个单类别检测任务,好像比通用目标检测简单,但真把相机架到果园里就会发现,难点全藏在“场景”二字里。

第一个难点是遮挡。苹果长在树上,叶子、枝条、相邻果实都会形成遮挡。很多时候一个苹果只露出来半边甚至三分之一,模型既要能认出来,还不能把被挡住的苹果漏掉。第二个是光照。晴天直射光下果实表面有高光,逆光时果子和背景的亮度对比极低,早晚低角度阳光还会在地面产生长阴影干扰。第三个是目标尺度差异大。同一张图里,近处的大苹果可能占了几百个像素,远处枝头的苹果只有二十几个像素,多尺度检测能力必须跟得上。第四个是小果与背景的纹理接近,尤其是青苹果、黄苹果和树叶的颜色分布有重叠,单靠颜色或者简单的边缘特征根本分不开。

所以这个任务本质上是“密集小目标 + 严重遮挡 + 光照变化 + 背景干扰”的组合考验。模型不能只做粗定位,必须能够在复杂背景下稳定输出每个可见果实的精确边界框。

1.2 为什么选择YOLO系列

苹果检测对实时性有硬要求。采摘机器人要边移动边识别,帧率至少要跑到15FPS以上才跟得上机械臂动作;无人机巡检更不用说,飞一圈采集几万张图,离线都希望处理得快。在这种前提下,两阶段检测器虽然精度上限高,但速度劣势太明显,不太适合现场部署。

YOLO系列是当前检测精度和速度平衡得最好的开源方案之一。模型体积小、生态成熟、部署工具链完整,从训练到导出ONNX再到TensorRT加速都有现成路径。而且YOLOv5和YOLOv8两个版本覆盖了不同的部署需求:v5在低算力设备上更灵活,v8在训练稳定性和特征提取能力上明显更好。

我这次同时跑通两个版本,目的也很简单——对比它们在同一个苹果数据集上的表现,搞清楚什么场景该用哪个版本,而不是盲目追新或者继续守着旧方案。

2. 苹果数据集:从采集到组织

2.1 数据构成与采集要点

模型能学到什么程度,数据说了算。我整理苹果数据集时定了一条原则:宁可数量少一点,也要保证环境多样性。

最终的数据集由三部分组成:来自公开来源的果园实拍图片、某合作果园提供的不同光照时段拍摄照片、以及我自己用手机和运动相机补拍的部分近景。总量控制在2300张左右,类别就一类“apple”。这个数量对单类别检测来说够用,如果类别超过三四个,单类样本量就要往上走。

采集阶段有几个容易被忽略的点。一是分辨率,训练图统一缩放到640×640,但原图最好不低于720p,否则远处的小苹果缩完之后只剩几个像素,标注都没法标。二是角度覆盖,除了水平视角,还要拍仰视和俯视角度,因为机械臂的相机视角经常是从下往上看的,树冠视角的俯视图也要覆盖。三是天气和时段,晴天、阴天、清晨、正午、傍晚的样本都要有,不然模型一到阴天就掉点。

采集完成后做了一次粗筛,把明显模糊的、过度曝光的、完全看不清果实的图片删掉。这一轮能删掉10%到15%,别心疼,脏数据在训练时带来的负作用远比少几张图严重。

2.2 标注规范与格式转换

标注环节是纯体力活,但边界规范决定了模型上限。我用标注工具逐个框出可见的苹果,遵循几条规则:

  • 被遮挡超过一半的苹果不标,勉强标出来只会让模型学混乱。
  • 相邻但紧贴的两个苹果,边界框必须分开且边界不重叠,避免模型把两个框合并。
  • 边界框贴紧果实的可见轮廓,不要留太大空白,也不要切掉果实边缘。
  • 同一张图里的所有可见苹果都要标全,漏标的负面影响大于标错。

这里补充一个实际很常见的坑:公开渠道下载的数据集格式经常不统一,VOC的XML格式、COCO的JSON格式、YOLO的TXT格式都有。我自己做了个脚本统一转成YOLO格式。YOLO的TXT标注是归一化的中心点加宽高的形式,每一行是:

class_id x_center y_center width height

比如一个苹果的边界框在1080×720的图上左上角是(200, 150),右下角是(320, 300),那么归一化计算是:

x_center = (200 + 320) / 2 / 1080 = 0.2407 y_center = (150 + 300) / 2 / 720 = 0.3125 width = (320 - 200) / 1080 = 0.1111 height = (300 - 150) / 720 = 0.2083

转换之后那行就是:

0 0.2407 0.3125 0.1111 0.2083

每张图和它的TXT标注文件要保持同名,放在images和labels两个对应目录下。目录组织我直接沿用YOLO标准结构:

datasets/ ├── apple/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/

train和val按8:2划分,划分的时候要按图片随机分配,不能按拍摄批次分,否则同一时段光照的图片全部进了验证集,指标虚高没有参考意义。

2.3 数据增强策略

苹果数据集数量有限,必须靠增强来扩展分布覆盖。YOLOv5和YOLOv8都内置了Mosaic增强,一次把四张图拼成一张训练,相当于每步训练能看到四张不同场景的上下文,对小目标检测特别有帮助。

我实际开启的增强项主要有:随机旋转(±15度以内,超过这个角度苹果的形态就失真了)、HSV色域扰动(饱和度±20%,亮度±30%,用来模拟不同光照)、随机水平翻转、随机缩放与平移。这几个组合下来,相当于把一个300张左右的类别样本扩充出了几倍的有效训练信息量。

有一点要特别提醒:马赛克增强虽然是训练利器,但验证阶段必须关闭。验证集要保证原始分布,打开增强会让验证指标失真。所以验证时统一用原始图像尺寸或者缩放到640后再测,不做任何增强。

3. YOLOv5与YOLOv8:两个版本的选型逻辑

3.1 网络结构与核心差异

只看推理效果的话,两个版本差距没想象中大,但结构设计思路确实迭代了一代。

YOLOv5的核心特征可以总结为三点:Focus模块做下采样、CSPDarknet作为骨干网络、Anchor-Based的检测头。它在Neck部分使用了FPN加PAN结构,把深层语义特征和浅层纹理特征做了充分融合。值得注意的是,YOLOv5的检测头没有完全解耦,分类和回归共享了一部分卷积特征,这在高密度目标互相遮挡时会增加一些定位误差。

YOLOv8在结构上做了几处关键调整。第一是骨干网络里的C3模块换成了C2f模块,梯度流更丰富,特征复用效率更高。第二是检测头改成解耦头,分类分支和回归分支各走各的卷积路径,任务冲突被明显缓解。第三是最核心的变化——从Anchor-Based转成Anchor-Free,不再依赖预设锚框,直接预测目标中心点和宽高。这个设计思路迭代其实有两层意义:省去了针对数据集调锚框的步骤,训练流程更省心;对尺度覆盖也更灵活,苹果这种尺度分布很广的目标,锚框设计得不好很容易出现小目标召回低的问题,Anchor-Free天然不存在这个问题。

另外,YOLOv8的损失函数里引入了DFL分布损失来优化边界框回归,再加上CIoU和分类BCE的配合,边界框精度比v5更细。

3.2 在苹果数据集上的实测表现

同一个数据集、同样的训练配置下,我分别用YOLOv5s和YOLOv8s训练了250个epoch,输入尺寸640×640,batch size 16。两组实验取了效果最好的几个指标:

模型mAP@0.5mAP@0.5:0.95模型体积1080Ti上推理帧率
YOLOv5s0.9310.724约14MB约95 FPS
YOLOv8s0.9480.758约22MB约82 FPS

mAP@0.5的差距大约是1.7个百分点,mAP@0.5:0.95的差距大约3.4个百分点。前者代表框得准不准,后者代表定位精度和召回的综合能力,说明v8在苹果边界框的精细度上确实更有优势。

推理速度v5s略快,但差距没有质变。在边端设备上这两个模型都还需要做TensorRT加速才能保证实时。

3.3 选型建议

经过这批数据集的对比实验,我的选型判断是这样:

  • 如果设备算力吃紧,比如用树莓派或者老款Jetson系列,优先考虑YOLOv5n或YOLOv5s,模型小、部署灵活,精度损失在可接受范围内。
  • 如果目标是做正式项目,算力不是卡脖子的瓶颈,直接上YOLOv8s甚至YOLOv8m。训练稳定、定位精度好、后处理简单,长期维护成本低。
  • 如果苹果检测要附带成熟度分类或者坏果检测,YOLOv8有现成的分类头支持多任务扩展,可以省掉额外训练一个分类模型的成本。
  • 如果是个人学习或者竞赛,YOLOv8的API更简洁,开箱即用程度比v5舒服不少。

4. 训练实操全流程

4.1 环境准备

训练环境我建议直接用Linux加NVIDIA显卡,Windows也能跑,但坑相对多。我的基准配置是:

  • Python 3.9以上
  • PyTorch 1.13或2.x都可以
  • CUDA 11.7以上
  • 显卡显存建议8GB以上,最低6GB也能跑小模型

YOLOv5的仓库环境安装基本是克隆仓库加安装依赖,然后根据显卡情况安装对应版本的PyTorch。YOLOv8装起来更省事,直接通过pip安装ultralytics包,命令行工具和Python API都带上了。核心依赖就是torch、torchvision,再加一些图像处理库。

安装完第一件事是确认GPU可用:

python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

输出True才是正常状态。这一步检查很关键,很多人装完依赖之后没验证CUDA,结果模型一直跑在CPU上,一个epoch要跑几个小时还以为是正常的。

4.2 数据组织与配置文件

数据目录建好后,要在项目里写一个YAML配置文件,告诉模型去哪里找图和标注。我的apple.yaml长这样:

path: /home/user/datasets/apple train: images/train val: images/val nc: 1 names: 0: apple

注意几个细节:path写绝对路径最省心,相对路径有时候会因为工作目录不一致报错;nc是类别数,单类别就是1;names里的索引顺序必须和标注文件里的class_id对应。

写完之后先做一次完整性校验,这一步能省掉训练中途才发现标注文件丢失的痛苦。用脚本统计一下labels/train下有多少个TXT,images/train下有多少张图,两个数量要一致。再抽查几个TXT里面的坐标值,确保在0到1之间。

4.3 关键训练参数解读

训练参数的设置直接影响最终效果。我用的训练命令是这样的:

YOLOv5:

python train.py --data apple.yaml --weights yolov5s.pt --batch-size 16 --epochs 250 --img 640 --device 0

YOLOv8:

yolo train data=apple.yaml model=yolov8s.pt batch=16 epochs=250 imgsz=640 device=0

几个关键参数逐个说清楚:

  • batch size:8GB显存建议8到16,显存够用就16。batch太大收敛速度并不线性变快,反而可能让训练后期震荡。
  • epochs:200到300之间比较合适。少于100个epoch模型通常还没收敛充分,多于300就边际递减了。我用250是综合考虑了收敛速度和早停机制。
  • imgsz:640是性价比最高的输入尺寸。512以下小目标可能掉点,768以上对小目标有少量提升,但训练和推理时间都明显增加。
  • weights:加载预训练权重做迁移学习,比从零训练收敛快得多。v5的预训练权重是COCO上训出来的,对边缘纹理特征已经有很强的先验。

优化器我建议直接用默认值,v5默认SGD,v8默认AdamW。如果没有充分理由,不用刻意改。学习率保持默认的0.01,配合前三轮warmup,对大部分数据集都适用。

v5和v8都内置了早停机制。我一般把patience设置成60到80个epoch,如果验证集mAP连续这么多轮没有改善就自动停止,能省不少时间。

4.4 训练、验证与推理测试

训练启动后,第一件事是看前几个epoch的loss变化。正常的loss曲线是稳步下降,偶有小幅波动。如果第一个epoch的loss就异常高,或者前10个epoch完全不降,优先检查标注文件有没有错。

训练完成后,会在runs/train/exp目录下生成权重文件和指标图表。我要重点看的是weights/best.pt和weights/last.pt。best.pt是验证集上mAP最高的权重,部署直接用best.pt,不要用last.pt。

验证用下面的命令:

# YOLOv5 python val.py --data apple.yaml --weights runs/train/exp/weights/best.pt # YOLOv8 yolo val model=runs/train/exp/weights/best.pt data=apple.yaml

验证输出会包含Precision、Recall、mAP@0.5、mAP@0.5:0.95几个核心指标。苹果这类单类别密集目标,我一般要求mAP@0.5至少到0.9才算合格,低于这个水平说明数据或训练环节有问题。

最后一步是拿没进过训练集的果园实拍照片做推理检验。这一步能看出整个验证集指标发现不了的问题。比如模型在密集场景下是不是把两个苹果框成了一个,逆光下是不是大量漏检。我碰到的典型情况是验证集mAP有0.94,但实地一测发现高处的远距离小苹果基本全漏,后来把输入分辨率从640提到768,再把训练图里增加高空俯拍角度的样本,漏检才明显缓解。

# YOLOv8推理 yolo predict model=runs/train/exp/weights/best.pt source=test_images/ save=True

推理输出会直接生成带边界框的图片,检查这些图基本就知道模型的真实水平了。

5. 常见问题与避坑指南

5.1 训练不收敛或loss异常

训练不收敛是目标检测最常见的坑,但排查路径其实很固定。

第一个查标注文件。看一下TXT里坐标有没有超出0到1范围,有没有空文件,有没有类别索引越界。我用脚本批量扫一遍,几秒就能定位问题文件。

第二个看图与标注是否对齐。发生过一次因为数据集拷贝时文件名错位,导致图像和标注对不上,模型训了50个epoch mAP还在0.1以下。

第三个查学习率。如果loss一开始下降很快,到某个节点后开始大幅震荡,多半是学习率偏大。把lr0从0.01降到0.005一般就能缓解。反过来loss下降极为缓慢,可以考虑适当增大学习率或者检查是否用错了优化器。

还有一个隐蔽问题:数据增强过强。有一次我把Mosaic的缩放范围设得很大,四张图拼出来之后很多苹果被压成了几个像素的小色块,模型直接被带偏。建议新数据集先用默认增强参数,跑通流程之后再做微调。

5.2 漏检误检怎么排查

漏检和误检是两个方向的问题,排查思路不同。

漏检(该框的没框出来)优先考虑三件事:输入分辨率太低导致小目标丢失,训练数据里对应场景样本太少,以及NMS阈值设置过严。苹果检测这种密集场景,NMS的IoU阈值我一般设在0.5到0.6之间。设太高会导致重叠的框大量保留,设太低会把相邻的苹果框一起过滤掉。

误检(不该框的框出来了)大概率是背景干扰没抑制好。我看到过模型把水印上的红色圆形标记当成苹果、把套袋的白色轮廓也框出来。解法是往训练集里补充这些负样本图片,让模型看到不同光照、不同背景下的反例。目标检测一定需要负样本,这一点很多教程没强调。

另一个细节是类别不均衡。如果数据集里不同品种苹果数量差距大,模型对样本量小的品种容易漏检。解决办法是给样本量小的类别在损失计算里适当调高权重,或者用重采样方式平衡各类别的图片数量。

5.3 部署与提速的实用技巧

训练跑通只是第一步,真正落地到果园设备上是另一回事。我总结几个实用提速手段:

  • 半精度FP16推理。绝大多数新显卡对FP16有硬件加速,精度损失极小,速度能提20%到40%。
  • 导出ONNX再转TensorRT。同样的YOLOv8s模型,TensorRT推理在Jetson上能比原生PyTorch快两到三倍。
  • 适当降低输入分辨率。如果检测目标主要是近处大苹果,640降到416甚至320,帧率可以翻倍,代价是远距离小目标基本丧失。
  • 调节conf_thres。推理时置信度阈值提高到0.35到0.45,能过滤掉大量低置信度误检框,视觉上模型“看起来”更干净。

部署端还有一个特殊问题:果园里通常是边缘设备供电和散热受限,模型不能只看精度,还要看功耗。实测下来YOLOv8n在低功耗设备上比v8s更有优势,如果用Jetson Nano这样的低算力设备,老老实实选择n级或者s级,强行上m级会导致帧率掉到不可用。

写在最后

跑完这轮苹果检测项目,我个人的体会是:目标检测模型的代码门槛并不高,真正拉开效果差距的是数据处理和对场景的理解。苹果这种看似简单的目标,一旦放进真实果园,遮挡、光照、小目标这些问题一个不落全都会找上门来。如果只满足于在公开数据集上刷几个漂亮的指标,模型很难在实地场景里真正发挥作用。

最后再分享一个小技巧:每次实验前把数据集版本、模型版本、关键超参这些信息记录下来。我在调试过程中吃过亏,两个实验之间只改了一个参数但忘了记录,效果变差之后根本说不清是哪个改动引起的问题。做实验养成记录习惯,能少走很多弯路。

返回列表