
简介一套面向深度学习与毕业设计场景的花卉识别系统完整项目围绕郁金香、玫瑰、蒲公英、向日葵四种花卉提供可直接运行的卷积神经网络实现适合高校学生用于课程设计、毕业设计以及科研项目初期的原型搭建与思路验证。压缩包内共有两千个文件主体为近两千张花卉图片数据集另包含若干源码文件、说明文档、交互页面和辅助脚本压缩包整体约二百八十兆字节目录结构按功能划分便于快速定位与后续维护。目前已有七十二人学习代码经多环境严格测试运行稳定配套演示视频完整展示模型训练与预测流程设计文档则涵盖环境配置、核心逻辑和常见问题排查能帮助初学者减少踩坑成本。具备一定技术基础的读者还可自行修改模型结构或增加花卉类别从而扩展到其他图像分类任务是一份兼顾教学、答辩与项目实践的可复用资源。1. 一张图片四分类TensorFlow 花卉识别系统到底在解决什么输入一张郁金香、玫瑰、蒲公英或向日葵的图片让程序告诉你它属于哪一类这就是 TensorFlow 花卉识别系统要做的事。它常被当作 CNN 实战的第一道练手项目因为任务规模刚好数据集不需要上百万张模型用几十行 Keras 代码就能搭起来却能完整覆盖数据加载、数据增强、卷积网络构建、训练调参、模型评估与推理导出这条链路。网上流传的“源码数据集演示视频”包里源码质量参差不齐有的停在 TensorFlow 1.x用着tf.contrib旧接口解压后第一关就把人卡住。与其到处找包不如从零把同一套实现重新走一遍。本文不假设你手上有任何现成代码只要求有一批可分类的花图片。文章会讲清楚目录结构怎么设计、CNN 结构图怎么画、训练要盯哪些参数以及最后如何用一条命令完成单图预测。正在做课程设计或者刚学完卷积原理想动手的读者跟着这条路走完就能得到一个可演示的四分类系统。2. 数据先行TensorFlow 花卉识别系统的数据集整理与增强2.1 从散图到标准目录结构训练开始前的第一件事不是写模型而是整理目录。TensorFlow 里两个常用的图片加载接口image_dataset_from_directory和flow_from_directory遵守同一个约定把图片按标签分文件夹文件夹名就是类别名。对四分类花卉任务目录结构通常长这样data/ train/ tulip/ rose/ dandelion/ sunflower/ validation/ tulip/ rose/ dandelion/ sunflower/ test/ tulip/ rose/ dandelion/ sunflower/训练集、验证集、测试集各自独立。训练集用于更新权重验证集用于观察是否过拟合、决定何时保存模型测试集只在最终评估时用一次。很多人图省事不建测试集拿验证集当测试集反复调参最后报出来的精度水分很大。验证集被“看上”的次数越多它对真实泛化能力的代表性就越差。如果下载的数据集是四类混在一个目录里先写脚本按 8:1:1 切分。切分之前一定要先洗牌因为很多数据集按采集时间排列前一半是晴天中午拍的后一半是阴天拍的不洗牌会让人为地把训练集和验证集分布拉开。import os import random import shutil classes [tulip, rose, dandelion, sunflower] for cls in classes: src os.path.join(origin, cls) files [f for f in os.listdir(src) if f.lower().endswith((.jpg, .jpeg, .png))] random.shuffle(files) n len(files) for i, f in enumerate(files): if i int(n * 0.8): phase train elif i int(n * 0.9): phase validation else: phase test dst os.path.join(data, phase, cls, f) shutil.copy(os.path.join(src, f), dst)脚本按int(n * 0.8)和int(n * 0.9)两个边界切分保证四个类在三个阶段的比例一致。这里用copy而不是move原始数据留着更安全如果磁盘紧张换成shutil.move也可以。class_indices这个属性会在flow_from_directory执行后自动生成顺序按文件夹名的字母序排列例如dandelion是 0rose是 1这个顺序在后面的预测阶段必须对齐。新版 Keras 官方更推荐image_dataset_from_directory但ImageDataGenerator在绝大多数课程源码里还在用目录约定完全相同迁移成本很低。2.2 数据增强组合与关键参数四类花各几百张图对 CNN 来说数据量偏小。一个简单卷积神经网络也有几十万个参数训练集不够时模型很容易把背景、拍摄角度、光线这些不相关因素一起记住最终表现是训练准确率接近 100%验证准确率卡在 60%。缓解过拟合最有效的办法是数据增强在训练过程中对图片随机做旋转、平移、缩放、翻转让模型每个 epoch 看到的都是同一张花的不同形态。from tensorflow.keras.preprocessing.image import ImageDataGenerator train_gen ImageDataGenerator( rescale1.0 / 255, rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.15, zoom_range0.2, horizontal_flipTrue, fill_modenearest, ) train_flow train_gen.flow_from_directory( data/train, target_size(224, 224), batch_size32, class_modecategorical, shuffleTrue, )rescale把 0 到 255 的像素值压缩到 0 到 1几乎任何模型都要做这一步否则数值范围过大会让梯度更新不稳定。rotation_range控制随机旋转角度对花这类方向不敏感的目标20 度以内的旋转既模拟了拍摄角度变化又不会把花旋转到语义上反常识的状态。width_shift_range和height_shift_range让花朵在画面里“跑动”模拟目标不在画面正中央的情况。zoom_range模拟远近不一的拍摄距离。horizontal_flip对花特别安全因为左右翻转不会改变花的类别。关键参数汇总如下参数含义推荐值实际作用rescale像素归一化1.0/255把数值压到 0 到 1必填rotation_range随机旋转角度20模拟拍摄角度变化width/height_shift_range平移比例0.2模拟花朵偏离画面中心zoom_range缩放范围0.2模拟远近拍摄horizontal_flip水平翻转True花翻转后仍是同一种花fill_mode变换后空白填充nearest旋转平移带来的空洞用最近像素补上验证集和测试集的生成器只写rescale不加任何随机变换。验证集的作用是评估模型在真实分布上的表现如果把增强也加进去每次验证时输入都不同得到的高高低低的验证准确率就不是一个可比较的稳定指标。2.3 验证集单独留好再用小脚本查一遍坏图训练前确认一下每个类别在各个目录里的图片数量。如果郁金香只有 200 张玫瑰有 700 张训练会不自觉偏向多数类。缓解办法有两个多数类保持原样少数类用更强的增强或者在训练时给少数类更高的class_weight。还有一个被忽略的坑是损坏图片某些 zip 包里的 jpg 文件是零字节的或者文件头损坏flow_from_directory读取时要么抛异常要么直接跳过等你发现时训练已经跑偏了。from PIL import Image import os bad [] for root, dirs, files in os.walk(data/train): for f in files: path os.path.join(root, f) try: img Image.open(path) img.load() except Exception: bad.append(path) print(损坏图片:, bad)脚本遍历训练目录下所有文件Image.open负责读取文件头load()里真正解码像素数据。能通过这两步的文件基本可以安全交给 TensorFlow 处理。如果bad列表不为空用os.remove清掉这些文件再开始训练。3. 用 CNN 卷积神经网络搭模型结构图与 Keras 实现3.1 先画一张 CNN 结构图把卷积和池化的关系理清搭模型之前先在纸上画结构图。输入是一张 224x224x3 的彩色图片经过若干组“卷积、批归一化、池化”的组合特征图的宽高不断缩小通道数不断加深。粗略流程是224x224x3 经过第一组卷积变成 224x224x32池化后变成 112x112x32第二组卷积后变成 112x112x64池化后 56x56x64第三组再变成 56x56x128池化后 28x28x128。最后接全局平均池化把所有特征图压成一个一维向量经过两层全连接输出 4 个概率。这种结构是经典的 VGG 风格叠法没有残差连接也没有深度可分离卷积但对四分类花卉任务完全够用而且能让原理说得清楚。第一层卷积只能看到 3x3 范围内的局部像素学到的是边缘、色块、方向这类底层纹理郁金香的杯状轮廓、玫瑰的层叠卷曲、向日葵中央的管状花盘、蒲公英的放射状细丝这些差异在浅层表现为边缘方向和颜色组合的不同。越到深层感受野越大卷积层才能把局部纹理组合成“花瓣”“花盘”这类语义部件。有个常见的误区是模型越深越好。数据只有几百张时加深网络带来的收益远小于过拟合带来的损失。四分类小任务里三组卷积已经足够重点是控制参数量而不是堆层数。3.2 用 TensorFlow Keras 写出可训练的花卉分类模型结构图定下来之后Keras 代码非常短。下面这段模型直接定义了完整的 CNN 卷积神经网络import tensorflow as tf from tensorflow.keras import layers, models model models.Sequential([ layers.Input(shape(224, 224, 3)), layers.Conv2D(32, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu, paddingsame), layers.BatchNormalization(), layers.MaxPooling2D((2, 2)), layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(4, activationsoftmax) ]) model.summary()Conv2D((3, 3))是 3x3 卷积核paddingsame让卷积输出保持和输入一样的宽高。每经过一次MaxPooling2D((2, 2))特征图宽高各自减半。BatchNormalization放在卷积层输出后面让每层输出分布更稳定收敛速度明显加快。GlobalAveragePooling2D把每张特征图取全局平均值替代先展平再连全连接层的旧方案参数更少还自带一点正则效果。Dropout(0.5)在训练时随机丢弃一半神经元等价于同时训练多个子网络再取平均是控制过拟合最直接的手段。Dense(4, activationsoftmax)输出 4 个值对应 dandelion、rose、sunflower、tulip 四个类别的概率。用model.summary()可以确认每层输出尺寸是不是符合预期28x28x128 的特征图经过全局平均池化后变成 128 维向量。这个模型总参数量大约 84 万对 CPU 训练来说也能在十几分钟内完成一轮像样的验证。如果想进一步提高精度更快的路线是把主干换成 ImageNet 预训练的 MobileNetV2冻结其卷积基座只训练新增的分类头。这种做法是迁移学习的标准套路但建议先把这个自定义 CNN 跑通因为冻结层数、Batch Normalization 的参数行为、输入归一化方式都会影响迁移效果。自定义模型能稳定过 90% 再换骨干问题排查起来会简单得多。3.3 郁金香和玫瑰为什么容易互相误判训练结束之后算一次混淆矩阵大概率看到郁金香和玫瑰互相误判。原因是两类花的颜色高度重叠红色玫瑰和红色郁金香在花瓣纹理不明显的情况下模型很容易被颜色主导。蒲公英和向日葵也容易出现类似问题因为它们都是黄色放射状花盘结构。模型在这种情况下学的不是“花的形状”而是“颜色加中心区域形态”。这时候首先检查的是数据本身而不是模型结构。如果训练图大多是单朵花特写、绿色背景、正常光照模型会倾向于把背景也学进特征里。把rotation_range从 20 提到 30把zoom_range从 0.2 提到 0.3一般能明显降低这类误判。颜色分布接近的类别需要在增强里加入轻微的颜色扰动例如用brightness_range调整亮度让模型不再依赖某一特定色调。4. 训练与调参让模型稳定收敛的完整配置4.1 编译参数与优化器选择模型结构只决定表达能力能不能稳定收敛靠的是编译参数和训练策略。四分类任务使用categorical_crossentropy作为损失函数配合 softmax 输出。优化器选择 Adam对学习率不那么敏感适合作为小项目的第一选择。关键是学习率的初始值1e-3对这个小模型来说偏大loss 会在前几个 epoch 震荡1e-5又收敛太慢四类花任务通常从1e-4起步最合适。model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-4), losscategorical_crossentropy, metrics[accuracy], )metrics里先只放accuracy。精确率、召回率这类指标等训练完在测试集上统一计算就好没必要在训练循环里每个 epoch 都算耗时且对判断收敛帮助有限。训练环境方面TensorFlow 2 搭配 Python 3.9 到 3.11 比较稳妥。常见做法是先建一个独立环境避免把系统 Python 搞乱conda create -n tf-flower python3.9 -y conda activate tf-flower pip install tensorflowGPU 版本需要注意 CUDA 和 cuDNN 的版本匹配TensorFlow 2.10 之后在 Windows 上不再提供官方 GPU 支持Linux 下用conda install cudatoolkit cudnn对齐比较省心。数据集只有几百张、模型只有几十万参数时CPU 跑完全没问题GPU 只是把每轮 epoch 耗时从几分钟压缩到几十秒。4.2 回调函数与训练代码小数据集训练最怕两个问题不知道该跑多少个 epoch以及最佳模型被后续过拟合的权重覆盖。回调函数就是为这两个问题设计的。callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue, ), tf.keras.callbacks.ModelCheckpoint( flower_cnn.keras, monitorval_accuracy, save_best_onlyTrue, verbose1, ), tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6, ), ] history model.fit( train_flow, steps_per_epochtrain_flow.samples // train_flow.batch_size, epochs100, validation_datavalid_flow, validation_stepsvalid_flow.samples // valid_flow.batch_size, callbackscallbacks, )EarlyStopping监控val_loss连续 10 个 epoch 不下降就停止训练并把权重回滚到验证集最优时刻。ModelCheckpoint保存最佳模型save_best_onlyTrue保证磁盘上始终是验证集上最好的版本。ReduceLROnPlateau在 loss 停滞时把学习率减半让训练从“大步搜索”切换到“精细收敛”。steps_per_epoch用整数除法计算避免最后一个不完整 batch 带来的训练步数不均匀。模型文件用.keras后缀保存这是 TensorFlow 2 新格式包含模型结构和权重加载时不需要再单独写结构代码。history对象里保存了每个 epoch 的 loss 和 accuracy之后画训练曲线、判断过拟合都靠它。4.3 训练常见坑和调试手段第一类坑是内存不足。batch_size从 32 调到 16 或 8 是第一反应其次是检查target_size是否被设置得过大。224x224 和 512x512 的内存占用相差数倍而四分类花卉任务在 224 尺寸下已经足够。第二类是类别不均衡。某个类别图片特别少时在fit里传入class_weightclass_weight { 0: 1.2, 1: 1.0, 2: 1.2, 3: 1.0, }这里的键对应class_indices里的顺序。数字越大该类样本在损失函数里的权重越高模型会更多地“关注”样本少的那一类。第三类是验证准确率一直不涨。先看训练准确率高不高如果训练准确率远高于验证准确率是过拟合优先加大 Dropout、缩小 Dense 层宽度、增强数据如果训练和验证准确率都低是欠拟合需要增加卷积层通道数或把学习率调小后再跑。另外建议优先观察val_loss而不是val_accuracy四分类任务下准确率经常连续几个 epoch 卡在同一个整数百分比上看着像没收敛实际上val_loss还在小幅下降。5. 单图预测与模型导出识别系统的最后一步5.1 写一个输入图片路径就返回类别名的脚本训练得到的.keras文件还差最后一公里一个能直接调用的预测入口。下面的脚本加载模型处理一张图片返回预测类别、置信度和完整概率向量。注意class_names的顺序必须和训练时flow_from_directory生成的一致否则输出名会和概率对不上。from tensorflow.keras.models import load_model from PIL import Image import numpy as np class_names [dandelion, rose, sunflower, tulip] model load_model(flower_cnn.keras) def predict_image(img_path): img Image.open(img_path).convert(RGB).resize((224, 224)) x np.asarray(img, dtypenp.float32) / 255.0 x np.expand_dims(x, axis0) probs model.predict(x, verbose0)[0] idx int(np.argmax(probs)) return class_names[idx], float(probs[idx]), probsresize((224, 224))必须和训练时的target_size完全一致否则输入维度不匹配。convert(RGB)统一把 RGBA 或灰度图转成三通道避免单通道图在预测时报 shape 错误。np.expand_dims给单张图增加一个 batch 维度因为模型接收的输入形状是(batch_size, 224, 224, 3)。模型加载放在函数外面避免每张图都重复加载一次。5.2 顺手导出 TensorFlow Lite 模型如果识别系统要放到手机或嵌入式设备上可以顺手用TFLiteConverter导出轻量模型converter tf.lite.TFLiteConverter.from_keras_model(model) tflite_model converter.convert() with open(flower_model.tflite, wb) as f: f.write(tflite_model)导出后的模型可以用官方 C 或 Java API 在移动端调用。转换本身不会改变模型参数量体积也不会自动变小真正缩小体积的手段是训练后量化例如把权重从 float32 降到 float16这需要在转换时设置优化选项。量化后的精度会有一点损失对四分类的花卉识别任务来说通常不明显但要在测试集上重新验证一遍才能放心。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。