基于YOLO26的葡萄叶病害检测系统

导读

本文基于YOLO26目标检测模型,开发了一套葡萄叶病害自动检测系统,可精准识别葡萄叶片4类常见病害状态。项目包含PyQt5 GUI桌面端与Flask Web网页端双实现,支持单张图片检测、文件夹批量检测、视频检测、实时摄像头检测及结果保存五大核心功能,帮助农业种植人员快速识别葡萄叶病害,提升病害防治效率。本项目提供完整训练代码、预训练权重、UI源码和数据集配置说明,可直接部署使用,也可基于该项目二次开发,扩展至其他农业病害检测场景。


一、软件功能演示

本系统提供PyQt5 GUI桌面端和Flask Web网页端两种使用方式,核心功能包含以下5类:
演示视频

1. 单张图片检测

操作流程:点击📁 模型选择加载训练好的YOLO权重文件 → 点击🖼️ 图片检测选择本地葡萄叶图片 → 系统自动完成推理并绘制检测框,同步显示类别与置信度。

检测完成后,系统会自动统计检测结果并生成明细表格:

序号 病害类别 置信度 检测框坐标 (xmin, ymin, xmax, ymax)
1 Grape__BlackRot 0.89 (42, 61, 328, 297)
2 Grape__BlackRot 0.76 (152, 120, 389, 315)
3 Grape__Healthy 0.96 (25, 15, 410, 425)

检测结果统计:

  • 总检测数:3
  • 检测类别数:2
  • 主要病害:Grape__BlackRot

2. 文件夹批量检测

操作流程:将待检测的多张图片放入同一文件夹 → 点击📂 文件夹检测选择目标文件夹 → 系统批量推理所有图片,自动将检测结果保存至输出目录,并生成汇总检测报告。

批量检测适合对大量田间采集的葡萄叶样本进行统一分析,可大幅节省人工逐个检测的时间成本。

3. 视频检测

操作流程:点击🎬 视频检测选择本地葡萄叶病害采集视频 → 系统逐帧进行推理,实时在界面显示检测结果并绘制检测框与类别信息,支持暂停、停止检测操作。

4. 实时摄像头检测

操作流程:连接本地摄像头后,点击📹 实时摄像头 → 系统实时读取摄像头画面并逐帧检测,可对田间葡萄叶进行实地实时病害检测,点击⏹️ 停止检测即可结束任务。

5. 结果保存

所有检测结果均支持保存:

  • 图片检测:点击💾 保存结果,可将带检测框的结果图片保存至输出目录
  • 视频/摄像头检测:系统自动将检测后的视频保存为MP4文件至输出目录
  • 批量检测:自动导出包含所有检测结果的CSV报告文件

二、环境搭建

本项目基于PyTorch框架开发,提供两种环境搭建方式:

1. pip安装方式

首先手动安装PyTorch和torchvision,可从官方下载对应版本的whl包:

  • torch下载地址:https://download.pytorch.org/whl/torch/
  • torchvision下载地址:https://download.pytorch.org/whl/torchvision/

安装完成PyTorch后,切换至项目根目录,执行以下命令安装其余依赖:

pip install -r requirements.txt

项目依赖包清单如下:

aiohttp==3.14.1
albumentations==2.0.8
ax==0.52.0
clip==0.2.0
comet_ml==3.58.3
ConfigSpace==1.2.2
coremltools==9.0
dvclive==3.49.1
dx_com==2.3.0
faster_coco_eval==1.7.2
Flask==3.1.3
hub_sdk==0.0.24
ipython==8.12.3
lap==0.5.13
matplotlib==3.11.0
mct_quantizers==1.7.0
MNN==3.6.0
mobileclip==0.0.1
model_compression_toolkit==2.6.0
moviepy==2.2.1

2. Conda安装方式

若使用Anaconda管理环境,可通过以下步骤创建并配置环境:

# 创建新的Python环境
conda create -n grape_disease python=3.10
conda activate grape_disease

# 安装PyTorch(以CUDA 11.8为例)
conda install pytorch torchvision pytorch-cuda=11.8 -c pytorch -c nvidia

# 安装其余依赖
pip install -r requirements.txt

三、模型训练、评估、预测

数据集上传说明

