☰
苹果叶病害识别系统实战:基于OpenCV与Python的图像分类全解析
2026/10/1 12:22:06 网站建设 项目流程

简介:面向智慧农业方向毕设与病害识别入门者,这是一份基于Python与OpenCV的苹果叶病害检测系统完整工程包,覆盖赤霉病、枯叶病与铁锈病三类常见病害的识别检测。项目采用YOLO系列检测方案,可直接读取图片或视频输出检测结果,也可替换数据集训练其他作物病害,适合本科毕设、课程设计或农业信息化项目二次开发。压缩包共382个文件,约54.26MB,包含Python源码、标注数据集(图片与txt标签)、yaml配置、训练好的pt权重模型、Dockerfile及启动脚本等,目录结构完整。内容预览可见训练/验证批次图与测试预测图,便于快速核对效果。已有508人访问学习,适合具备Python基础、希望快速搭建完整识别流程的开发者参考使用。

1. 苹果叶病害识别系统这套毕设源码,能识别什么、凭什么识别

毕设季最常见的场景,就是拿到一个“基于 python+opencv 的苹果叶病害识别检测系统”源码包,里面装着标注好的病害数据集、训练好的模型和操作说明。它的核心价值是把一张叶片照片自动归类为健康的还是得了病,如果得了病,进一步分出是黑星病、黑腐病还是锈病——这类靠肉眼经验才能判断的事,模型能在几秒内出结果。我拿到这种项目包的第一步从来不是解压就运行,而是先搞清三件事:数据集用什么结构标注的、模型用什么框架保存的、预测脚本从哪里读图。要拿这个方向做课设或毕设的本科生,以及想找一条完整图像分类链路练手的开发者,都可以往下看。

2. OpenCV 在苹果叶病害识别里的真实分工:从读图到颜色空间转换的关键环节

系统名字里写着 Python 和 OpenCV,但真正判断“这是什么病”的不是 OpenCV,而是训练好的分类模型。OpenCV 的位置在模型前后:前面负责把一张随手拍的叶片照片变成模型能吃的标准输入,后面负责把模型输出的数字翻译成看得懂的框、标签和病斑区域。把这条链路的边界划清楚,你才知道调参的时候该动谁。

2.1 一条完整的“叶片图像 → 病害类别”流水线是怎么训练出来的

常见做法是搭建一条图像分类流水线:读图 → 预处理 → 模型推理 → 后处理。数据预处理环节几乎全部由 OpenCV 完成。以下是一个典型的预处理函数,几乎每个识别项目里都有类似的一段:

import cv2 import numpy as np def preprocess_leaf(image_path, target_size=(224, 224)): # 1. 读图:OpenCV 默认返回 BGR 顺序,很多模型是按 RGB 训练的,顺序错了颜色特征完全错位 image = cv2.imread(image_path) if image is None: raise ValueError(f"无法读取图片: {image_path},检查路径是否含中文或文件损坏") # 2. 颜色空间转换:BGR -> RGB,这一步被漏掉是新手最常见的翻车点 image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 3. 统一缩放:模型输入层尺寸固定,不匹配时前向传播直接报 shape 错误 image = cv2.resize(image, target_size, interpolation=cv2.INTER_AREA) # 4. 归一化:把 0~255 的像素值压到 0~1,避免大数值把模型激活函数推到饱和区 image = image.astype(np.float32) / 255.0 # 5. 增加 batch 维:模型推理接口要求输入至少是四维 (batch, h, w, c) return np.expand_dims(image, axis=0)

这段代码逻辑上分成三层:前面是物理图像的读取和色彩校正,中间是几何和亮度归一化,最后是数据结构对齐。第 3 步的 INTER_AREA 是 cv2.resize 里适合缩小图像的插值方式,比默认的双线性在叶片纹理上保留更多边缘信息;如果你要把小图放大,反而应该用 INTER_CUBIC。第 4 步有没有做,直接影响迁移学习效果——预训练模型大多是在归一化后的输入上训练的,直接喂 0~255 的原始像素,第一层卷积的响应会全部偏大,训练起来收敛慢,甚至 logits 溢出。

第 5 步是给 model.predict() 用的。Keras、PyTorch 甚至是 OpenCV 自带的 dnn 模块,推理接口都要求一个 batch 维,单张图也要扩成四维。记住这个函数,后面你拿到的所有源码包都能套用。

2.2 为什么预处理首选 HSV 颜色空间:病斑分割的阈值经验

