news 2026/6/10 13:03:41

基于YOLO系列算法的植物叶片病害智能识别系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLO系列算法的植物叶片病害智能识别系统

摘要

植物病害是农业生产中面临的主要挑战之一,传统的人工识别方法效率低下且依赖专家经验。本文介绍了一个基于深度学习的植物叶片病害识别系统,该系统整合了YOLOv5、YOLOv8和YOLOv10三种先进的物体检测算法,并开发了用户友好的PySide6图形界面。系统能够实时检测和识别多种植物叶片病害,为农业生产者提供快速、准确的病害诊断工具。本文详细阐述了系统架构、算法原理、数据集处理、模型训练策略以及图形界面的实现,并提供了完整的代码实现。

关键词:深度学习;植物病害识别;YOLO;PySide6;目标检测;智能农业

1. 引言

1.1 研究背景与意义

全球农业面临着日益严重的病害威胁,据联合国粮农组织统计,每年因植物病害造成的农作物损失高达20-40%。传统的病害识别方法主要依赖农业专家的肉眼观察和经验判断,这种方法存在效率低、主观性强、成本高等问题。随着深度学习技术的发展,基于计算机视觉的自动病害识别系统成为解决这一问题的有效途径。

1.2 相关研究现状

近年来,基于深度学习的植物病害识别研究取得了显著进展。早期研究多采用传统的图像处理方法,如颜色特征提取、纹理分析和形态学操作。随着卷积神经网络(CNN)的发展,基于分类的网络如AlexNet、VGG、ResNet等被广泛应用于病害识别。然而,这些方法通常只能判断图像中是否存在病害,无法定位病害的具体位置。

目标检测算法的发展为解决这一问题提供了新的思路。YOLO(You Only Look Once)系列算法以其高效的单阶段检测特性,在保持较高检测精度的同时实现了实时性能,特别适合于农业场景中的病害识别应用。

1.3 本文贡献

本文的主要贡献包括:

  1. 构建了一个整合YOLOv5、YOLOv8和YOLOv10三种算法的植物叶片病害识别系统

  2. 开发了基于PySide6的用户友好图形界面

  3. 提供了完整的训练代码和数据集处理方法

  4. 实现了三种算法的性能对比分析

  5. 设计了灵活的系统架构,支持算法切换和参数调整

2. 系统架构设计

2.1 整体架构

系统采用模块化设计,主要包括以下组件:

text

植物叶片病害识别系统 ├── 数据预处理模块 │ ├── 图像增强 │ ├── 数据标注 │ └── 数据集划分 ├── 模型训练模块 │ ├── YOLOv5训练 │ ├── YOLOv8训练 │ └── YOLOv10训练 ├── 推理检测模块 │ ├── 单图像检测 │ ├── 批量检测 │ └── 实时视频检测 └── 图形界面模块 ├── 模型选择 ├── 参数配置 ├── 结果可视化 └── 报告生成

2.2 技术选型

  • 深度学习框架:PyTorch 1.12+

  • 目标检测算法:YOLOv5 v6.0, YOLOv8, YOLOv10

  • 图形界面:PySide6 (Qt for Python)

  • 图像处理:OpenCV 4.5+, PIL

  • 数据标注:LabelImg/Roboflow

  • 性能优化:CUDA 11.3+ (GPU加速)

3. YOLO算法原理与实现

3.1 YOLOv5算法详解

YOLOv5采用CSPDarknet作为骨干网络,结合PANet进行特征金字塔构建。其主要创新点包括:

python

