简介面向深度学习目标检测与农业智能化应用该资源包集成了YOLOv8玉米叶病害检测权重、PyQt可视化界面和1500余张已标注的玉米叶数据集适合农业科研人员、算法工程师及高校学生作为病害检测项目起点。数据集已按train/val/test划分好附有data.yaml和txt格式标签可直接用于YOLOv5、v7、v8、v9等系列算法训练。压缩包共2000个文件、约182.28MB核心包括1700个txt标签文件、Python训练/推理脚本、yaml配置、PyQt界面ui以及预训练pt权重目录结构清晰。已有438人浏览学习。资源覆盖blight、common_rust、gray_leaf_spot、healthy四类标签省去数据采集与手动标注环节可快速完成从模型训练到界面部署的完整流程便于进一步扩展为大田监测或教学演示系统。1. 从权重到能用的检测工具玉米叶病害项目差的不只是训练很多人拿到 yolov8 玉米叶病害检测权重就以为项目做完了其实训练出 best.pt 只是第一步。标题里的「权重 pyqt 界面 1500 数据集」三样东西放在一起才构成一个能交付的桌面检测工具。实际做农业视觉项目时目标不是算法竞赛榜单而是某个现场人员打开窗口、拖进图片、点一下就能看到病斑框和置信度。1500 张图片听着不算多但按每类病害 300 张、加健康叶片做负样本配合合理的划分和训练参数足够训练出一个可用的检测器。这篇笔记按「数据准备 → 训练权重 → PyQt 界面 → 部署验证」完整讲一遍适合课题需要交付界面端工具的同学也适合想快速评估这个方向值不值得投入的开发者。2. 从1500张图片到标准数据集目录、类别与划分的硬规矩2.1 玉米叶病害数据从哪来类别怎么定1500张够不够玉米叶病害检测常见做法是分五类健康叶片、大斑病、小斑病、锈病、灰斑病。类别数不固定但不管分几类都要保证每类不少于 250 张健康叶片单独作为一类别把「没病」当背景丢掉。1500 张够不够用取决于标注质量和划分方式。我见过用 900 张就训练出可用界面的项目也见过某团队拿了 3000 张图因为框标准不统一mAP 一直卡在 0.6。结论是数量只是底线标注一致性才是关键。这里有个容易犯的错有人会按类别从网上下载拼凑图片导致同一类病害来自不同拍摄设备、不同光照模型学到的是「设备特征」而不是「病斑特征」。比较稳妥的做法是尽量让每类病害的图片来源分散一些宁可每类数量少一点也不要把某一块田的照片全部堆进训练集。2.2 目录结构按YOLOv8的约定建目录YOLOv8 训练时读取的是 images 和 labels 两个目录对图片和标注文件同名只是扩展名不同。常见做法是在项目根目录下建一个干净的数据目录train 和 val 分开绝不把原始图片直接扔给训练脚本。# 在项目根目录下创建YOLOv8约定的数据目录 mkdir -p corn_leaf_dataset/train/images mkdir -p corn_leaf_dataset/train/labels mkdir -p corn_leaf_dataset/val/images mkdir -p corn_leaf_dataset/val/labelsimages 目录放 .jpg 原图labels 目录放同名的 .txt 标注文件。标注文件每一行是「类别编号 cx cy w h」其中 cx、cy、w、h 都是归一化到 01 的坐标类别编号从 0 开始。如果你的标注工具输出的是 Pascal VOC 的 xml 格式需要先转成 YOLO 格式再放进 labels 目录直接混用会导致训练时找不到标注报错还不明显。数据集的类别配置文件建议放在项目根目录下内容大致是path 指向数据目录绝对路径train 和 val 是相对 path 的子目录names 按类别编号列出病害名。注意 names 的顺序必须和标注文件里的编号严格一致否则模型训练不会报错但界面画框时会把大斑病标成锈病这类问题排查起来很费时间。2.3 划分脚本与核对按文件随机切分只是及格线很多教程会给一个 8:2 随机划分脚本但随机划分有一个隐患如果同一块田拍摄的图片被同时切进 train 和 val模型等于提前看到了验证集答案验证 mAP 虚高。我一般会先给图片按拍摄批次分组再按组划分。下面这个脚本适合文件量不大、已经按来源分目录的场景先做一个简单随机版重点是把「没标注的图跳过」和「可复现」这两件事做对。import os import random from shutil import copy2 random.seed(2024) # 固定随机种子保证每次划分结果一致 src_img corn_raw/images src_lbl corn_raw/labels train_img corn_leaf_dataset/train/images train_lbl corn_leaf_dataset/train/labels val_img corn_leaf_dataset/val/images val_lbl corn_leaf_dataset/val/labels images [f for f in os.listdir(src_img) if f.lower().endswith(.jpg)] random.shuffle(images) val_num int(len(images) * 0.2) # 预留20%做验证集 for i, img in enumerate(images): name os.path.splitext(img)[0] label name .txt if not os.path.exists(os.path.join(src_lbl, label)): # 没有标注的图直接跳过防止训练时读取不到标签而中断 print(skip no label:, img) continue if i val_num: copy2(os.path.join(src_img, img), os.path.join(val_img, img)) copy2(os.path.join(src_lbl, label), os.path.join(val_lbl, label)) else: copy2(os.path.join(src_img, img), os.path.join(train_img, img)) copy2(os.path.join(src_lbl, label), os.path.join(train_lbl, label)) print(train images:, len(os.listdir(train_img))) print(val images:, len(os.listdir(val_img)))脚本逻辑不难关键是 random.seed(2024) 让每次运行结果一致方便复现实验跳过无标注图片能避免训练器中途报错8:2 是训练集与验证集的常见比例数据集只有 1500 张时验证集 300 张足够看出模型有没有过拟合。划分完之后一定要做一次核对统计每个类别在 train 和 val 中的标签行数防止某类病害在验证集里只有一两张图导致验证指标波动很大。3. 训练玉米叶病害权重最小命令与参数取舍3.1 最小训练命令别一上来就调 YOLOv8m数据目录准备好之后训练命令很简单。我一般先用 YOLOv8n 跑一版确认数据链路没问题再换 YOLOv8s 做正式实验。直接上大模型在 1500 张图的小数据集上很容易过拟合训练速度慢界面端推理也卡。yolo detect train \ modelyolov8n.pt \ datacorn_leaf_dataset.yaml \ epochs120 \ imgsz640 \ batch16 \ patience20 \ projectruns \ nameleaf逐个看参数modelyolov8n.pt 表示加载预训练权重起步不是从零训练这一点对 1500 张图来说非常关键预训练特征能大幅缩短收敛时间imgsz640 是训练输入尺寸玉米叶病斑普遍偏小后面可以试着提到 768但先跑 640 做基线batch16 受显存限制如果训练时报 CUDA out of memory就把 batch 降到 8patience20 表示连续 20 个 epoch 验证集指标没有提升就早停配合 120 的 epochs 使用。跑完一轮后去 runs/leaf 目录看 weights 文件夹里面有 best.pt 和 last.pt。界面加载哪个权重这个问题很常见默认加载 best.pt它是在验证集上指标最好的那一轮。last.pt 是最后一个 epoch 的模型可能已经过拟合。3.2 看训练日志别只盯 mAP50 一个数训练结束后runs/leaf 目录下会有 results.csv、confusion_matrix.png、results.png 等文件。results.png 里的 loss 曲线可以判断模型有没有收敛如果训练 loss 一直降但验证 loss 在后期反弹就是过拟合信号。confusion_matrix.png 更能说明问题玉米叶病害类别之间误检很常见尤其小斑病和灰斑病视觉特征接近如果混淆矩阵里这两个类别互相串说明标注边界不清晰这时候调参数没有用得回头统一标注规范。mAP50 达到 0.7 以上在 1500 张图这个体量下算是合格线达到 0.8 以上就是不错的模型。如果只有 0.5 左右先排除三类问题标注框是否贴合病斑、训练集和验证集是否混了同源图片、类别数量是否均衡。3.3 模型尺寸选择界面端到底选 n 还是 s训练时常见的纠结是选 YOLOv8n、s 还是 m。做一个简单的对比表方便新手判断模型推理速度显存占用小病斑表现适用场景YOLOv8n最快最低一般快速验证、低配机器YOLOv8s较快较低较好桌面界面端首选YOLOv8m较慢较高好病斑极小且机器配置高我自己的习惯是先 n 后 s。n 用来走通数据集和训练流程s 做正式权重。如果验证集结果显示小病斑漏检明显再尝试把 imgsz 从 640 提到 768而不是直接上 m 或 l。因为小病斑漏检往往是输入分辨率不够导致病斑在特征图上只占几个像素提高分辨率比增大模型参数量更直接。4. PyQt界面接权重三块代码把一个模型变成工具4.1 界面结构一个窗口一张图一个按钮就够了PyQt 界面不需要做得多复杂常见结构是上方图片显示区下方一个「选择图片并检测」按钮再加一个置信度阈值滑条。用纯代码写布局比 Qt Designer 生成的 ui 文件更容易维护。下面给一个最小可用的窗口骨架重点是把权重加载和检测逻辑写清楚。4.2 加载权重跑推理坐标、画框、显示一鼓作气核心代码分三件事加载 best.pt、对图片执行 predict、把检测框画到原图上再转成 QPixmap 显示。注意 PyQt 里显示图片用的是 QImage而模型推理用的 OpenCV 读出来的是 BGR 格式中间要做一次 rgbSwapped。import sys import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QSlider, QVBoxLayout, QWidget) from PyQt5.QtGui import QPixmap, QImage from PyQt5.QtCore import Qt from ultralytics import YOLO class LeafWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(玉米叶病害检测) self.resize(900, 700) # 权重路径建议写绝对路径避免打包后相对路径失效 self.model YOLO(runs/leaf/weights/best.pt) self.conf 0.25 self.label QLabel(请选择图片) self.label.setAlignment(Qt.AlignCenter) 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) def on_detect(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.jpg *.png)) if not path: return img_bgr cv2.imread(path) if img_bgr is None: self.label.setText(图片读取失败) return results self.model.predict(sourceimg_bgr, confself.conf) for r in results: boxes r.boxes.xyxy.cpu().numpy() confs r.boxes.conf.cpu().numpy() clss r.boxes.cls.cpu().numpy().astype(int) for box, conf, cls in zip(boxes, confs, clss): x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(img_bgr, (x1, y1), (x2, y2), (0, 255, 0), 2) label_text f{self.model.names[cls]} {conf:.2f} cv2.putText(img_bgr, label_text, (x1, y1 - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) h, w, ch img_bgr.shape qimg QImage(img_bgr.data, w, h, ch * w, QImage.Format_RGB888).rgbSwapped() self.label.setPixmap(QPixmap.fromImage(qimg).scaled( self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) if __name__ __main__: app QApplication(sys.argv) win LeafWindow() win.show() sys.exit(app.exec_())这段代码里有个细节容易踩坑cv2.imread 读不到图片时返回 None需要先判断再继续否则下一行 predict 会直接抛异常。另一个细节是画框用的坐标来自 r.boxes.xyxy它是浮点型的 tensor转成 int 再给 cv2.rectangle 用否则 OpenCV 会报类型错误。self.model.predict 默认入参可以传图片路径也可以传 numpy 数组传数组的好处是路径含中文也不影响读取但前提是图片本身已经用 cv2.imread 或下面要讲的替代方案读进来了。4.3 用QThread把推理从主线程挪走界面卡死是新手必踩的坑上面的代码在图片分辨率较高时会有明显卡顿因为 predict 是阻塞操作主线程被占住窗口拖不动、按钮点不了。常见做法是把检测放到 QThread 里跑跑完通过信号把结果发回主线程更新界面。这样做的本质是让 UI 事件循环保持响应。from PyQt5.QtCore import QThread, pyqtSignal class DetectWorker(QThread): result_ready pyqtSignal(object) def __init__(self, model, path, conf): super().__init__() self.model model self.path path self.conf conf def run(self): # 子线程里只做模型推理和画框不碰任何界面控件 img_bgr cv2.imread(self.path) if img_bgr is None: self.result_ready.emit(None) return results self.model.predict(sourceimg_bgr, confself.conf) self.result_ready.emit((img_bgr, results))主线程里这样使用点击按钮时创建 worker连接 result_ready 信号到更新界面的槽函数worker 结束后让它自我销毁。注意不要在 run 方法里调用 self.label.setTextQt 的界面控件只能在主线程操作跨线程访问会造成程序随机崩溃而且不是每次都会崩一旦出现非常难排查。信号槽机制把结果传回主线程才是 PyQt 多线程的正确写法。5. 避坑玉米叶病害检测里最容易翻车的5个位置5.1 病斑太小导致漏检一堆现象验证集 mAP 看起来还行界面里打开一张大图肉眼能看到的病斑只框出来一半。 原因标注时把过小的斑点也标了模型在 640 分辨率下根本看不清楚这些目标或者标注规范不统一同一个病斑有人框叶片边缘有人框病斑中心。 解决统一标注最小框规则比如病斑短边小于 8 像素就并入邻近大框或者忽略再把训练 imgsz 从 640 提到 768 或 1024。这两个动作比换大模型更有效。5.2 验证集和训练集同源mAP虚高现象训练时报到 0.92 的 mAP50拿去新田块测试只有 0.5。 原因随机划分时同一批拍摄的图片同时进入了 train 和 val模型记住了拍摄环境。 解决按拍摄批次或地块划分数据集先人工把图片分组再做划分。1500 张图人工分组很快不要省这一步。血的教训是某项目做了三次实验才发现验证集里全是同一块田的图片白白浪费了两周调参时间。5.3 中文路径图片读不出来现象界面选择图片后提示「图片读取失败」把图片路径改成英文或纯数字又能正常显示。 原因cv2.imread 在 Windows 和某些 Linux 环境下不支持中文路径这是 OpenCV 的老毛病。 解决用 np.fromfile 配合 cv2.imdecode 替代 cv2.imreaddef cv_imread(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)这个方法读中文路径的图片很稳。把 on_detect 里的 cv2.imread 替换成这个函数基本就根治了。如果界面后续要支持拖拽文件还要注意拖拽路径可能带 file:// 前缀需要先去掉。5.4 子线程直接更新界面控件导致随机崩溃现象程序运行时不定时崩溃崩溃堆栈指向 QLabel 或 QPushButton 相关代码重试几次又正常。 原因在 DetectWorker.run 里直接操作了窗口控件违背 Qt 的单线程 UI 模型。 解决子线程只做推理界面更新全部通过信号槽回到主线程完成。如果一次检测要处理很多张图还可以把图片列表传给 worker让它在子线程里批量推理每处理完一张 emit 一次结果。5.5 best.pt 和 last.pt 选错现象界面打包出来给别人用对方反馈检测效果比训练日志里看到的差很多。 原因有的人为了省事直接加载 last.pt而 last.pt 可能是过拟合后的状态也可能早停发生在验证指标最高点之后。 解决默认用 best.pt。如果训练日志显示验证指标还在上升但 patience 被触发提前停了就把 patience 从 20 增加到 40 重新训练。还有一种方法在 projects/leaf/weights 目录里同时保留两个权重界面里做成下拉框方便现场对比。6. 验证这一步别省用不同拍摄条件下的照片验收界面训练指标和界面能跑是两回事。我之前交付过一个界面自己在工位上测试一切正常结果到现场对方用手机逆光拍的照片喂进去漏检率直接翻倍。后来养成了一个习惯把模型最终验收放在「验证集之外的实拍照片」上模拟真实使用场景。具体操作是准备三组照片每组十张正常光照、弱光、逆光每张都手动数一遍病斑数量再跑界面统计检出数量计算漏检率。这个数字比 mAP 更贴近交付体验。顺手还有一个很实用的小技巧在界面上加一个「保存结果」按钮。点击后把当前画完框的图片按原路径保存同时导出一份 CSV内容是每个框的类别、置信度、坐标。代码如下只有几行但现场验收时特别有用import csv def export_result(boxes, confs, clss, out_csvresult.csv): with open(out_csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([class, conf, x1, y1, x2, y2]) for box, conf, cls in zip(boxes, confs, clss): x1, y1, x2, y2 [int(v) for v in box] writer.writerow([self.model.names[cls], f{conf:.2f}, x1, y1, x2, y2])导出 CSV 的意义在于现场人员不用对着屏幕截图汇报直接把检测结果表格发过来你远程就能定位是漏检还是误检。如果后续要做模型迭代这批导出结果还能当初步标注参考省掉一部分人工标注时间。关于推理速度如果界面需要接摄像头实时预览YOLOv8s 在 CPU 上会比较吃力。先测一下单张推理耗时再考虑是否用 ONNX 格式导出一版模型加速。不过量化加速有时会掉精度我的习惯是先把完整权重的界面跑顺确认效果达到预期后再做加速优化顺序反了会很难定位问题。玉米叶病害检测这个方向真正值不值得投入不在于用多新的模型而在于把数据划分、权重选择、界面交互这几件事做到位。希望帮到你。本文还有配套的精品资源点击获取