ARTICLE DETAIL

资讯详情

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

YOLOv8玉米叶病害检测实战:数据集、训练推理与PyQt界面全流程

YOLOv8玉米叶病害检测实战:数据集、训练推理与PyQt界面全流程 简介YOLOv8玉米叶病害检测权重与PyQt界面方案专为农业病害识别场景设计面向需要快速开展玉米叶病害检测实验的开发者、科研人员及毕业设计学生一份资源即可覆盖数据、训练与可视化展示。压缩包共2000个文件体积约182.28MB结构上以1700个txt标签文件为主体配合171个Python脚本、56个yaml配置文件和49张jpg样例图可支撑从数据准备、模型训练到界面运行的完整链路。数据集包含1500张玉米叶病害图像分为blight、common_rust、gray_leaf_spot和healthy四类train/val/test目录已分割完毕并内置data.yaml可直接用于YOLOv5/v7/v8/v9等算法训练。随包附带的PyQt界面通过ui文件和py脚本可快速调用已有权重实现病害检测结果的可视化交互。目前已有436人学习下载适用于农业智能监测、病害预警等应用场景的快速验证。1. 玉米叶病害检测项目从数据到界面一次性打通做农业视觉的同学应该都有同感模型训出来只算走完一半真正要落地给植保站或者农户用还得配一个能传图、能出结果、能看得懂的界面。这套 yolov8 玉米叶病害检测资源就是这么个定位——它把数据集、权重和 PyQt 界面三件事打包到了一起目录已经按 train、val、test 划分好data.yaml 直接指向对应文件夹YOLOv5 到 YOLOv9 都能直接开训。我拆这套东西的时候最关心的不是模型多先进而是三件事标签标得规不规范、权重到底能不能直接出结果、PyQt 界面接上之后是不是真的不卡。这篇文章就把这三件事逐一拆开讲适合正在做毕设、比赛或者植保小工具的开发者照着走能省掉不少自己拼装的功夫。2. 把 1500 张玉米叶病害数据集跑起来目录结构、data.yaml 与标签格式拆解2.1 数据集目录结构与 train/val/test 划分逻辑这份资源的核心是Corn_yumiye_bad_dataset数据集目录原始划分里已经生成了 train、valid、test 三个子目录每个子目录下再按 images 和 labels 分类存放。这种目录结构对应 YOLO 系列训练器的读取习惯ultralytics 仓库里的YOLODataset类会直接根据 data.yaml 提供的路径去子目录里搜索图片和标签不需要额外写数据加载逻辑。从文件列表来看图片命名带有Corn_Health、Corn_Blight、Corn_Common_Rust、Corn_Gray_Spot这样的前缀对应了类别区分。标签文件是同名.txt每行格式是class_id x_center y_center width height坐标值已经归一化到 01 区间。我随机抽查了几组图片与标签的对应关系发现标注框贴合度比较理想叶斑区域框得比较紧凑没有出现大面积框住整片叶子的偷懒标注这对训练收敛速度影响很大。整体划分比例建议沿用原始配置。对于 1500 张的规模常见的做法是 train 占 70%80%val 占 10%15%test 占 10%15%。这套数据集在配置时已经划分完毕训练时只需确认 test 目录内容在预测阶段启用而不是训练阶段就用 test 数据否则会提前泄漏测试集信息。2.2 data.yaml 逐行拆解与路径改写资源包自带的 data.yaml 内容非常关键它决定了你的训练器能不能找到数据。文件默认内容如下train: E:\python_code\dataset\Corn_yumiye_bad_dataset\train/images val: E:\python_code\dataset\Corn_yumiye_bad_dataset\valid/images test: E:\python_code\dataset\Corn_yumiye_bad_dataset\test/images nc: 4 names: - blight - common_rust - gray_leaf_spot - healthy这里有两个地方要特别注意。第一路径是原作者机器的绝对路径而且用的是E:\python_code\...这种 Windows 风格写法。如果你在 Linux 服务器或者别的机器上跑必须改成你自己的实际路径否则数据集加载阶段就会报AssertionError: train: ... does not exist这一步是接触这类资源时最先遇到的坑。第二nc: 4和names列表的顺序必须和标签 txt 里的class_id一一对应。比如某张图片的 txt 里写0 0.5 0.5 0.2 0.3那这个框对应的就是names[0]也就是blight。如果你后续自己用 labelme 或者其它工具标注新数据导出标签时一定要保证类别顺序和 names 一致否则模型会学错语义界面里显示的结果也会对应错名字。实际使用中我一般会把 data.yaml 改成相对路径风格便于换机器复现。比如train: ./train/images val: ./valid/images test: ./test/images nc: 4 names: - blight - common_rust - gray_leaf_spot - healthy但注意当你把train路径改成相对路径后后续执行训练命令时工作目录必须位于Corn_yumiye_bad_dataset的上一级目录这样相对路径才能正确拼接。稳妥起见大多数人还是直接用绝对路径只是每次换机器都要记得改一遍。2.3 验证数据集完整性的快速脚本在开始训练之前我建议先花一分钟跑一遍目录校验脚本避免训练跑到一半才发现少了某个类别的图片或者标签错位。下面这个 Python 脚本可以快速统计各子集的图片数量、标签数量和类别分布import os from collections import Counter base_dir Corn_yumiye_bad_dataset class_names [blight, common_rust, gray_leaf_spot, healthy] for subset in [train, valid, test]: img_dir os.path.join(base_dir, subset, images) lbl_dir os.path.join(base_dir, subset, labels) imgs sorted(f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))) lbls sorted(f for f in os.listdir(lbl_dir) if f.endswith(.txt)) assert len(imgs) len(lbls), f{subset} 图片与标签数量不一致! cat_counter Counter() total_boxes 0 for lbl in lbls: with open(os.path.join(lbl_dir, lbl), r) as f: rows f.read().strip().splitlines() total_boxes len(rows) for row in rows: cat_counter[int(row.split()[0])] 1 print(f[{subset}] 图片: {len(imgs)}, 标签: {len(lbls)}, 目标框总数: {total_boxes}) print(f 类别分布: { {class_names[k]: v for k, v in cat_counter.items()} })这段脚本做了三件事校验图片和标签文件数量是否一致统计每个子集的目标框总数统计每个类别的框数量分布。如果某个子集里某个类别的框数量为 0那你训练出来的模型对这个类别的检测能力就会很差甚至推理阶段完全漏检。我在实际拆包时就发现 test 集中 healthy 类别的占比略高这并不影响训练但会让人误以为模型精度很高其实只是测试样本偏简单。参数上endswith((.jpg, .png))这里可以根据你的实际图片格式扩展如果有.jpeg或者.bmp的图需要把后缀加进去。另外脚本里class_names的顺序要和 data.yaml 的 names 保持一致否则统计结果会张冠李戴。3. 加载训练好的权重做推理YOLOv8 Detect 脚本与 PyQt 界面对接点3.1 推理脚本的参数说明与输出结构这套资源附带训练好的权重文件可以用 ultralytics 库直接加载推理这也是 PyQt 界面的核心引擎。具体推理代码如下from ultralytics import YOLO # 加载权重权重路径按实际情况修改 model YOLO(best.pt) # 对单张图片推理 results model.predict( sourcetest/images/Corn_Blight-230-_JPG.rf.fe52137550a2222bc05602585f017f08.jpg, conf0.25, iou0.45, saveTrue, projectruns/detect, namecorn_demo ) # 解析结果 for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() label model.names[cls_id] print(f类别: {label}, 置信度: {conf:.2f}, 坐标: {xyxy})conf0.25是置信度阈值低于这个值的框会被过滤掉。检测玉米叶病害时如果场景里背景复杂这个值可以适当调高到 0.30.4减少误报但如果你的目标是尽量不漏检早期病斑0.2 也可以接受。iou0.45是 NMS 的 IoU 阈值控制重叠框的合并力度对于叶片上病斑密集重叠的场景这个值不宜调太高否则相邻的病斑会被合并成一个影响严重程度的判断。模型输出的boxes对象里有xyxy左上角、右下角坐标、conf置信度和cls类别索引。PyQt 界面里画框就是拿这组坐标在 QLabel 或 QGraphicsView 上绘制矩形框。需要注意xyxy是相对于原始输入图片的像素坐标如果你在界面上对图片做了缩放显示绘制时要做坐标换算否则框会画偏。这块我在后面避坑章节会详细说。3.2 推理脚本与 PyQt 的对接要点PyQt 界面调用 YOLO 模型核心是保证推理不阻塞 UI 线程。直接在按钮槽函数里调用model.predict()会让界面在推理期间完全卡死表现就是窗口假死、鼠标转圈。正确的做法是把推理放到QThread里执行推理完成后通过信号把结果传回主线程更新界面。这个资源里的 PyQt 界面就是按这个思路封装的界面侧主要包含三个部分文件选择区、图片显示区、检测结果文字区。用户通过按钮选择图片触发后台线程执行检测完成后在界面上绘制框和类别标签。整个流程的核心是线程与信号槽的配合我在下一章给出可以参考的界面代码骨架。4. 用 PyQt 5 包一层识别界面线程模型与信号槽实现4.1 为什么必须把推理放到独立线程PyQt 的主线程负责事件循环和界面绘制。如果在主线程里执行 YOLOv8 推理一张 1280×1280 的图片在 CPU 上推理可能耗时 25 秒GPU 上也要几十毫秒到几百毫秒这期间界面会处于完全无响应状态用户会以为程序崩了。所以必须使用QThread或者QThreadPoolQRunnable把推理任务挪到子线程。我推荐用QThread的继承方案把run()方法重写为推理主流程。在run()内部模型加载、图片预处理、推理、后处理全部在子线程完成最终通过自定义信号把结果发回主线程。信号里携带的数据包括类别列表、置信度列表、坐标列表、绘制好的标注图像这样主线程只需要负责显示不参与任何计算逻辑。4.2 PyQt 界面核心代码骨架下面是一个简化但可直接运行的 PyQt 检测界面模型代码注释标明了关键逻辑import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget, QFileDialog from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QPixmap, QImage from ultralytics import YOLO import cv2 import numpy as np class DetectThread(QThread): # 自定义信号携带标注图和检测结果 result_ready pyqtSignal(QImage, list) def __init__(self, model_path, img_path, conf): super().__init__() self.model YOLO(model_path) self.img_path img_path self.conf conf def run(self): # 推理在子线程执行不阻塞 UI results self.model.predict( sourceself.img_path, confself.conf, iou0.45, verboseFalse ) r results[0] # 绘制标注框和类别文字 annotated r.plot() # ultralytics 自带绘制函数返回 BGR ndarray annotated_rgb cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, c annotated_rgb.shape qimg QImage(annotated_rgb.data, w, h, c * w, QImage.Format_RGB888) # 收集检测信息类别、置信度、坐标 dets [] for box in r.boxes: dets.append(( r.names[int(box.cls[0])], float(box.conf[0]), box.xyxy[0].tolist() )) self.result_ready.emit(qimg, dets) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(玉米叶病害检测) self.label QLabel(请选择图片) self.btn QPushButton(选择图片并检测) self.btn.clicked.connect(self.on_detect) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.thread None def on_detect(self): img_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Images (*.jpg *.png *.jpeg) ) if not img_path: return # 每次推理创建新线程防止上一次未结束 self.thread DetectThread(best.pt, img_path, conf0.25) self.thread.result_ready.connect(self.show_result) self.thread.start() def show_result(self, qimg, dets): self.label.setPixmap(QPixmap.fromImage(qimg).scaled( self.label.width(), self.label.height(), aspectRatioMode1 )) for det in dets: print(f类别: {det[0]}, 置信度: {det[1]:.2f})这段代码的核心是DetectThread里的run()方法它把模型加载放在__init__里是因为加载权重本身就耗时创建一个线程时加载一次后续多次推理可以复用同一个模型实例避免反复加载。result_ready信号携带两个参数一个是QImage类型的标注图一个是list类型的检测结果。PyQt 信号传list是可以的但如果结构过于复杂更稳妥的做法是传 JSON 字符串在槽函数里再解析。show_result槽函数只做显示操作QPixmap.fromImage(qimg).scaled(...)里aspectRatioMode1表示保持原图宽高比缩放避免图片变形。注意主窗口的setCentralWidget方式组织布局没有贴边距实际项目中建议用QScrollArea包一层显示区域否则大图会撑爆界面。4.3 界面与模型分离的注意点这套资源的 PyQt 界面和模型权重是分离的模型权重文件放在项目根目录界面文件通过相对路径加载权重。实际部署时我建议保持这个结构不变把best.pt、data.yaml、界面脚本放在同一目录这样换机器拷贝时不容易漏文件。另外要提醒一点r.plot()返回的标注图上类别名和置信度用的颜色是 ultralytics 随机分配的同一个类别每次运行的颜色可能不同如果你希望界面里固定颜色需要自己写绘制函数。常见的做法是建立一个类别到 RGB 颜色的映射字典再用cv2.rectangle和cv2.putText自己画这样界面风格能统一也方便做“红色表示 blight、绿色表示 healthy”这种可视化约定。5. 避坑清单路径、线程与标签错位三条血泪经验5.1 data.yaml 路径反斜杠在 Linux 环境解析报错现象把整个项目拷贝到 Linux 服务器后运行训练命令提示找不到 train 路径。检查 data.yaml 发现路径写的是E:\python_code\dataset\...在 Linux 下这个路径完全无效。原因原作者在 Windows 上配置的绝对路径未做平台兼容处理。解决在 Linux 环境将 data.yaml 改为本地实际路径train: /home/user/corn_dataset/train/images val: /home/user/corn_dataset/valid/images test: /home/user/corn_dataset/test/images或者直接改成相对路径把数据集目录作为工作目录启动训练。从那以后我每次拿到带 data.yaml 的资源第一件事就是检查路径分隔符和盘符确认目标机器的实际目录结构。5.2 PyQt 界面推理时窗口假死现象点击“检测”按钮后窗口变白没反应拖动窗口也无法刷新过几秒后突然一次性弹出检测结果。原因推理直接写在按钮的槽函数里阻塞了主线程事件循环界面无法重绘。解决按照第 4 章的QThread方案改造。这里还有一个隐含问题如果图片很大或者连续点了几次按钮多个线程同时推理会造成资源竞争。解决的办法是在on_detect里先判断self.thread是否还在运行如果是就直接弹出提示不再启动新线程。if self.thread is not None and self.thread.isRunning(): print(检测进行中请等待完成) return这段逻辑能避免用户多次点击导致的线程堆积也是界面响应性的最后一道保险。5.3 标签类别序号与 names 列表错位导致误检现象训练出来的模型对 gray_leaf_spot 的检测准确率很高但对 healthy 的误报率异常高。排查发现标签 txt 中 healthy 的 class_id 为 3但某个脚本在统计类别时按names[3]对应了 gray_leaf_spot导致评估指标全部错乱。原因data.yaml 里 names 的顺序和标签文件中 class_id 的对应关系被破坏了。解决用第 2.3 节的统计脚本检查每个类别的class_id分布同时打开几张标签 txt 人工核对。比如Corn_Health-14-_jpg.rf.b03f82b8676fd8736c8b995388a2dd0b.jpg对应的 txt 内容应该是3 0.5 0.5 0.2 0.3之类的如果发现类别前缀和 class_id 对不上说明标注文件有问题需要重新映射类别索引。这类错误最隐蔽因为训练不会报错只会在推理阶段以语义错乱的方式暴露。6. 进阶技巧把界面推理速度再压一截的实用招数拿到这套资源跑通基础流程后大多数人都会问一个问题界面能不能更快一点这里我给三个实用技巧。第一个技巧是模型导出与推理引擎切换。YOLOv8 原生支持导出为 ONNX 或 TensorRT 格式推理时不再走 PyTorch 动态图路径。具体做法是model.export(formatonnx, imgsz640, halfTrue)导出后用onnxruntime替代ultralytics做推理CPU 环境下通常能获得 1.52 倍的速度提升。imgsz640是与训练时一致的输入尺寸不要轻易改大否则精度和速度都会受损失。halfTrue表示导出 FP16 精度模型适合 GPU 平台CPU 平台就不用了因为很多 CPU 对 FP16 支持不友好。第二个技巧是固定输入尺寸。PyQt 界面里的图片可能是手机拍的 3000×4000 大图直接送进模型会被 resize 到 640×640这个过程每次都在做耗时且浪费内存。常见做法是在界面端先把图片缩放到适当尺寸再送推理img cv2.imread(img_path) img_resized cv2.resize(img, (960, 960), interpolationcv2.INTER_AREA)INTER_AREA适合缩小能保留更多纹理信息。注意这里选择的缩放尺寸不宜过小不然叶片上的小病斑会被抹掉检测结果会漏检。我一般选 9601280 之间兼顾速度和精度。第三个技巧是控制置信度阈值的策略。界面场景下用户更关心“有没有病、什么病”而不是“边界框精不精确”所以可以把conf适度调高到 0.350.45。这样可以减少界面里显示一堆低置信度框的干扰让结果更干净。但如果做批量评估建议仍然用 0.25 跑一遍保存完整的检测结果再按业务需求过滤。最后分享一个我的个人习惯每次拿到权重后我会固定跑一遍 10 张不同光线条件下的样本图把检测结果截图存档。这样之后改任何界面代码只要对比新结果和存档结果就能快速判断到底是代码改出了问题还是模型本身就存在误检。这个办法救过我很多次有一个晚上我发现界面里 blight 的置信度突然降了 0.3排查了半天最后才发现是某次代码里顺手改了输入尺寸没有改回 640。从那以后我的所有推理脚本都会在运行前打印一次实际使用的输入尺寸和模型路径避免“黑匣子”式调试。希望这些细节能帮你少走几个弯路。本文还有配套的精品资源点击获取
返回列表