
简介基于Python语言的树叶识别系统源码是针对课程设计、期末大作业和毕业设计场景开发的完整项目适合Python初学者、计算机专业学生以及需要快速构建图像识别应用的开发者。系统以树叶图像为识别对象将图像处理算法与交互式界面整合在一起功能完善、界面美观、操作简单代码中附有详细注释并配有可交互的UI文件下载后只需简单配置环境即可运行非常适合作为高分大作业或入门级图像识别项目学习。压缩包采用zip格式大小仅6.98MB共包含4个文件2个Python脚本、1个UI界面文件和1个演示视频。脚本按功能拆分为核心识别与界面两部分UI文件对应界面布局演示视频直观呈现了树叶识别的完整流程与实际效果便于快速上手、对照调试和二次开发。目前已有98人学习下载资源轻量且目录清晰既可直接作为课程设计或期末大作业的完整方案也可帮助初学者理解Python图像识别与GUI开发的基本思路具有较好的实用价值。1. 拿到树叶识别源码包后第一步不是跑通而是拆开看基于Python语言的树叶识别系统源码演示视频.zip这个压缩包在课程设计资源里辨识度很高关键不在它用了多前沿的模型而在于它把「图像识别」这件事完整落到了一个带界面的桌面上Leaf.py负责特征提取和识别UI_Leaf.ui是 Qt Designer 画的界面UI_Leaf.py是转出来的界面代码外加一段演示录屏。对于要做期末大作业或者查重后想扩展的人来说最该先搞清楚的是四个文件之间的调用链而不是急着python Leaf.py。这套系统的典型价值在于它是传统视觉特征加分类器的方案没有深度学习依赖安装依赖少普通笔记本就能跑这恰恰是答辩时能讲清楚原理的优势。2. 从文件结构反推系统架构四份素材如何组成一个可运行的桌面应用2.1Leaf.py、UI_Leaf.py、UI_Leaf.ui三者的职责边界解压后你能看到典型的 PyQt 项目布局.ui文件是 Qt Designer 生成的界面描述文件本质是 XMLUI_Leaf.py是pyuic5把 XML 转出来的 Python 类里面只有控件的创建和布局代码没有任何业务逻辑Leaf.py才是真正干活的文件包含图像预处理、特征提取、分类器加载与预测。这种「UI 与业务分离」的结构在课程设计里属于加分项因为答辩时可以直接说Leaf.py里改识别逻辑不影响界面UI_Leaf.py里调整控件布局也不会碰算法。下面是四个文件的职责对照。文件类型核心职责修改频率Leaf.pyPython 脚本主程序入口、特征提取、分类推理高识别效果全看这里UI_Leaf.uiXML 描述界面的原始设计稿低改布局时需要UI_Leaf.pyPython 脚本界面控件的实例化代码由.ui自动生成低手动改会被重新生成覆盖树叶识别录屏.mp4演示视频展示系统运行效果和交互流程不修改2.2 主流程的时间线从点击按钮到显示识别结果运行Leaf.py后程序先初始化QApplication和主窗口主窗口类会继承UI_Leaf.py里生成的界面类Ui_MainWindow再通过self.setupUi(self)把控件绘制出来。用户点击「打开图片」按钮时槽函数弹出QFileDialog选择图片图片在QLabel上展示点击「识别」按钮时槽函数调用Leaf.py中的特征提取函数把图像喂给分类器再把结果写进状态栏或文本控件。这里的核心逻辑链是图像读取cv2.imread→ 预处理缩放、去噪、背景分割→ 特征向量生成 → 分类器predict→ 结果映射为树叶名称。这套链路里最容易出错的是特征维度不一致训练时提取的特征向量是 100 维识别时如果图像缩放尺寸不同导致特征数量不同predict会直接抛维度错误。所以源码里通常会在特征提取入口做一个统一的target_size参数我一般建议固定为(224, 224)或(256, 256)。2.3 运行前环境准备版本不对代码注释再多也白搭这套系统最常见的运行环境是 Python 3.8 到 3.10依赖主要是PyQt5、opencv-python、numpy如果分类器用到了 scikit-learn还需要scikit-learn。安装时最容易踩的坑是opencv-python和opencv-contrib-python同时存在导致命名冲突两个包的模块名都是cv2装在一起会出现AttributeError: module cv2 has no attribute dnn之类的诡异报错。建议用虚拟环境隔离安装命令如下python -m venv leaf_env source leaf_env/bin/activate # Windows 下改为 leaf_env\Scripts\activate pip install pyqt5 opencv-python numpy scikit-learnpyqt5负责 GUI 渲染opencv-python提供cv2模块做图像读写和颜色空间转换numpy用来做数组运算和特征向量拼接scikit-learn提供 SVM 或随机森林分类器。如果 demo 里用的是.pkl或.joblib后缀的模型文件说明分类器是提前训练好的你需要确认scikit-learn版本与训练环境一致否则加载模型时会报ModuleNotFoundError或ValueError。版本不匹配时优先尝试降低 scikit-learn 到 1.0.x这是加载旧模型最常见的修复路径。3. 树叶特征提取实战颜色直方图与轮廓形状参数如何组合成一个能分类的特征向量3.1 为什么这套系统可以用传统视觉方案而不必上深度学习树叶识别的难点在于同类叶片形态差异大、不同类之间又有相似性但对一个课程设计而言目标只是区分数据集里的几个类别传统特征完全够用。颜色特征能区分的类别需要叶片颜色有明显差异形状特征则能抓取叶缘、叶基、长宽比这些几何信息。两者的组合向量通常在几十到上百维SVM 或随机森林在这个维度下训练和推理都非常快CPU 上单张图片识别时间能控制在 100 毫秒以内。深度学习方案需要准备大量标注样本、训练时间按小时计、还要考虑 GPU这些对一个期末大作业来说成本过高。从答辩角度讲传统特征加 SVM 反而更容易把原理说到点子上老师更愿意听到「我提取了哪些特征、为什么这些特征对树叶分类有效」而不是「我调了个预训练模型」。3.2 颜色特征量化直方图是性价比最高的编码方式颜色特征最常见的做法是在 HSV 空间统计直方图而不是直接用 RGB。原因在于 HSV 把色相Hue、饱和度Saturation、亮度Value分离了光照变化主要影响 V 通道计算直方图时可以把 V 通道的权重压低或者干脆只统计 H 和 S 通道这样识别对光线强弱不敏感。绿叶在 H 通道大约落在 35 到 77 这个区间OpenCV 中 H 范围是 0 到 179黄叶、红叶会落在其他区间所以色相直方图天然有区分度。import cv2 import numpy as np def extract_color_histogram(image, h_bins32, s_bins32): # 转换到 HSV 空间 hsv cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 计算 H 和 S 通道的二维直方图 hist cv2.calcHist([hsv], [0, 1], None, [h_bins, s_bins], [0, 180, 0, 256]) # 归一化消除叶片面积差异带来的影响 cv2.normalize(hist, hist, 0, 1, cv2.NORM_MINMAX) return hist.flatten()calcHist的参数[0, 1]表示同时统计第 0 通道H和第 1 通道S[h_bins, s_bins]决定两个方向的直方图格子数32 乘 32 得到 1024 维向量。NORM_MINMAX归一化是必须的否则叶片大的样本直方图整体数值偏高分类器会误把「面积」当成特征。实际使用中如果发现识别结果偏色严重可以把h_bins调大到 64但维度过高会增加过拟合风险尤其当训练样本只有每类几十张时。3.3 形状特征从轮廓中提取面积、周长、圆形度和长宽比形状特征的提取分为三步图像灰度化 → 二值化分割背景 →findContours找轮廓。树叶在纯色背景下比较容易分割但背景如果有阴影简单阈值会失效建议先用cv2.GaussianBlur去噪再用cv2.Canny找边缘或直接用cv2.threshold的 Otsu 模式自适应选阈值。拿到最大轮廓后就能计算一系列几何参数。def extract_shape_features(gray_image): # 高斯模糊减少噪点Otsu 自动选择二值化阈值 blurred cv2.GaussianBlur(gray_image, (5, 5), 0) _, binary cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 查找轮廓RETR_EXTERNAL 只取最外层轮廓 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 取面积最大的轮廓通常是叶片主体 leaf_contour max(contours, keycv2.contourArea) area cv2.contourArea(leaf_contour) perimeter cv2.arcLength(leaf_contour, True) # 最小外接矩形长宽比能区分狭长叶与阔叶 rect cv2.minAreaRect(leaf_contour) width, height rect[1] if rect[1][0] rect[1][1] else (rect[1][1], rect[1][0]) aspect_ratio width / max(height, 1e-6) # 圆形度1 接近圆越小越不规则 circularity 4 * np.pi * area / max(perimeter * perimeter, 1e-6) return np.array([area, perimeter, aspect_ratio, circularity], dtypenp.float32)THRESH_BINARY_INV是因为树叶在亮背景上通常是暗色取反后树叶区域变成白色、背景变黑色这样findContours找的是叶子边界。RETR_EXTERNAL避免把叶片叶脉里的空洞也当作轮廓。area和perimeter受图像分辨率影响如果训练和识别时图像尺寸不一致需要把最终特征向量再做一次标准化或者干脆在预处理阶段统一缩放尺寸。aspect_ratio和circularity是尺度无关的比值能有效区分像柳叶这样细长和像荷叶这样圆润的叶片。3.4 特征拼接与归一化决定了分类器能不能收敛颜色直方图是 1024 维形状特征是 4 维直接拼接后数值量纲差距巨大面积可能是五位数而圆形度是 0 到 1 之间的小数。SVM 默认使用径向基核核函数依赖样本间的距离计算量纲不一致会让面积特征主导距离度量颜色和形状就白提了。因此拼接后的向量必须做标准化常见的实现有两种sklearn.preprocessing.StandardScaler或MinMaxScaler。from sklearn.preprocessing import StandardScaler def build_feature_vector(image): gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) color_hist extract_color_histogram(image) shape_feats extract_shape_features(gray) # 拼接前把浮点数统一为 float32 combined np.concatenate([color_hist, shape_feats]).astype(np.float32) # StandardScaler 在训练集上 fit 后保存测试时只做 transform return combined注意StandardScaler必须在训练集上先fit把每个维度的均值和方差存下来然后对训练集和测试集统一transform。课程设计里常见的错误是测试时重新fit导致分布不一致识别率大幅下降。如果源码里没有保存标准化器你可以自己补一个训练阶段scaler.fit(train_features)随后joblib.dump(scaler, scaler.pkl)保存识别阶段加载后只调用transform。这个细节在答辩时主动讲出来老师会觉得你真正理解特征工程而不是只会跑通代码。4. PyQt5 界面集成把识别逻辑挂到UI_Leaf.ui的按钮上才算一个完整的系统4.1 用 pyuic5 把.ui转成.py但别去手动改生成的代码UI_Leaf.ui里的控件布局和信号连接都可以直接用 Qt Designer 拖拽完成但程序运行时不直接读.ui文件PyQt5 有两种使用方式一种是用uic.loadUi()在运行时动态加载另一种是用pyuic5命令把.ui转换成.py。这套压缩包里的UI_Leaf.py显然是第二种方式生成的因为转换后代码里全是QtCore.QObject.connect或self.pushButton.clicked.connect(...)这类固定写法。pyuic5 UI_Leaf.ui -o UI_Leaf.py转换命令只需要指定输入文件UI_Leaf.ui和输出文件UI_Leaf.py。生成后的文件会包含一个Ui_MainWindow类内部有setupUi()和retranslateUi()两个方法。不要往生成的UI_Leaf.py里添加自己的识别逻辑因为一旦你改了.ui再执行上面的命令手动添加的代码会被整体覆盖。正确做法是在Leaf.py里定义一个主窗口子类继承Ui_MainWindow并扩展槽函数。4.2 界面控件与识别逻辑的对接方式Leaf.py的主窗口类结构通常是构造函数里先setupUi完成界面初始化再手动绑定按钮信号。比如界面里有一个QPushButton名为btn_open一个QLabel名为label_image一个QPushButton名为btn_identify还有一个QLabel或QTextEdit显示结果。from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox from PyQt5.QtGui import QPixmap from UI_Leaf import Ui_MainWindow import cv2 class LeafWindow(QMainWindow): def __init__(self): super().__init__() self.ui Ui_MainWindow() self.ui.setupUi(self) self.current_image None # 绑定按钮信号 self.ui.btn_open.clicked.connect(self.open_image) self.ui.btn_identify.clicked.connect(self.identify_leaf) def open_image(self): path, _ QFileDialog.getOpenFileName( self, 选择树叶图片, , Image Files (*.jpg *.png *.bmp)) if not path: return self.current_image cv2.imread(path) # 避免图像过大撑爆界面统一缩放到 QLabel 尺寸 pixmap QPixmap(path).scaled( self.ui.label_image.width(), self.ui.label_image.height()) self.ui.label_image.setPixmap(pixmap) def identify_leaf(self): if self.current_image is None: QMessageBox.warning(self, 提示, 请先打开图片) return # 调用特征提取与分类 feat build_feature_vector(self.current_image) label model.predict([feat])[0] self.ui.label_result.setText(f识别结果{label})clicked.connect是 PyQt5 最常用的信号槽连接方式按钮被点击时自动调用对应槽函数。QPixmap(path).scaled缩放图片到界面显示区域避免高分辨率图片把窗口撑变形。model是全局加载的分类器对象在Leaf.py的if __name__ __main__:块里用joblib.load加载不要在每次点击识别时重复加载否则界面会有明显的卡顿感。build_feature_vector返回的是二维数组形式model.predict要求输入必须是二维数组所以这里用[feat]包了一层。4.3 界面卡顿的坑不要在槽函数里做耗时推理如果识别一张图需要一两秒直接在当前线程里做特征提取和分类会导致窗口无响应因为你阻塞了 Qt 的事件循环。对于课程设计最简单的规避是把识别逻辑放到单独线程或者直接用QApplication.processEvents()强制刷新界面让用户先看到「识别中」的状态。更优雅的方案是用QThread把预测任务丢到子线程但涉及信号传递和线程安全复杂度会上升。对于树叶识别这种单张图片推理常见做法是先用QTimer.singleShot(0, ...)或者threading.Thread把任务放到后台。import threading def identify_leaf(self): if self.current_image is None: return self.ui.label_result.setText(识别中...) img self.current_image.copy() thread threading.Thread(targetself._run_identify, args(img,)) thread.daemon True thread.start() def _run_identify(self, img): feat build_feature_vector(img) label model.predict([feat])[0] # 槽函数在子线程里不能直接改界面需要信号回传主线程 self.signal_result.emit(str(label))threading.Thread启动一个后台线程daemon True保证主窗口关闭时线程自动结束。注意 PyQt 的界面控件不允许在子线程里直接调用必须通过信号把结果传回主线程所以需要自定义一个pyqtSignal对象在主线程里连接到更新标签的槽函数。如果不做这一步程序会随机崩溃或报QObject::setParent: Cannot set parent, new parent is in a different thread。答辩时不一定要展示多线程版本但能解释清楚这个问题说明你真的动手跑过、遇到过问题。5. 演示视频该录什么部署验证与识别精度调节的进阶技巧录制演示视频前先做三件事确认测试图片与训练数据不是同一批准备至少一张背景不那么干净的照片测试鲁棒性把模型加载提前到程序启动时完成避免视频里出现长时间白屏。录屏时按「打开图片 → 预处理 → 识别 → 显示结果」的顺序操作时间控制在 60 到 90 秒重点展示识别结果的置信度或类别标签这比展示代码更打动评审。识别精度调节有几个立竿见影的口子。背景分割阈值用 Otsu 计算后可以加一个轮廓面积过滤cv2.contourArea(contour) 5000直接忽略能去掉小面积噪点。颜色直方图的h_bins从 32 改成 48对叶色相近的类别有改善但有维度爆炸的代价建议同时把s_bins降到 16 来平衡总量。形状特征中加入凸包面积比hull_area cv2.contourArea(cv2.convexHull(contour))用它除以原始面积得到凸度能有效区分锯齿叶缘和光滑叶缘的叶片。如果测试集准确率在 85% 以下优先检查训练集每个类别的图片数量是否均衡最少类别的样本最好不低于 30 张。model.predict_proba输出的概率列表里最大概率低于 0.5 时界面直接提示「未识别请更换角度重新拍摄」这是把工程化意识体现出来的好细节评审通常会追问这一点提前准备好能说得出来。本文还有配套的精品资源点击获取