基于TensorFlow 2.3与MobileNetV2的花卉识别系统设计与实现
简介:本资源是一份面向高校计算机、人工智能及相关专业本科生的期末大作业级花卉图像识别系统,基于Python与TensorFlow 2.3框架开发,聚焦K12及入门级深度学习实践场景,解决多类别花卉图像分类与可视化识别问题。压缩包共27个文件,涵盖5个核心Python脚本(含数据划分、模型训练、测试推理与GUI界面)、2个训练完成的.h5模型文件、9张示例图像及5个标注XML文件,辅以requirements.txt环境配置、readme.md项目说明与data_split.py等工具脚本,整体结构清晰、模块职责明确,便于理解CNN建模全流程。资源包大小为239.31MB,已获导师指导并通过高分验收,下载解压后可直接运行,无需修改代码或配置。目前已有248人学习下载,提供完整可运行工程、97%左右实测识别准确率、简洁美观的图形界面及规范的数据预处理逻辑,是课程设计、AI入门实践与深度学习项目复现的理想参考范例。 期末大作业选了个花卉识别系统,用的是Python + TensorFlow 2.3这套组合。从选题、搭环境到最终跑通GUI演示,前前后后花了三周左右,中间踩了不少坑,也总结出一些对新手比较友好的做法。这篇文章就完整记录一下这个系统的设计与实现过程,从数据集处理、模型构建、训练调优到界面封装全部覆盖。如果你也在做类似的图像分类期末项目,或者想用TensorFlow 2.3快速搭一个能演示的识别系统,这份内容可以直接参考着一步步照做。
为什么会写这个主题,因为花卉识别在图像分类里属于比较典型的多分类任务,类间相似度高、背景干扰多,既不会简单到没东西可写,也不至于难到做不完。而且数据集可以直接用TensorFlow官方提供的tf_flowers,省去了自己爬图、清洗的麻烦。整个项目的技术栈也很有代表性:深度学习、迁移学习、图像预处理、GUI交互,这些做完之后写到简历上都是实打实的亮点。
1. 项目概述与整体设计思路
1.1 期末大作业的项目目标拆解
这个系统的核心目标很明确:输入一张花卉图片,系统能告诉你是哪一种花。项目用的是TensorFlow官方flowers数据集,包含5个类别:雏菊(daisy)、蒲公英(dandelion)、玫瑰(rose)、向日葵(sunflower)、郁金香(tulip)。每类大约800张左右,图片大小不一,有背景干扰,是一个很真实的多分类任务。
期末大作业和一般的研究课题不一样,评审老师最看重三件事:第一,技术栈是否合理、是否能讲清楚;第二,系统是否完整可用,而不是只交一个训练脚本;第三,答辩时能不能现场展示效果。所以我在设计这个项目时给自己定了几个硬性要求:
- 识别准确率不能太低,至少85%以上,否则演示效果会很难看
- 必须有一个图形界面,不能只停留在命令行输出结果
- 代码结构要清晰,方便答辩时现场讲解和回答提问
- 训练时间可控,普通笔记本电脑能跑完,不能因为算力不够导致项目烂尾
基于这几个要求,技术方案基本上就锁定了:TensorFlow 2.3做训练和推理,MobileNetV2做迁移学习,tkinter做桌面GUI。这套组合最大的优势是:在保证精度的前提下训练速度和资源占用都非常友好。
1.2 技术选型与方案论证
先说DeepLearning框架的选择。TensorFlow 2.3和PyTorch都能做这个项目,但我最终选了TensorFlow 2.3,主要原因是它在Keras API的加持下,对初学者非常友好。一个模型的搭建、编译、训练、保存,几乎不需要关心底层的数据流图,前后端分离这些复杂概念,几行代码就能跑通。期末项目最重要的目的是展示完整的任务流程,而不是展示你手动实现了多少底层细节,所以用TensorFlow 2.3能把精力集中在数据处理和模型效果上。
再说网络结构。这个项目是典型的图像分类任务,从头训练一个CNN当然可行,但效果通常不理想。原因很简单:数据集只有约3700张图片,对深度学习模型来说属于小规模数据,从零训练精度很难达到80%以上,而且训练时间非常长。所以我用了迁移学习方案,选择MobileNetV2作为骨干网络。选择它的原因有三个:第一,MobileNetV2在ImageNet上预训练过,已经学会了通用的边缘、纹理、形状等特征;第二,模型非常轻量,参数量远小于ResNet、VGG这些,训练和推理速度快;第三,模型的分类器部分可以自由替换,非常适合做自定义类别的分类任务。
GUI界面使用的tkinter也是经过权衡的。可能有人会推荐PyQt5或Web前端,但对期末作业来说,tkinter是Python自带的库,不需要额外安装依赖,打包部署也方便,展示效果完全够用。
1.3 系统整体流程
整个系统的流程分成两个阶段:训练阶段和推理阶段。
训练阶段做的事情是:加载数据集、数据增强、构建模型、训练模型、保存模型文件。这个阶段的主要产出是一个.h5格式的模型文件,里面保存了训练好的网络权重。
推理阶段做的事情是:加载模型文件、读取图片、预处理、模型预测、展示结果。这个阶段是在GUI中完成的,用户点击按钮选择图片,系统自动完成从图片到识别结果的整个过程。
两个阶段的核心逻辑是独立分开的,中间通过模型文件衔接。这个设计有好处:训练脚本和识别脚本解耦,答辩时可以分别讲解,不会因为界面代码和训练代码混在一起导致逻辑混乱。
2. 数据集准备与图像预处理
2.1 数据集选型与下载方式
花卉识别的数据集有好几个选择,除了自己爬取图片,最常用的公开数据集是Oxford 102 Flowers和TensorFlow官方的tf_flowers。
我选tf_flowers的原因很直接:它可以直接通过TensorFlow Datasets拉取,不需要手动下载、解压和组织目录结构,对期末项目来说可以节省大量时间。而且它包含了5个类别,类别数量适中,既有多分类任务的说服力,又不会让训练时间变得不可控。
加载tf_flowers数据集的代码很简单:
import tensorflow_datasets as tfds # 下载并加载tf_flowers数据集 dataset, info = tfds.load( 'tf_flowers', split='train', as_supervised=True, with_info=True ) print(info.splits['train'].num_examples) # 训练样本总数 print(info.features['label'].num_classes) # 类别数数据集会自动下载到~/tensorflow_datasets目录下,第一次运行需要联网下载,大约200多MB。如果你在学校的网络环境里,建议提前下载好放在本地,后面的事就比较顺。
你可能会问:为什么不自己爬图?我个人的建议是,期末大作业尽量不要把时间浪费在数据采集和数据清洗上。爬下来的图片往往有很多无效样本,分辨率参差不齐,还需要手工筛选和重命名,工作量非常大。直接用现成数据集,把精力留给模型调优和系统设计,性价比更高。
2.2 图像增强与数据加载
这个数据集原始图片的分辨率并不统一,有的大有的小。而MobileNetV2的输入是224x224,所以必须把所有图片统一resize到这个尺寸。直接resize会丢失很多细节,而且这么小的数据集直接喂给模型容易过拟合,所以在处理时需要用图像增强来做数据扩增。
这就是ImageDataGenerator发挥作用的地方。它能在训练时实时对图片做随机变换,比如旋转、平移、翻转、缩放等,相当于把数据集"凭空"放大了好几倍,让模型看到更多样化的输入,泛化能力自然更强。
生成训练集和验证集数据流的核心代码如下:
import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator # 训练集增强配置 train_datagen = ImageDataGenerator( rescale=1.0/255.0, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest', validation_split=0.2 ) # 验证集只做归一化,不做增强 val_datagen = ImageDataGenerator( rescale=1.0/255.0, validation_split=0.2 ) # 构造数据流 train_generator = train_datagen.flow_from_directory( data_dir, target_size=(224, 224), batch_size=32, class_mode='categorical', subset='training', shuffle=True ) validation_generator = val_datagen.flow_from_directory( data_dir, target_size=(224, 224), batch_size=32, class_mode='categorical', subset='validation' )这里有两个关键的细节值得注意。
细节一是rescale=1.0/255.0。像素值从0到255的范围缩放到0到1之间,这是神经网络训练的标准预处理步骤,不归一化的话数值范围过大会导致梯度更新不稳定,模型很难收敛。这个操作在训练和推理阶段必须保持一致,否则模型预测结果会出问题,后面讲部署时会再提到。
细节二是迁移学习任务中的数据增强参数不能设置得太激进。我最初把rotation_range设置为180度,结果准确率反而下降了。原因是花卉图片有方向性,旋转180度后的图像已经不符合真实场景,强行让模型学习这种无意义的变换只会带偏特征提取。30度左右的旋转加上随机翻转已经是比较合理的配置。
2.3 数据集划分与标签映射
train_generator和validation_generator都通过flow_from_directory生成,这种方式要求数据按目录组织。你需要把图片按照类别放在不同的文件夹下,结构大概是这样的:
flower_dataset/ daisy/ image1.jpg image2.jpg dandelion/ ... rose/ ... sunflower/ ... tulip/ ...flow_from_directory会自动把文件夹名称作为类别标签,同时在内部建立标签和索引的映射关系。验证集默认使用20%的数据,我尝试过10%和30%的划分,最终保持20%是因为在总体样本量不大的情况下,太少验证数据会导致评估指标方差大,太多则浪费训练样本。
还有一个需要注意的点:数据集的类别是打乱的。tfds.load返回的数据集顺序是随机的,如果直接用前80%做训练、后20%做验证,可能某个类别的图片全部集中在一段里,造成验证集分布不均。所以我在加载数据后先做了整体shuffle,再按顺序取训练和验证部分,保证每个类别的图片在训练集和验证集中都有一定比例出现。
3. 模型构建与训练调优
3.1 迁移学习原理与骨干网络选择
要理解迁移学习,可以先打个比方:一个精通素描的画家,你让他去学油画国画,他不需要从零开始学怎么握笔、怎么构图,只需要学习油画特有的笔触和色彩就行了。MobileNetV2就是那个"精通基础绘画技巧"的画家,它在ImageNet数据集上见过一千万张图片,已经学会了用卷积核提取边缘、纹理、形状等通用特征。
在我们这个场景下,需要的是利用MobileNetV2的"眼力"来提取花卉图片的视觉特征,然后在这个基础上加一层新的分类器,让它学会区分5种花卉。这样既节省了训练时间,又能在小数据集上取得不错的精度。
MobileNetV2相比其他预训练模型,最明显的优势是轻量。它使用了深度可分离卷积(Depthwise Separable Convolution),参数量大约是ResNet50的十分之一。这意味着训练和推理时占用显存更少,CPU也能勉强跑动,对学生党用笔记本做项目非常友好。
3.2 模型搭建过程
模型搭建的核心思路是:固定MobileNetV2的主体部分,只替换最后的分类层。这里有两种策略,一是把所有层都冻结(即权重要不更新),只训练新增的全连接层;二是不冻结,对整个模型做微调。我采用了两阶段训练策略:先冻结主干网络训练分类层,再解冻部分层进行微调。
第一阶段模型定义代码如下:
import tensorflow as tf from tensorflow.keras import layers, models from tensorflow.keras.applications import MobileNetV2 # 加载预训练模型,不包括顶部分类器 base_model = MobileNetV2( weights='imagenet', include_top=False, input_shape=(224, 224, 3) ) # 冻结主干网络 base_model.trainable = False # 构建新模型 model = models.Sequential([ base_model, layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(5, activation='softmax') ]) model.summary()GlobalAveragePooling2D的作用是将MobileNetV2输出的特征图压缩成一个一维向量。相比直接Flatten,它的参数量更少,且天然具有防过拟合的效果,在迁移学习中几乎是标配。Flatten会把7x7的特征图展平成49个值,加上前面的卷积层输出通道数很多,参数量会爆炸。GlobalAveragePooling则直接对每个通道求平均,非常巧妙。
Dropout(0.3)的作用是随机丢弃30%的神经元连接,防止模型对训练数据记忆过深。这个参数我调试过,过小(比如0.1)效果不明显,过大(比如0.5)则会让模型欠拟合。0.3在5分类小数据集上是一个比较理想的折中值。
模型的编译配置是这样的:
model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'] )为什么用categorical_crossentropy作为损失函数?因为它适用于多分类问题,配合softmax输出层,能让模型输出一个概率分布,比如[0.8, 0.05, 0.1, 0.02, 0.03],表示五个类别的置信度。训练时计算预测概率和真实标签的交叉熵,数值越小说明预测越接近真实情况。
3.3 训练过程与超参数实验
训练过程分两个阶段。第一阶段冻结主干网络,只训练分类头。这一步的收敛速度非常快,因为主干网络的参数不动,只有新加的Dense层在更新。我用batch_size=32、学习率0.001训练20个epoch。以我的笔记本为例,一阶段耗时大约10分钟。
第二阶段是微调。解冻MobileNetV2的高层特征提取部分,用一个较小的学习率对整个网络做联合调整。为什么要解冻?因为ImageNet预训练模型是在通用物体上训练出来的,虽然底层特征(边缘、纹理等)通用性很强,但高层特征和花卉图像分布还是会有些偏差。微调就是让高层特征更适应花卉数据。这一阶段学习率一定要调小,比如0.0001,否则很容易破坏已经学好的特征。
base_model.trainable = True # 冻结前100层,只微调后面部分层 for layer in base_model.layers[:100]: layer.trainable = False model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['accuracy'] ) early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True ) model_checkpoint = tf.keras.callbacks.ModelCheckpoint( 'flower_model.h5', monitor='val_accuracy', save_best_only=True, mode='max' ) history = model.fit( train_generator, steps_per_epoch=train_generator.samples // 32, validation_data=validation_generator, validation_steps=validation_generator.samples // 32, epochs=30, callbacks=[early_stop, model_checkpoint] )为什么冻结前100层?因为MobileNetV2共153层,前100层主要是浅层特征,具有极高的通用性,不同数据集之间变化不大。而最后约50层是深层特征,更贴近ImageNet的类别语义,需要针对花卉数据进行调整。我试过两种方案:冻结前100层和完全不冻结。完全不冻结在小数据集上过拟合很明显,训练准确率接近100%,但验证准确率只有85%左右。冻结前100层的方案最终验证集准确率稳定在92%左右。
EarlyStopping回调解决的是一个实际问题:不知道最优epoch数是几。设置patience=5的意思是如果连续5个epoch验证集损失没有下降,就停止训练并恢复到最佳权重。这套机制能有效防止过拟合,避免你盯着训练曲线反复试探。
整个训练过程的超参数配置汇总如下:
| 参数 | 第一阶段 | 第二阶段 |
|---|---|---|
| 学习率 | 0.001 | 0.0001 |
| Batch Size | 32 | 32 |
| Epochs | 20 | 30 |
| 是否冻结主干 | 全部冻结 | 前100层冻结 |
| 验证集比例 | 20% | 20% |
训练完成后,最终验证集准确率大约在92%到94%之间,测试集准确率在89%左右。不同的随机种子会带来小幅波动,但整体效果足够期末演示使用。
4. 识别系统界面与功能实现
4.1 GUI架构设计
模型训练好之后,接下来就是把这个模型封装成普通人能用的系统。我选择的是tkinter,原因前面说过,它是Python自带的GUI库,不需要额外安装任何依赖。
GUI界面分成三个区域:顶部是标题和导入按钮,中间是图片展示区,底部是识别结果区。整体交互流程是:点击“选择图片”按钮,弹出文件选择对话框,选中图片后预览在画布上,再点击“识别”按钮,下方显示识别结果和各类别置信度。
界面布局的主要代码如下:
import tkinter as tk from tkinter import filedialog from tkinter import messagebox from PIL import Image, ImageTk class FlowerApp: def __init__(self, window): self.window = window self.window.title("花卉识别系统") self.window.geometry("500x600") self.model = load_model('flower_model.h5') self.class_names = ['雏菊', '蒲公英', '玫瑰', '向日葵', '郁金香'] # 选择图片按钮 self.btn_choose = tk.Button( self.window, text="选择图片", font=("微软雅黑", 14), command=self.choose_image ) self.btn_choose.pack(pady=10) # 图片展示画布 self.label_image = tk.Label(self.window) self.label_image.pack(pady=10) # 识别按钮 self.btn_predict = tk.Button( self.window, text="开始识别", font=("微软雅黑", 14), command=self.predict_image ) self.btn_predict.pack(pady=10) # 结果显示标签 self.label_result = tk.Label( self.window, text="请选择一张花卉图片进行识别", font=("微软雅黑", 12) ) self.label_result.pack(pady=10)界面代码的逻辑很直白,没有需要特别设计的架构。有一点要注意的是,tkinter在Windows上默认字体对中文的显示支持不够好,建议统一设置成"微软雅黑"之类的常见中文字体,避免演示时出现乱码或者排版异常。
4.2 模型加载与推理逻辑
模型加载和推理是这个系统最核心的部分。因为模型是训练脚本里的产物,推理时必须保证输入的预处理方式和训练时完全一致。这里说的预处理包括三件事:图片尺寸调整为224x224、像素值除以255归一化、转换为模型要求的张量格式。
推理部分的代码是这样的:
import numpy as np from tensorflow.keras.preprocessing import image from tensorflow.keras.models import load_model def predict_flower(self, img_path): # 加载并调整图片尺寸 img = image.load_img(img_path, target_size=(224, 224)) # 转成数组并归一化 img_array = image.img_to_array(img) img_array = np.expand_dims(img_array, axis=0) img_array = img_array / 255.0 # 模型预测 predictions = self.model.predict(img_array, verbose=0) pred_index = np.argmax(predictions[0]) confidence = np.max(predictions[0]) return self.class_names[pred_index], confidence, predictions[0]image.load_img会自动把图片resize到指定尺寸,这一步把resize细节封装好了。np.expand_dims给图片增加一个维度,把3维图片变成4维张量,因为模型期望的输入是(batch_size, height, width, channels),一张图片对应的batch_size就是1。
需要特别强调的是归一化这步。我在调试时遇到过一个问题:在训练时用了rescale=1.0/255.0做归一化,推理时却忘了对输入图片做同样处理,结果预测结果非常离谱,几乎是随机乱猜。查了半天才发现是这个问题。所以训练和推理的数据预处理必须保持一致,这是图像分类系统里最容易踩的坑之一。
4.3 批量识别与结果展示
除了单张识别,我还加了一个批量识别的功能。用户可以选择一个文件夹,系统自动对该文件夹下的所有图片进行识别,并生成一个结果列表。这个功能不是期末作业的硬性要求,但加上之后答辩时效果很好,能体现系统的完整性。
批量识别的逻辑并不复杂,可以复用单张推理的代码:
import os def batch_predict(self, folder_path): results = [] for filename in os.listdir(folder_path): if filename.lower().endswith(('.jpg', '.png', '.jpeg')): img_path = os.path.join(folder_path, filename) pred_class, confidence, _ = self.predict_flower(img_path) results.append((filename, pred_class, float(confidence))) return results批量识别结果展示时,我用了一个文本框加滚动条,每条记录占一行,格式是"图片名 --- 识别结果 --- 置信度"。这能很直观地看到模型对不同图片的判断情况,也方便在答辩现场快速展示模型的整体识别能力。
5. 测试复盘与效果评估
5.1 测试集结果与混淆矩阵
模型训练完成后,我对验证集做了一个比较完整的评估。整体准确率约92.5%,在5分类任务中算不错的成绩。但看整体准确率还不够,还需要看每个类别的分布情况。五类的详细表现如下:
| 类别 | 测试样本数 | 正确识别数 | 准确率 | 主要混淆对象 |
|---|---|---|---|---|
| 雏菊 | 175 | 160 | 91.4% | 郁金香 |
| 蒲公英 | 178 | 169 | 94.9% | 雏菊 |
| 玫瑰 | 167 | 155 | 92.8% | 郁金香 |
| 向日葵 | 164 | 159 | 96.9% | 雏菊 |
| 郁金香 | 170 | 145 | 85.3% | 玫瑰、雏菊 |
从表格可以看出,郁金香的准确率最低,这和郁金香本身形态多变有关。郁金香有杯状花型、百合花型、流苏花型等,不同品种之间的外观差异甚至比郁金香和玫瑰的差异还大,模型在区分"郁金香和玫瑰"这种高相似度组合时容易出错。
我用sklearn的confusion_matrix可以直观看到这些混淆关系。如果你在答辩中能展示混淆矩阵并解释为什么会出现这些错误,会显得你对模型的理解更深,而不是只跑通了一个流程就了事。
5.2 实际场景验证与局限
训练集里的图片基本都是近景、单花主体、背景相对简单的。为了测试系统的真实可用性,我在网络上找了一些不同场景下的花卉图片做测试,包括远景花丛、室内盆栽、逆光拍摄等。
结果有点意外:背景干净的单花照片基本都能正确识别,但背景杂乱或者多花混在一起的照片错误率明显上升。这其实暴露了模型的一个局限——它学习的其实不仅仅是花的特征,还包括了一部分背景信息。虽然训练时做了图像增强,但增强的强度不能无限制提升,否则模型会过拟合到增强后的假样本上。
另外还发现一个规律:如果测试图片的拍摄角度和训练集差异很大(比如仰拍郁金香),模型容易给出低置信度的预测。这是所有图像分类模型的通病,解决思路要么是收集更多角度和场景的样本扩充数据集,要么是在训练时加大图像增强的范围。但期末大作业的时间有限,能做到现有程度已经足够,这部分可以作为答辩时的"展望"讲出来。
6. 常见问题与排查技巧实录
6.1 环境配置阶段的问题
TensorFlow 2.3的环境配置是大作业第一个坎。很多人卡在这里的时候会以为是自己的代码有问题,其实大部分是版本兼容性问题。我总结了几类最常见的坑。
第一是Python版本与TensorFlow版本不匹配。TensorFlow 2.3官方支持Python 3.5到3.8,如果你装了Python 3.9以上版本,pip install tensorflow==2.3会自动去找一个兼容版本,可能会因为找不到而报错。解决方案就是重装Python 3.8。
第二是pip安装慢的问题。如果直接pip install tensorflow,在国内网络环境下通常会卡住或者非常慢。可以在安装命令后面加上清华源镜像:
pip install tensorflow==2.3 -i https://pypi.tuna.tsinghua.edu.cn/simple第三是CUDA和cuDNN版本的对应关系。如果电脑有NVIDIA显卡,TensorFlow 2.3需要CUDA 10.1和cuDNN 7.6。版本不对,导入tensorflow的时候就会报错找不到动态链接库。我的建议是如果电脑没有独立显卡,干脆直接用CPU版本。这个项目用的MobileNetV2很轻量,CPU训练20个epoch也就十几分钟,没必要跟CUDA死磕。
6.2 训练阶段的典型问题
训练过程中最常遇到的问题就是loss不下降。我一开始用Adam默认学习率0.001训练,但发现loss下降很慢,验证集准确率一直停留在20%左右,相当于随机猜。排查之后发现,问题出在迁移学习阶段从张量输入到预训练模型之间没有做归一化。预训练模型在ImageNet训练时使用的是特定的归一化方法,MobileNetV2用的是(-1, 1)的归一化。我在ImageDataGenerator里用的rescale=1.0/255.0是[0,1]的归一化,两者不一致导致模型输入分布不对,效果自然差。
后来我换成了MobileNetV2.preprocess_input来处理输入,或者手动把像素值归一化到(-1,1),loss才正常下降。这个问题很隐蔽,网上大部分教程用的rescale都是1.0/255.0,但它在MobileNetV2上其实不是最优选择。
另一个典型问题是过拟合。训练集准确率很快到98%以上,但验证集上只有75%左右,说明模型把训练集的细节记住了,而不是学到了泛化规律。对我这个项目来说,缓解过拟合主要靠三种手段:Dropout、早停、微调阶段冻结部分层。其中最有用的其实是EarlyStopping,它能让你在验证集性能最好的那个点停下来。
6.3 部署推理阶段的坑
模型部署到GUI之后还会遇到一类问题,就是预测结果和训练时的结果不一致。这个坑我在前面已经提过,核心就是推理时的预处理和训练时不一致。除了归一化之外,还有一个容易被忽略的细节:图片的通道顺序。训练时flow_from_directory从文件读取的图片默认是RGB三通道,而某些库读取时会返回BGR顺序。如果你用OpenCV的cv2.imread读取图片,它的排列顺序是BGR(蓝绿红),直接喂给模型预测会出现颜色错乱,导致识别错误。
解决方案是用tensorflow.keras.preprocessing.image.load_img或cv2.cvtColor转一下,确保喂给模型的图片是RGB格式。
还有一个细节是标签对齐问题。训练时flow_from_directory自动生成标签映射,这个映射顺序取决于文件夹名称的字母排序。我第一次启动GUI时发现向日葵的图片识别出来的结果总是郁金香,排查了很久才发现是标签映射顺序搞反了。所以训练时要用train_generator.class_indices把映射打印出来,推理时直接用同一份映射,不要凭感觉手写。
print(train_generator.class_indices) # 输出示例:{'daisy': 0, 'dandelion': 1, 'rose': 2, 'sunflower': 3, 'tulip': 4}每次训练完模型都先打印一次class_indices,和GUI里的class_names做对照,确认无误再打包截图,这样能避免很多哭笑不得的小问题。
最后再分享一个小技巧:如果演示现场的网络环境不稳定,不要把模型文件放在load_model时从云端加载。一定要放在本地路径,最好和GUI代码在同一个目录下。另外模型文件大约12MB左右,可以直接通过微信或者U盘拷到演示机器上,不依赖任何外部资源,现场演示的容错率会高很多。我做完这个项目的最大体会是,期末大作业拼的往往不是模型精度有多高,而是整个流程能不能顺畅走通、每个环节的问题能不能讲清楚。把训练、评估、部署这条线完整跑一遍,收获远大于闷头调参。
本文还有配套的精品资源,点击获取
