简介:基于机器学习实现的农作物病虫害识别系统,是面向计算机相关专业毕业生和学习者的完整毕业设计项目,涵盖源码与配套数据集,经导师指导并调试通过,可直接用于毕业设计、课程设计或期末大作业。项目结合Python与机器学习技术,围绕病虫害图像识别流程,提供模型训练、预测评估及前端展示等模块,适合正在选题或需要项目实战练习的学生参考与二次开发。包体共477个文件,压缩包约82.01MB,主要包含Python源码、html/js/css前端页面、图片与gif演示素材、txt说明文档,以及sqlite数据库和pth模型权重等类型,目录结构清晰,便于按模块查阅和运行。目前已有1264人学习下载,项目经过严格调试并附带数据集,能帮助读者快速搭建运行环境,理解从数据处理、模型构建到界面交互的完整实现思路,是一份具备较高完成度和实用性的毕业设计参考资料。
1. 农作物病虫害识别系统:机器学习毕设怎么才算真正“跑通”
做人工智能方向毕业设计,最怕的是答辩时被问一句“换一批数据还能跑吗”就卡壳。这套基于机器学习实现的农作物病虫害识别系统,是真正带你走完数据到模型全链路的毕设资源:解压后有组织好的数据集、可执行的源码,以及一整套从预处理到训练评估的代码逻辑。它解决的不只是识别某几种病害,而是让一个机器学习项目在有限样本下能被持续迭代和复现。适合两类人:想交一份完整毕设的学生,和想找带数据项目入门机器学习图像分类的开发者。以下从数据目录开始拆。
2. 数据集组织与预处理:目录结构、标签编码与增强策略
2.1 数据集的目录结构与类别映射
拿到压缩包解压后,首先要搞清楚图片按什么规则放在哪里。多数毕设数据集采用按类别建文件夹的方式,例如 train/ 下每个类别一个子目录,子目录名就是类别名。这是最直观的格式,也是很多开源数据集沿用的习惯。我一般会先写一个扫描脚本,把目录结构打印出来,确认类别数量和每个类别的样本数。很多翻车问题都出在这一步:有人假设了 5 类,结果数据里混进了背景图、重复图,甚至有两类其实是同一病虫害的不同严重程度。
import os from collections import Counter data_root = "data/train" class_counts = Counter() for class_name in sorted(os.listdir(data_root)): class_path = os.path.join(data_root, class_name) if not os.path.isdir(class_path): continue n = len([f for f in os.listdir(class_path) if f.lower().endswith((".jpg", ".jpeg", ".png"))]) class_counts[class_name] = n print("类别数量:", len(class_counts)) for c, n in class_counts.most_common(): print(f"{c}: {n}张")这段代码做的事很笨但很关键:遍历 train 目录下每个子目录,统计图片数量并输出。参数说明:.jpg .jpeg .png后缀过滤是必须的,因为有些数据集混入了.txt或.db文件;Counter排序后能一眼看出类别是否严重失衡。如果某个类别只有几十张,而其他类别有上千张,后面训练就会很容易出现偏向多数类的模型。
统计完类别分布后,需要把类别名映射成数字标签。常见做法是维护一个字典,按类别名字母序排序生成索引;不建议直接拿目录名当标签传给分类器,因为字符串类型在 sklearn 的部分模型里虽然能算,但在输出混淆矩阵和做交叉验证时会带来不必要的麻烦。类别名里如果带中文,更建议在项目一开始就统一重命名成拼音或数字,这个习惯能省掉后面一堆诡异问题,尤其是 Windows 环境下 OpenCV 读中文路径图片常常静默失败。
class_names = sorted(os.listdir(data_root)) class_to_idx = {name: i for i, name in enumerate(class_names)} print(class_to_idx)映射字典确定后,建议立刻存成 JSON 文件,后续训练、预测和答辩展示都要复用同一份映射,避免各模块自己定义标签导致结果对不上。我见过不止一个项目,训练时用 A 顺序、预测时用 B 顺序,最后的输出结果全部错位,排查了很久才发现是标签映射不一致。
2.2 图像加载与统一尺寸
农作物叶片照片的来源很杂:手机拍的大头照、扫描仪扫的标本图、从论文里截图翻拍的。这些图片的尺寸、通道数、光照条件都不一样,直接进模型是不可能完成的任务。预处理的第一步是把所有图片统一到固定尺寸,比如 224×224 或 256×256。选 224×224 是图像分类任务的常用值,视觉上足够保留病斑细节,特征提取的计算量也可控。
用 OpenCV 加载时要注意一个高频坑:它读出来的是 BGR 顺序,不是常用的 RGB。做颜色特征提取时如果忘记这一步,病斑的颜色分布会被整体扭曲,后续所有结果都建立在一个错误的前提上。虽然最后训练出来的模型在验证集上也有可能“看起来正常”,但换到真实拍摄环境下预测结果容易全乱。
import cv2 def load_image(path, target_size=(224, 224)): img = cv2.imread(path) if img is None: raise ValueError(f"无法读取图片: {path}") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, target_size, interpolation=cv2.INTER_AREA) img = img / 255.0 # 归一化到 [0,1] return img逻辑说明:先imread读图,再用cvtColor把 BGR 转成 RGB;resize到统一尺寸,缩小时用INTER_AREA能保留更多细节、减少锯齿;最后除以 255 做归一化。归一化对机器学习模型很重要,SVM 这类对特征尺度敏感的分类器,特征值范围不一致时,数值大的特征会主导距离计算,等于人为给某些通道加了过高的权重。如果遇到无法解析的损坏图片,cv2.imread会静默返回None,所以代码里做了显式检查,能帮你从几百张图里快速找到损坏文件,而不是等训练时报一个莫名其妙的维度错误。
2.3 数据增强与样本均衡
很多入门项目会忽略数据增强,直接在原始图片上训练。对于深度学习模型,增强是标配;对于经典机器学习加手工特征,增强同样有效。叶片照片的旋转、水平翻转、亮度变化不会改变病虫害类别,但这些操作能让特征提取器对拍摄角度和光照更鲁棒。不过增强要克制,不要用随机裁剪、椒盐噪声这类可能把病斑裁掉的变换,因为病斑位置和面积本身就是识别特征。
import numpy as np def augment(img): # 水平翻转 if np.random.rand() < 0.5: img = np.flip(img, axis=1) # 随机旋转90度 k = np.random.randint(0, 4) img = np.rot90(img, k) return img参数说明:np.random.rand() < 0.5控制是否翻转,让增强样本与原图各占一半;np.random.randint(0, 4)随机选择旋转 0/90/180/270 度,叶片不像手写数字,旋转 180 度后仍然是同一类病虫害。注意不要做随机翻转加旋转的组合,实测下来 270 度和水平翻转叠加会把叶片纹理方向完全打乱,小数据集下反而干扰训练。
样本不均衡的处理要和增强配合起来。某类样本只有 80 张,另一类有 600 张,最简单有效的办法是先对少数类做几轮过采样复制,再配合增强生成新样本。不要指望class_weight参数能完全解决,先让每个类别的样本量在数量级上一致,模型的行为会稳得多。类别严重不均衡时,SVM 的决策边界会被多数类主导,少数类的支持向量被挤压在很小的区域,预测时很难命中。
3. 特征提取与模型选型:HOG、颜色直方图与 SVM 的搭配
3.1 为什么用经典机器学习而不是直接上 CNN
这是答辩时几乎必被问到的问题,解释得当能体现你对技术选型的理解。经典机器学习方案用 HOG、颜色直方图、LBP 等手工特征,配合 SVM 或随机森林,在小数据集上往往能获得不错的效果,训练时间短,特征可解释。计算机视觉和机器学习之间的界限在这里体现得很清楚:CNN 是端到端自动学特征,而经典方案需要人类知识介入,把“病斑什么颜色、叶片什么纹理”变成显式的特征向量,这对答辩者反而是优势,因为你可以说清楚每个特征在干什么。
另一个现实理由是环境约束:评选现场不一定有 GPU,CPU 上训练随机森林或 SVM 用分钟级衡量,而 CNN 的完整训练可能需要几个小时甚至更久。用经典机器学习方案,答辩演示完全可控,不用赌现场机器的性能。这不是说深度学习不好,如果数据量有成千上万张、有 GPU 可用,yolov8 这类目标检测方案自然更强,但拿几百张图去跑 CNN,过拟合风险和调参成本都太高。
3.2 特征提取:HOG、颜色直方图与 LBP
病害识别的视觉线索主要有三类:病斑的颜色、叶片的纹理、病斑的形状。对应到特征上,颜色直方图捕捉颜色分布,HOG 捕捉局部梯度方向(也就是边缘和形状),LBP 捕捉纹理模式。三种特征互补,拼接后作为一条样本的完整特征向量。实际项目里我会先跑 HOG 加颜色直方图的组合,LBP 作为锦上添花,因为 HOG 加颜色的组合已经能区分大部分常见病害,加 LBP 会把特征维度推高不少,小数据集下反而容易过拟合。
from skimage import feature, color import numpy as np def extract_features(img_rgb, hog_cell=16, bins=32): # 灰度图用于 HOG 和 LBP gray = color.rgb2gray(img_rgb) # HOG 特征 hog_feat = feature.hog(gray, pixels_per_cell=(hog_cell, hog_cell), cells_per_block=(2, 2), feature_vector=True) # 颜色直方图 hist_feat = [] for ch in range(3): hist, _ = np.histogram(img_rgb[:, :, ch], bins=bins, range=(0, 1)) hist_feat.extend(hist) hist_feat = np.array(hist_feat) / img_rgb.shape[0] / img_rgb.shape[1] # 拼接 X = np.concatenate([hog_feat, hist_feat]) return X参数说明:pixels_per_cell是 HOG 的细胞大小,16 表示 16×16 像素一个细胞,细胞越小特征越细但对噪声也越敏感,实测 8 时 HOG 维度会翻好几倍,训练时间明显变长但准确率提升有限;bins=32是颜色直方图的柱数,柱数太多会稀疏、太少会丢失区分度,32 对三通道全图来说足够。代码最后把直方图除以图片宽高,是做面积归一化,让不同分辨率的图片直方图可比。
特征向量拼接后维度往往很高,HOG 在 224×224 图上产生大约 1000 维以上特征,加上颜色直方图的 96 维,直接喂给线性模型没问题,但一定要做标准化。需要说明的是,特征标准化不是可选项,SVM 这类模型在未标准化特征上训练,决策边界会被数值范围大的维度主导。如果你发现验证准确率忽高忽低,先去排查标准化这一步,而不是怀疑模型参数。
3.3 模型选型:SVM 与随机森林的取舍
对图像手工特征,默认的两个候选是带 RBF 核的 SVM 和随机森林。SVM 在小样本高维特征下表现稳定,决策边界清晰,是这类任务最常见的基线;随机森林对特征缩放不敏感,训练更快,能输出特征重要性,做分析和答辩展示很方便。我的习惯是先两个模型都用默认参数跑一遍,记录准确率和 F1,再对表现更好的那个做参数搜索。
from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier svm_model = SVC(kernel="rbf", C=10.0, gamma="scale", probability=True, random_state=42) rf_model = RandomForestClassifier(n_estimators=200, max_depth=None, random_state=42)代码逻辑是初始化两个候选模型。值得说明的是probability=True会让 SVM 计算概率估计,代价是训练变慢,但对答辩演示很重要,因为你需要输出“某种病害置信度 87%”这种表达;gamma="scale"是 sklearn 推荐的自适应缩放方式,避免手动调 gamma 的麻烦。随机森林的n_estimators取 200 已经足够,更多树在小数据集上带来的收益不明显,反而拖慢调参速度。
正式选型流程是:先对比两个模型在验证集上的 F1,SVM 通常在小数据集上略胜一筹,但如果类别之间有大量特征重叠,随机森林可能更稳。选型不是拍脑袋,而是用一个快速验证脚本跑完,把数字摆在面前。这样做在答辩时也站得住脚,你可以说“我对比了 SVM 和随机森林,最终选择验证集表现更好的方案”,这是完整的技术决策过程。
4. 训练评估与调参:分层划分、Pipeline 与混淆矩阵
4.1 分层划分数据
数据划分不是简单train_test_split就结束。病虫害数据集的类别分布通常不均衡,普通随机划分可能让某个小类在训练集或验证集中只有几张甚至没有样本,这时验证结果是完全不可信的。分层采样的核心逻辑是:每个类别在训练集和验证集中的占比与原始数据集一致,这样评估结果才有意义。
from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 )stratify=y是关键参数,它按 y 的类别比例做分层采样。random_state=42固定随机种子让结果可复现,答辩时被问到“为什么结果不同”时,这一步能省很多解释。划分的时机也有讲究:一定要在特征提取完成后、标准化之前划分。如果先对整个数据集标准化再划分,验证集的信息已经混进了标准化参数,这种泄漏很难肉眼发现,但会让模型泛化能力的评估虚高。
4.2 训练脚本与 Pipeline
特征提取是纯 Python 循环,训练用 sklearn 的 Pipeline 打包,避免在预测阶段漏掉标准化这一环。很多人在训练时手动StandardScaler.fit_transform(X_train),预测时忘了对新图片做同样的标准化,结果模型直接瘫痪。Pipeline 的好处是:fit时在训练集上计算标准化参数,predict时自动用同一组参数处理新数据,这样换个新图片也不会忘记预处理。
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC pipeline = Pipeline([ ("scaler", StandardScaler()), ("svm", SVC(kernel="rbf", C=10.0, gamma="scale", probability=True, random_state=42)) ]) pipeline.fit(X_train, y_train) print("验证集准确率: {:.4f}".format(pipeline.score(X_val, y_val)))训练过程中重点关注的是训练集与验证集准确率的差值。如果训练集接近 100% 而验证集明显偏低,说明过拟合,需要降低 SVM 的 C 值、增大 HOG 的pixels_per_cell,或者增加增强样本。如果两者都低,问题多半在特征提取环节,比如颜色直方图没有做面积归一化,或者图片加载时通道顺序错了。
4.3 评估指标:准确率之外还要看什么
单看准确率在病害识别里很容易被误导。如果某种病害占数据集的 70%,一个全预测为该类的模型准确率也有 70%。对病虫害识别来说,漏检的代价很高——漏掉一种病害等于放任它扩散,所以精确率和召回率必须同时看。
from sklearn.metrics import classification_report, confusion_matrix y_pred = pipeline.predict(X_val) print(classification_report(y_val, y_pred, target_names=class_names)) print(confusion_matrix(y_val, y_pred))看报告的顺序我一般是这样:先看每个类别的 recall,找出哪个类别被严重漏检;再看混淆矩阵,确认漏检的样本通常被误判成了哪一类。如果两种病害频繁相互误判,说明它们视觉上确实相似,比如稻瘟病和胡麻叶斑病都是褐色斑点,需要针对性补充特征或样本。调参用网格搜索时,别一上来就全参数跑,对小数据集跑 C 在 1/10/100 三档、gamma 在 scale/0.01/0.001 三档足够,9 组组合在几分钟内跑完。再多的参数组合容易过拟合验证集,调出的高分对真实场景意义不大。
5. 避坑:图像、特征缩放与类别不平衡的五个常见问题
5.1 图像读取与通道顺序
现象:用 OpenCV 读取图片提取颜色特征后,训练出来的模型在验证集上准确率不低,但拿手机新拍的照片一预测,结果全乱。
原因:OpenCV 的imread返回 BGR 顺序,训练时忘了转 RGB,颜色直方图的通道语义和人眼看到的不一样;手机照片的 JPEG 压缩、白平衡和数据集里的图片又不一样,偏差被放大了。还有一个隐蔽情况:训练脚本转了、预测脚本没转,通道顺序不一致导致“训练时用的特征”和“预测时用的特征”根本不是同一套。
解决:加载后统一cv2.cvtColor(img, cv2.COLOR_BGR2RGB),并且在预处理函数里显式固定这个转换,不要依赖调用方记住。我把预处理逻辑单独抽成一个模块文件,所有脚本只能调用这个统一接口,不允许各写各的,这类问题就能被前置拦截。
5.2 特征缩放的泄漏与遗忘
现象:训练集上准确率 95%,验证集 80%,换成新图片直接掉到 50% 以下,像随机猜测。
原因:最常见是标准化参数泄漏,先对全数据集StandardScaler.fit()再划分,验证集的均值和方差已经混进训练过程;另一种是训练时手动标准化,预测时没有对新图片做同样的标准化处理。
解决:把 StandardScaler 和分类器一起放进 Pipeline,确保 fit 和 predict 走同一套流程。如果坚持手动标准化,就在项目一开始定义好一个preprocess()函数,训练和预测都调它。从那以后我再也不手动拼标准化步骤了,Pipeline 是更不容易出错的选择,等于给两个阶段上了同一把锁。
5.3 训练集上的“假高分”
现象:训练集准确率 100%,验证集准确率只有 60% 左右,差距巨大,加数据增强也没改善。
原因:小数据集上的典型过拟合。特征维度过高(HOG 上千维),样本量只有几百,SVM 的 RBF 核完全有能力记住所有训练样本。另一个隐蔽原因是数据本身有重复或高度相似的图片——同一片叶子的不同截图可能同时出现在训练集和验证集,这种情况下的验证结果水分很大。
解决:先检查数据集有无重复文件,用文件 MD5 去重;再看特征维度与样本量的比例,如果单样本维度超过样本数三分之一,考虑降低 HOG 的pixels_per_cell或先做 PCA 降维。过拟合本身不可怕,答辩时能说出验证集表现和过拟合应对策略,比“训练集 100%”更能体现工程判断。
5.4 类别不平衡导致的小类全灭
现象:多数类别的 F1 在 0.9 以上,但某个只有几十张样本的类别 recall 是 0,模型完全没有识别出该类。
原因:类别样本量差距悬殊时,SVM 的决策边界会被多数类主导,少数类的支持向量被挤压在很小的区域,预测时很难命中。数据增强如果不配合过采样,少数类被翻转旋转后仍然只有几十张,本质问题没有解决。
解决:先做少数类过采样复制,再用旋转翻转增强,让各类样本量趋近同数量级;同时给模型设置class_weight="balanced"。SVM 和随机森林都支持这个参数,两者的差别在于 SVM 的平衡权重会改变决策边界位置,随机森林则是影响叶子节点的分裂权重。做增强要克制,不要复制几十份相同的原图,那样只是让模型记住同一张图。
5.5 图片读取的静默失败
现象:日志没有报错,但最后统计的特征矩阵样本量比图片文件数少了十几条,训练倒是正常跑完了。
原因:cv2.imread对损坏图片、路径含中文的图片会静默返回None,后续往特征矩阵里塞数据时要么报错、要么跳过,而跳过的情况不会产生任何提示。这个问题在 Windows 上尤其常见,解压工具和解压路径里的中文目录名都会诱发。
解决:预处理环节加显式检查,读出的数组为 None 就抛出异常并打印文件名,单独建一个bad_files.txt记录下来。批量跑完先看这个文件,把所有读不出来的图修复或剔除,再做训练。路径含中文的图片,建议在项目一开始就统一重命名成拼音或数字。
6. 从 checkpoint 到单图预测:验证模型能不能被真实使用
6.1 加载模型做单张图片预测
训练完成的模型用joblib保存,预测时重新加载,并复用训练阶段定义的预处理函数。单张图片预测的完整链路是:读图、转 RGB、resize、归一化、特征提取、标准化(Pipeline 内部完成)、分类。我发现很多初学者在这里容易踩坑,把特征提取和分类分开写,结果两边的特征维度都对不上。
import joblib model = joblib.load("models/plant_disease.pkl") img_rgb = load_image("demo/test_leaf.jpg") feat = extract_features(img_rgb).reshape(1, -1) proba = model.predict_proba(feat)[0] top_idx = int(proba.argmax()) print("预测类别:", class_names[top_idx]) print("置信度: {:.2%}".format(proba[top_idx]))这个脚本是答辩演示时最常跑的。proba.argmax()取概率最高的类别,proba[top_idx]输出对应置信度。如果置信度低于 0.5,我建议在演示时把结果解释成“该类别疑似,需要人工复核”,这反而比全对更有说服力,因为农业生产场景里任何辅助决策系统都应该保留人工兜底。
6.2 结合混淆矩阵判断下一步改进方向
单图预测跑通后,把混淆矩阵可视化出来,粘贴到论文的实验分析章节里,能直观说明哪些病害容易被混为一类。比如稻瘟病和胡麻叶斑病都是褐色斑点,视觉特征高度相似,这是数据本身的难度,不是模型的缺陷。在论文里承认这一点,比硬撑着说准确率有多高更可信。
从那以后,我每次提交项目前,都会强制走一遍完整流程:从原始图片目录开始,重新执行预处理、训练、单图预测三步,确认没有遗漏任何一步转换。代码能跑通不是终点,从原始数据到最终预测结果的每一步都可复现,才算真正做完了一个机器学习项目。希望这次的拆解过程能让你少走弯路,把这个题目做成自己真正理解的东西。
本文还有配套的精品资源,点击获取