Python+OpenCV+YOLOv8:苹果叶病害识别检测系统实战解析 简介这是一套面向毕业设计与智慧农业场景的苹果叶病害识别检测系统源码包基于Python和OpenCV实现可检测赤霉病、枯叶病、铁锈病等常见叶片病害适合计算机视觉、深度学习方向的毕设项目参考与二次开发。压缩包共382个文件约54.26MB含162张JPG病害图像、161个TXT标注文件、20个YAML配置、16个Python源文件以及3个PT模型文件同时附带操作说明与评估指标曲线能帮助使用者快速理解并复现识别流程。目前已有508人学习。除完整源码和带标注数据集外资源还提供了可直接调用的训练权重、推理脚本及Dockerfile等配套内容按文档说明即可运行也可修改代码后继续训练迁移至其他植物病害识别任务整体结构清晰、扩展性较好。1. 基于pythonopencv的苹果叶病害识别检测系统一个zip里装着毕设的完整闭环身边做毕设选“图像识别”方向的人不少但能像这个标题一样把源码、带标注的病害数据集、模型和操作说明打包齐活的并不多。它核心是围绕python和opencv搭出一套苹果叶病害识别检测系统解决两件事判断叶片有没有病、病斑具体在哪个位置。适合正在赶大四毕设的学生也适合第一次拿目标检测数据集练手的入门者。这套方案对新手友好在“拿到就能跑”但有一句必须说在前面数据集规模通常有限模型效果会受背景和光照影响后面需要自己补数据和调参不要指望解压即完美。2. 数据集是第一优先级病害目录结构、标注格式与质量检查拿到这类毕设项目我先不碰模型先把数据集翻一遍。说句大实话图像识别项目的成败八成在数据集不在模型。YOLO系列、OpenCV的预处理流程都是现成的真正决定精度的是你的标注框贴不贴病斑、类别是不是统一、图片和标签能不能一一对上。2.1 一张毕设数据集长什么样目录结构与YOLO标注格式常见做法是数据集分成images和labels两个根目录各自再拆成train与val像下面这样组织dataset/ ├─ images/ │ ├─ train/ # 训练图片jpg或png │ └─ val/ # 验证图片 ├─ labels/ │ ├─ train/ # 与图片同名的txt标注 │ └─ val/ └─ apple_leaf.yaml # 数据集配置文件每个txt标注文件里一行代表一个病斑框YOLO格式是class_id cx cy w h四个坐标值都是归一化到0~1之间的浮点数。举例来说0 0.5078 0.4231 0.2105 0.1863表示类别0的框中心点在图片的相对位置是(0.5078, 0.4231)框宽高分别是图片宽高的21%和18.6%。这里有个新手最容易犯的错直接用记事本打开txt看到坐标是小数就以为数据坏了。实际上整数像素坐标是VOC格式或LabelMe格式的习惯YOLO训练必须要归一化坐标。我需要确认的是类别id是否从0开始连续编号以及猫腻最多的地方——apple_leaf.yaml里写的类别顺序和标注文件的id对不对得齐。# 项目里的apple_leaf.yaml路径基于数据集根目录 path: dataset train: images/train val: images/val names: 0: apple_scab # 黑星病 1: rust # 锈病 2: brown_spot # 褐斑病 3: mosaic # 花叶病如果标注文件里出现id4而names只写到3训练时就会报错或者静默丢掉这部分样本。我一般在训练前先跑一遍统计脚本这类问题十秒钟就能暴露。2.2 数据集质量检查坏图、空标注与越界框一次性查完解压数据集后第一件事不是急着装环境而是用python写一个检查脚本把三种问题一次性找出来OpenCV读不了的坏图、内容为空的标注文件、坐标越界的标注框。import cv2 from pathlib import Path from collections import Counter, defaultdict # 以训练集为例逐个检查图片能否被opencv读取并统计标签分布 images_root Path(dataset/images/train) labels_root Path(dataset/labels/train) missing_label [] # 有图没标注 empty_label [] # 标注文件是空的 bad_images [] # opencv读不出来的坏图 out_of_range defaultdict(list) # 坐标越界的标注 for img_path in images_root.glob(*.jpg): img cv2.imread(str(img_path)) if img is None: bad_images.append(str(img_path)) continue label_path labels_root / (img_path.stem .txt) if not label_path.exists(): missing_label.append(str(img_path)) continue lines label_path.read_text().strip().splitlines() if not lines: empty_label.append(str(img_path)) continue for line in lines: parts line.split() cls_id int(parts[0]) cx, cy, w, h map(float, parts[1:]) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): out_of_range[cls_id].append(str(label_path)) print(坏图数量:, len(bad_images)) print(缺标注数量:, len(missing_label)) print(空标注数量:, len(empty_label)) print(越界标注统计:, {k: len(v) for k, v in out_of_range.items()})这段脚本逻辑很直接cv2.imread返回None说明文件损坏或格式伪装标注文件不存在和文件为空是两种不同的问题前者多半是图片与标注文件名后缀不一致后者是标注过程漏了框。越界检查很重要如果cx w/2大于1训练时YOLO会强行把框剪到边界外导致回归loss异常波动肉眼很难看出来。跑完脚本后我还会用Counter统计每个类别在训练集和验证集的数量。毕设数据集的类别分布经常是“黑星病300个框、花叶病20个框”这种极端不均衡会让模型偏向数量多的类小类彻底学不出来。如果差得太多我一般直接做简单的过采样把小类图片复制几份到训练目录文件名加后缀避免覆盖。2.3 训练集与验证集的划分按类别比例切别按文件顺序切如果项目里已经划分好了train和val这一步可以跳过但不少毕设数据集的图片是全部堆在一个目录里的需要自己划分。我吃过亏的地方是直接用random.shuffle按文件名全局切忽略了类别不均衡。正确做法是按类别分层抽样先按类别把所有图片分桶再从每个桶里各取10%~20%进验证集。import random import shutil from pathlib import Path random.seed(42) images list(Path(dataset/all_images).glob(*.jpg)) labels_map {} # 先建立 图片名 - 类别集合 的关系 for img_path in images: label_path Path(dataset/all_labels) / (img_path.stem .txt) if not label_path.exists(): continue classes set() for line in label_path.read_text().strip().splitlines(): classes.add(int(line.split()[0])) labels_map[img_path.stem] classes # 按类别分桶 buckets {} for stem, classes in labels_map.items(): for cls_id in classes: buckets.setdefault(cls_id, []).append(stem) # 从每个桶里抽15%进验证集确保每个病种在val里都有代表 val_stems set() for cls_id, stems in buckets.items(): random.shuffle(stems) take max(1, int(len(stems) * 0.15)) val_stems.update(stems[:take]) # 移动文件 for stem in val_stems: shutil.move(fdataset/all_images/{stem}.jpg, dataset/images/val/) shutil.move(fdataset/all_labels/{stem}.txt, dataset/labels/val/)这段脚本的关键在设计val_stems的去重逻辑同一张图可能同时有黑星病和锈病两个框它会在两个桶里都被抽到直接用set就不会重复移动。另一个参数是take max(1, int(len(stems) * 0.15))对样本数小于7个的小类保证至少抽1个进验证集否则验证集里没有这个类mAP计算会直接报错或缺失。3. 模型选择与训练流程OpenCV预处理、YOLOv8微调和推理数据集检查完才轮到模型。整个标题最容易被误解的地方是OpenCV不是拿来直接做病害识别的它是干活的手脚负责图像预处理和结果显示真正做识别的是YOLOv8这类检测模型。这套分工在毕设答辩中也很讲得通OpenCV承担传统图像处理部分深度学习模型承担特征提取与分类定位。3.1 OpenCV在这套系统里干什么预处理辅助通道与结果可视化在纯OpenCV时代苹果叶病害检测的主流做法是颜色分割加形态学处理把病斑区域从绿色叶片中分离出来。这套路对单一背景的实验室照片有效一到田间复杂背景就崩。所以现在更合理的做法是OpenCV只做三件事——读图与统一尺寸、构建辅助遮罩、画框输出。import cv2 import numpy as np def preprocess(image_path, target_size(640, 640)): # 读取原图 img cv2.imread(image_path) if img is None: return None, None # 统一模型输入尺寸 resized cv2.resize(img, target_size, interpolationcv2.INTER_LINEAR) # 转HSV粗筛黄褐色病斑区域作为辅助信息 hsv cv2.cvtColor(resized, cv2.COLOR_BGR2HSV) lower np.array([10, 40, 40]) upper np.array([40, 255, 255]) mask cv2.inRange(hsv, lower, upper) # 开运算去掉杂点 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) return resized, maskcv2.resize的INTER_LINEAR是默认双线性插值对叶片这类自然图像足够HSV色相范围取10~40覆盖的是黄褐色到暗黄色的病斑区域。这个mask不是用来做最终判断的而是给模型结果做一个交叉验证如果模型输出置信度很高但mask区域完全没覆盖就要警惕是不是误检。这个设计在答辩时很加分它说明你不是只会调包。真正的检测交给YOLO。毕设场景我强烈建议选YOLOv8n或YOLOv8s原因很实际n模型参数量最小没有独立GPU的机器也能用CPU跑推理只是慢一些s模型精度更好但显存占用翻倍。如果你手里只有一块4GB显存的旧卡别碰yolov8m以上batch设成8照样会爆显存。3.2 用YOLOv8训练自己的苹果叶病害数据集配置文件与训练命令训练之前先确认数据集配置文件写对。前面的apple_leaf.yaml是核心这里特别提醒path字段的坑很多毕设项目解压后目录结构不同有人把数据集放在项目根目录下有人放在data/子目录里。path一旦写错训练时报错信息是FileNotFoundError看上去像是代码问题实际是相对路径没对上。训练环节我一般直接用ultralytics的命令行不自己写训练循环pip install ultralytics # 用yolov8n预训练权重微调150个epoch输入尺寸640 yolo detect train dataapple_leaf.yaml modelyolov8n.pt epochs150 imgsz640 batch16 patience20这里每个参数都不是随手写的。epochs150是毕设数据集的合理区间数据量在几百张级别时通常50轮就能收敛设150是给足余量配合patience20做早停连续20个epoch验证集指标不提升就自动终止。imgsz640是精度和速度的平衡点如果你的病斑是早期小黑点那种小目标可以考虑imgsz832但显存消耗会明显上升。batch16是个分水岭有RTX 3060以上显卡可以保持用CPU训练或显卡只有4GB显存要降到8甚至4。CPU训练时还可以加workers2避免数据加载线程抢占CPU资源。还有一组数据增强参数默认值不是最优的参数推荐值说明hsv_h0.015色相扰动大会让叶子变紫hsv_s0.3饱和度扰动模拟光照变化hsv_v0.4亮度扰动保持默认即可fliplr0.5水平翻转默认开启mosaic1.0默认开启最后10个epoch自动关闭degrees0.0旋转扰动苹果叶片有方向性不建议开大调参这条线我说点血泪经验数据增强是门玄学照着别人的参数抄一遍效果不一定好。自然光照变化不等于可以随便扭曲颜色hsv_h超过0.05训练集里能出现紫色叶子模型学到的特征就带偏了。你的病斑是黑褐色色相扰动稍微给一点就行重点放在饱和度和亮度扰动上。3.3 训练完怎么调用模型ultralytics推理与OpenCV DNN两种写法训练结束后runs/detect/train/weights/下会生成best.pt和last.pt。best是验证集指标最好的权重毕设和实际部署都用它。最快跑通推理的方式是用ultralytics自带APIfrom ultralytics import YOLO # 加载训练好的权重 model YOLO(runs/detect/train/weights/best.pt) # 推理并解析结果 results model.predict(test_leaf.jpg, conf0.35, iou0.5) for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 map(int, box.xyxy[0]) print(r.names[cls_id], conf, (x1, y1, x2, y2))conf0.35是置信度阈值低于这个分数的框直接丢弃毕设演示时0.35比较稳妥误检少想要多召回一些病斑可以降到0.25代价是更多假阳性框。iou0.5是NMS的IoU阈值同一片病斑被多个框覆盖时IoU超过0.5的框会被合并。如果你的GUI界面只用OpenCV不想让界面进程额外依赖torch和ultralytics可以把权重导出成ONNX格式再用OpenCV DNN模块加载yolo export modelbest.pt formatonnx opset12import cv2 import numpy as np net cv2.dnn.readNetFromONNX(best.onnx) blob cv2.dnn.blobFromImage(cv2.imread(test_leaf.jpg), 1/255.0, (640, 640), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward()但注意net.forward()拿到的原始输出张量需要自己解析边界框和类别概率不同opencv版本对YOLOv8 ONNX输出的张量维度处理还不完全一样这是纯手工活容易翻车。我的建议是毕设阶段老老实实让GUI依赖ultralytics把精力放在系统逻辑上ONNX导出这种优化等答辩完再折腾不迟。4. 源码运行与系统搭建环境配置、代码结构和端到端检测流水线这个zip里真正决定你能不能跑起来的是环境依赖和代码结构。我接过不少卡在第一步的咨询几乎全是环境问题Python版本不对、opencv装了两遍、torch装成CPU版还在用GPU训练。下面按我平时复现这类项目的顺序来。4.1 Python环境与依赖安装先建虚拟环境再装opencv和ultralytics我最不建议的做法是直接往系统Python里pip install因为这台机器后面可能还要跑其他项目opencv和numpy版本互相打架是常事。先建一个干净的虚拟环境# 创建虚拟环境python版本建议3.8~3.10 python -m venv .venv # Windows激活 .venv\Scripts\activate # Linux或macOS激活 source .venv/bin/activate # 先升级pip再装依赖 python -m pip install --upgrade pip # opencv单独装版本锁成4.x python -m pip install opencv-python4.8.1.78 # 再装ultralytics它会自动带torch和numpy python -m pip install ultralytics # 可视化界面常用库 python -m pip install labelimg PyQt5关于python安装教程网上随便一搜就是一大把我只强调和这个项目相关的三点第一虚拟环境创建和执行要用同一个python命令Windows下经常出现python指向3.11、python3指向3.9的情况我统一用python -m pip而不是裸pip第二VSCode里跑脚本前先看右下角解释器是不是选了.venv里的那个选错会出现“终端里装好了VSCode里import不了”的怪问题第三opencv-python和opencv-contrib-python千万不要同时装两个包会互相覆盖cv2目录下的模块产生的报错非常难排查。4.2 源码目录结构怎么读训练、推理、界面各自管什么解压zip后常见源码包的组织方式大差不差我以自己整理过的工程为例帮你建立一个心理预期apple_leaf_project/ ├─ dataset/ # 数据集images labels yaml ├─ runs/detect/train/ # 训练日志和权重 │ └─ weights/ │ ├─ best.pt │ └─ last.pt ├─ utils/ │ ├─ dataset_preprocess.py # 数据集检查与划分 │ └─ visualize.py # OpenCV画框与结果图保存 ├─ train.py # 训练入口 ├─ predict.py # 单张图片推理 ├─ gui.py # 图形界面 ├─ requirements.txt # 依赖清单 └─ 操作说明.md我个人拿到手会先读requirements.txt和操作说明.md的开头部分这两份东西能告诉你作者实际用的Python版本和依赖版本号。如果requirements里写的是opencv-python4.5这种宽松约束建议直接按我上面的版本装至少我能确认这套组合兼容。操作说明里最值得读的三个部分训练顺序、模型文件放哪、GUI怎么启动。毕设项目的常规流程是先跑train.py训练再把best.pt放进模型目录最后启动gui.py选择图片测试。有些项目的gui.py写死了模型路径你改了权重文件名就会报找不到文件所以看到FileNotFoundError先查代码里是不是硬编码了路径。4.3 端到端跑通一个检测样例从图片输入到结果保存把训练和推理串成一条流水线最简单的做法是封装一个检测类供命令行和GUI共用import cv2 import numpy as np from ultralytics import YOLO class LeafDetector: def __init__(self, weights_pathruns/detect/train/weights/best.pt): self.model YOLO(weights_path) # OpenCV读不了中文路径用imdecode绕过去 def imread_unicode(self, image_path): data np.fromfile(image_path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR) def detect(self, image_path, conf0.35, save_pathNone): img self.imread_unicode(image_path) if img is None: raise ValueError(图片读取失败请检查文件是否损坏) results self.model.predict(img, confconf, iou0.5) # r.plot() 返回带检测框的BGR图像 annotated results[0].plot() if save_path: cv2.imwrite(save_path, annotated) return annotated这个类里最容易被忽视的是imread_unicode。很多毕设项目的测试照片放在“桌面\毕设图片”这种中文路径下cv2.imread遇到中文路径直接返回None而且不报错。用np.fromfile把文件读成二进制数组再交给cv2.imdecode就能绕开这个限制。results[0].plot()是ultralytics内置的可视化方法会在图上画出边框、类别名和置信度返回的是BGR图像可以直接cv2.imwrite保存。GUI里显示这张图时需要先把BGR转成RGB再封装成QImage不然颜色会偏蓝偏绿from PyQt5.QtGui import QImage, QPixmap def bgr_to_qpixmap(bgr_img): rgb_img cv2.cvtColor(bgr_img, cv2.COLOR_BGR2RGB) h, w, ch rgb_img.shape qimg QImage(rgb_img.data, w, h, 3 * w, QImage.Format_RGB888) return QPixmap.fromImage(qimg.copy())qimg.copy()是必须的因为QImage的data指向的还是numpy数组的内存如果原始图像变量被释放或修改界面上会花屏甚至崩掉。这里面每一行都有讲究前端的细节往往比模型更耗时间。5. 从标注到跑通的踩坑记录高频报错与现象逐个排查这套流程我完整走过不止一遍下面的问题几乎每个毕设项目都会撞上几个。按现象、原因、解决三部分写你排查时直接对号入座。5.1 环境报错ModuleNotFoundError与解释器错位现象终端里执行import cv2正常但VSCode里运行报no module named cv2或者pip list明明显示opencv-python已安装import时照样找不到。原因最常见的是装到了另一个环境。Windows下pip可能指向Python 3.7而VSCode选中的解释器是Python 3.10两个环境互不相通。解决先确认当前解释器到底是谁再统一用python -m pip安装。在VSCode的终端里执行python -c import sys; print(sys.executable) python -m pip install opencv-python4.8.1.78如果sys.executable显示的路径不是你虚拟环境里的路径说明解释器没被正确激活。另外torch的CPU版本和GPU版本在ultralytics里都能用但GPU版需要pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118这部分安装教程很长我建议直接用ultralytics自动依赖的CPU版torch毕设规模的数据量CPU训练也等得起。5.2 训练异常loss为NaN与验证mAP一直为0现象训练输出里loss出现nan或者训练正常结束但验证集mAP是0precision和recall全是0。原因loss为NaN大概率是学习率过大或标注框坐标异常检查标注文件里有没有w0或h0的畸形框归一化坐标里出现负数也会触发NaN。mAP为0则通常是类别id和yaml里的names对不上比如标注里写的是1开头但names的id是从0开始的模型把所有预测都归到错误类别上验证时算出来自然全是0。解决先用第2章的检查脚本把所有越界框和空标注清掉再把yaml里names的id顺序和标注逐一核对。学习率先降到lr00.005重跑一次如果还出NaN问题基本锁定在标注数据。顺便说一个容易被忽视的点data.yaml的names最好不要用中文标签训练本身能跑但后面导出到ONNX或者GUI显示时容易乱码改用拼音或英文界面上再映射成中文展示。5.3 推理结果异常全部漏检与重复框包围病斑现象模型训练指标不差但拿一张新照片测试时什么都检测不到或者同一个病斑被三四个框同时框住。原因全部漏检先看置信度阈值。训练时mAP50是0.85不代表推理要按0.5去卡conf0.35只是经验值如果你的测试照片和训练集差异较大模型输出分数普遍偏低可以先降到0.2看看到底是阈值问题还是模型泛化问题。重复框多半是NMS的IoU阈值设得不对iou0.5是常规值设成0.7时相邻的重叠框就不容易被合并。解决建议做一个批量验证脚本用验证集图片逐个跑推理把假阳性和漏检的图片单独存到两个文件夹里肉眼翻一遍。这个动作看起来原始但比盯着终端里的指标有效得多。漏检的病斑集中在某一个小类别时别急着调NMS回第2章看那个类别的样本量数据不均衡才是病根。5.4 数据路径问题中文路径、后缀不一致与坏图现象加载图片列表时有一部分图读取失败训练时发现图片数量比标注数量多出几十张或者推理时尺寸为0的框把程序崩掉。原因中文路径是最坑的Windows下cv2.imread(path)一旦路径里含有中文字符不会报错而是返回None程序一脸无辜地跳过数据量悄悄变少。图片后缀不一致的情况更隐蔽训练集里jpg有100张jpeg有30张标注是按stem匹配的如果标注文件只给jpg命名那30张jpeg等于白放。解决把所有图片统一后缀和格式用python批量改名或转码import cv2 from pathlib import Path p Path(dataset/images/train) for img_path in p.iterdir(): if img_path.suffix.lower() .jpeg: img cv2.imread(str(img_path)) new_path img_path.with_suffix(.jpg) cv2.imwrite(str(new_path), img) img_path.unlink() # 删掉原文件做完这步再跑一次第2章的检查脚本确保每一个jpg都有同名txt。我一般在训练前固定跑一遍这个脚本十秒钟的事能省掉后面两小时的无效训练。坏图处理同理cv2.imwrite重写一遍就能让绝大多数损坏文件恢复可用。6. 评估与进阶用验证指标和坏样本反推优化方向训练结束不代表系统能用先跑一遍官方验证命令把指标量化出来再针对坏样本做定向优化。这套验证流程对毕设答辩极有价值它把“模型效果不错”变成“mAP50达到0.89锈病类漏检率从20%降到8%”这种可以写在论文里的结论。6.1 用yolo val把训练成果量化成可答辩的指标yolo val modelruns/detect/train/weights/best.pt dataapple_leaf.yaml看输出里的四列Precision是所有预测框中真的占比Recall是所有真病斑中被找回来的比例mAP50是IoU阈值0.5下的平均精度mAP50-95是更严格的综合指标。毕设场景主要看mAP50一般能做到0.8以上就算可用mAP50-95受小目标影响大不必死磕。6.2 从坏样本里反推数据增强策略并加一个可解释输出如果漏检集中在某一类我惯用的做法是裁剪该类的病斑区域用OpenCV粘贴到新的背景上做局部增强比全局调增强参数更有效。再往前一步可以输出每个病斑的HSV统计特征把模型置信度和颜色特征放在同一张报告里答辩时老师问“模型为什么这么判断”你能给出可解释的辅助证据。现在做新的数据集训练我的习惯是先把检查脚本挂在训练命令前面见不到检查结果不开训练进度条跑一半才发现标签错位就太晚了。这套基于python和opencv的方案说到底是“数据先行、模型跟上”的工程活数据集踏实了YOLOv8的默认参数就足够撑起一个像样的毕设。希望帮到你。 p a hrefhttps://download.csdn.net/download/liufang_imei/87804649 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p