# YOLOv5模型核心组件示例 import torch import torch.nn as nn class Conv(nn.Module): """标准卷积层""" def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): super().__init__() self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) self.bn = nn.BatchNorm2d(c2) self.act = nn.SiLU() if act is True else ( act if isinstance(act, nn.Module) else nn.Identity()) def forward(self, x): return self.act(self.bn(self.conv(x))) class Bottleneck(nn.Module): """标准瓶颈层""" def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): super().__init__() c_ = int(c2 * e) self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c_, c2, 3, 1, g=g) self.add = shortcut and c1 == c2 def forward(self, x): return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) class C3(nn.Module): """CSP瓶颈层,含3个卷积""" def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): super().__init__() c_ = int(c2 * e) self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c1, c_, 1, 1) self.cv3 = Conv(2 * c_, c2, 1) self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) def forward(self, x): return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))

3.2 YOLOv8算法改进

YOLOv8在YOLOv5的基础上进行了多项改进:

  1. 新的骨干网络设计,使用C2f模块替代C3模块

  2. 无锚框(Anchor-Free)检测头设计

  3. 解耦头部结构,分别处理分类和回归任务

  4. 改进的训练策略和损失函数

python

# YOLOv8 C2f模块实现 class C2f(nn.Module): """YOLOv8的C2f模块,更丰富的特征融合""" def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): super().__init__() self.c = int(c2 * e) self.cv1 = Conv(c1, 2 * self.c, 1, 1) self.cv2 = Conv((2 + n) * self.c, c2, 1) self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) def forward(self, x): y = list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1)) # YOLOv8解耦头部 class Detect(nn.Module): """YOLOv8解耦检测头""" def __init__(self, nc=80, ch=()): super().__init__() self.nc = nc # 类别数 self.nl = len(ch) # 检测层数 self.reg_max = 16 # DFL卷积核大小 # 分类和回归分支 self.cv2 = nn.ModuleList( nn.Sequential(Conv(x, x, 3), Conv(x, x, 3), nn.Conv2d(x, 4 * self.reg_max, 1)) for x in ch) self.cv3 = nn.ModuleList( nn.Sequential(Conv(x, x, 3), Conv(x, x, 3), nn.Conv2d(x, self.nc, 1)) for x in ch) # 缩放因子 self.stride = torch.zeros(self.nl)

3.3 YOLOv10最新进展

YOLOv10是YOLO系列的最新版本,主要创新包括:

  1. 无NMS(Non-Maximum Suppression)设计,提高推理速度

  2. 双分支结构,同时优化性能和效率

  3. 增强的特征提取网络

  4. 改进的损失函数和训练策略

python

# YOLOv10核心模块 class AIFI(nn.Module): """YOLOv10的注意力机制模块""" def __init__(self, c1, c2, num_heads=8, dropout=0.0): super().__init__() self.attention = nn.MultiheadAttention(c1, num_heads, dropout=dropout) self.norm = nn.LayerNorm(c1) self.ffn = nn.Sequential( nn.Linear(c1, c1 * 4), nn.GELU(), nn.Linear(c1 * 4, c1) ) def forward(self, x): # 注意力机制前向传播 attn_output, _ = self.attention(x, x, x) x = x + attn_output x = self.norm(x) ffn_output = self.ffn(x) x = x + ffn_output return x class PSA(nn.Module): """金字塔分裂注意力模块""" def __init__(self, c1, c2): super().__init__() # 金字塔分裂注意力实现 pass

4. 数据集准备与处理

4.1 数据集收集

本文使用的植物叶片病害数据集包含以下类别:

病害类别样本数量描述
叶锈病1,200叶片表面出现锈色斑点
白粉病1,500叶片覆盖白色粉状物
褐斑病1,000叶片出现褐色坏死斑
霜霉病900叶片背面有霜状霉层
健康叶片2,000无病害症状

4.2 数据预处理流程

python