叶片病害里,黑星病初期是橄榄绿色斑点,黑腐病是黑褐色腐烂斑,锈病是橙黄色粉状孢子堆——这些差异在 RGB 空间里容易被光照和反光干扰,转到 HSV 之后,色调(H)分量对不同颜色病斑的区分干净得多。这就是几乎所有这类源码都会加一段 HSV 处理的原因。

# 转到 HSV 颜色空间 hsv = cv2.cvtColor(leaf_bgr, cv2.COLOR_BGR2HSV) # 病斑区域阈值:黄褐色到深褐色,H 在 10~35,S 和 V 取中间偏上 lower_brown = np.array([10, 40, 40]) upper_brown = np.array([35, 255, 255]) disease_mask = cv2.inRange(hsv, lower_brown, upper_brown) # 形态学闭运算:填补病斑内部的小空洞,让区域连续 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) disease_mask = cv2.morphologyEx(disease_mask, cv2.MORPH_CLOSE, kernel)

这段代码的核心是 inRange 的两个阈值数组。lower_brown 的 H=10 近似黄褐色起点,H=35 覆盖到深褐;S 和 V 设置 40 起步是为了把浅色背景和亮度太低的阴影排除出去。这组数是经验值,不是金科玉律——不同相机、不同光照拍出来的病斑,色调范围会整体平移。你拿到数据集后应该先抽十张病斑明显的图,用 Matplotlib 拖一个 HSV 滑块观察病斑落在哪个区间,再回来改阈值,别直接拿默认值跑。

有了 mask 之后还能算病斑面积占比。统计 disease_mask 中非零像素数除以整片叶子的像素数,这个比值可以直接作为病害严重程度的量化特征,输出到答辩用的报告里。很多系统只做“有没有病”的分类,加上这个面积比值,就把系统从“识别”提升到了“评估”的层次,这是后面第六章要展开讲的方向。

2.3 标注数据集在源码里怎么组织:目录结构、划分比例和标注格式

“带标注好的病害数据集”是这类源码包里最值钱的部分。常见做法是按类别分文件夹,因为 torchvision 的 ImageFolder 和 Keras 的 flow_from_directory 都直接吃这种结构,源码里不需要写多余的解析逻辑。如果你之前跑过 YOLOv8 训练自己的数据集,对这种目录组织一定不陌生。典型目录长这样:

dataset/ ├── train/ │ ├── apple_scab/ # 苹果黑星病 │ ├── black_rot/ # 黑腐病 │ ├── cedar_rust/ # 雪松苹果锈病 │ └── healthy/ # 健康叶片 ├── val/ └── test/

目录名就是类别名,也是模型输出层的类别顺序。这里有一个坑:Keras 的 flow_from_directory 按字典序排序类别,而 PyTorch 的 ImageFolder 按文件夹遍历顺序,两者不一定一致。也就是说同一批数据在 TensorFlow 里训出来的类别编号,和 PyTorch 里训出来的可能对不上。如果源码包的模型是用 TensorFlow 保存的,预测脚本里又用了 PyTorch 风格的顺序,那 100% 会出现“把黑星病识别成健康”的错位。

标注方式上,纯分类项目通常不需要画框,目录本身即标注;如果要搞“检测”,数据里才会出现 PASCAL VOC 的 xml 或者 COCO 的 json。标题写的是“识别检测系统”,如果源码包里有 xml/json,说明除了分类还有一个目标检测分支,脚本结构会比纯分类复杂不少。你拿到包后第一件事就是确认这一点:打开操作说明,看它说的“识别”是只输出类别还是要把病斑位置框出来。两者对应的模型文件格式通常是不同的——纯分类是 h5/pth 这样的全图分类权重,检测则可能是 YOLO 权重或 Faster RCNN 系列。

数据划分比例上,毕设项目里 7:1.5:1.5 和 8:1:1 都常见。样本量不大的时候,我一般倾向 8:1:1,因为验证集和测试集各占 10% 足够评估,再留更多就是浪费训练数据。你需要做的第一件事是数每个类别的图片张数。如果某一类只有几十张而其他类有几百张,那训练时一定要开类别权重,否则模型很快会退化成“全判健康”的懒惰分类器——这会在第五章的避坑里再细说。

3. 把源码包跑通:python 环境、opencv 依赖和最小预测命令

很多毕设源码包做出来是在作者自己电脑上跑的,换台机器跑不通十有八九是环境问题。操作说明里通常会给一页环境配置,但最常见的失败不在包名,而在版本。这一章用最小步骤带你把环境搭起来、目录认清、预测跑通。