1. 数据集格式要求

本项目采用标准YOLO数据集格式,目录结构如下:

grape_disease_dataset/
├── images/
│   ├── train/       # 训练集图片
│   │   ├── img1.jpg
│   │   ├── img2.jpg
│   │   └── ...
│   ├── val/         # 验证集图片
│   │   ├── img3.jpg
│   │   └── ...
│   └── test/        # 测试集图片(可选)
│       ├── img4.jpg
│       └── ...
└── labels/
    ├── train/       # 训练集标签
    │   ├── img1.txt
    │   ├── img2.txt
    │   └── ...
    ├── val/         # 验证集标签
    │   ├── img3.txt
    │   └── ...
    └── test/        # 测试集标签(可选)
        ├── img4.txt
        └── ...
2. YOLO格式标签说明

YOLO格式标签为每张图片对应一个同名TXT文件,每一行表示一个目标标注,格式为:

<class_id> <x_center> <y_center> <width> <height>

所有坐标均为归一化坐标(取值范围0~1),示例:

0 0.428 0.357 0.512 0.468
2 0.125 0.214 0.236 0.389

转换公式:

  • x_center = (xmin + xmax) / (2 * image_width)
  • y_center = (ymin + ymax) / (2 * image_height)
  • width = (xmax - xmin) / image_width
  • height = (ymax - ymin) / image_height
3. 从VOC格式转换(XML转YOLO TXT)

若数据集标注为VOC格式(XML文件),可使用项目提供的xml2txt.py脚本完成格式转换,完整代码如下:

import os
import xml.etree.ElementTree as ET

# 定义类别顺序,对应本项目葡萄叶病害类别
categories = ['Grape__BlackRot', 'Grape__Esca', 'Grape__Healthy', 'Grape__LeafBlight']
category_to_index = {category: index for index, category in enumerate(categories)}

# 定义输入文件夹和输出文件夹,替换为实际路径
input_folder = r'./VOCData/Annotations'  # XML文件夹路径
output_folder = r'./dataset/labels'       # 输出TXT文件夹路径

# 确保输出文件夹存在
os.makedirs(output_folder, exist_ok=True)

# 遍历输入文件夹中的所有XML文件
for filename in os.listdir(input_folder):
    if filename.endswith('.xml'):
        xml_path = os.path.join(input_folder, filename)
        # 解析XML文件
        tree = ET.parse(xml_path)
        root = tree.getroot()
        
        # 提取图像尺寸
        size = root.find('size')
        width = int(size.find('width').text)
        height = int(size.find('height').text)
        
        # 存储标签内容
        label_lines = []
        
        # 遍历XML中的object标签
        for obj in root.findall('object'):
            name = obj.find('name').text
            if name in category_to_index:
                category_index = category_to_index[name]
            else:
                continue  # 若name不在指定类别中,跳过该object
            
            bndbox = obj.find('bndbox')
            xmin = int(bndbox.find('xmin').text)
            ymin = int(bndbox.find('ymin').text)
            xmax = int(bndbox.find('xmax').text)
            ymax = int(bndbox.find('ymax').text)
            
            # 转换为中心点坐标和宽高(归一化)
            x_center = (xmin + xmax) / 2.0 / width
            y_center = (ymin + ymax) / 2.0 / height
            w = (xmax - xmin) / width
            h = (ymax - ymin) / height
            
            label_lines.append(f"{category_index} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}")
        
        # 保存标签文件
        txt_name = os.path.splitext(filename)[0] + '.txt'
        txt_path = os.path.join(output_folder, txt_name)
        with open(txt_path, 'w') as f:
            f.write('\n'.join(label_lines))

print("格式转换完成!所有标签已保存到输出目录。")
4. 数据集划分脚本

格式转换完成后,使用SplitDataset.py将数据集划分为训练集、验证集和测试集,默认比例为8:1:1,完整代码如下:

import os
import shutil
import random

# 设置随机种子保证划分可复现
random.seed(42)

