☰
基于YOLOv11的苹果检测系统实战:从数据标注到PyQt5界面部署
2026/10/20 2:50:26 网站建设 项目流程

这几年做果树视觉检测,陆陆续续折腾过不少目标检测模型。今天想聊聊一套我自己从零完整跑通的树上自然生长苹果检测系统,核心用的是YOLOv11,数据集是从果园实拍+标注开始做的,后端模型训练完再嵌进Python的UI界面,顺带加了一套登录注册功能,最后交付的是完整源码和训练好的模型文件。

这套东西能做的不只是“识别出苹果”这么简单,它可以直接用在果园产量预估、智能采摘机器人视觉、果树生长状态监测这些场景里。如果你正在做视觉识别相关的项目,或者是需要给农业信息化系统做毕业设计/课程设计,又或者想在PyQt这类桌面界面里集成深度学习推理,这篇文章基本可以当作一套从0到1的实操笔记来参考。我不会按文档的写法来整理,而是按我实际踩过的坑和习惯顺序,把选型逻辑、标注细节、训练参数、UI联调这几个环节逐个拆开讲清楚。

1. 项目核心思路与技术选型解析

很多人拿到一个“检测苹果”的需求,第一反应就是拉个现成的模型去推理。但真正落地到“树上自然生长的苹果”这个场景,事情没那么简单。

1.1 为什么选YOLOv11而不是传统视觉或更早的YOLO版本

先说结论:在这个项目里选择YOLOv11,核心原因是它在自然场景小目标和遮挡目标的平衡上做得更好,而且训练和部署的生态足够成熟。

果树上的苹果有几个很麻烦的特点:大小差异极大,近处的大到占半幅画面,树顶远处的小到只有几十个像素;叶片和枝干遮挡严重,很多苹果只露出三分之一;光线变化剧烈,晴天正午有高光斑,阴天又有阴影,傍晚整体偏暗。传统图像处理方法,比如颜色阈值分割加形态学操作,在受控环境下能跑,但一旦光照变了、叶片颜色和苹果颜色相近的时候,误检率就高得没法用。

而YOLO系列是一阶段的回归式检测器,直接预测边界框和类别概率,速度优势和精度平衡一直做得不错。YOLOv11相比之前版本的主要改进在于网络结构的骨干特征提取能力和更灵活的训练策略,实测下来在中等算力设备上能做到实时推理,这对后面要接采摘机器人或者移动端摄像头很重要。

另外,YOLO的官方仓库把训练、验证、导出、推理都做成了命令行工具,数据集格式文档也齐全,我对接自定义数据集时省了很多事。这也是我选它的一个现实原因——做项目不能光看模型指标天花板,还要看工程落地的人力和时间成本。

1.2 整体技术栈与工作流程设计

系统整体分三块:

  • 模型层:YOLOv11检测模型,负责“从图里找出苹果并框出来”
  • 应用层:Python + PyQt5,负责UI展示、图片/摄像头输入、结果渲染
  • 用户层:SQLite + 登录注册逻辑,负责用户账号管理,让系统具备一个完整软件该有的基本形态

技术选型上,PyTorch作为深度学习框架,配合ultralytics库来调用YOLOv11。界面这块我对比了Tkinter和PyQt5,后面第4章会详细说,综合体验上PyQt5确实更适合做这种带实时画面的工具型软件。

工作流程是这样:用户登录系统后,可以上传本地图片、连摄像头拍摄,或拖入一段批次图片,程序把图像交给YOLO模型推理,得到检测框坐标和置信度,然后在UI上绘制并显示。同时把检测结果统计成数量、总框数等信息,方便做产量估算。

流程核心就一句话:输入图像 -> 预处理 -> 模型推理 -> 后处理 -> 界面展示。整个链路看上去不复杂,但每个节点都有大量细节,尤其是数据这一环,最容易拉开项目差距。

1.3 为什么要做登录注册而不是直接打开检测

很多类似项目Demo只有一个检测窗口。我加上登录注册界面,不是为了凑功能,而是考虑到这类系统将来如果放在果园管理后台或实验室设备上,需要区分不同人员的操作权限,比如管理员能查看检测记录,普通用户只能用检测功能。这时候账号系统就是必要的。

用SQLite做本地用户存储,够用也轻量。密码不能明文存,我用hashlib加盐哈希处理。第4章会给出具体的表结构和代码思路。

