ARTICLE DETAIL

资讯详情

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

基于Python与OpenCV的苹果叶病害识别:从数据格式到部署避坑

基于Python与OpenCV的苹果叶病害识别:从数据格式到部署避坑 简介面向毕业设计与智慧农业场景这份资源是基于Python与OpenCV的苹果叶病害识别检测系统可检测赤霉病、枯叶病、铁锈病三类常见病害并支持替换数据集以扩展其他植物病害适合计算机视觉、深度学习方向的本科生与开发者参考。压缩包共382个文件、54.26MB涵盖162张jpg病害图片、161个txt标注文件、20个yaml配置、16个py源码脚本以及pt模型、Dockerfile等图片与标注用于模型训练与验证py脚本和yaml配置负责检测流程与参数设定pt模型为训练好的权重文件。已有508人学习。资源同时附带操作说明、评估指标曲线与环境依赖清单从数据集标注到推理检测形成完整闭环按文档配置好Anaconda环境安装依赖后运行detect.py即可在inference/images中放入图片或视频完成识别结果输出至output文件夹。整体内容结构清晰、可直接运行便于二次开发可作为毕设初版系统快速验证方案。1. 一套苹果叶病害识别系统的完整组成拿到“PythonOpenCV”源码后该关注什么这个毕设项目压缩包的标题写得很直白基于 Python 和 OpenCV 的苹果叶病害识别检测系统里面是源码、带标注的病害数据集、训练好的模型和一份操作说明。说白了它解决的问题就是一个你拍一张苹果叶子的照片程序告诉你叶片是健康还是得了病得的是哪种病病斑位置在哪。适合拿来当毕业设计主干代码也适合想快速跑通一个图像识别流程再替换成自己数据的同学。对这类项目我建议别急着训练先把包里的数据格式和模型入口搞清楚格式决定了你后面跑的是分类还是目标检测入口决定了你改哪些文件、怎么改。下面按动手顺序把这套流程拆开从数据与模型选型讲到最小可运行脚本再列几个你大概率会踩的坑。2. 先分清分类还是检测苹果叶病害数据集的标注格式决定技术路线拿到压缩包后第一步不是跑代码而是看数据集。很多同学被标题里的“识别检测”四个字带偏以为必须做目标检测结果打开代码发现是图像分类或者反过来代码里写了检测框数据集却是按文件夹分类的。这两种技术路线在代码层面差异很大不先搞清楚后面改到你怀疑人生。苹果叶病害识别这个场景常见的病害有苹果黑星病、苹果锈病、褐斑病、花叶病、斑点落叶病再加上健康叶片通常就是 4 到 6 个类别。数据集的标注方式直接决定了项目的技术路线如果每个类别一个文件夹图片天然带着类别标签那这是图像分类任务如果每张图附带一个 XML 或 TXT 文件里面写明了病斑的矩形框坐标那这是目标检测任务。下面先教你怎么从目录结构判断。2.1 苹果叶常见病害与两种数据组织方式先看一份典型的分类数据集目录长什么样data/ ├── train/ │ ├── Apple_black_rot/ │ ├── Apple_rust/ │ ├── Apple_scab/ │ └── Healthy/ └── test/ ├── Apple_black_rot/ ├── Apple_rust/ ├── Apple_scab/ └── Healthy/这种组织方式不需要任何额外标签文件文件所在的文件夹就是类别。训练时只需要把图片路径和文件夹名组成“图片-标签”对交给torchvision.datasets.ImageFolder或 Keras 的flow_from_directory就能直接读。优点是实现简单答辩时展示“输入一张图输出病种和置信度”非常直观。检测任务的组织方式则复杂一些常见有三种格式标注格式文件后缀常见工具关键信息VOC XML.xmlLabelImg左上角 xmin/ymin 和右下角 xmax/ymax像素坐标YOLO TXT.txtLabelImg / labelme类别 id 中心点归一化坐标 xc/yc/w/hCOCO JSON.jsonlabelme / CVAT包含 segmentation 和 bbox需要额外解析如果压缩包里有labels或Annotations目录里面的文件数量和图片数量一一对应那基本就是检测任务。很多毕设源码会把标注好的数据集放在data/下模型放在models/下操作说明文档放在根目录。你先不要急着 pip install把数据集目录翻一遍判断好任务类型再动环境。2.2 通过目录结构判断分类还是目标检测两条命令搞定在 Linux 上可以用find快速扫描在 Windows 上直接用资源管理器也能看个大概# 在解压后的项目根目录执行 find . -maxdepth 3 -type d | grep -i train find . -name *.xml | head -20 find . -name *.txt | head -20如果第一条命令列出了train/Apple_black_rot这类文件夹且没有二、三条输出别犹豫这就是分类项目。如果第二条或第三条输出了一堆和图片同名的文件那就要按检测项目来改。拿到检测标签后最值得看的是标签内容。YOLO 格式的标签每一行有五个数字第一个是类别 id后面四个是中心点 x、中心点 y、宽、高它们都是相对图片宽高归一化到 0 到 1 之间的数。可以用下面这段代码快速解析验证def parse_yolo_label(label_path): with open(label_path, r, encodingutf-8) as f: lines f.readlines() boxes [] for line in lines: parts list(map(float, line.strip().split())) cls, xc, yc, w, h parts # 归一化坐标像素坐标需要乘以原图宽高 boxes.append((int(cls), xc, yc, w, h)) return boxes labels parse_yolo_label(data/labels/IMG_001.txt) print(labels[:5])这段代码的逻辑很简单按空格把一行文本拆成数字然后依次取出类别、中心坐标和宽高。参数说明cls必须是整数因为你没法把类别 id 作为小数转成字符串xc, yc, w, h是 0 到 1 的小数如果看到 0 到 1 之外的值说明标注框可能越界后面推理画框时会出现负坐标或超出图片边界的框。2.3 训练、导出与 OpenCV 的关系DNN 模块才是“部署”很多人的疑问是训练深度学习模型不是用 PyTorch 或者 TensorFlow 吗为什么标题里只写 Python 和 OpenCV常做法的做法是训练阶段确实用 PyTorch 或者 YOLO 系列框架但 OpenCV 在整个系统里负责了所有图像读入、缩放、颜色转换、画框、保存结果这些脏活累活。除此之外OpenCV 从 3.4 开始提供了dnn模块可以加载 ONNX、TensorFlow、Caffe 格式的模型。真正做到“只用 OpenCV 也能在无 GPU 机器上进行深度学习推理”。为了让答辩现场在没有 PyTorch 的机器上也能跑我一般会建议把训练好的模型导出成 ONNX再用cv2.dnn.readNetFromONNX加载。以分类模型为例PyTorch 导出 ONNX 的标准流程是这个样子import torch # 假设你训练好的是一个输入为 224x224 RGB 的分类模型 model torch.load(best_apple_classifier.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, apple_model.onnx, input_names[images], output_names[scores], opset_version11, dynamic_axes{images: {0: batch}} ) print(导出完成)这里dummy_input的1, 3, 224, 224分别对应批次大小、RGB 三个通道、图片宽高。opset_version11是 OpenCV 兼容性较好的算子版本太高了可能遇到dnn模块不支持某层的问题太低了有些新层又导不出来。dynamic_axes允许运行时不固定 batch 大小方便你在测试脚本里一次只推理一张图时不用改模型。如果你拿到的模型已经是.onnx那可以直接跳到下一步用 OpenCV 加载。如果压缩包里给的是.pt或.pth那么 OpenCV 是读不进去的要么跑原作者的 PyTorch 推理脚本要么自己按上面的方式导出一次。2.4 分类模型和检测模型在推理时的本质差别分类模型的推理结果是一组概率值比如[0.05, 0.91, 0.02, 0.02]取最大值下标就能得到病害类别。检测模型的推理结果则复杂得多YOLO 系输出通常是[1, 25200, 85]这种形状25200 是不同尺度上的候选框总数85 是 4 个框坐标、1 个目标置信度、80 个类别概率或按你的类别数调整。这意味着你必须做置信度过滤和 NMS 非极大值抑制才能得到最终框。用 OpenCV 推理分类模型的代码很短scores model.forward()[0] pred_idx int(np.argmax(scores)) confidence float(scores[pred_idx])而检测模型需要额外的后处理indices cv2.dnn.NMSBoxes(boxes, confidences, score_threshold0.25, nms_threshold0.45)score_threshold决定了低于多少置信度的框直接丢掉nms_threshold控制两个重叠框的合并程度。这两个参数直接影响你最后画出来的框多不多、准不准。很多项目源码里把这两个值写死在main.py里你调模型效果时不要只调模型本身先看看这里。想清楚自己是分类还是检测之后下一步再进入环境搭建和运行。下面这套流程我按最保守的顺序写每一步都能直接在拷贝到项目的虚拟环境里执行。3. 把环境和推理跑通从解压 zip 到 OpenCV 加载模型并输出识别结果这一章解决的是“怎么让项目跑起来”。很多毕设源码的问题不在于算法多先进而在于作者本机环境特殊直接运行会报各种莫名其妙的错误。我的建议是永远不要直接在全局 Python 里安装依赖而是为这个项目单独创建一个虚拟环境。这样即使装坏了删掉整个venv文件夹就可以重来不会影响你系统里的其他 Python 项目。3.1 准备 python 与 opencv 环境建议 Python 3.8-3.10 与 opencv-python 4.x先打开命令行进入项目解压目录依次执行以下命令cd 项目解压目录 # 创建虚拟环境Windows 和 Linux/macOS 命令略有不同 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 # source venv/bin/activate pip install --upgrade pip pip install opencv-python numpy pip install -r requirements.txt这里python -m venv venv会创建一个名为venv的虚拟环境激活后命令行前缀会变成(venv)。opencv-python是带cv2接口的标准包包含dnn模块如果你还需要 SIFT、ORB 等特征得装opencv-contrib-python但一般苹果叶病害识别用不到装标准版就够了。关于版本我建议opencv-python不低于 4.5.0太老版本的dnn对 ONNX 算子支持不全导出时选了较新的算子版本就容易报错。如果requirements.txt里锁定了一个很旧的版本而你的 Python 版本又很高中间可能遇到编译兼容问题。最稳的组合是 Python 3.9 或 3.10 opencv-python 4.5.5 以上。验证环境是否装好执行python -c import cv2; print(cv2.__version__)如果输出类似4.10.0说明 OpenCV 安装成功。如果报ModuleNotFoundError: No module named cv2先不要怀疑人生绝大多数情况是没激活虚拟环境或者 PyCharm 里选择了错误的解释器。3.2 跑通主程序必改的三个配置项操作说明文档里一般会告诉你“直接运行python main.py”但直接跑大概率会报错因为你电脑上的路径和作者的不一样。我看了很多同类项目发现入口脚本里通常有三个地方必须改数据源source、模型路径model、输出目录output。典型的入口参数长这样import argparse import os parser argparse.ArgumentParser() parser.add_argument(--source, defaultdata/test, help测试图片或图片目录) parser.add_argument(--model, defaultmodels/apple_leaf.onnx, help模型文件路径) parser.add_argument(--output, defaultruns/detect, help结果保存目录) parser.add_argument(--conf, typefloat, default0.25, help置信度阈值) args parser.parse_args() os.makedirs(args.output, exist_okTrue) print(输入目录:, args.source) print(模型文件:, args.model) print(输出目录:, args.output)这段代码里的args.source默认指向data/test如果压缩包里的测试图片实际放在dataset/test_images你就得改。args.model默认是models/apple_leaf.onnx如果压缩包里只有best.pt你就不能指望cv2.dnn去读它老老实实改回 PyTorch 推理脚本或者先完成 ONNX 导出。args.output不存在时程序会自动创建但如果目录路径包含中文且 OpenCV 版本较老保存图片时可能失败这个坑后面专门讲。改完这三个参数后先跑一遍python main.py --source data/test --model models/apple_leaf.onnx如果报错把完整报错日志复制下来不要只看最后一行。90% 的错误是路径问题、依赖缺失、模型格式不匹配三种按照第四章的排查点逐条过。3.3 用 OpenCV 读取图片并完成分类推理一张图从 BGR 到模型输入如果你的项目是图像分类推理脚本可以简化成下面这段。这段代码我用 OpenCV 的dnn模块不依赖 PyTorch可以在没有 GPU 的普通笔记本上运行import cv2 import numpy as np # 类别顺序务必要和训练时保持一致 CLASSES [Apple_black_rot, Apple_rust, Apple_scab, Healthy] net cv2.dnn.readNetFromONNX(models/apple_leaf.onnx) def preprocess_for_mobilenet(img, input_size224): # OpenCV 读进来是 BGR模型训练时用的通常是 RGB img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img cv2.resize(img, (input_size, input_size)) img img.astype(np.float32) / 255.0 # ImageNet 的均值方差归一化 img (img - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225]) # HWC - CHW img np.transpose(img, (2, 0, 1)) # 加上 batch 维度 img np.expand_dims(img, axis0) return img def predict(image_path): img cv2.imread(image_path) if img is None: raise FileNotFoundError(f图片读取失败: {image_path}) blob preprocess_for_mobilenet(img) net.setInput(blob) scores net.forward()[0] idx int(np.argmax(scores)) confidence float(scores[idx]) label f{CLASSES[idx]} {confidence:.2f} cv2.putText(img, label, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imwrite(runs/result.jpg, img) return CLASSES[idx], confidence cls_name, conf predict(data/test/rust_01.jpg) print(f识别结果: {cls_name}, 置信度: {conf:.2%})这段代码有几处关键参数值得说明第一cv2.cvtColor把 BGR 转成 RGB。很多人训练模型时用 PyTorch 的ImageFolder加载图片那个流程默认读 RGB而 OpenCV 的cv2.imread返回的是 BGR不转换的话颜色通道错位模型输出的概率会非常离谱但又不至于报错。这是最隐蔽的预处理坑。第二img / 255.0之后又减均值除方差这套参数来自 ImageNet。如果你的训练代码用的不是这个归一化方式推理时就必须改成一致的。很多项目训练时用的是 0 到 1 缩放但不减均值你直接把推理脚本砍掉两行效果立刻回正。第三np.transpose(img, (2, 0, 1))把高宽通道的 HWC 布局变成 CHW这才是 PyTorch 和 ONNX 模型默认接受的布局。顺序错了数据完全不通。3.4 检测模型的后处理置信度过滤、NMS 与画框如果你的系统是目标检测推理代码比分类多一块后处理。以 ONNX 导出的 YOLO 模型为例核心流程是把图片blob送入网络拿到一堆候选框然后过滤掉低置信度框再用cv2.dnn.NMSBoxes去除同一目标上的重复框。import cv2 import numpy as np def detect_yolo(model_path, image_path, input_size640, conf_th0.25, nms_th0.45): net cv2.dnn.readNetFromONNX(model_path) img cv2.imread(image_path) if img is None: return [], img h, w img.shape[:2] blob cv2.dnn.blobFromImage(img, 1/255.0, (input_size, input_size), swapRBTrue, cropFalse) net.setInput(blob) output net.forward()[0] boxes, confidences, class_ids [], [], [] for row in output: scores row[5:] class_id int(np.argmax(scores)) conf float(scores[class_id] * row[4]) if conf conf_th: continue x, y, bw, bh row[:4] x (x - bw / 2) * w / input_size y (y - bh / 2) * h / input_size bw bw * w / input_size bh bh * h / input_size boxes.append([int(x), int(y), int(bw), int(bh)]) confidences.append(conf) class_ids.append(class_id) idxs cv2.dnn.NMSBoxes(boxes, confidences, conf_th, nms_th) for i in idxs.flatten() if len(idxs) else []: x, y, bw, bh boxes[i] label fclass_{class_ids[i]} {confidences[i]:.2f} cv2.rectangle(img, (x, y), (x bw, y bh), (0, 0, 255), 2) cv2.putText(img, label, (x, max(0, y - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 1) cv2.imwrite(runs/detect_result.jpg, img) return boxes, confidences, class_ids这里的关键是坐标恢复。YOLO 推理输出里x, y是中心点坐标bw, bh是宽高并且这些值都是相对于网络输入尺寸归一化的。所以恢复成像素坐标需要两步先把中心点转成左上角再乘以原图宽高 / input_size。很多同学直接把row[:4]当成像素坐标画框结果就是框全部画在图片左上角一小块区域里看起来像乱飞。cv2.dnn.NMSBoxes返回的idxs在高版本 OpenCV 里是一个二维数组每个元素代表一个保留框的下标。用idxs.flatten()统一处理兼容 OpenCV 4.x 的多种返回值形态。conf_th和nms_th这两个参数我建议在调试时分别单独调先调conf_th把误报压下去再调nms_th消除同一个病斑上的多个重叠框。4. 避坑指南毕设新人最常见的 5 个踩坑点这一章是我从实际排查同类项目经验里挑出来的高频问题不是理论推演。每一个我都按“现象 → 原因 → 解决”的顺序写你遇到的时候直接对照着改就行。4.1ModuleNotFoundError: No module named cv2和环境版本错乱现象在项目目录里运行python main.py终端立刻报错ModuleNotFoundError: No module named cv2。有些同学在 PyCharm 里装好了 OpenCV但运行时仍然报这个错或者在命令行里pip list能看到 opencv-python但 Python 依然找不到。原因最常见的是解释器错乱。PyCharm 默认使用项目虚拟环境但如果你在终端里直接执行python main.py用的却是全局 Python。另外如果你的电脑装了多个 Python 版本比如 3.8 和 3.11pip 把包装到了某一个版本而命令行的 python 指向另一个版本也会出现这种矛盾。解决先统一执行环境。在项目目录下创建虚拟环境激活后重新安装依赖python -m venv venv venv\Scripts\activate pip install opencv-python numpy python -c import cv2; print(cv2.__version__)如果pip install时看到网络下载很慢可以临时换国内镜像pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple。装完后务必再验证一次导入因为 OpenCV 的安装包很大有时 pip 提示成功但动态库解压失败导入时会报 DLL 错误而不是找不到模块。4.2 模型文件加载失败压缩包里给的是.ptOpenCV 却不认现象使用cv2.dnn.readNetFromONNX(models/best.pt)直接报错或者运行源码时提示无法解析网络模型。打开models目录发现明明有模型文件但程序就是加载失败。原因OpenCV 的dnn模块只认 ONNX、TensorFlow、Caffe、Darknet 等格式不认 PyTorch 的.pt/.pth。很多毕设作者训练完存的是best.pt然后又用 OpenCV 写推理这本身上是矛盾的。解决先看源码是依赖 PyTorch 还是 OpenCV。如果主程序里有import torch并且用torch.load加载权重那就按 PyTorch 推理走如果主程序里只有cv2.dnn那就必须先把.pt转成.onnx。转换时注意权重文件的加载方式PyTorch 的torch.load在新版本里会给出安全警告只加载自己信任的训练产出即可。python export_onnx.py导出 ONNX 后再改回--model best.onnx。如果导出遇到算子不支持优先尝试opset_version12或修改dynamic_axes不要盲目换更高版本。4.3 中文路径导致读图失败cv2.imread返回 None现象图片文件明明在但cv2.imread(image_path)返回None后续代码一执行就报错。常见于 Windows 下项目路径或文件名包含中文比如D:\毕业设计\苹果叶\test.jpg。原因OpenCV 在 Windows 下的旧版本对非 ASCII 路径支持不完善底层imread走的是 C 标准库文件操作中文路径会被错误编码。你的代码在纯英文路径下一切正常一放到桌面或者带中文的文件夹里就翻车。解决用np.fromfile配合cv2.imdecode替代imread保存图片用cv2.imencode配合tofiledef imread_unicode(path): return cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR) def imwrite_unicode(path, img): ext . path.rsplit(., 1)[-1] ok, buf cv2.imencode(ext, img) if ok: buf.tofile(path)这段代码利用 NumPy 从任意路径读取二进制文件再把字节流解码为图片绕过了 OpenCV 的路径编码问题。如果你改写了这两处记得把主程序里所有cv2.imread和cv2.imwrite都替换掉不要只替换一处。4.4 训练集准确率虚高但实测拉胯数据泄漏和预处理不一致现象模型评估报告显示准确率 97% 以上但你自己拍一张新叶子照片识别结果完全不对。严重时连病斑位置都差得很远。原因有两个常见病根。第一是数据泄漏比如把同一片叶子的多张不同角度照片随机分到了训练集和测试集模型实际上记住了图片背景而不是病害特征测试时当然分数虚高。第二是训练和推理前处理不一致训练时图片做了随机裁剪、翻转、亮度变化推理时却只用简单缩放模型没见过这种输入分布。解决在项目划分数据集时保证同一个样本的所有图片都放进同一个集合不要随机打散。验证数据泄漏的快速方法是随机挑选几张训练集图片检查它们的多角度副本是否出现在测试集里。前处理方面把训练脚本里的transform完整复制到推理里尤其是Resize尺寸、归一化均值和方差。少一步损失很小多一步偏差就可能很大。4.5 检测框乱飞坐标恢复时用错了宽高基准现象模型训练时 loss 正常测试时也能出框但框的位置明显不对有的画在叶片外有的框超大有的挤在图片左上角。原因YOLO 标签使用的归一化坐标是相对整张原图算的而推理输出又是相对网络输入尺寸算的。恢复坐标时需要把网络输出先除以input_size再乘以原图宽高。很多同学直接把row[:4]当作像素坐标用或者只乘了一个系数就完事导致框的位置和尺度全部错误。解决回到 3.4 节的代码重点看这四行x (x - bw / 2) * w / input_size y (y - bh / 2) * h / input_size bw bw * w / input_size bh bh * h / input_size这里w和h是cv2.imread得到的原始图片宽高不是网络输入的 640。如果项目里用了 letterbox 填充而不是直接 resize那么还需要额外处理填充偏移量。最简单的验证办法是找一张已知病斑位置的图片把画框结果打印出来和原图对比看左上角坐标是否合理。5. 让系统在答辩现场不翻车混淆矩阵、置信度阈值和一份最终检查清单项目能单张跑通只是第一步毕业设计答辩时要面对的是“你这模型可靠吗”这类追问。与其空口说准确率高不如在答辩前跑一次完整评估把混淆矩阵、每类查准率和查全率列出来再现场演示一两张最有代表性的图片。这里我有一个比较省事的验证脚本可以把你测试集里所有图片过一遍统计真实类别和预测类别的匹配情况import glob import os import cv2 results {} for img_path in glob.glob(data/test/*/*.jpg): true_cls os.path.basename(os.path.dirname(img_path)) pred_cls, conf predict(img_path) key (true_cls, pred_cls) results[key] results.get(key, 0) 1 for (true_cls, pred_cls), count in sorted(results.items()): print(f{true_cls} - {pred_cls}: {count})跑完这个脚本你会看到哪些类别容易被混淆。比如 Apple_rust 经常被预测成 Apple_scab那就说明这两个类别在纹理上接近需要补样本或者调阈值而不是盲目提升模型容量。置信度阈值也是一个很值得调的点。分类任务里如果某几类病害经常会给出 0.3 到 0.5 之间的低置信度你可以把结果改成“低置信度提示重新拍摄”这在答辩场景里反而像个加分项。检测任务里调高score_threshold会减少误检框但可能漏掉小病斑调低则会画出很多无效框。我的建议是按测试集跑一次不同阈值的效果对比选一个平衡点写进操作说明。最后给你一条我的习惯在答辩前一天把项目复制到一台干净机器上用全新的虚拟环境跑一遍完整流程确认pip install后不需要手动配置任何额外 DLL。同时把模型文件替换成只读的 ONNX 格式避免答辩时因为路径中存在中文目录导致读图失败。这些都是小事但每年都有同学倒在“本机跑得好好的换台电脑就白屏”上。希望帮到你。本文还有配套的精品资源点击获取
返回列表