import cv2 import numpy as np from PIL import Image import albumentations as A from albumentations.pytorch import ToTensorV2 class PlantDiseaseDataset: """植物病害数据集类""" def __init__(self, image_paths, labels, transform=None, augment=False): self.image_paths = image_paths self.labels = labels self.transform = transform self.augment = augment # 数据增强管道 self.train_transform = A.Compose([ A.Resize(640, 640), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.RandomRotate90(p=0.5), A.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1, p=0.5), A.RandomBrightnessContrast(p=0.5), A.GaussNoise(p=0.3), A.CoarseDropout(max_holes=8, max_height=32, max_width=32, p=0.3), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2() ], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) # 验证/测试转换 self.val_transform = A.Compose([ A.Resize(640, 640), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2() ], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) def __len__(self): return len(self.image_paths) def __getitem__(self, idx): # 读取图像和标注 image = cv2.imread(self.image_paths[idx]) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 获取标注信息 label = self.labels[idx] bboxes = label['bboxes'] class_labels = label['labels'] # 应用数据增强 if self.augment: transformed = self.train_transform( image=image, bboxes=bboxes, class_labels=class_labels ) else: transformed = self.val_transform( image=image, bboxes=bboxes, class_labels=class_labels ) return transformed['image'], transformed['bboxes'], transformed['class_labels'] def create_dataloaders(data_dir, batch_size=16, num_workers=4): """创建数据加载器""" from sklearn.model_selection import train_test_split # 加载数据 image_paths, labels = load_annotations(data_dir) # 划分训练集、验证集、测试集 train_paths, temp_paths, train_labels, temp_labels = train_test_split( image_paths, labels, test_size=0.3, random_state=42 ) val_paths, test_paths, val_labels, test_labels = train_test_split( temp_paths, temp_labels, test_size=0.5, random_state=42 ) # 创建数据集 train_dataset = PlantDiseaseDataset( train_paths, train_labels, augment=True ) val_dataset = PlantDiseaseDataset( val_paths, val_labels, augment=False ) test_dataset = PlantDiseaseDataset( test_paths, test_labels, augment=False ) # 创建数据加载器 train_loader = DataLoader( train_dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers, collate_fn=collate_fn, pin_memory=True ) val_loader = DataLoader( val_dataset, batch_size=batch_size, shuffle=False, num_workers=num_workers, collate_fn=collate_fn, pin_memory=True ) return train_loader, val_loader, test_loader def collate_fn(batch): """批处理函数""" images, bboxes, labels = zip(*batch) images = torch.stack(images, 0) return images, bboxes, labels

5. 模型训练与优化

5.1 YOLOv5训练配置

python

# yolov5_training.py import torch import yaml from pathlib import Path class YOLOv5Trainer: def __init__(self, config_path='configs/yolov5.yaml'): with open(config_path, 'r') as f: self.config = yaml.safe_load(f) self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.setup_model() def setup_model(self): """初始化YOLOv5模型""" from models.yolo import Model # 创建模型 self.model = Model(self.config['model_cfg']).to(self.device) # 优化器设置 self.optimizer = torch.optim.SGD( self.model.parameters(), lr=self.config['lr0'], momentum=self.config['momentum'], weight_decay=self.config['weight_decay'] ) # 学习率调度器 self.scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( self.optimizer, T_max=self.config['epochs'] ) # 损失函数 self.criterion = self.model.compute_loss def train_epoch(self, train_loader): """训练一个epoch""" self.model.train() total_loss = 0 for batch_idx, (images, targets) in enumerate(train_loader): images = images.to(self.device) targets = targets.to(self.device) # 前向传播 preds = self.model(images) loss, loss_items = self.criterion(preds, targets) # 反向传播 self.optimizer.zero_grad() loss.backward() self.optimizer.step() total_loss += loss.item() if batch_idx % 50 == 0: print(f'Batch {batch_idx}, Loss: {loss.item():.4f}') return total_loss / len(train_loader) def validate(self, val_loader): """验证模型""" self.model.eval() metrics = {} with torch.no_grad(): for images, targets in val_loader: images = images.to(self.device) targets = targets.to(self.device) # 推理 preds = self.model(images) # 计算评价指标 # ... 实现mAP、Precision、Recall等计算 return metrics def train(self, train_loader, val_loader, epochs=100): """完整的训练过程""" best_map = 0 for epoch in range(epochs): print(f'Epoch {epoch+1}/{epochs}') # 训练 train_loss = self.train_epoch(train_loader) # 验证 val_metrics = self.validate(val_loader) # 学习率调整 self.scheduler.step() # 保存最佳模型 if val_metrics['mAP'] > best_map: best_map = val_metrics['mAP'] torch.save({ 'epoch': epoch, 'model_state_dict': self.model.state_dict(), 'optimizer_state_dict': self.optimizer.state_dict(), 'metrics': val_metrics }, 'best_model.pth') print(f'Train Loss: {train_loss:.4f}, ' f'Val mAP: {val_metrics["mAP"]:.4f}')