2. 数据集构建与预处理实操

数据集是这个项目的命门。模型能识别到什么程度,很大程度不取决于网络结构,而取决于喂进去的数据是否贴近真实场景。

2.1 自然生长苹果图像的采集要点

我采集数据参考的是某果园的真实环境,当时用了手机和运动相机在多个时间段拍摄。拍摄时有几条经验:

  • 别只挑拍得好看的苹果拍,刻意去拍遮挡严重的、背光的、远距离的苹果,模型才会学会在各种条件下找目标
  • 同一棵树不要连续拍几十张相似角度的照片,否则数据集高度冗余,训练出来的模型泛化能力差
  • 尽量涵盖早中晚、晴天阴天、顺光逆光几种光照条件,这些是自然场景下最大的干扰源
  • 如果做的是套袋苹果或者成熟后期红苹果,注意颜色差异要覆盖到,不能让模型只认识一种红色

总数据量方面,单类目标检测我用的是1500多张标注图。如果你的需求更复杂,比如还要区分成熟度等级,建议至少2000张起步,每类保持均衡。数据量太大也没必要,YOLOv11本身预训练权重已经学到通用特征了,用几百到一两千张针对性数据微调,效果往往就够用。

2.2 标注规范与YOLO格式转换

标注工具我用的标签工具,支持Pascal VOC格式、YOLO格式等。标注原则要提前定好,不然返工很痛苦:

  • 对于被遮挡超过一半的苹果,不标,避免模型学到“半个苹果也算完整框”的错误认知
  • 对于簇生重叠的苹果,只要可见轮廓足够清晰,就分别标框
  • 对于边缘模糊的远距离小目标,宁可漏标也不要勉强拉框,否则会教会模型产生位置漂移的预测

YOLO格式的标签是txt文件,每一行格式为:

类别索引 归一化中心x 归一化中心y 归一化框宽 归一化框高

转换公式很简单,就是从VOC的x1,y1,x2,y2像素坐标换成归一化值:

cx = ((x1 + x2) / 2) / 图像宽度 cy = ((y1 + y2) / 2) / 图像高度 w = (x2 - x1) / 图像宽度 h = (y2 - y1) / 图像高度

建议写一个Python脚本批量处理,不要手动去改txt。我当时就写了类似下面的逻辑来统一转换和按比例划分数据集。

2.3 数据增强与训练集/验证集划分

数据增强我用的是YOLO官方内置的配置,包含mosaic、随机上下翻转、色调饱和度变化、缩放等。Mosaic可以有效利用多张图拼接,让模型在小目标检测上更强。但是要注意一点:如果目标本身特别小且密集,mosaic会导致目标被切碎,这时候要降低mosaic概率或关闭它。

数据集划分我用了一个脚本,按9:1划分训练集和验证集,并且划分时保证同一场景下的图片不会全部进入训练集或验证集。目录结构如下:

dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── apple.yaml

apple.yaml内容:

path: /你的绝对路径/dataset train: images/train val: images/val nc: 1 names: ['apple']

这里我要特别提醒,路径一定写绝对路径,否则训练会报找不到数据集。

3. YOLOv11模型训练与调试实录

环境准备和训练细节直接决定模型效果,这部分我尽量把参数解释清楚。

3.1 环境搭建与依赖清单一览

我的环境参考如下:

组件版本建议
操作系统Windows 10/11 或 Ubuntu 20.04
Python3.9~3.11
PyTorch2.0及以上
CUDA11.8或12.x,对应显卡驱动
ultralytics最新稳定版
OpenCV4.8+
PyQt55.15

安装命令一般两条搞定:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python pyqt5

千万别直接pip install torch一把梭,不指定CUDA版本的话很可能会装到CPU版,训练速度天差地别。可以用nvidia-smi先看一下驱动支持的CUDA版本,再去PyTorch官网选对应的安装命令。

3.2 训练配置与超参数选择

我训练时用的命令是:

yolo detect train data=apple.yaml model=yolo11s.pt epochs=100 imgsz=640 batch=16 device=0

几点解释:

  • model=yolo11s.pt表示加载YOLOv11s预训练权重,s是小模型,速度和精度比较均衡。如果担心小目标效果,用yolo11m.pt也可以,代价是训练和推理速度变慢
  • epochs不是越大越好,我建议先跑100轮,看验证集mAP是否在后期还在明显上升,如果50轮后基本不再变化,就可以提前终止
  • imgsz设成640是官方默认,但我做小目标检测时试过896,mAP有提升,不过显存占用也明显增加。如果你的显卡显存不超过8G,老老实实用640
  • batch在显存允许范围内尽量大,但别设到直接OOM,边跑边调

