尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

YOLOv8番茄成熟度检测实战:从数据集标注到PyQt5 GUI部署全流程

YOLOv8番茄成熟度检测实战:从数据集标注到PyQt5 GUI部署全流程 做过农产品分拣这一块的朋友应该都有体感靠人眼判断果蔬成熟度效率低还容易疲劳标准也不统一——同一批番茄早上和晚上看可能得出两个结论。后来我发现这个问题完全可以交给目标检测模型解决。用YOLOv8训练一个成熟度识别模型再套一个GUI界面导出一套能直接运行的桌面程序整体下来并没有想象中那么复杂。这篇就把我从零构建番茄成熟度智能检测GUI系统的完整过程拆开讲清楚包括数据集怎么搞、模型怎么训、界面怎么写、推理怎么接以及实测阶段我踩过的那些坑。适合想用YOLOv8做物体检测实战项目、或者需要一个“能演示也能落地”的视觉检测Demo的朋友参考。1. 从需求到方案为什么“番茄成熟度检测”最终选了YOLOv81.1 成熟度分级到底在分什么番茄成熟度不是一句“熟了没熟”就能概括的。在果蔬分级标准里番茄通常被分为绿熟期、转色期、半熟期、成熟期等阶段。农业收购和商超供货时不同成熟度的番茄对应不同的用途和价格青果耐运输但不甜全红果口感好但货架期短转色果则是长途运输的主力。我做这个系统的核心目标是把“人工目测”变成“算法识别”用摄像头拍一张或多张番茄图像自动输出每个果实的成熟度类别以及对应数量和比例。这样放在分拣现场就是一个低成本、可复现的辅助判断工具。目标清晰之后问题就变成了用什么技术路线来实现。1.2 为什么不用传统图像处理和通用目标检测预训练模型传统图像处理方案里最常见的是基于颜色空间的分割——把RGB转到HSV或Lab空间通过色相阈值找红色区域占比来判断成熟度。这个方法对背景干净、光线均匀的单果图效果还行但一遇到多果重叠、枝叶遮挡、自然光照变化阈值就很难调住。稍微换个环境参数就得重来一遍。通用目标检测预训练模型比如COCO预训练的YOLOv8能解决“番茄在哪里”的问题但COCO的80个类别里并没有“成熟番茄”“生番茄”这种细粒度属性模型只能画出人、车、杯子对农产品细分类无能为力。这也是实际项目里最常被新手忽略的点预训练模型只提供通用特征提取能力要识别“成熟度”这种业务级概念必须自己构建数据集做微调。YOLOv8的优势在于它在精度和速度之间平衡得比较好官方预训练权重能作为很好的初始参数单卡能训部署也方便可以导出ONNX、TensorRT等格式。对番茄成熟度检测这种类别少、目标尺寸中等偏小的场景来说YOLOv8n或YOLOv8s完全够用。1.3 系统整体架构与功能边界我最终搭建的系统由四部分组成数据层番茄成熟度图像数据集标注为3个类别green、turning、ripe模型层基于YOLOv8n微调的训练流程输出最佳权重推理层加载训练好的模型对图片、视频、摄像头画面做推理展示层PyQt5编写的GUI界面负责文件选择、实时预览、结果统计、导出报告。这里要澄清一下系统边界我做的是“辅助检测工具”不是全自动分拣设备。它解决的是“识别与统计”的视觉问题不包括机械臂抓取、传送带控制等硬件联动。不过GUI里的识别结果可以输出为CSV或JSON给后端PLC或机械控制系统留了接口后续扩展不算难。明确了目标和技术选型下一步就是最耗时、也最决定成败的数据准备环节。2. 数据才是真正的工程量番茄数据采集与标注的完整链路2.1 数据来源公开数据集之外自己拍也很重要很多教程会用公开数据集起步番茄成熟度方面确实有一些开源数据可以直接用比如Roboflow上就能找到“Tomato Detection”或“Tomato Ripeness”之类的项目。下载下来就能快速跑通训练流程适合验证流程。但公开数据集有两个问题一是类别定义不一定符合你的业务需求比如对方把番茄分成“绿色、红色、腐烂”三类而你需要的是“青果、转色果、成熟果”三级二是拍摄环境和使用场景不一致公开数据大多是固定光源下的近景单果图到了你的现场未必适配。我的建议是公开数据做“预跑”自采数据做“主力”。如果条件允许自己拿手机或相机在真实场景中拍摄覆盖不同角度、距离、遮挡程度和光照情况然后补充到训练集里。实测下来自采数据哪怕只有两三白张图片只要标注质量过关对模型实际场景表现的提升也非常明显。2.2 拍摄多样性与光照控制的取舍训练集拍摄环节最需要重视的就是“多样性”。模型没见过的情况推理时八成会翻车。我在采集番茄图像时刻意覆盖了以下几种变化拍摄角度俯拍、平拍、斜上45度果实数量单果、多果密集堆叠背景白板、纸箱、木桌、田间植株光照自然光、室内LED、逆光、阴影遮挡果实被叶片遮挡、半遮挡、完全露出成熟度过渡尽量采集处于中间状态的番茄比如半边红半边绿的转色果。光照是其中影响最大的变量。同一颗番茄在不同色温光线下拍出来色调差异很大。如果只在一个固定光源下拍摄训练出的模型换到自然光环境就会误判。所以数据集的多样性比绝对数量更重要。你甚至可以刻意加一些“难例”样本比如光照很强导致过曝、阴影遮挡导致部分暗区这些样本能让模型学得更稳。2.3 标注规范与标签设定的细节标注工具我用的是LabelImg开源免费操作简单——框选目标选择或输入类别标签保存为YOLO格式的txt即可。每张图片对应一个同名txt文件文件里每行内容为类别id 归一化中心x 归一化中心y 归一化宽 归一化高例如1 0.532 0.441 0.231 0.318表示一个类别id为1turning的目标框。关于类别设定我做了三个类别标签名含义典型视觉特征green青果绿熟期果面主要呈绿色或黄绿色turning转色果果面红绿相间即将成熟ripe成熟果果面主要为红色、深红色相比于“过熟”和“腐烂”这些类别这三个类别在视觉上区分度更高对标注一致性也友好。如果你需要更细的分类可以再加类别但要注意每个类别的样本量必须足够均衡否则模型会对少数类别产生严重偏置。2.4 数据增强与脏数据清理数据集整理完成后YOLOv8在训练时会自动做在线数据增强包括马赛克拼接、随机缩放、翻转、色调变换、模糊等。这些默认增强我已经实测过基本够用不需要额外写脚本增强。更需要注意的是脏数据清理。启动训练之前花点时间把标注质量过一遍有没有漏标的果实有没有框得过大或过小、框不贴边的有没有类别标错的比如把转色果标成了成熟果有没有图片和标注文件不对应的情况。这一步比较枯燥但回报很高。脏数据混在训练集里会让模型的收敛曲线异常表现为loss降不下去或者验证集mAP总是波动。宁可用1000张干净准确的数据也别用3000张标得乱七八糟的数据。3. 从零搭建训练环境跑通YOLOv8训练全流程3.1 环境配置与痛点YOLOv8的环境配置整体上是友好的核心依赖就是PyTorch和Ultralytics库。我的环境是Python 3.9PyTorch 2.xCUDA 11.8Ultralytics 8.xCUDA 11.8 cuDNN 8.6显卡GTX 1660Ti6GB显存如果你是第一次配置最容易出问题的几个点在于CUDA版本和PyTorch版本不匹配、conda环境混乱、下载权重时网络不稳定。关于网络解决方案比较成熟——多试几次、用镜像站加速或者让旁边的同事帮忙下载好离线包。这些问题网上都有大量的坑位记录照着排查就行。安装命令很简单pip install ultralytics torch torchvision --index-url https://download.pytorch.org/whl/cu118然后验证一下环境python -c from ultralytics import YOLO; model YOLO(yolov8n.pt); print(OK)第一次运行会自动下载yolov8n.pt预训练权重大约6MB下载完成后输出OK说明环境基本没问题。3.2 数据集目录与YAML配置Ultralytics的训练代码约定了一个标准数据集目录结构。我的目录长这样datasets/ └── tomato/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/数据集准备好后分成训练集和验证集比例大概9:1或8:2都可以。划分时可以加一点随机性但尽量保证两个集合里都覆盖到所有类别、各种光照和遮挡情况。然后写一个data.yamlpath: D:/projects/tomato/datasets/tomato train: images/train val: images/val nc: 3 names: [green, turning, ripe]path字段用绝对路径避免相对路径在后续切换到其他目录时找不到数据。3.3 训练超参数与损失曲线解读训练命令如下yolo detect train dataD:/projects/tomato/datasets/tomato/data.yaml \ modelyolov8n.pt \ epochs150 \ imgsz640 \ batch16 \ patience30 \ projectruns/detect \ nametomato_exp1我选的epochs150看起来多但因为设了patience30模型如果在30个epoch内mAP没有提升就会提前停止实际一般训到90到120轮左右就会收敛。对只有6GB显存的我来说batch16、imgsz640已经是上限了。训练过程中要养成看指标的习惯重点看两个东西损失曲线和验证集混淆矩阵。每个epoch结束后Ultralytics都会保存结果包括results.png曲线图。正常情况下训练损失和验证损失应该同步下降并趋于平缓。如果训练损失一直降但验证损失上升到后期拉高说明过拟合了。如果训练损失和验证损失都降不动且mAP一直很低大概率是数据或标注出了问题。混淆矩阵则直接告诉模型在哪些类别之间容易混淆。我的实验里一开始green被误判为turning的情况不少尤其是那些黄绿色系的番茄——人眼看着都犹豫模型就更难分。定位到这一问题后我针对黄绿色系和红绿相间的过渡状态图片做了补充采集和标注之后turning类的精确率和召回率都有了明显改善。3.4 从pt到onnx部署准备训练完成之后runs/detect/tomato_exp1/weights/里会生成best.pt和last.pt。best.pt是验证集上mAP最高的权重部署时直接用这个。YOLOv8官方库自己带了导出工具转ONNX只需要一行命令yolo export modelbest.pt formatonnx opset12ONNX格式的好处是跨平台、跨框架C、C#、Python都能调用。如果你的目标是嵌入式设备还可以尝试导出TensorRT格式需要NVIDIA显卡和TensorRT环境在GTX 1660Ti上实测TensorRT的推理速度比PyTorch原生快2到3倍左右。我的GUI系统目前用的是ONNX Runtime推理兼顾速度和部署便利性。4. GUI系统的设计与实现让模型“能被人使用”4.1 GUI工具选型PyQt5的理由Python的GUI库有几个常见选项Tkinter、PyQt5/PySide2、Kivy、wxPython。很多人第一步会纠结选哪个。我的取舍逻辑是这样的Tkinter优点是最简单的Python自带不需要额外安装缺点是控件风格比较老旧做多线程和复杂布局的效果一般PyQt5控件库丰富QSS可以改样式做出来的界面可以达到商用软件的外观水平而且QThread线程处理得很好Kivy偏向触摸屏和移动端开发桌面端的鼠标键盘交互体验反而一般。最终选了PyQt5原因就两个一是布局灵活可以比较轻松地做出“左边预览、右边控制面板、下方统计栏”的典型检测界面二是线程机制成熟模型推理放到子线程里不会卡死GUI主界面。4.2 界面模块设计与功能规划界面我按功能区域划分成了四块顶部工具栏选择图片、打开摄像头、开始/停止检测主预览区显示原始图像和检测结果的画布右侧控制栏置信度阈值调节滑块、模型文件选择框、检测类别勾选底部状态栏显示当前处理状态、帧率、各类别检测数量统计。整体结构用QMainWindow做宿主中间区域放QLabel或者自定义的QPaintWidget用于绘图。控制栏用QVBoxLayout垂直排布参数简洁直观。4.3 推理逻辑与线程安全推理放到子线程是必须的。如果直接在GUI主线程里跑YOLOv8推理一次检测耗时几百毫秒到一两秒不等界面会直接卡死用户体验很差看起来就像程序崩溃了。我的做法是定义一个YOLODetector类内部封装ONNX Runtime推理import cv2 import numpy as np import onnxruntime as ort class YOLODetector: def __init__(self, onnx_path): self.session ort.InferenceSession(onnx_path) self.input_name self.session.get_inputs()[0].name self.conf_threshold 0.25 def detect(self, frame_bgr): img, ratio, dw, dh self._preprocess(frame_bgr) outputs self.session.run(None, {self.input_name: img})[0] boxes, scores, class_ids self._postprocess(outputs, frame_bgr.shape, ratio, dw, dh) return boxes, scores, class_ids def _preprocess(self, frame_bgr): image cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) image cv2.resize(image, (640, 640)) image image.astype(np.float32) / 255.0 image np.transpose(image, (2, 0, 1))[None] return image, 1.0, 0, 0然后通过QThread在子线程里循环读取视频帧检测完成后发出一个pyqtSignal把结果传回主线程class DetectWorker(QThread): frame_ready pyqtSignal(object) stats_updated pyqtSignal(dict) def __init__(self, detector): super().__init__() self.detector detector self.running True def run(self): cap cv2.VideoCapture(0) while self.running: ret, frame cap.read() if not ret: break boxes, scores, class_ids self.detector.detect(frame) annotated self.draw_boxes(frame, boxes, scores, class_ids) self.frame_ready.emit(annotated) self.stats_updated.emit(self.count_stats(class_ids)) cv2.waitKey(30) cap.release()这里有个关键细节frame_ready信号携带的是QImage或numpy对象不要在子线程里直接修改或显示控件。Qt的UI操作只能发生在主线程子线程里操作控件会偶发崩溃这是新手最容易踩的坑。4.4 实时摄像头与离线图片的接入GUI里我同时实现了两种输入源本地图片/视频文件、RTSP摄像头或USB摄像头。图片模式点击“选择图片”按钮用QFileDialog.getOpenFileName弹出文件选择框加载后跑一次推理并显示结果。摄像头模式点击“打开摄像头”启动DetectWorker子线程循环读取摄像头帧边检测边显示同时把FPS实时显示在状态栏。摄像头打开时要注意两个问题一是资源释放关闭窗口或停止检测时要确保调用cap.release()并安全退出线程二是摄像头分辨率过高会明显降低推理帧率。实测中我建议把摄像头分辨率设为1280x720或更低的640x480因为检测模型输入就是640x640摄像头采集的高分辨率帧最终也要缩放高分辨率只会增加预处理耗时而不会提升精度。4.5 结果展示与统计输出检测结果的可视化我直接复用了YOLOv8的绘图逻辑利用置信度阈值筛选出高置信度的检测框在图片上用矩形框表示目标位置绿框表示青果橙框表示转色果红框表示成熟果并将类别名称和置信度标在框的上方。界面的底部统计栏会展示实时统计信息类别数量green青果12turning转色果8ripe成熟果9总计29我还在GUI上加了“导出报告”功能点击后把当前帧的检测结果保存为CSV文件便于分拣后用于数量核对或农产品入库统计。字段包含图片名、类别id、类别名、置信度、检测框坐标。这样虽然只是一个小功能却能让系统从“看看而已”真正变成能交付的工具。5. 实测中的误判、卡顿与边界问题一份技术经验沉淀5.1 成熟度误判的三种典型场景系统在实验室里表现挺好但拿到真实场景之后我发现了不少边界问题。以下是实测中误判率最高的三种场景第一种弱光与逆光。暗光环境下成熟番茄的红色饱和度降低色调向暗红色偏移模型很容易把ripe误判为turning。逆光场景更夸张整个番茄变成剪影颜色信息几乎丢失模型甚至会漏检。我的缓解方案是在部署端加入简单的自动曝光处理即推理前用直方图均衡化增强暗部细节对弱光画面效果明显。第二种果实重叠造成的漏检。成串或堆叠的番茄会有大面积的相互遮挡一个目标框里往往包含了多个果实。YOLOv8默认输出矩形框对密集遮挡场景多个目标会合并成一个大框或被抑制掉。解决思路有两个一是训练时把密集遮挡样本的数据增强权重加大Ultralytics默认的马赛克增强本身就有帮助二是后续后处理时加入NMS阈值调整适当降低IoU阈值能减少漏检但也会增加重复框。第三种绿色果实和背景的颜色混淆。带叶片或未成熟绿色的番茄在绿色背景植株上时目标与背景的颜色极其接近模型会倾向于把绿色果实漏检或把叶片误检为green。这个问题的根源是语义信息太单一。只靠颜色区分青果和叶片模型理论上能学但需要大量“前景绿色”与“背景绿色”同时出现的负样本。我在训练时额外加入了整株番茄植株的图片并明确把叶片、枝干这种干扰背景暴露给模型漏检率有明显下降。5.2 速度优化与硬件选择模型推理速度受硬件制约很大。我用GTX 1660Ti跑YOLOv8n640输入尺寸ONNX Runtime推理时间约30到50ms一帧即20到30FPS用于半静态的分拣场景完全够用。如果换成YOLOv8s推理时间会升到80到120msGUI界面明显能感到卡顿。如果要在纯CPU环境跑建议使用ONNX Runtime的CPU版本YOLOv8n单帧大约200到500ms做离线图片检测还行实时视频就会比较吃力。对实时摄像头场景我的建议排序是有NVIDIA显卡优先导出TensorRT引擎速度提升巨大有显卡但不想折腾用ONNX Runtime GPU版本无显卡换YOLOv8n把输入尺寸降到320或416帧率可以拉到接近实时。输入尺寸降了一半检测精度会略降但换来的是可用的实时性能这个取舍在部署阶段要自己权衡。5.3 训练与部署时的几个建议最后沉淀几条我给后来者的建议这些都是在实际开发中花了不少时间才摸出来的训练完不要急着部署。先多看几次results.png曲线图分析验证集上的混淆矩阵明确模型在哪些类别上分不好再有针对性地补数据或调整类别定义而不是盲目加大epoch数。ONNX版本要和opset对齐。导出ONNX时opset版本和部署端的ONNX Runtime版本有兼容性要求。我遇到过一次本地opset12导出的模型在同事的ONNX Runtime 1.16环境里可以正常加载但在1.10旧版本里报错。部署前最好固定ONNX Runtime版本。GUI项目打包收尾。用PyInstaller打包PyQt5 ONNX Runtime项目时记得把onnxruntime的dll文件一并包含进去并在spec文件里加上数据文件路径。我第一次打包出来的exe在本机能跑换一台电脑就提示缺少onnxruntime_providers_shared.dll折腾了一晚上才定位到。建议打包完成后专门在一台干净环境的Windows机器上跑一遍验证。模型文件不要放在打包后的临时目录。sys._MEIPASS路径在打包后可执行文件运行时指向临时解压目录程序退出就没了。模型文件应该放在exe同级的固定目录或用户目录下运行时通过相对路径或配置文件读取。这是很多打包后“功能一切正常但模型找不到”问题的根源。回到整个项目本身我这套做法你如果复刻下来工程量其实不算太大数据集部分花最多时间训练在单卡上几个小时能跑完GUI代码几百行就能实现。过程中最大的收获不是代码本身而是把“模型准确率挺高”变成一个“现场能有人愿意用”的完整系统。那一整套关于数据、训练、部署、交互之间如何衔接的经验比最终跑起来的那条检测管线值钱得多。我个人的体会是这类视觉检测项目真正拉开差距的其实就是你有没有把整条链路走完、走通。
返回列表