5.2 训练策略优化

python

# training_strategies.py import torch from torch.optim.lr_scheduler import LambdaLR class AdvancedTrainingStrategies: """高级训练策略""" @staticmethod def warmup_scheduler(optimizer, warmup_epochs, warmup_lr, base_lr): """热身学习率调度""" def lr_lambda(epoch): if epoch < warmup_epochs: # 线性热身 return warmup_lr + (base_lr - warmup_lr) * epoch / warmup_epochs else: # 余弦退火 return 0.5 * (1 + torch.cos(torch.tensor( (epoch - warmup_epochs) * torch.pi / (100 - warmup_epochs) ))) return LambdaLR(optimizer, lr_lambda) @staticmethod def mixed_precision_training(model, train_loader, optimizer): """混合精度训练""" from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() for images, targets in train_loader: images = images.cuda() targets = targets.cuda() optimizer.zero_grad() # 混合精度前向传播 with autocast(): predictions = model(images) loss = compute_loss(predictions, targets) # 缩放梯度并反向传播 scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() @staticmethod def label_smoothing(labels, num_classes, smoothing=0.1): """标签平滑""" confidence = 1.0 - smoothing smoothed_labels = labels * confidence + smoothing / num_classes return smoothed_labels @staticmethod def cutmix_augmentation(images, labels, alpha=1.0): """CutMix数据增强""" batch_size = images.size(0) indices = torch.randperm(batch_size) lam = np.random.beta(alpha, alpha) # 生成剪裁区域 bbx1, bby1, bbx2, bby2 = rand_bbox(images.size(), lam) # 应用CutMix images[:, :, bbx1:bbx2, bby1:bby2] = images[indices, :, bbx1:bbx2, bby1:bby2] # 调整标签 new_labels = labels.clone() new_labels = lam * labels + (1 - lam) * labels[indices] return images, new_labels

6. PySide6图形界面实现

6.1 主界面设计

python