训练过程中,ultralytics会在runs/detect/train目录下生成结果,包括各类损失曲线、验证集预测结果图和混淆矩阵。这些文件建议每轮训练结束都翻一遍,尤其要看验证集上的预测图,肉眼观察有没有把树枝误检成苹果。

3.3 训练指标怎么读

训练时主要看三个损失:box_loss(框回归损失)、cls_loss(分类损失)、dfl_loss(分布焦点损失)。一般趋势是快速下降后趋于平缓,如果出现验证集损失持续上升而训练损失还在下降,说明过拟合了,需要加数据增强或减少训练轮数。

模型好坏的核心指标是mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度,更通俗点说就是“框得准不准,认得到多少”。mAP50-95更严格,连续计算多个IoU阈值的平均精度,更能反映边界框定位精度。

我这次训练结束后的mAP50大概在0.92左右,mAP50-95在0.78左右,对于自然场景遮挡这么严重的苹果检测项目,已经具备实用价值了。如果你做的数据集和我的类似,也可以拿这个数做个基准参考。

如果mAP50一直上不去,优先排查标注质量,其次是数据增强和超参数。我在第5章会展开讲。

3.4 模型推理测试与导出

训练完可以做一次简单推理验证:

from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model('test.jpg', conf=0.3, iou=0.5) results[0].plot()

这里注意conf阈值的选择。项目最终部署想减少误检就调高到0.4~0.5,想尽量多检出被遮挡的苹果就调低到0.2~0.25。我会在UI里把这个参数开放给用户调节,而不是写死。

要部署到没有PyTorch环境的机器上,可以导成ONNX:

yolo export model=best.pt format=onnx dynamic=True

ONNX后续可以接OpenVINO或ONNX Runtime,推理速度快不少,模型文件也更小。如果只是做桌面项目,直接保留.pt也可以。

4. UI界面与登录注册系统的设计实现

模型再强,没有界面很难直接交付给用户用。这一章讲我把检测能力封装成桌面应用的完整过程。

4.1 UI框架选型:PyQt5还是Tkinter

Tkinter是Python自带,上手快,但做出来的界面风格偏老旧,做复杂布局和自定义样式都比较累。PyQt5虽然要额外安装,但控件丰富,样式表机制类似CSS,可以做出比较有质感的界面,而且在实时画面刷新和摄像头调用方面更流畅。

我做这类工具的首选是PyQt5,因为它还提供了QThread这种多线程机制,可以把深度学习推理放到子线程里,避免界面在检测过程中卡死。Tkinter本身也有线程处理方式,但要写更多额外代码。

4.2 登录注册模块实现

用户表设计很简单:

CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, salt TEXT NOT NULL, password_hash TEXT NOT NULL, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

密码处理千万不能明文存。我的做法是给每个用户生成一个随机盐值,把盐值和密码拼接后做哈希,保存盐值和哈希结果。校验登录时再用用户名查到的盐值去重新哈希比对。

注册逻辑核心代码:

import hashlib, os, sqlite3 def register(username, password): salt = os.urandom(16).hex() pwd_hash = hashlib.sha256((password + salt).encode()).hexdigest() conn = sqlite3.connect('system.db') try: conn.execute( 'INSERT INTO users (username, salt, password_hash) VALUES (?, ?, ?)', (username, salt, pwd_hash)) conn.commit() return True except sqlite3.IntegrityError: return False # 用户名重复 finally: conn.close()

登录函数类似,查出salt再算哈希比对。这里我不会用加密算法把逻辑写复杂,因为本地桌面应用足够用了。如果你后续要放到Web后端,就得换更严谨的密码哈希方案。

4.3 检测主界面与模型推理的联动

主界面我设计成左右结构:左侧是图片/视频显示区,右侧是操作区,包括选择图片、选择文件夹、打开摄像头、检测按钮、置信度滑块和检测结果列表。

推理部分的核心是使用QThread避免阻塞UI线程,信号槽机制把模型返回的结果传给界面刷新。大致模式如下:

class DetectThread(QThread): result_ready = pyqtSignal(list) def __init__(self, model, img_path): super().__init__() self.model = model self.img_path = img_path def run(self): results = self.model(self.img_path, conf=conf_threshold) dets = [] for box in results[0].boxes: dets.append([int(box.xyxy[0][0]), int(box.xyxy[0][1]), int(box.xyxy[0][2]), int(box.xyxy[0][3]), float(box.conf[0])]) self.result_ready.emit(dets)

主界面拿到检测框后,用OpenCV或QPainter在原图上绘制矩形框,并标注置信度,同时更新统计信息。

这个结构看起来简单,但当时给我上了一课:不要在主线程里直接调用模型推理,尤其是摄像头实时检测时,PyQt界面会一直无响应。用线程去做之后,界面流畅度提升非常明显。

5. 常见问题与排查技巧实录

这里我整理几个最常被问到的坑,每个都是我自己测试时真实遇到过的。

5.1 训练时CUDA显存不足

YOLOv11s本身对显存要求不算高,但如果你把imgsz调到896,batch又设得比较大,8G显存很容易直接OOM。解决办法有几个方向:降低batch、关闭mosaic或把它放后段轮数、使用混合精度训练。YOLO官方默认会启用混合精度,正常情况下显存占用已经比纯FP32小很多。

还有一个技巧是在train参数里加fraction=0.5,先用一半数据试跑一次,确认显存占用正常再开全量训练,能省很多调试时间。

5.2 Loss不收敛或收敛后验证指标波动大

Loss不收敛的原因我遇到过三种:

  • 数据集里有大量过于模糊的图片,模型怎么学都学不出稳定的特征,果断清掉这类图
  • 标签和图片对应关系错位,比如图片改名后label没跟着改,训练时模型见到的是错误配对
  • 初始学习率不合适,降低初始学习率或者改用AdamW优化器往往能有改善

验证指标波动大,则要看是不是验证集图片目标太少或者某一批图片场景过于极端。把验证集按场景比例重新抽一遍,通常能缓解。

5.3 苹果漏检与误检的调试

漏检主要是小目标问题。离摄像头远的苹果只有几十像素时,YOLOv11s确实有漏检。我建议在UI里提供两级选型:一是推理时把imgsz提到960,小目标召回率会提升;二是置信度阈值调低到0.2,让模型输出更多候选框。

误检多出现在背景有圆形物体,比如远处遮阳伞、树下的反光石块。这时候加大置信度阈值,或者手动给数据集补充标注这些“难负样本”,然后重新训练几轮,模型会逐渐学会区分。

5.4 UI打包体积过大与运行卡顿

PyQt5加PyTorch的exe打包出来动辄几百MB,这是正常的。但有一点要注意:打包时不要把整个训练好的模型塞进根目录导致exe加载慢。最稳妥的做法是把模型放在exe同级的models/目录里,启动时用相对路径定位加载,这样既方便模型替换,又避免了打包工具把模型文件压缩进exe后每次启动解压的等待。

运行卡顿方面,除了用线程,另一个坑是摄像头采集和推理没有做帧率控制。我后来在实时检测时加了跳帧逻辑,每3帧调用一次模型,其余帧直接显示当前画面,CPU和GPU占用率立刻降了一半,画面也稳定很多。

5.5 关于数据集标注的力度

我标注时其实走了一次弯路。刚开始把图片里所有能看到的苹果都标了,结果模型预测出来一堆零碎的框,很多只有轮廓的一点点。后来调整策略,只标可见面积超过一半的苹果,模型输出立刻干净了很多。

农田场景里数据质量大于数据数量,这个准则在你跑多个模型对比后体会会更深。

结尾

这个苹果检测系统前前后后我改了三轮,从最早的数据采集,到第一次训练出的模型满脸漏检,再到最后跑出UI流畅的成品,最大的感受是这类“模型+软件”的项目,难点往往不在算法本身,而在工程整合。YOLOv11已经帮你把高难度的检测部分降到了很低门槛,真正拉开差距的是数据集质量、UI交互细节和异常调试的能力。

最后再分享一个小技巧:如果你后续想把这个系统接到采摘机器人上,先把模型导出为ONNX或TensorRT格式,再用优化的推理引擎替换ultralytics原生推理,帧率基本能再翻一倍。但桌面演示阶段,直接用训练好的.pt文件反而是最方便稳定的方案。希望这篇笔记能帮你少踩几个我踩过的坑,顺利完成你自己的苹果检测项目。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询