def split_data(image_root, label_root, output_root, train_rate=0.8, val_rate=0.1, test_rate=0.1):
    # 获取所有图片和标签文件名
    images = os.listdir(image_root)
    labels = os.listdir(label_root)
    
    # 匹配图片和标签(通过文件名前缀匹配)
    images_no_ext = {os.path.splitext(img)[0]: img for img in images}
    labels_no_ext = {os.path.splitext(lbl)[0]: lbl for lbl in labels}
    
    # 只保留同时有图片和标签的数据
    matched_data = [
        (img_id, images_no_ext[img_id], labels_no_ext[img_id]) 
        for img_id in images_no_ext if img_id in labels_no_ext
    ]
    
    # 输出未匹配提示
    unmatched_images = [img for img in images_no_ext if img not in labels_no_ext]
    unmatched_labels = [lbl for lbl in labels_no_ext if lbl not in images_no_ext]
    if unmatched_images:
        print("未匹配标签的图片:")
        for img in unmatched_images:
            print(f"  {images_no_ext[img]}")
    if unmatched_labels:
        print("未匹配图片的标签:")
        for lbl in unmatched_labels:
            print(f"  {labels_no_ext[lbl]}")
    
    # 打乱数据顺序
    random.shuffle(matched_data)
    total = len(matched_data)
    
    # 划分数据集
    train_end = int(train_rate * total)
    val_end = train_end + int(val_rate * total)
    train_data = matched_data[:train_end]
    val_data = matched_data[train_end:val_end]
    test_data = matched_data[val_end:]
    
    # 处理训练集
    for img_id, img_file, label_file in train_data:
        old_img_path = os.path.join(image_root, img_file)
        old_label_path = os.path.join(label_root, label_file)
        new_img_dir = os.path.join(output_root, 'train', 'images')
        new_label_dir = os.path.join(output_root, 'train', 'labels')
        os.makedirs(new_img_dir, exist_ok=True)
        os.makedirs(new_label_dir, exist_ok=True)
        shutil.copy(old_img_path, os.path.join(new_img_dir, img_file))
        shutil.copy(old_label_path, os.path.join(new_label_dir, label_file))
    
    # 处理验证集
    for img_id, img_file, label_file in val_data:
        old_img_path = os.path.join(image_root, img_file)
        old_label_path = os.path.join(label_root, label_file)
        new_img_dir = os.path.join(output_root, 'val', 'images')
        new_label_dir = os.path.join(output_root, 'val', 'labels')
        os.makedirs(new_img_dir, exist_ok=True)
        os.makedirs(new_label_dir, exist_ok=True)
        shutil.copy(old_img_path, os.path.join(new_img_dir, img_file))
        shutil.copy(old_label_path, os.path.join(new_label_dir, label_file))
    
    # 处理测试集
    for img_id, img_file, label_file in test_data:
        old_img_path = os.path.join(image_root, img_file)
        old_label_path = os.path.join(label_root, label_file)
        new_img_dir = os.path.join(output_root, 'test', 'images')
        new_label_dir = os.path.join(output_root, 'test', 'labels')
        os.makedirs(new_img_dir, exist_ok=True)
        os.makedirs(new_label_dir, exist_ok=True)
        shutil.copy(old_img_path, os.path.join(new_img_dir, img_file))
        shutil.copy(old_label_path, os.path.join(new_label_dir, label_file))
    
    print(f"数据集划分完成!总样本数: {total}, 训练集: {len(train_data)}, 验证集: {len(val_data)}, 测试集: {len(test_data)}")

if __name__ == '__main__':
    # 修改为实际路径
    image_root = r'./VOCData/JPEGImages'
    label_root = r'./VOCData/labels'
    output_root = r'./VOCData/VOdevkit'
    split_data(image_root, label_root, output_root)
5. 配置数据集yaml文件

划分完成后,创建数据集配置yaml文件(本项目为dataset.yaml),内容如下:

# 数据集根路径
path: ./VOCData/VOdevkit  # 替换为你的数据集实际根目录

# 训练/验证/测试集相对路径
train: train/images  # 训练集图片路径
val: val/images      # 验证集图片路径
test: test/images    # 测试集图片路径(可选)

# 类别数量
nc: 4

# 类别名称
names:
  0: Grape__BlackRot
  1: Grape__Esca
  2: Grape__Healthy
  3: Grape__LeafBlight

模型训练

数据集准备完成后,执行训练脚本train.py,核心代码如下:

from ultralytics import YOLO

if __name__ == '__main__':
    # 加载YOLO26n预训练模型,追求更高精度可更换为yolo26s.pt
    model = YOLO('yolo26n.pt')
    
    # 开始训练
    results = model.train(
        data=r'./VOCData/VOdevkit/dataset.yaml',
        epochs=50,                # 最大训练轮数,配合早停自动终止
        patience=10,              # 连续10轮验证集指标无提升则触发早停
        batch=24,                 # 批次大小:12G显存建议设为24,8G显存建议改为16
        imgsz=512,                # 输入图片尺寸
        workers=4,                # 数据加载线程数:Windows建议4,Linux建议8
        device=0,                 # GPU设备编号,CPU训练可改为device='cpu'
        optimizer='SGD',          # 优化器选择,SGD泛化性更优
        amp=True,                 # 开启自动混合精度训练,减少显存占用
        cache='disk',             # 启用磁盘缓存数据,避免内存溢出
        save_period=11,           # 每11轮保存一次权重文件
        scale=0.3,                # 多尺度训练增强,提升模型鲁棒性
        rect=False,               # 关闭矩形训练,配合多尺度训练使用
        mosaic=1.0,               # 开启Mosaic数据增强
    )

关键训练参数说明:

参数 说明 推荐值
epochs 最大训练轮数 50
patience 早停触发阈值(连续N轮验证集指标无提升则终止训练) 10
batch 批次大小(需根据显存容量调整) 16~24
imgsz 输入图片统一尺寸 512
device 训练设备(GPU编号或CPU) 0(GPU)/ ‘cpu’(CPU)
amp 是否开启自动混合精度训练(可减少显存占用) True
cache 数据集缓存方式(disk为磁盘缓存,可避免内存不足) ‘disk’

执行训练命令:

python train.py

模型评估

训练完成后,使用val.py对最优权重进行评估,核心代码如下:

from ultralytics import YOLO
import numpy as np
from datetime import datetime

# 加载训练完成的最优权重
model = YOLO(r"./runs/detect/train/weights/best.pt")

# 在验证集上执行评估
metrics = model.val(data=r"./VOCData/VOdevkit/dataset.yaml")

# 计算平均精确率和召回率
mean_p = np.mean(metrics.box.p)
mean_r = np.mean(metrics.box.r)