# main_window.py import sys from PySide6.QtWidgets import * from PySide6.QtCore import * from PySide6.QtGui import * import cv2 import numpy as np class PlantDiseaseDetector(QMainWindow): """植物病害检测主窗口""" def __init__(self): super().__init__() self.setWindowTitle("智能植物叶片病害识别系统") self.setGeometry(100, 100, 1400, 800) # 初始化模型和配置 self.current_model = None self.model_type = "YOLOv8" # 默认模型 self.confidence_threshold = 0.5 self.iou_threshold = 0.45 self.init_ui() self.load_default_model() def init_ui(self): """初始化用户界面""" # 创建中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QHBoxLayout(central_widget) # 左侧控制面板 left_panel = self.create_left_panel() main_layout.addWidget(left_panel, 1) # 右侧图像显示区域 right_panel = self.create_right_panel() main_layout.addWidget(right_panel, 2) def create_left_panel(self): """创建左侧控制面板""" panel = QFrame() panel.setFrameStyle(QFrame.StyledPanel) panel.setMaximumWidth(350) layout = QVBoxLayout(panel) # 模型选择部分 model_group = QGroupBox("模型选择") model_layout = QVBoxLayout() self.model_combo = QComboBox() self.model_combo.addItems(["YOLOv5", "YOLOv8", "YOLOv10"]) self.model_combo.currentTextChanged.connect(self.on_model_changed) model_layout.addWidget(QLabel("选择模型:")) model_layout.addWidget(self.model_combo) # 模型加载按钮 self.load_model_btn = QPushButton("加载模型") self.load_model_btn.clicked.connect(self.load_model) model_layout.addWidget(self.load_model_btn) model_group.setLayout(model_layout) layout.addWidget(model_group) # 参数设置部分 params_group = QGroupBox("检测参数") params_layout = QGridLayout() # 置信度阈值 params_layout.addWidget(QLabel("置信度阈值:"), 0, 0) self.conf_slider = QSlider(Qt.Horizontal) self.conf_slider.setRange(10, 90) self.conf_slider.setValue(50) self.conf_slider.valueChanged.connect(self.update_conf_label) params_layout.addWidget(self.conf_slider, 0, 1) self.conf_label = QLabel("0.5") params_layout.addWidget(self.conf_label, 0, 2) # IOU阈值 params_layout.addWidget(QLabel("IOU阈值:"), 1, 0) self.iou_slider = QSlider(Qt.Horizontal) self.iou_slider.setRange(10, 90) self.iou_slider.setValue(45) self.iou_slider.valueChanged.connect(self.update_iou_label) params_layout.addWidget(self.iou_slider, 1, 1) self.iou_label = QLabel("0.45") params_layout.addWidget(self.iou_label, 1, 2) params_group.setLayout(params_layout) layout.addWidget(params_group) # 检测模式 mode_group = QGroupBox("检测模式") mode_layout = QVBoxLayout() self.mode_combo = QComboBox() self.mode_combo.addItems(["单图像检测", "批量检测", "实时摄像头"]) mode_layout.addWidget(self.mode_combo) # 操作按钮 self.open_image_btn = QPushButton("打开图像") self.open_image_btn.clicked.connect(self.open_image) mode_layout.addWidget(self.open_image_btn) self.open_folder_btn = QPushButton("打开文件夹") self.open_folder_btn.clicked.connect(self.open_folder) mode_layout.addWidget(self.open_folder_btn) self.camera_btn = QPushButton("开启摄像头") self.camera_btn.clicked.connect(self.toggle_camera) mode_layout.addWidget(self.camera_btn) mode_group.setLayout(mode_layout) layout.addWidget(mode_group) # 结果统计 stats_group = QGroupBox("检测统计") stats_layout = QFormLayout() self.total_detections = QLabel("0") self.disease_count = QLabel("0") self.healthy_count = QLabel("0") stats_layout.addRow("总检测数:", self.total_detections) stats_layout.addRow("病害数量:", self.disease_count) stats_layout.addRow("健康数量:", self.healthy_count) stats_group.setLayout(stats_layout) layout.addWidget(stats_group) # 导出结果按钮 self.export_btn = QPushButton("导出检测报告") self.export_btn.clicked.connect(self.export_report) layout.addWidget(self.export_btn) layout.addStretch() return panel def create_right_panel(self): """创建右侧图像显示区域""" panel = QFrame() panel.setFrameStyle(QFrame.StyledPanel) layout = QVBoxLayout(panel) # 图像显示区域 self.image_label = QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(800, 600) self.image_label.setStyleSheet("border: 2px solid #cccccc; background-color: #f0f0f0;") layout.addWidget(self.image_label) # 检测结果表格 self.results_table = QTableWidget() self.results_table.setColumnCount(5) self.results_table.setHorizontalHeaderLabels([ "病害类型", "置信度", "位置", "面积", "严重程度" ]) layout.addWidget(self.results_table) return panel def on_model_changed(self, model_name): """模型选择改变事件""" self.model_type = model_name print(f"切换到模型: {model_name}") def load_default_model(self): """加载默认模型""" # 这里加载预训练模型 pass def load_model(self): """加载用户选择的模型""" model_path = QFileDialog.getOpenFileName( self, "选择模型文件", "", "PyTorch Model (*.pt *.pth)" )[0] if model_path: try: if self.model_type == "YOLOv5": self.current_model = self.load_yolov5_model(model_path) elif self.model_type == "YOLOv8": self.current_model = self.load_yolov8_model(model_path) elif self.model_type == "YOLOv10": self.current_model = self.load_yolov10_model(model_path) QMessageBox.information(self, "成功", "模型加载成功!") except Exception as e: QMessageBox.critical(self, "错误", f"模型加载失败: {str(e)}") def open_image(self): """打开单张图像进行检测""" file_path, _ = QFileDialog.getOpenFileName( self, "选择图像", "", "图像文件 (*.jpg *.jpeg *.png *.bmp)" ) if file_path: # 读取图像 image = cv2.imread(file_path) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 显示原始图像 self.display_image(image_rgb) # 进行检测 if self.current_model: results = self.detect_image(image_rgb) self.display_results(image_rgb, results) def detect_image(self, image): """检测单张图像""" # 根据选择的模型调用不同的检测函数 if self.model_type == "YOLOv5": return self.detect_yolov5(image) elif self.model_type == "YOLOv8": return self.detect_yolov8(image) elif self.model_type == "YOLOv10": return self.detect_yolov10(image) return [] def display_image(self, image): """在界面中显示图像""" height, width, channel = image.shape bytes_per_line = 3 * width qt_image = QImage( image.data, width, height, bytes_per_line, QImage.Format_RGB888 ) # 缩放图像以适应显示区域 scaled_image = qt_image.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(QPixmap.fromImage(scaled_image)) def display_results(self, original_image, results): """显示检测结果""" # 绘制检测框和标签 image_with_boxes = original_image.copy() for result in results: label = result['label'] confidence = result['confidence'] bbox = result['bbox'] # 绘制边界框 x1, y1, x2, y2 = map(int, bbox) color = self.get_color_by_label(label) cv2.rectangle(image_with_boxes, (x1, y1), (x2, y2), color, 2) # 绘制标签 label_text = f"{label}: {confidence:.2f}" cv2.putText( image_with_boxes, label_text, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2 ) # 更新显示 self.display_image(image_with_boxes) # 更新结果表格 self.update_results_table(results) # 更新统计信息 self.update_statistics(results) def get_color_by_label(self, label): """根据标签获取颜色""" color_map = { 'leaf_rust': (255, 0, 0), # 红色 'powdery_mildew': (0, 255, 0), # 绿色 'brown_spot': (0, 0, 255), # 蓝色 'downy_mildew': (255, 255, 0), # 黄色 'healthy': (128, 128, 128) # 灰色 } return color_map.get(label, (255, 255, 255)) def update_results_table(self, results): """更新结果表格""" self.results_table.setRowCount(len(results)) for i, result in enumerate(results): label = result['label'] confidence = result['confidence'] bbox = result['bbox'] area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) severity = self.calculate_severity(bbox, area) self.results_table.setItem(i, 0, QTableWidgetItem(label)) self.results_table.setItem(i, 1, QTableWidgetItem(f"{confidence:.3f}")) self.results_table.setItem(i, 2, QTableWidgetItem(str(bbox))) self.results_table.setItem(i, 3, QTableWidgetItem(f"{area:.1f}")) self.results_table.setItem(i, 4, QTableWidgetItem(severity)) def calculate_severity(self, bbox, area): """计算病害严重程度""" if area < 1000: return "轻微" elif area < 5000: return "中等" else: return "严重" def update_statistics(self, results): """更新统计信息""" total = len(results) disease_count = sum(1 for r in results if r['label'] != 'healthy') healthy_count = total - disease_count self.total_detections.setText(str(total)) self.disease_count.setText(str(disease_count)) self.healthy_count.setText(str(healthy_count)) def update_conf_label(self, value): """更新置信度标签""" conf = value / 100.0 self.conf_label.setText(f"{conf:.2f}") self.confidence_threshold = conf def update_iou_label(self, value): """更新IOU标签""" iou = value / 100.0 self.iou_label.setText(f"{iou:.2f}") self.iou_threshold = iou def open_folder(self): """批量处理文件夹中的图像""" folder_path = QFileDialog.getExistingDirectory( self, "选择图像文件夹" ) if folder_path: # 批量处理逻辑 pass def toggle_camera(self): """切换摄像头模式""" pass def export_report(self): """导出检测报告""" pass # 模型加载和检测的具体实现 def load_yolov5_model(self, model_path): """加载YOLOv5模型""" import torch model = torch.hub.load('ultralytics/yolov5', 'custom', path=model_path, force_reload=True) model.conf = self.confidence_threshold model.iou = self.iou_threshold return model def load_yolov8_model(self, model_path): """加载YOLOv8模型""" from ultralytics import YOLO model = YOLO(model_path) return model def load_yolov10_model(self, model_path): """加载YOLOv10模型""" # YOLOv10的加载逻辑 pass def detect_yolov5(self, image): """使用YOLOv5进行检测""" results = self.current_model(image) detections = [] for *box, conf, cls in results.xyxy[0]: label = results.names[int(cls)] bbox = [float(x) for x in box] detections.append({ 'label': label, 'confidence': float(conf), 'bbox': bbox }) return detections def detect_yolov8(self, image): """使用YOLOv8进行检测""" results = self.current_model(image, conf=self.confidence_threshold, iou=self.iou_threshold) detections = [] for result in results: boxes = result.boxes for box in boxes: label = result.names[int(box.cls)] confidence = float(box.conf) bbox = box.xyxy[0].tolist() detections.append({ 'label': label, 'confidence': confidence, 'bbox': bbox }) return detections def main(): """主函数""" app = QApplication(sys.argv) # 设置应用程序样式 app.setStyle('Fusion') # 创建主窗口 window = PlantDiseaseDetector() window.show() sys.exit(app.exec()) if __name__ == '__main__': main()