3.1 环境版本怎么搭:python 3.8~3.10 配 opencv-python 的常见组合

装 Python 就一条原则:从官网下载安装包,别用乱七八糟的一键安装渠道。我一般建议先装 Python,再建虚拟环境,最后用 pip 安装依赖。Python 版本不要追新:opencv-python 对 Python 3.11 之后的版本支持会滞后,尤其某些带 dnn 或 contrib 特性的旧版本轮子不一定有匹配包。最不容易出问题的组合是 Python 3.9 或 3.10,配 opencv-python 4.6 到 4.8 这样的大版本。下面是一组典型命令:

# 进入解压后的项目根目录 cd apple_leaf_disease # 创建虚拟环境,命令在 Windows/macOS/Linux 通用 python -m venv venv # 激活虚拟环境(Windows 用 venv\Scripts\activate) source venv/bin/activate # 优先按项目自带的 requirements 安装,装不上再单独处理单个包 pip install -r requirements.txt

建虚拟环境而不是直接 pip install 到全局,是因为装错版本有后悔药——环境删掉重来,不会污染全局。requirements.txt 里一般会有 opencv-python、numpy、pillow、scikit-learn、matplotlib,以及模型框架对应的包(tensorflow 或 torch 二选一)。如果 pip 在某个包上报错,把那一行版本号去掉重装即可,不必整个环境推倒重来。另外,你很可能遇到两个选择:opencv-python 和 opencv-contrib-python。毕设这个体量基本不需要 contrib,装最普通的 opencv-python 就够,contrib 里那些 SIFT、ORB 特征点在叶片病害场景下作用有限,别为了一个炫酷功能给自己增加维护成本。

验证环境是否装对,用一条命令:

python -c "import cv2; print(cv2.__version__); import numpy; print(numpy.__version__)"

如果输出两个版本号,说明 cv2 和 numpy 都就绪;如果 import cv2 直接抛 ModuleNotFoundError,那 95% 是没有装 opencv-python,先执行 pip install opencv-python 再验证。

注意:如果操作说明里明确写了 Python 版本要求,以说明为准,不用强行降级折腾。

3.2 解压后先看目录:源码、数据集、模型和说明文档的分工

解压一个毕设源码 zip,我最怕的就是直接双击 train.py。正确顺序是先把目录结构过一遍。通常这类包会包含这些部分:

文件/目录作用运行时机
train.py训练脚本,读数据集产出模型权重你想重新训练时
predict.py单图推理脚本日常识别
gui.py图形界面入口展示用
models/*.h5/pth/onnx预训练好的模型文件predict 时加载
dataset/标注好的病害图片训练和测试
requirements.txt依赖清单装环境
操作说明/README作者写的运行手册先读这个

这里有个判断技巧:先看操作说明里“模型文件”那一节,确认权重格式。Keras 老格式是单个 .h5,新版是 .keras;PyTorch 是 .pth 或 .pt;ONNX 是 .onnx。格式不同,对应的加载代码完全不同。如果操作说明没写清楚,你可以在源码里搜“load_model”或“torch.load”,看看它加载了什么后缀的文件,就能反推出模型类型。千万别根据文件夹名字猜,模型加载部分最容易被改名误导。

模型加载的语句通常长这样:model = tf.keras.models.load_model("models/leaf_model.h5"),或者 model = torch.load("models/leaf_model.pth", map_location="cpu")。你要是看到 torch.load,注意它默认会加载到 GPU 上,没 GPU 的电脑必须加 map_location="cpu",否则会报显存相关错误。

3.3 用最小命令跑一次预测:从 venv 到输出结果图

环境就绪、模型文件确认存在之后,最小操作就是跑一次推理。源码包如果带 GUI,我建议先不要开 GUI,而是找 predict.py 这种命令行脚本,它能更快暴露问题。运行方式一般是:

# --image 后跟一张测试图片,具体参数名以源码 argparse 里为准 python predict.py --image dataset/test/black_rot/sample_01.jpg # 常见输出:类别名 + 置信度,并保存一张带标注的结果图

predict.py 的 argparse 里通常有 --image、--weights、--save_dir 三个参数。--weights 没指定时会拿默认路径的模型文件;--save_dir 控制结果图输出到哪个目录,避免覆盖原图。如果脚本成功,你会看到类似 predicted class: black_rot (0.956) 的输出,并在指定输出目录下看到一张画了框或标了标签的图。如果报错,优先看错误栈最下面三行——绝大多数情况下要么是路径不存在,要么是模型文件加载失败,要么是输入尺寸不对。

跑通预测这个动作本身不需要 GPU,CPU 就够,因为推理单张图只是若干次矩阵乘法。真正吃资源的是训练:如果你打算重新训练,下面一章的参数话题就绕不过去了。

4. 参数调优:让识别精度从“能跑”到“能答辩”

答辩时老师最爱问的不是“你怎么跑通的”,而是“这两个参数为什么这么设,改了会怎样”。所以调参不只是为了提高精度,更是为了给自己留出可以说出道理的空间。这一章只讲最容易见效的几组参数。

4.1 OpenCV 预处理参数:resize 尺寸、滤波核与阈值的经验取值

resize 尺寸必须等于模型输入,这是硬约束。常见 CNN 输入是 224x224(ResNet、VGG)或 256x256(部分 Inception),你只要在加载模型后打印一下模型的 input shape 就能确认。插值方式也会轻微影响边缘锐度,下采样用 INTER_AREA,上采样用 INTER_CUBIC,这是一条不容易出错的经验。

# 打印模型输入尺寸,决定 resize 的目标值 import cv2 from model_utils import load_model model = load_model("models/leaf_model.h5") # Keras 模型可以这样看输入 print(model.input_shape)

滤波核方面,GaussianBlur 的核大小写成 (3,3) 就够,别迷信更大。核从 3 加到 5,噪声确实多滤掉一点,但病斑边缘也会被提前抹平;如果病斑本身就是小斑点,核越大越分不清。阈值方面,HSV 分割的阈值前面已经给了一组起点,这里补充一个验证技巧:把每张训练图的病斑区域像素值打印成直方图,看 H 分量的波峰位置,再反向修正阈值数组。如果 H 分量波峰在 20 附近,那 [10,35] 是合理的;在 45 附近,就要把 upper 改成 55。

还有两个容易被忽略但很影响结果的小参数:cv2.adaptiveThreshold 的 blockSize 和 C 值,它们用于把叶片从背景分离。blockSize 必须是奇数,一般取 11 或 15;C 是偏置项,取值 2 到 10,越大越保守。如果你发现叶片边缘被裁掉,就把 C 调大一点。

# 光照不均时,Otsu 自动阈值未必好使,不如手动 HSV + inRange _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

Otsu 自动阈值更适合整张图光照一致的情况;光照不均的叶片图,还是手动调 HSV 的 inRange 更靠得住。OpenCV 预处理看起来琐碎,但这些参数直接决定模型输入质量,远比后期调学习率更直观。

4.2 模型训练超参:学习率、batch size 和 epoch 的起点方案

如果你打算重新训练模型,而不是直接用作者给的权重,超参数建议从保守组合开始:Adam 优化器,初始学习率 1e-3,batch size 16 或 32,训练 30 到 50 个 epoch,配合早停。数据量不大时,学习率 1e-3 是安全的起点;如果损失在第一个 epoch 就发散到 nan,把学习率降到 1e-4。

# 以 Keras 为例的一组可起步的编译与训练配置 model.compile( optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"] ) history = model.fit( train_generator, validation_data=val_generator, epochs=50, batch_size=16, callbacks=[ tf.keras.callbacks.EarlyStopping( monitor="val_loss", patience=5, restore_best_weights=True ) ] )

batch size 的选取原则是:能放进显存或内存的前提下取大一点,但毕设数据集通常只有几千张,16 或 32 的差异不大,反而是数据增强的影响更明显。后面如果验证集准确率上不去,先别急着调学习率,回来检查是不是类别不均衡,以及增强有没有过度扭曲叶片纹理。patience=5 的意思是连续 5 个 epoch 验证损失不降就停,并且恢复最优权重,这个回滚机制就是后悔药。

迁移学习在这个方向几乎是必选动作:拿 ImageNet 预训练的权重初始化模型,冻结前几层,只训练最后几层全连接,学习率降到 1e-4,通常几十个 epoch 就能收敛到 90% 以上。完全从零训练一个小型 CNN 不是不行,但需要的数据量和调参时间至少要翻两倍,答辩时老师也很容易用“为什么不用迁移学习”把你问住。

4.3 用混淆矩阵和准确率判断模型有没有真的学会

准确率是最容易骗人的指标。当四类数据比例是 8:1:1:1,模型全判第一类也有 80% 的准确率,看起来很高,实际是废物。所以评估阶段必须做两件事:输出混淆矩阵,并单独看每一类的召回率。

# 用 sklearn 直接生成混淆矩阵和每一类的分类报告 from sklearn.metrics import confusion_matrix, classification_report # y_true 是真实标签下标列表,y_pred 是模型 argmax 后的预测下标列表 cm = confusion_matrix(y_true, y_pred) print(cm) report = classification_report( y_true, y_pred, target_names=["apple_scab", "black_rot", "cedar_rust", "healthy"], digits=3 ) print(report)

读混淆矩阵时盯两个位置:主对角线是否明显高于其他格子,以及哪两类最容易互相混。苹果叶病害里,黑星病初期和健康叶最容易混,因为病斑还没变褐;锈病和黑腐病颜色接近也容易混。如果这两类互相错判,说明模型学到的主要是颜色特征,缺少纹理特征,下一步该在预处理时强化边缘信息,比如加一个 Sobel 梯度通道。

如果某类召回率明显偏低,基本可以断定训练数据里它的样本最少或最难分。解决方法是给这类加权重,或者在数据增强时对它做更多样的扰动。这是评估环节给你指出的改进方向,比无脑调参有依据得多。把这些指标截图放进答辩 PPT,比贴十张运行截图都管用。

5. 避坑指南:数据集、模型文件和 opencv 依赖的 4 个典型翻车现场

前面几章提供的是正常路径,但这套源码包真正耗时间的环节往往是异常路径。下面四条是这类项目里出现频率最高的现场,每条我都按“现象 → 原因 → 解决”写清楚,也算是我带毕设这几年攒下的血泪经验浓缩。

5.1 import cv2 直接报 ModuleNotFoundError:环境没对

现象:启动预测脚本,第一行就红字:ModuleNotFoundError: No module named 'cv2'。

原因:绝大多数情况是虚拟环境没有激活就执行了 pip install,包装进了全局;或者 requirements.txt 里写的是 opencv 而不是 opencv-python,系统装的是别的名字。还有一个隐蔽原因是 Python 版本太新,opencv-python 还没发布对应 wheel,pip 会尝试从源码编译然后失败。

解决:先激活虚拟环境再执行 pip install opencv-python。装完后输入 python -c "import cv2; print(cv2.version)",能打印出 4.x 就没问题。如果仍然装不上,把 Python 降级到 3.9 或 3.10,别折腾源码编译——顺便说一句,别闲着去走 opencv 的 cmake 编译步骤,除非你的代码真的用了 contrib 模块,否则编译一次两小时,对毕设没有任何好处。

5.2 预测结果一直指向同一个类别:索引映射与类别不平衡问题

现象:不论输入哪张图,输出总是“healthy”或固定另一个类,且置信度接近 1。

原因:两个方向要分开看。一是类别索引映射错位:训练时用 ImageFolder 按目录字母序生成 class_idx,预测脚本里却用了一个手写的列表,顺序对不上,于是模型其实分对了,但脚本把下标翻译错了。二是数据极端不均衡:训练集里 healthy 占了 85%,模型学到的捷径就是“不管什么都猜 healthy”,因为它能最小化整体 loss。

解决:先在源码里打印模型输出的类别索引字典,再和 data/train 下的目录顺序逐一比对,修正 label_map。然后做一次类别数量统计,如果最大类是最小类的十倍以上,训练时加 class_weight,或者在采样时对小类做重复采样。改完这两个地方,预测结果一般会恢复。

5.3 cv2.imread 读不了中文路径图片:预处理脚本里加一行解码

现象:脚本跑起来不报错,但 cv2.imread 返回 None,程序在下一步 cvtColor 时崩掉;或者数据集放在 Windows 桌面上,路径里带“毕设”两个字,就出现大量警告。

原因:OpenCV 的 imread 在 Windows 上对非 ASCII 路径的处理一直有问题。它在底层调的是 C 风格文件接口,传中文字符串容易失败。macOS 和 Linux 上少见,但国内毕设数据集几乎都放在中文目录下,所以这个坑命中率极高。

解决:不用 imread,改用 np.fromfile 配合 imdecode,这是公认的兼容方案,直接替换读取函数即可:

import cv2 import numpy as np def imread_unicode(path): # np.fromfile 以二进制把文件读成数组,绕开 imread 的路径解析问题 data = np.fromfile(path, dtype=np.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)

然后把这个函数统一替换源码里所有 cv2.imread 调用。同时在项目根目录跑一个批量改名脚本,把数据集路径改成英文,双保险。还有一点,路径里的空格也会让部分解析逻辑出问题,英文路径加下划线最省心。

提示:替换 imread 后,记得把脚本里所有依赖 cv2.imread 返回非 None 的判断同步更新,否则返回值从 None 变成 ndarray,类型判断逻辑会失效。

5.4 训练到一半卡死或者直接被系统杀掉:batch size 和损坏图片在捣乱

现象:训练在第一个 epoch 之后进程消失,没有任何错误堆栈,或者 Jupyter 内核直接重启;推理时偶尔也会卡住不动。

原因:要么是 batch size 设得太大,内存或显存被吃满;要么是数据加载方式在频繁读磁盘小文件,IO 成为瓶颈;还有一种常被忽略的情况是数据集中混进了损坏图片或零字节文件,在数据加载的 decode 环节抛了异常但被生成器吞掉,线程就卡死在原地。

解决:先把 batch size 降到 8 或 16 验证训练能推进,再用 opencv 批量检查一遍所有图片能否被正常解码:

import cv2 import os bad_files = [] for root, _, files in os.walk("dataset"): for name in files: if not name.lower().endswith((".jpg", ".jpeg", ".png")): continue path = os.path.join(root, name) # 只做解码检查,不关心图片内容 img = cv2.imread(path) if img is None: bad_files.append(path) print(f"损坏或不可读文件数: {len(bad_files)}") for f in bad_files[:10]: print(f)

这段批量检查代码走一遍全量数据集,把读不出来的图列出来,然后手动删掉或重新导出。数据加载侧,优先考虑把所有图片在训练前预缩放到模型输入尺寸并打包成 .npy 或 TFRecord,训练时就是纯内存读取,速度会快好几倍,也避免边训练边读图时 IO 波动。

6. 给毕设加分:用 ROI 提取和可视化报告让识别系统更完整

6.1 用 OpenCV 轮廓检测自动裁出叶片区域

如果你的源码包里的识别脚本是整张图直接送进模型,那当背景里有土、草、手指的时候,模型很容易被环境干扰。一个成本不高的改进是在预处理时先用 HSV 绿色掩膜找出叶片主体,再裁出 ROI 后送模型。这段逻辑用 OpenCV 写不超过 15 行:

# 分离绿色叶片与背景,再裁出主体区域 hsv = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2HSV) # 绿色范围,叶片偏黄绿时可把 H 上限放宽到 80 mask = cv2.inRange(hsv, np.array([35, 30, 30]), np.array([85, 255, 255])) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((7, 7), np.uint8)) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: x, y, w, h = cv2.boundingRect(max(contours, key=cv2.contourArea)) leaf_crop = image_bgr[y:y+h, x:x+w]

这段的核心是 RETR_EXTERNAL 只取最外层轮廓,避免叶子边缘锯齿或虫洞被当作多个轮廓;boundingRect 取最小外接矩形。裁出来的区域再送进前面 2.1 的预处理函数,背景干扰被压到最低。

6.2 输出带病斑标记和面积统计的识别报告

把识别结果从“一行文字”变成“一张带框的图 + 面积占比”,放进论文和 PPT 里说服力完全不同。下面这段把病斑 mask 的像素比例和类别标签一起写到结果图上:

# 在裁好的叶片 ROI 上画出病斑轮廓并标注类别与面积占比 scale = disease_mask.shape[0] * disease_mask.shape[1] ratio = cv2.countNonZero(disease_mask) / scale # 找病斑轮廓并画框,颜色用红色,线宽 2 contours, _ = cv2.findContours( disease_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) for c in contours: x, y, w, h = cv2.boundingRect(c) if w * h < 50: # 过滤太小的噪点区域 continue cv2.rectangle(leaf_crop, (x, y), (x + w, y + h), (0, 0, 255), 2) label_text = f"{class_name} | 病斑面积占比: {ratio * 100:.1f}%" cv2.putText(leaf_crop, label_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) cv2.imwrite("output/result.jpg", leaf_crop)

这组代码里 w*h<50 是去掉单个像素噪点的经验阈值,具体数值可随图像分辨率调整。病斑面积占比是个连续量,它让分类模型输出的“患病”直接升级成“病情程度”,老师看到这个,答办难度会低很多。

我现在拿到任何图像分类的源码包,第一件事就是在模型加载和预测之间把类别映射表打印出来逐行核对一遍。这个习惯帮我避开了至少两次答辩现场的翻车。这套流程从环境到参数再到这里,每一步都是有据可查的;如果你照着重跑一遍,遇到最多的其实不是模型问题,而是路径和依赖问题。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询