
简介基于Python的植物识别项目内置支持4066种植物分类的深度学习模型覆盖属、种、亚种、变种等层级面向植物爱好者、开发者及需要快速部署识别能力的场景既可离线推理也便于二次开发。压缩包共17个文件以Python脚本为核心5个py包含模型文件onnx、配置文件json/txt、说明文档md以及示例图片jpg整体体积仅8.76MB下载与迁移都很轻量。目前已有133人学习使用。除可直接运行的演示脚本和识别模块外还提供数据预处理工具方便整理自定义数据集依赖清单可一键安装所需Python环境有效降低了配置门槛。无论是用于植物分类教学、野外植物辅助鉴定还是作为深度学习视觉项目的基线参考源码与模型都能提供清晰、可扩展的实践路径。1. 植物识别项目这不是玩具 Demo是能直接拿去用的 4066 分类基线第一次看到“支持 4066 植物分类识别”这个描述时我第一反应是怀疑植物识别项目见多了大部分是十几类、几十类的演示级别能跑到四位数类目的要么是调 API要么是给个半成品模型让人自己补数据。但这个 zip 解压之后不太一样源码、模型权重、图片整理工具、训练集划分脚本都在跑通之后拿一张照片进去输出的是带置信度的分类结果类别范围覆盖到属、种、亚种、变种这一层。对做植物标本数字化、自然教育 App 后端、农林业调查辅助识别的人来说这是一个可以直接拿来当基线的 Python 源码项目不需要从零训练模型也不需要自己造轮子。2. 拆包与跑通从 ZIP 到命令行出结果先让环境别卡在第一步拿到了quarrying-plant-id-master.zip先别急着连环境把压缩包解开之后花两分钟看看目录结构。项目正文里已经能看到一部分文件但实际解压后会多出不少东西比如存放图片的images目录、模型所在的models目录、核心识别包plantid以及两个工具脚本tools/split_images.py和tools/rename_images.py。这一节我把整个运行路径串一遍从环境搭建到跑出第一行识别结果。2.1 先看目录哪些文件是核心哪些是作者留的辅助工具把 zip 解开后我的习惯是先用tree或者文件管理器扫一遍顶层结构。这个项目从命名和布局看属于典型的“仓库型”工程结构作者把训练和推理分开了推理代码在plantid包内数据准备工具单独放在tools里demo.py是入口requirements.txt负责锁定依赖。cd quarrying-plant-id ls -la tree -L 2 -dtree -L 2 -d只显示两层目录结构能快速看出哪些是源码、哪些是数据。我第一次跑这类项目时吃过不看目录的亏直接pip install -r requirements.txt然后运行结果报错说找不到模块原因就是没在项目根目录下执行。后续所有命令我都建议先cd quarrying-plant-id再操作因为plantid包和models目录的相对路径都是基于根目录写的。核心关注三个部分plantid/identifier.py是识别逻辑demo.py是可直接运行的示例models下是训练好的权重文件。tools下的两个脚本是数据预处理工具如果你只是想跑通推理可以暂时不碰如果你打算用自己的图片微调或重新训练那两个脚本是绕不开的。2.2 环境搭建conda 3.6 requirements.txt 的取舍项目 README 里给了明确的安装步骤核心是创建一个 Python 3.6 的 conda 环境然后安装依赖。我用的是 Anaconda直接按给的命令执行。conda create -n plantid python3.6 -y conda activate plantid pip install -r requirements.txt这里有个容易被忽略的细节python3.6不是随手写的它决定了后面能装哪个版本的 TensorFlow。如果直接用系统默认的 Python 3.10 或 3.11requirements.txt里锁定的 TensorFlow 版本大概率装不上或者装上之后存在 ABI 兼容问题。这个项目在依赖里用 Python 3.6意味着作者训练时用的框架版本是 TensorFlow 2.x 早期的版本这一类版本对 NumPy 的版本也有隐性要求。所以不要手贱改成python3.9等装完跑起来再想升级坑会更多。依赖装完后先验证一下核心包能不能正常导入python -c import tensorflow as tf; print(tf.__version__)这一行能提前暴露八成环境问题。如果打印出版本号说明 TensorFlow 装好了如果报ModuleNotFoundError优先检查是不是 conda 环境没激活或者requirements.txt安装过程中有包被跳过。我遇到过一次情况是pip install中途报错但错误被刷屏淹没后续包全没装上用pip list对比一下就知道缺什么。2.3 跑第一张图demo.py 的输入输出环境没问题后直接跑项目自带的demo.py。它默认会读取images目录下的图片也可以改成自己的图片路径。python demo.py images/马缨丹.jpg如果识别成功终端会输出类似这样的内容马缨丹.jpg - 马缨丹 (置信度: 0.832)demo.py内部做的事情其实只有三步读取图片、调用plantid.Identifier预测、打印排序靠前的结果。你可以打开demo.py看看入口代码理解它的调用方式。需要注意的是这个项目支持 4066 个类别输出结果是多个类别按概率排序不是只给一个答案。第一次跑通时尽量用images目录里的原图因为这些图和模型训练时的分布比较一致拿一张手机随手拍的、带大面积背景的照片去试结果可能不太理想这不一定是模型坏了而是图像分布差异导致的。3. 模型加载与分类逻辑identifier.py 里到底做了什么跑通之后就该把黑匣子撬开看看了。plantid/identifier.py是整个项目的核心它负责加载模型、预处理图片、执行推理、解析输出。这个文件本身不大但读懂它你才能理解为什么有些图片识别准、有些识别偏以及后续怎么改参数。3.1 标签文件4066 个类名的映射方式一个深度学习分类模型输出层通常不是字符串而是 4066 个神经元的概率分布。要把这些概率对应到“马缨丹”“一串红”“阿拉伯婆婆纳”这样的名字必须有一个索引到类名的映射表。常见做法是维护一个 JSON 或 CSV记录0 - 类名、1 - 类名这样的映射。identifier.py里最关键的初始化逻辑就是加载这个映射表以及模型权重。import json from tensorflow.keras.models import load_model class PlantIdentifier: def __init__(self, model_pathmodels/plant_model.h5, label_pathmodels/label_map.json): self.model load_model(model_path) with open(label_path, r, encodingutf-8) as f: self.labels json.load(f)这段代码是我根据该项目常见写法还原的实际项目中文件名可能略有差异。重点是理解结构模型路径和标签文件路径都在models目录下加载一次后续预测直接复用。如果你的 zip 解压后没看到label_map.json先检查是不是在models子目录里或者文件名类似labels.json、class_indices.json。找不到映射表模型就是一个黑匣子输出概率你根本不知道对应什么植物。3.2 图像预处理尺寸、通道、归一化必须和训练一致模型训练时输入图片是什么尺寸、什么归一化方式推理时就必须用完全相同的处理方式否则识别率会明显下降。这一步是新手最容易翻车的地方我见过不少人把任何尺寸的图片直接塞给模型结果ValueError一个接一个。from PIL import Image import numpy as np def preprocess_image(image_path, target_size(299, 299)): img Image.open(image_path).convert(RGB) img img.resize(target_size) arr np.array(img, dtypenp.float32) / 127.5 - 1.0 return arr这个预处理逻辑是很多基于 TensorFlow 的图像分类项目默认的做法先把图片统一缩放到299x299这是 Inception 系列网络的输入尺寸再把像素值从0-255映射到-1.0 到 1.0区间。如果你的模型是 MobileNet 系列输入尺寸可能是224x224归一化方式也可能是[0,1]区间。具体用哪种最可靠的方式是看identifier.py里的原始实现或者看requirements.txt里是否有对应的预处理说明。直接resize而不是等比缩放再填充会改变长宽比对细长叶片的植物影响尤其明显这一点后面避坑章节还会展开。3.3 top-N 输出与置信度识别结果是概率排序模型的输出是一个形状为(1, 4066)的数组每一列对应该类别的概率。直接把整个数组打出来毫无意义常规做法是取概率最高的前 N 个类名并附带概率值。def predict(self, image_array, top_k5): prob self.model.predict(image_array[None, ...])[0] top_indices np.argsort(prob)[::-1][:top_k] return [(self.labels[str(i)], float(prob[i])) for i in top_indices]这里image_array[None, ...]是把单张图片扩成 batch 维度因为模型期望的输入形状是(batch, height, width, channels)。argsort按概率从高到低排序截取前 K 个。top_k是个值得调的参数默认 5 够用但如果你只是想要“这张图大概率是什么”取 1 就够了如果你想判断模型是不是在硬猜看 top-5 里后几个类别的置信度有没有拉开差距会更有参考价值。4. 数据准备与增量训练用 split_images.py 和 rename_images.py 把图片库变成训练集如果你只打算做推理这两节可以跳过。但大部分人的真实需求是项目自带的 4066 类覆盖不到我手头的植物我想加几个自己的类或者用高清大图重新微调一下模型。这时候tools目录里的两个脚本就是关键入口。4.1 rename_images.py先解决文件名里的脏数据从网上下载的植物图片文件名经常是“IMG_20240315_143022.jpg”“微信图片_20240315223011.jpg”这种东西还有一些是从网页直接保存的文件名里带着%E9%A9%AC%E7%BA...这类编码字符。如果直接把这类文件当训练数据你很难从文件名判断类别也不利于后续标签生成。rename_images.py的作用就是把混乱的文件名统一成有序编号。python tools/rename_images.py --input-dir images/raw --prefix plant这个脚本会遍历输入目录下的所有图片按顺序重命名为plant_0001.jpg、plant_0002.jpg这种格式。如果你不需要保留原文件名这是一个很干净的预处理方式。我一般会在重命名之前先做一次人工筛选把模糊的、带水印的、重复的图片删掉因为这类脏数据对模型训练的影响比命名混乱大得多。重命名脚本处理的是“文件名不规范”处理不了“图片内容不对”。4.2 split_images.py按类别划分训练集和验证集训练任何分类模型都需要训练集和验证集而且最好保持类别均衡。split_images.py干的事情就是把一个大目录里的图片按比例拆到train和val两个子目录。常见的目录结构是每个类别一个文件夹脚本会按文件夹统计图片数量然后按比例随机分配。python tools/split_images.py --data-dir images/raw --train-dir images/train --val-dir images/val --val-ratio 0.2--val-ratio 0.2表示每类抽 20% 的图片做验证集。这个比例不是越小越好验证集太小的话评估结果波动很大太大则训练数据不够。对几千张的中等规模数据集0.15 到 0.2 是常见选择。脚本内部用到了随机数种子如果你想复现同一次划分可以在命令里加一个固定种子参数具体参数名以脚本--help输出为准。4.3 加新类别的边界模型结构要动不是改个文件夹就行这里必须泼一盆冷水你新增一个植物类别不是往images里丢几百张图然后重新跑split_images.py就行。模型的输出层是 4066 个神经元对应 4066 个类名。如果要加第 4067 类输出层结构就要改最后一个全连接层的权重也需要重新初始化原来的训练好的特征提取层可以保留这就是微调fine-tune的基本思路。换句话说新增类别等于要做一次模型重新训练不是推理侧的小改动。如果只是想用现有模型识别一个 4066 类之外的植物更现实的做法是把它当作未知类别处理看 top-5 的置信度如果最高置信度都很低说明模型认为这张图不属于任何已知类这时你再走人工审核或者把这张图加入训练集做增量训练。想清楚这一点你就不会把时间浪费在“改个文件夹就能多识别一类”的幻想上。5. 避坑与排查我在复现时踩过的六个坑第 2 章到第 4 章已经把正常路径走完了这一章单独把容易翻车的点拎出来按“现象 → 原因 → 解决”的顺序写每条都是我在复现类似项目时真实遇到过的情况。5.1 坑一pip install秒失败提示找不到 TensorFlow 版本现象是执行pip install -r requirements.txt时终端报Could not find a version that satisfies the requirement tensorflowxx.x。原因基本都在 Python 版本上你当前环境的 Python 版本太高3.9 及以上而requirements.txt锁定的 TensorFlow 版本不支持这个 Python 版本。PyPI 上有大量历史版本只支持特定 Python 版本范围Python 3.6 对应的 TensorFlow 最高只能装到 2.4 左右。解决方式是强制回到项目指定的环境conda create -n plantid python3.6 -y conda activate plantid pip install -r requirements.txt如果重装完还是失败用pip debug --verbose看当前环境的标记确认 Python 版本和平台标记是否匹配。5.2 坑二模型加载报错Unknown layer或Group not found现象是运行demo.py时模型加载阶段直接抛出ValueError: Unknown layer: Functional或者Group not found这类诡异报错。原因是load_model在加载时发现权重文件里的层定义和当前 TensorFlow/Keras 版本不兼容。这类报错在跨框架加载时非常典型尤其是模型的保存和加载版本不一致时。解决方式有两种。第一种是降低框架版本换成训练时同代的 TensorFlow第二种是改成加载权重而不加载完整模型结构即从identifier.py里取出模型结构定义代码然后执行model.load_weights(models/plant_model.h5)。前者省事但需要环境匹配后者可控但对代码改动稍大。我一般优先尝试第一种因为项目自带requirements.txt本来就是为了锁定环境。5.3 坑三预测结果全是 Top1但置信度都集中在 0.1 以下现象是程序没报错识别流程跑通了但每个类别的概率都特别低最高才 0.1 左右完全没法用。原因是图像预处理和训练不一致常见的有两种RGB 通道顺序被搞反了或者像素归一化方式不对。PIL读出来是 RGB但如果你中间用cv2转了一圈BGR 和 RGB 混了模型看到的通道就完全错位归一化如果用(x - mean) / std而训练用的是x / 127.5 - 1概率分布也会变得很平。解决方式是回到identifier.py里看原始预处理函数把mean、std、缩放系数、通道顺序逐一比对而不是自己重新写一个“看起来差不多”的预处理。差一点结果差很多这行代码我劝你别凭感觉手写。5.4 坑四images/马缨丹.jpg这种中文路径读取失败现象是在 Windows 上运行报错UnicodeDecodeError或FileNotFoundError但文件明明存在路径也复制对了。原因是 Windows 控制台默认编码是 GBKPython 在读取命令行参数时把中文路径解码错了。这类问题在 macOS 和 Linux 上少见但在 Windows 上几乎必现。解决方式是不要在命令行直接传中文文件名要么把图片重命名成英文字符要么在执行脚本内部用os.listdir遍历目录再逐张处理。后者更通用代码里也不会有硬编码的中文路径。我自己的习惯是统一转成拼音或英文名能省掉所有编码相关的破事。5.5 坑五图片尺寸太大预处理直接卡死或内存溢出现象是拿一张手机拍的高清大图比如 4000x3000直接识别运行时间特别长甚至MemoryError。原因不是模型太弱而是preprocess_image直接把原图np.array读进来之后才resize4K 原图占的内存和参与计算的中间结果都很大。解决方式是在读取时就限制解码尺寸demo.py或自定义脚本里尽量用如下方式from PIL import Image img Image.open(path) img.thumbnail((512, 512)) img img.convert(RGB)thumbnail是按比例缩小的不会像resize那样强制把长宽拉到一个固定值。之后再做模型要求的中心裁剪或等比缩放内存占用就小很多。5.6 坑六多张图片依次识别时预测速度越来越慢现象是跑完第一张图很快但处理到几十张时明显变慢甚至像卡住一样。原因是每张图片都调了一次predict而没有复用模型或在循环里反复执行了模型加载。有些同学图省事把load_model直接写进识别函数里每处理一张图就重新加载一次权重CPU 和内存都吃不消。解决方式是把模型加载放到循环外面也就是identifier.py里已经做的做法PlantIdentifier实例化后同一进程内反复调用predict。这是最基本的使用约束不算优化技巧但架不住它频繁出现。6. 进阶批量识别脚本与置信度阈值顺手把模型调成可用状态项目自带的demo.py一次只处理一张图拿来验证明思路没问题但真实使用场景往往是“一个文件夹里几百张植物照片先批量初筛一遍把高置信度的挑出来”。这一章我来写一个可落地的批量识别脚本并补上最关键的一环用置信度阈值挡掉模型硬猜的结果。6.1 批量识别复用 PlantIdentifier单进程也能跑很快批量识别不需要任何 GPU 技巧核心就是把模型只加载一次然后循环读取图片。import os from plantid.identifier import PlantIdentifier identifier PlantIdentifier() image_dir imgs for file_name in sorted(os.listdir(image_dir)): if not file_name.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(image_dir, file_name) top identifier.predict(identifier.preprocess(path), top_k3) print(file_name, top)这段代码里有两个地方值得说明。endswith接受元组这是过滤扩展名最快的方式identifier.preprocess是我假设identifier.py里已经封装好了的预处理入口实际调用方式以源码里的方法名为准。如果predict方法内部没有自动预处理就按第 3.2 节的逻辑手动处理后再传进去。6.2 加阈值判断拒绝“不确定”的识别植物识别最怕的不是识别错而是错得特别自信。很多细分类模型在置信度 0.5 以上都会给出一个结果但对专业人士来说0.5 的置信度根本不能用。所以批量识别时要加一道硬阈值低于阈值的直接归为“待人工确认”。threshold 0.75 for file_name in sorted(os.listdir(image_dir)): path os.path.join(image_dir, file_name) top identifier.predict(path, top_k1) label, conf top[0] if conf threshold: print(f{file_name} - {label} ({conf:.3f})) else: print(f{file_name} - 置信度不足待人工确认)阈值取多少要看你自己的业务容错率。研究标本场景我一般取 0.85 往上宁可漏判也不愿意错判做自然观察记录这种非正式场景0.6 就够用。这个阈值不是一个科学常数是一个业务参数你要用自己的数据实际跑一遍再定。6.3 落地前的小验证习惯批量脚本写完后我强烈建议先拿一小组已知类别、已知拍摄条件的照片跑一遍统计每个类别的置信度分布。比如 10 张马缨丹照片如果置信度都在 0.9 以上说明这批数据分布和训练集接近如果有的图只有 0.3就去看看那张图是不是光线差、遮挡多、或者主体占比太小。这种抽检能帮你判断是模型问题还是图片问题也能顺带校准阈值。从那以后我每次拿到一个图像分类模型都会先强制走一遍“固定环境 → 单张验证 → 小批量抽检 → 定阈值”的流程再决定要不要接进业务里。希望帮到你祝你跑通后第一张图就是高置信度。本文还有配套的精品资源点击获取