6.2 实时摄像头检测

python

# camera_thread.py from PySide6.QtCore import QThread, Signal import cv2 import time class CameraThread(QThread): """摄像头线程类""" frame_ready = Signal(object) detection_ready = Signal(object, object) def __init__(self, model, conf_threshold=0.5): super().__init__() self.model = model self.conf_threshold = conf_threshold self.running = False self.cap = None def run(self): """线程运行函数""" self.cap = cv2.VideoCapture(0) self.running = True while self.running: ret, frame = self.cap.read() if not ret: break # 转换为RGB frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 发送原始帧 self.frame_ready.emit(frame_rgb) # 进行检测 if self.model: results = self.detect_frame(frame_rgb) self.detection_ready.emit(frame_rgb, results) # 控制帧率 time.sleep(0.03) # 约30 FPS def detect_frame(self, frame): """检测单帧""" # 根据模型类型进行检测 pass def stop(self): """停止线程""" self.running = False if self.cap: self.cap.release() self.wait()

7. 实验结果与分析

7.1 实验设置

  • 硬件环境:NVIDIA RTX 3080 GPU, Intel i7-12700K CPU, 32GB RAM

  • 软件环境:Ubuntu 20.04, Python 3.8, PyTorch 1.12, CUDA 11.3

  • 训练参数:批次大小16,初始学习率0.01,权重衰减0.0005,训练300个epoch

