ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于YOLOv8和PyQt5的番茄成熟度智能检测GUI系统实现

基于YOLOv8和PyQt5的番茄成熟度智能检测GUI系统实现 搞农业视觉项目这几年我越来越觉得一个核心问题YOLOv8这类目标检测模型单独跑命令行验证精度是一回事真正交付给用户做检测是另一回事。很多同学模型训练完mAP50都到0.95了但用户拿不到手因为缺一个能看、能点、能现场出结果的界面。这次分享的番茄成熟度智能检测GUI系统就是用YOLOv8做检测核心外面套一层PyQt5图形界面把图片检测、摄像头实时识别、成熟度分级统计全部收进一个窗口里打包成一个完整工程。整套内容从数据采集、标注、训练、评估到GUI封装、线程优化、实机调试我全部跑通了一遍。适合正在做YOLOv8相关毕业设计、农业无损检测项目以及刚接触YOLOv8、想从“只训模型”迈向“完整项目”的同学参考。文本不会给你一堆孤立的代码片段而是把整个工程拆开讲清楚最后你手里会有一个能跑的完整结构。1. 项目整体设计与方案选型1.1 为什么是YOLOv8而不是YOLOv5或者Faster R-CNN先回答一个最常见的问题番茄成熟度检测为什么一定要用YOLOv8从模型选型角度说番茄检测面对的场景是棚室或分拣线目标多、尺寸小、相互遮挡而且成熟度判断依赖的是颜色细节。这类任务有两个关键诉求一是实时性二是精度。传统Faster R-CNN精度不错但一张图几十毫秒往上走做实时摄像头检测就吃力YOLOv5虽然成熟但官方维护节奏已经放缓代码结构上还得自己处理anchor。YOLOv8走的是anchor-free路线把anchor相关的调参问题直接抹掉了同时C2f模块、Decoupled Head这些设计让小目标检测和边框回归的精度都有实打实的提升。再实际一点说YOLOv8是Ultralytics官方长期维护的版本从训练到导出onnx、tensorrt都有现成指令社区资料也多。真到部署阶段无论你是想跑在Windows电脑上还是后面往RK3588这类嵌入式板子上迁移YOLOv8的生态都是最省事的。这也是我把工程基座放在YOLOv8上的核心原因。1.2 为什么是目标检测而不是图像分类很多人拿到“番茄成熟度检测”这个题目第一反应是做一个图像分类模型绿番茄一类、半熟一类、全熟一类。这个思路看起来通但实际现场根本没法用。分类模型解决的是“这张图整体是什么”而现场一张图里往往同时有好几个番茄有青的、有转色的、有红透的甚至还有枝叶遮挡。分类模型只能给出整张图一个标签你没法知道每个番茄在哪、各自什么成熟度。而目标检测不一样它同时输出目标框和类别框住每个番茄的位置再给它打上绿果、半熟、熟果的标签后续做数量统计、上市分级、成熟度比例分析全都有据可依。这里的区别就好比你要数一筐水果里每种有几个分类模型告诉你的只是“这筐看起来像混合水果”检测模型则直接告诉你“你左上角那个是青果右下角那两个是熟果”。所以这个项目选检测路线不是因为检测比分类高级而是场景需求决定的。1.3 为什么要加GUI而不是继续用命令行说实话纯命令行跑YOLOv8对开发者来说效率最高一张照片输入结果就打印出来了。但你把这套东西交给一个农场管理员或者采收负责人你让他打开终端敲python detect.py这是不现实的。所以GUI不是锦上添花是把模型真正用起来的关键一环。我的设计思路是打开软件选文件夹按一个按钮就出检测结果界面上直观显示每个番茄的成熟度标签和置信度旁边还有一个统计面板统计当前批次三个级别各有多少个。如果能接上摄像头打开摄像头就是一个实时检测窗口。这套工程做下来模型训练部分其实只占30%的工作量剩下的全在工程化、界面交互、线程稳定性这些细节上。这也是很多实际项目里最容易被忽略、后边又不得不补的功课——模型训得再好工程不好用照样无法落地。2. 环境准备与数据集构建2.1 环境配置版本组合就是最大的避坑点先说环境。YOLOv8本身对硬件要求不算高但如果你乱装版本坑多到你怀疑人生。我实测下来最省心的一套组合是下面这个表直接照着装基本不会出幺蛾子组件推荐版本说明操作系统Windows 10/11 或 Ubuntu 20.04/22.04本工程Windows实测通过Python3.9 或 3.103.11以上个别依赖兼容性要验证CUDA11.8这个版本目前最稳兼容最广cuDNN8.6.0与CUDA 11.8配套PyTorch2.0.1cu118与YOLOv8官方测试对齐Ultralytics8.0.x 以上均可建议锁定一个最近稳定版安装PyTorch的时候用官方命令pip install torch2.0.1 torchvision0.15.2 torchaudio2.0.1 --index-url https://download.pytorch.org/whl/cu118装完再装Ultralytics和GUI相关依赖pip install ultralytics pip install pyqt5 pyqt5-tools opencv-python pillow numpy如果你用的是GTX 1660 Ti这类6GB显存显卡完全够跑就是batch size别贪训练时8到16都行。我实测GTX 1660 Ti用yolov8n训练番茄数据集batch8显存占用3.5GB左右温度控制也很好。真正吃显存的反而是推理端的某些操作但GUI推理一般用nano或small模型压力不大。2.2 番茄成熟度的分级定义做农业检测项目最怕的不是模型调不好而是标签本身定义不一致。番茄成熟度分几级、边界线在哪这必须在标注之前定死不然后面模型学的时候自己都晕。我采用的归类标准是未成熟green果实表面整体为浅绿到深绿色没有明显红色或橙色斑块。注意这里包含白熟期就是果面变白但未转红的那个阶段也归到未成熟。半成熟semi-ripe果面出现明显转色红色或橙色区域占果面面积约10%到50%。这是转色期的核心区间也是判断最难、标注最容易纠结的一类。成熟ripe果面大部分转红或全部转红红色或橙色区域超过50%达到可采摘上市标准。第二类“半成熟”是最容易标乱的人眼判断都有主观偏差。实操中我建议定一条硬标准以果面红色或橙色面积是否过半为界不到一半全部标成半熟超过一半全部标成熟减少中间态的主观摇摆。宁可让模型在这个边界上有些许模糊也比标注混乱导致的训练失败强得多。2.3 数据采集、标注与目录组织数据集我前后采集了大约1800张番茄图片来源于三个渠道自己用手机拍的棚室照片、网上公开的番茄图片、以及从视频里抽帧截取的画面。最终清洗之后留下1500张左右按8:1:1比例划分训练、验证、测试集。采集照片时要注意三点第一光线要多样化晴天、阴天、早晚不同时段都应当覆盖否则模型换一个棚就水土不服第二角度多样化不要只拍正面侧面、俯视、遮挡场景都要有让模型学会从残缺信息里判断成熟度第三不要全部特写要有远距离多目标图片模拟现场人员拿摄像头扫一眼的场景。标注工具我用的LabelImg标签就三个类green、semi-ripe、ripe。标注时框要紧贴果实边缘别把果柄和叶子包进去但也不要卡得太死稍微留1%到2%的余量。标完之后目录结构按YOLOv8要求整理成datasets/tomato/ ├── data.yaml ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/data.yaml内容如下path: D:/datasets/tomato train: images/train val: images/val nc: 3 names: [green, semi-ripe, ripe]标注文件是txt格式每行“class x_center y_center width height”都是归一化坐标。LabelImg会自动生成不需要手工改。有些同学用CVAT或者Roboflow导出YOLO格式也是同样的目录结构不影响后面的训练流程。3. YOLOv8模型训练、评估与导出3.1 训练命令与关键参数说明数据准备好了训练这一步反而最简单。YOLOv8把训练入口收敛成一条命令yolo detect train datadatasets/tomato/data.yaml modelyolov8n.pt epochs100 imgsz640 batch8 device0 projectruns/train nametomato_exp1如果你习惯用Python脚本也可以写成from ultralytics import YOLO model YOLO(yolov8n.pt) # 加载COCO预训练权重 model.train(datadatasets/tomato/data.yaml, epochs100, imgsz640, batch8, device0, projectruns/train, nametomato_exp1)有几个参数我想单独说一下这些是我反复试出来的经验epochs设100对1500张的小数据集正合适一般到60到80轮就收敛了后面属于微调。imgsz设640是准确率和速度的平衡点你把图片改成1280训练平均精度可能再高两三个点但推理时间也直接翻倍做GUI实时检测不划算。batch根据显存定6GB显存设8最稳妥实测训练过程不会OOM。device0就是强制用GPU如果你只有CPU环境训练时间会长很多我建议直接把epochs降到30到50先跑通流程。第一轮训练开始前程序会自动下载yolov8n.pt预训练权重如果你网络环境不好这里可能会卡住。解决办法是用浏览器或者下载工具先手动把权重文件下载到当前目录然后再跑训练命令Ultralytics检测到本地已有同名权重就会跳过下载。3.2 从训练日志判断模型状态训练过程中你会看到每轮的loss值、精度、召回率这些指标实时刷新。很多人不会看这些数字只关心最后结果这是个误区。你要学会从曲线里判断模型学得对不对。我习惯把训练日志保存下来用TensorBoard或者Ultralytics自带的results.png曲线图来看。最常见的两种异常情况你要记一下第一种是box_loss和cls_loss下降缓慢甚至在十几轮之后出现平台期。这大概率是数据标注不一致导致的比如同一成熟度的番茄你标注的框有大有小、类别边缘模糊。解决方法是回头检查标注把明显的错误标注重标。第二种是训练损失还在降但验证集的mAP50开始波动甚至下降这是过拟合的典型信号。番茄数据集规模不大模型很容易把背景纹理、光照条件这些环境信息一起记下来。遇到这种情况优先做数据增强把YOLOv8默认的增强参数打开其次可以用早停机制设置patience15连续15轮验证指标不涨就自动停止保存最优权重。我这里贴一下我训练完成后的关键输出方便你对照看模型是否正常epoch val_box_loss val_cls_loss mAP50 mAP50-95 55 0.023 0.011 0.942 0.753 70 0.019 0.008 0.957 0.781 85 0.018 0.007 0.961 0.795 100 0.017 0.007 0.958 0.788到这里就能看出来模型在90轮左右基本收敛mAP50稳定在0.95以上mAP50-95在0.78附近。对番茄成熟度这种场景mAP50持续过0.93就足够用了不要一味追求涨点模型复杂度上去了推理延迟也跟着上去得不偿失。3.3 评估指标与模型导出训练完以后在runs/train/tomato_exp1/weights目录下会生成best.pt和last.pt。best.pt是验证集上表现最好的权重GUI推理就用它。评估模型我建议再单独跑一遍验证命令yolo detect val datadatasets/tomato/data.yaml modelruns/train/tomato_exp1/weights/best.pt这条命令会输出详细的precision、recall、mAP50、mAP50-95并且把每一类的结果分开列出来。有时候整体mAP很高但某一类单独拉出来偏低尤其是“半成熟”这一类原因大概率还是标注时边界标准不一致。我实测半成熟类别的precision明显低于另外两类但归一化到整体指标后看不出来。如果你的项目对某一类有特殊要求一定要单独看per-class指标。导出模型到部署格式也是YOLOv8的强项直接一条命令yolo export modelruns/train/tomato_exp1/weights/best.pt formatonnx imgsz640导出onnx有两大好处一是推理速度比PyTorch原生方式快一点二是以后往嵌入式平台迁移省事。虽然在GUI项目里我不强制用onnx但提前导出放着后续你要上RK3588或者Jetson的时候就能直接拿走用。4. PyQt5 GUI系统实现与关键代码4.1 GUI功能拆解与界面布局GUI是整个工程里花时间最多、最容易踩坑的部分。我的设计原则是功能不一定多但用户拿到手不用看说明就能用。界面布局我分成了三个区域左侧是文件操作区负责选择图片文件夹、打开单张图片、打开摄像头开关中间是结果展示区用来显示原图和标注检测框右侧是统计信息区显示当前检测到的总番茄数量、绿果数量、半熟果数量、成熟果数量以及一张简单的比例条。下面整体贴一个界面搭建的核心代码import sys from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QFileDialog, QSplitter, QGroupBox, QListWidget, QTextEdit) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt class TomatoGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(番茄成熟度智能检测系统) self.setMinimumSize(1280, 800) self.model None self.init_ui() def init_ui(self): # 左侧文件操作区 left_panel QWidget() left_layout QVBoxLayout(left_panel) self.btn_open_img QPushButton(打开单张图片) self.btn_open_dir QPushButton(选择图片文件夹) self.btn_camera QPushButton(开启摄像头) self.btn_clear QPushButton(清空结果) self.file_list QListWidget() left_layout.addWidget(self.btn_open_img) left_layout.addWidget(self.btn_open_dir) left_layout.addWidget(self.btn_camera) left_layout.addWidget(self.btn_clear) left_layout.addWidget(self.file_list) # 中间结果展示区 self.image_label QLabel(等待加载图片...) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(800, 600) self.image_label.setStyleSheet(background: #1e1e1e; color: #ccc;) # 右侧统计信息区 right_panel QWidget() right_layout QVBoxLayout(right_panel) self.text_log QTextEdit() self.text_log.setReadOnly(True) right_layout.addWidget(self.text_log) splitter QSplitter(Qt.Horizontal) splitter.addWidget(left_panel) splitter.addWidget(self.image_label) splitter.addWidget(right_panel) splitter.setSizes([180, 780, 240]) self.setCentralWidget(splitter)界面代码不复杂但有几个细节值得强调。第一窗口左右区域尺寸用sizes固定避免用户随意拖动导致布局错乱。第二QLabel显示图片时要手动做缩放不然图片大了直接溢出界面。第三OpenCV读取的是BGR通道显示到Qt里面之前一定要转成RGB不转的话番茄会严重偏色这是新手最容易踩的坑。4.2 模型加载与单张图片推理模型加载我建议做一个全局的单例不要每次推理都重新初始化YOLO不然每张图要等好几秒。加载和推理的核心代码如下from ultralytics import YOLO class TomatoDetector: def __init__(self, weights_pathweights/best.pt): self.model YOLO(weights_path, taskdetect) self.names [green, semi-ripe, ripe] self.conf_thres 0.45 def predict(self, img): results self.model.predict( sourceimg, confself.conf_thres, iou0.45, imgsz640, verboseFalse ) return results def draw_boxes(self, img): results self.predict(img) boxes results[0].boxes.xyxy.cpu().numpy() classes results[0].boxes.cls.cpu().numpy().astype(int) confs results[0].boxes.conf.cpu().numpy() stats {total: 0, green: 0, semi-ripe: 0, ripe: 0} for box, cls, conf in zip(boxes, classes, confs): x1, y1, x2, y2 [int(v) for v in box] label self.names[cls] stats[total] 1 stats[label] 1 # 在这里画框、写标签颜色可按类别区分 # ... return img, statsconf_thres这个参数我单独说一下。默认值0.5是Ultralytics官方给的在番茄场景下偏高。现场光线复杂部分半熟果检测置信度可能只有0.4上下你设0.5就把它们滤掉了。我实测下来GUI里置信度阈值设0.4到0.45比较合适既能滤掉明显误检又不会漏掉边界样本。为了现场可调我在GUI面板里加了一个滑动条直接调节这个阈值用户不用改代码。4.3 摄像头实时检测线程处理不卡界面摄像头实时检测是GUI项目里最难的工程点比模型本身难得多。如果直接在Qt主线程里循环读摄像头帧并推理界面会直接卡死窗口无法拖动、按钮点不了用户体验非常糟糕。解决思路是单独开一个工作线程负责采集和推理主线程只负责接收结果并刷新界面。我封装了一个CaptureThread继承自QThread代码结构大致如下import cv2 from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class CaptureThread(QThread): frame_ready pyqtSignal(object, str, dict) def __init__(self, detector, source0): super().__init__() self.detector detector self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) if not cap.isOpened(): self.frame_ready.emit(None, 摄像头打不开请检查设备编号, {}) return while self.running: ret, frame cap.read() if not ret: continue annotated, stats self.detector.draw_boxes(frame) self.frame_ready.emit(annotated, 检测中..., stats) # 控制帧率避免噪声 self.msleep(30) cap.release() def stop(self): self.running False self.wait()主界面上接这个信号收到一帧就更新一次QLabeldef on_frame_ready(self, img, msg, stats): if img is None: self.text_log.append(msg) return rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, ch rgb_img.shape bytes_per_line ch * w qimg QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888) self.image_label.setPixmap(QPixmap.fromImage(qimg).scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) # 更新右侧统计信息关闭摄像头时务必调用th.stop()还要把VideoCapture释放干净不然下次再打开摄像头会报设备被占用这是OpenCV的老毛病。5. 完整工程结构与实测效果5.1 工程目录说明我不想只给一堆零散代码一个能跑的工程必须有一个清晰的结构。下面是我这个项目的最终目录组织tomato_gui_project/ ├── main.py # 程序入口 ├── requirements.txt # 依赖清单 ├── README.md # 使用说明 ├── weights/ │ └── best.pt # 训练好的模型权重 ├── datasets/ │ └── tomato/ │ ├── data.yaml │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ ├── gui/ │ ├── __init__.py │ ├── main_window.py # 主界面 │ ├── detector.py # YOLOv8推理封装 │ ├── capture_thread.py # 摄像头线程 │ └── style.py # 样式表 ├── utils/ │ ├── __init__.py │ └── image_tools.py # 图片格式转换工具 └── runs/ └── train/ └── tomato_exp1/ # 训练日志和权重main.py是整个程序的入口负责初始化QApplication、加载配置、创建主窗口。detector.py是模型推理的封装类把所有和torch、ultralytics相关的逻辑都隔离在GUI外面以后你要换模型或者改推理逻辑只动这一个文件就行。capture_thread.py只做摄像头读取和推理不掺界面代码。这样分层下来每个文件职责明确调试和二次开发都轻松很多。5.2 推理速度与资源占用实录我把这套系统在我本机跑了一遍配置是i5-12400F、GTX 1660 Ti 6G、16GB内存记录了一组实测数据给你一个概念场景输入尺寸推理耗时端到端刷新延迟显存占用单张图片640x640约28ms约150ms含缩放绘制约1.1GB摄像头实时640x480约22ms/帧约50ms约900MB摄像头实时1280x720约35ms/帧约70ms约1.4GBGTX 1660 Ti跑yolov8n完全没有压力单帧推理20到30毫秒实时检测30帧以上没问题。就算跑yolov8s模型640输入下延迟也就50毫秒上下现场够用。显存占用都在1.5GB以内说明这套系统做下来门槛并没有你想象中那么高一张入门级显卡就撑起来了。图片缩放和画框反而占了不小开销因为Qt本身要做图像格式转换和界面重绘。这部分优化空间有限我的建议是如果追求极致帧率就用640x480的摄像头输入视觉上完全够用还能给显卡留出余量。6. 常见问题与排查技巧实录6.1 训练期翻车现场第一个常见问题是loss直接出现nan。这个大概率是学习率设置过高或者预训练权重加载失败。YOLOv8默认lr00.01对大多数任务没问题但如果你用小 batch size加小数据集可以考虑降到0.005。如果跑着跑着loss突然变nan先检查是不是gpu显存不足导致某些计算被杀掉了再看数据集中有没有空标注文件。第二个问题是mAP一直上不去始终在0.6附近徘徊。这种时候别急着调模型先回头看数据。我遇到过一类情况是标注框把果柄和叶子都包进去了模型学着学着把叶子也当特征。再一个常见原因是训练集和验证集来源太接近比如都来自同一段视频抽帧模型记住了特定背景验证指标虚高换现场图就拉胯。分组时要尽量让同一株或者同一段视频的帧只出现在一个集合里。第三个问题是训练时间长得离谱。如果你发现一epoch要跑很久先看一下是不是device参数没生效CPU和GPU的训练速度能差几十倍。在训练代码里打印一下torch.cuda.is_available()确认PyTorch真的用上了GPU而不是代码没报错但一直在CPU上跑。6.2 GUI运行期典型问题GUI阶段的问题主要集中在图片显示和线程管理上。图片偏蓝偏绿是出现频率最高的bug十个人里八个会踩。原因就是OpenCV读进来是BGR格式而Qt显示要RGB。你在cv2转QImage之前必须加一行cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。不转的话番茄是蓝绿色看着非常奇怪用户第一眼就觉得系统是坏的。摄像头打不开或者第二次打开报错是另一个高频问题。核心原因是上一次的cv2.VideoCapture没有释放干净或者volume线程还在占用设备。我在stop方法里做了两步先置running为False调用wait()等待线程退出然后显式调用cap.release()。实测这个流程能让摄像头开关反复操作几十次不报错。另外摄像头设备编号也要注意笔记本自带的摄像头是0外接USB摄像头可能是1打不开时先用脚本单独测一下是哪个编号。第三个问题是界面卡顿常见于图片文件夹连选模式下。如果你循环读取文件夹里所有图片并逐张推理千万不能把这些操作都放在GUI线程里。我实际测试过一次性处理50张图片如果不做线程隔离界面会假死十几秒。最后我把批量检测也放进了QThread里每推理完一张就通过信号回传一帧界面实时刷新体验好了很多。6.3 现场部署的小经验把系统带到不同环境下部署你会发现模型精度在棚室和分拣线上表现不完全一样。棚室光线复杂逆光环境下深绿色番茄很容易被漏检。我的调参建议是现场使用的时候把置信度阈值从0.45往下调到0.35左右同时把IoU阈值保持0.5以上减少重叠框。这会带来一部分误检但在农产品检测场景里你宁可多框几个让用户人工排除也不能漏检这直接关系到用户对系统的信任。另外GUI界面上的字体和按钮大小要考虑用户是现场操作经常是戴着棉手套掂番茄的按钮做得太小是完全不实用的。我给主按钮统一做了80px以上高度文字加粗选中和点击状态做了颜色变化操作区背景也做得和普通区域不同。这些细节看起来不起眼但用户用起来感受完全不同。到这里这套从零构建的番茄成熟度智能检测GUI系统已经完整走了一遍。你去复现的时候记住一条模型训练只是其中一环连接模型和真实用户的是工程化能力包括不限于界面设计、线程稳定性、数据标注规范和现场调参手段。最后分享一下我自己的习惯动手之前先把所有标注样例打印出来让团队成员按统一标准挑毛病标注规范对齐了训练阶段能省掉一半返工时间。希望这套工程能帮你少走一些弯路。
返回列表