# 整理评估结果
content = []
content.append("="*60)
content.append(f"验证时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
content.append("="*60)
content.append(f"整体 mAP50:        {metrics.box.map50:.4f}")
content.append(f"整体 mAP50-95:     {metrics.box.map:.4f}")
content.append(f"平均 Precision:    {mean_p:.4f}")
content.append(f"平均 Recall:       {mean_r:.4f}")
content.append("-"*60)
content.append(f"{'类别':<16}{'Precision':<12}{'Recall':<12}{'AP50':<12}")
content.append("-"*60)

class_names = ['Grape__BlackRot', 'Grape__Esca', 'Grape__Healthy', 'Grape__LeafBlight']
for i, name in enumerate(class_names):
    ap50 = metrics.box.ap50[i]
    p = metrics.box.p[i]
    r = metrics.box.r[i]
    content.append(f"{name:<16}{p:.4f}{'':<4}{r:.4f}{'':<4}{ap50:.4f}")

# 保存评估结果至文件
with open("val_result.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(content))

# 打印评估结果
print("\n".join(content))

训练结果默认保存在runs/detect/train/目录下,核心输出文件说明:

文件/目录 说明
weights/best.pt 验证集表现最优的权重文件
weights/last.pt 最后一轮训练的权重文件
weights/epoch*.pt 指定训练轮次的权重文件
args.yaml 训练参数配置记录
results.csv 每轮训练的指标数据记录
results.png 训练损失曲线、指标变化曲线图
confusion_matrix.png 混淆矩阵可视化图
confusion_matrix_normalized.png 归一化混淆矩阵可视化图
BoxPR_curve.png 精确率-召回率(PR)曲线图
BoxF1_curve.png F1值变化曲线图
labels.jpg 训练集标签分布可视化图
train_batch*.jpg 训练批次样本数据增强效果可视化图
val_batch*_labels.jpg 验证集标注数据可视化图
val_batch*_pred.jpg 验证集预测结果与标注对比可视化图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

执行评估命令:

python val.py

模型预测

本项目支持多种预测方式,单张图片预测核心代码如下:

from ultralytics import YOLO
import os

# 加载训练好的最优模型
root = os.getcwd()
model = YOLO(rf'{root}/runs/detect/train/weights/best.pt')

# 设置待检测图片路径
source = r'./img_video/test_imgs/test_grape.jpg'

# 执行推理
results = model.predict(source, save=True, conf=0.45, iou=0.25)

# 弹窗展示检测结果
results[0].show()

# 保存检测结果到指定路径
results[0].save(filename=r"./results/test_result.jpg")

执行预测命令:

python predict.py

除单张图片预测外,系统还支持文件夹批量预测、视频预测、摄像头实时预测,所有功能已集成至UI界面,可直接通过界面操作使用。


四、项目目录结构

基于YOLO26葡萄叶病害检测系统/
├── README.md                    # 项目说明文档
├── requirements.txt             # Python依赖列表
├── yolo26n.pt                   # YOLO26n预训练权重
├── train.py                     # 模型训练入口脚本
├── predict.py                   # 模型推理预测脚本
├── val.py                       # 模型验证评估脚本
├── UI/                         # 界面相关代码
│   ├── web.py                  # Flask Web后端主程序
│   ├── GUI.py                  # PyQt5 GUI界面逻辑
│   ├── config.yaml             # UI全局配置文件
│   ├── user.txt                # 用户登录信息存储文件
│   ├── templates/
│   │   ├── index.html          # Web首页
│   │   └── login.html          # Web登录页
│   └── models/
│       └── best.pt             # UI使用的最优模型权重
├── img_video/                  # 测试素材目录
│   ├── test_imgs/              # 测试图片文件夹
│   └── test_MP4.mp4            # 测试视频文件
├── results/                    # 推理结果输出目录
├── runs/                       # 训练输出目录
│   └── detect/train/
│       ├── args.yaml           # 训练参数配置记录
│       ├── results.csv         # 训练指标数据记录
│       ├── results.png         # 训练指标变化曲线图
│       ├── confusion_matrix.png # 混淆矩阵可视化图
│       ├── BoxPR_curve.png     # PR曲线图
│       ├── labels.jpg          # 训练集标签分布可视化图
│       └── weights/
│           ├── best.pt         # 最优权重文件
│           └── last.pt         # 最后一轮训练权重文件
├── VOCData/                    # 数据集处理工具目录
│   ├── SplitDataset.py         # 数据集划分脚本
│   ├── ViewCategory.py         # VOC标注类别统计脚本
│   ├── xml2txt.py              # VOC XML转YOLO TXT格式脚本
│   ├── pic to mp4.py           # 图片转视频脚本
│   └── VOdevkit/
│       ├── dataset.yaml        # 数据集配置文件
│       └── 数据集单独下载.txt   # 数据集下载说明文件
└── ultralytics/                # YOLO框架核心源码目录
    ├── cfg/                    # 模型和数据集配置文件
    ├── data/                   # 数据加载与增强模块
    ├── engine/                 # 训练/预测/验证引擎模块
    └── models/                 # 模型定义模块

五、项目README摘要

本项目是基于YOLO26目标检测模型开发的葡萄叶病害检测系统,核心特性如下:

  1. 支持4类葡萄叶病害识别:Black Rot、Esca、Healthy、Leaf Blight
  2. 提供PyQt5 GUI桌面端和Flask Web网页端双界面实现
  3. 支持5种检测模式:单张图片检测、文件夹批量检测、视频检测、实时摄像头检测、结果保存导出
  4. 提供完整的数据处理脚本、训练代码、评估代码,支持用户基于自有数据集重新训练
  5. 所有UI文字、参数均可通过config.yaml全局配置,无需修改代码即可自定义系统

项目启动方式:

  • 启动GUI桌面端:cd UI && python GUI.py
  • 启动Web网页端:cd UI && python web.py

文末互动区

如果您觉得本项目有帮助,欢迎点击【分享】【推荐】支持我们!

如需获取完整项目代码与数据集, 回复**「基于YOLO26葡萄叶病害」**获取。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