7.2 性能评估指标

python

# evaluation_metrics.py import numpy as np from sklearn.metrics import precision_recall_curve, average_precision_score class EvaluationMetrics: """评估指标计算类""" @staticmethod def calculate_map(predictions, ground_truths, iou_threshold=0.5): """计算平均精度均值(mAP)""" aps = [] for class_id in range(len(predictions)): class_preds = predictions[class_id] class_gts = ground_truths[class_id] if len(class_preds) == 0: aps.append(0) continue # 计算每个类别的AP ap = EvaluationMetrics.calculate_ap( class_preds, class_gts, iou_threshold ) aps.append(ap) return np.mean(aps) @staticmethod def calculate_ap(predictions, ground_truths, iou_threshold): """计算平均精度(AP)""" # 按置信度排序 predictions = sorted(predictions, key=lambda x: x['confidence'], reverse=True) tp = np.zeros(len(predictions)) fp = np.zeros(len(predictions)) gt_detected = [False] * len(ground_truths) for i, pred in enumerate(predictions): iou_max = -1 gt_match = -1 # 找到最佳匹配的ground truth for j, gt in enumerate(ground_truths): if gt_detected[j]: continue iou = EvaluationMetrics.calculate_iou(pred['bbox'], gt['bbox']) if iou > iou_max: iou_max = iou gt_match = j # 判断是否为真正例 if iou_max >= iou_threshold: tp[i] = 1 gt_detected[gt_match] = True else: fp[i] = 1 # 计算精确率和召回率 tp_cumsum = np.cumsum(tp) fp_cumsum = np.cumsum(fp) recalls = tp_cumsum / len(ground_truths) precisions = tp_cumsum / (tp_cumsum + fp_cumsum + 1e-6) # 计算AP (11点插值法) ap = 0 for t in np.arange(0, 1.1, 0.1): mask = recalls >= t if mask.any(): ap += np.max(precisions[mask]) return ap / 11 @staticmethod def calculate_iou(box1, box2): """计算交并比(IoU)""" # 计算交集面积 x1 = max(box1[0], box2[0]) y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]) y2 = min(box1[3], box2[3]) intersection = max(0, x2 - x1) * max(0, y2 - y1) # 计算并集面积 area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) union = area1 + area2 - intersection return intersection / union if union > 0 else 0 @staticmethod def calculate_fps(model, test_images, repetitions=100): """计算帧率(FPS)""" import time # 预热 for _ in range(10): _ = model(test_images[0]) # 测量推理时间 start_time = time.time() for _ in range(repetitions): for img in test_images[:10]: # 使用前10张图像 _ = model(img) end_time = time.time() total_time = end_time - start_time fps = (10 * repetitions) / total_time return fps
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 11:35:54

ARP 欺骗:原理、现象与防御方案,网络安全必备指南!

地址解析协议&#xff08;Address Resolution Protocol&#xff0c;ARP&#xff09;是在仅知道主机的IP地址时确定其物理地址的一种协议。 下面假设在一个局域网内&#xff0c;主机A要向主机B发送IP数据报。 ARP协议工作过程 A先在其ARP高速缓存中查看有无B的IP地址。如有&am…

作者头像 李华
网站建设 2026/5/29 5:53:13

智启蓝膜新“视”代|维视创新破解锂电蓝膜检测难题

在全球锂电行业进入“TWh时代”的背景下&#xff0c;品质即是生命线。锂电蓝膜作为电池安全防护的关键环节&#xff0c;其表面缺陷检测直接关乎电池性能与使用安全。然而传统质检中&#xff0c;微小的气泡、隐蔽的划痕、极难捕捉的折皱&#xff0c;正成为制约产能与品质的“隐形…

作者头像 李华
网站建设 2026/5/22 13:01:16

智慧农业茶叶病害检测系统 YOLOV8模型结合deepseek大模型 茶叶病虫害检测系统 YOLO+DeepSeek+Pytorch+SpringBoot+Flask+Vue

茶叶病虫害检测系统 YOLODeepSeekPytorchSpringBootFlaskVue 支持批量检测、图片检测、视频检测、摄像头检测四种方式。可以上传文件夹批量检测&#xff0c;检测结果可导出PDF。 结合DeepSeek、Qwen等大模型&#xff0c;给出相关建议。支持分用户管理&#xff0c;管理员可查看…

作者头像 李华