ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CNN图像分类实战:猫狗识别项目从数据准备到GUI部署全流程

CNN图像分类实战:猫狗识别项目从数据准备到GUI部署全流程 简介基于Python与CNN的猫狗识别项目是一份面向高校学生期末大作业、课程设计与毕业设计的完整源码包。资源共包含6个文件分别为5个Python脚本与1份Markdown说明文档其中GUI脚本用于构建图形交互界面训练脚本负责CNN模型定义与训练流程预测脚本执行分类推理图像处理脚本处理输入数据说明文档则提供部署与使用指引。代码全程附有注释结构清晰即使是新手也能较快上手并根据需求修改模型参数或界面布局。整套资源仅7KB压缩为zip格式体量轻巧、便于分发项目已通过严格调试简单配置即可运行。目前已有244人学习适合需要快速搭建图像识别项目或完成实践作业的学习者。项目源于高分期末作品系统功能完善、界面美观能帮助读者完整掌握CNN建模、数据处理与GUI整合的实践方法具备很高的复用价值。1. 期末大作业里那个猫狗识别项目到底在考什么、值得做吗期末大作业里猫狗识别是绕不开的题目。网上能搜到一堆基于PythonCNN模型的源码自己从头跑一遍却容易卡在三个地方目录组织不对、验证集乱增广、GUI预测就崩。本质就是图像二分类输入猫图或狗图CNN提特征最后给出是dog的概率。难的不是网络结构而是把数据准备、训练、界面、打包串成能交付的完整链路。适合Python入门后想完整走一遍深度学习流程的在校生也适合想复现一个可运行CNN例子的从业者。提醒一句顺序错了脚本报错的次数会比训练时间还多。装好Python环境后先确认PyCharm配置的解释器和GUI运行用的是同一个免得训练完在界面上报ModuleNotFoundError这种低级翻车最亏。2. 先把图片喂明白数据集准备与ImageDataGenerator的四个参数2.1 目录结构要按Keras的习惯摆train、val、test一个不能少flow_from_directory不是像plt.imread那样读单张图片而是自动扫描train目录下每个子目录把子目录名当作类别。所以数据集必须在磁盘上先摆成结构化目录。网上那些“把所有图片堆在一个文件夹里然后代码自动分类”的脚本不是不能跑而是做完之后你根本没法盯着验证集调参最后GUI里输入一张新图片时你也不知道它和训练分布是不是一回事。一个规范目录长这样data/ ├── train/ │ ├── cat/ cat.12.jpg ... │ └── dog/ dog.23.jpg ... ├── val/ │ ├── cat/ ... │ └── dog/ ... └── test/ ├── test_cat.jpg └── test_dog.jpg如果你的原始图片文件名带前缀比如cat.100.jpg、dog.200.jpg可以用脚本按比例拆分顺序是“先打乱、再切分、再复制”import os import random import shutil raw_dir raw_images # 原始图片所在目录 data_dir data # 目标数据集目录 train_ratio 0.8 # 训练集占比其余进验证集 for cls in [cat, dog]: files [f for f in os.listdir(raw_dir) if f.lower().startswith(cls .)] random.shuffle(files) split int(len(files) * train_ratio) for i, name in enumerate(files): part train if i split else val dest os.path.join(data_dir, part, cls, name) os.makedirs(os.path.dirname(dest), exist_okTrue) shutil.copy(os.path.join(raw_dir, name), dest)逻辑说明第一轮按类别前缀筛出cat和dog两个文件列表分别打乱再切分这样每个类里都有80%进训练、20%进验证。复制而不是移动原始素材保底万一改了代码想重新切分还有后悔药。参数说明train_ratio0.8对一两千张的数据够用数据量超过一万张时0.9训练比例更常见因为验证集只需要保证每类几百张就能看出趋势。exist_okTrue保证目标目录不存在时自动创建避免自己在data/train/cat外再多包一层目录——这是新手最高频的目录坑多套一层就会让flow_from_directory打印出0张图片。2.2 ImageDataGenerator训练集做增广验证集只做归一化图像进CNN之前要统一尺寸、统一数值范围。常见做法是写一个ImageDataGenerator把“读图、增广、归一化、成batch”整个过程串起来训练时每次拿到一批新变换后的图验证时不加任何随机变换。from tensorflow.keras.preprocessing.image import ImageDataGenerator IMG_W, IMG_H 150, 150 BATCH_SIZE 32 train_datagen ImageDataGenerator( rescale1.0 / 255, rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.2, zoom_range0.2, horizontal_flipTrue ) val_datagen ImageDataGenerator(rescale1.0 / 255) train_gen train_datagen.flow_from_directory( data/train, target_size(IMG_W, IMG_H), batch_sizeBATCH_SIZE, class_modebinary ) val_gen val_datagen.flow_from_directory( data/val, target_size(IMG_W, IMG_H), batch_sizeBATCH_SIZE, class_modebinary ) print(train_gen.class_indices)逻辑说明rescale1.0/255把0到255的像素值压到0到1避免大数值让激活函数直接饱和。rotation_range20是在±20度范围内随机旋转width_shift_range和height_shift_range都是随机平移20%shear_range做错切zoom_range0.2做缩放horizontal_flip做水平翻转。猫狗图片有左右对称性水平翻转非常安全。参数说明验证集只写rescale不做任何翻转裁剪。验证集做增广会让validation准确率忽高忽低你还会误以为模型没过拟合实际是验证分布被改了。class_modebinary告诉Keras标签是一维0/1对应二分类sigmoid输出print(train_gen.class_indices)打印出来通常是{cat: 0, dog: 1}这个顺序后面GUI里要对照用。提示如果flow_from_directory打印Found 0 images第一反应去检查目录层数train/cat下面必须直接是jpg不能是train/cat/2024/xxx.jpg。还有人会问为什么不把图片提前批量转成numpy数组直接塞给模型全量load到内存对两千张图确实没问题但CNN训练指望每个epoch都看到随机增广后的新样本一次性存好的数组就失去了在线增广的意义。保留生成器这种写法本质是让模型每轮都在和“数据采集器”交互这是这个项目里最值得保留的习惯。3. 搭一个够用的CNN卷积层、池化层与二分类输出3.1 CNN基本结构就三件事提特征、压维度、出概率核心就是CNN卷积神经网络最基本的结构组合。第一层卷积找边缘和纹理第二层卷积组合出五官、耳朵轮廓第三层卷积能抓住更全局的形状。对猫狗这种小数据集来说不需要上ResNet或VGG那么深的预训练网络三层卷积加两个全连接已经能把准确率稳定撑到90%上下训练速度快很多参数也更容易解释。各层作用用一个表说清楚层类型做的事常用参数Conv2D用卷积核扫过图像提取局部特征kernel_size(3,3), filters32/64/128MaxPooling2D取2x2区域最大值压缩尺寸、增强平移不变性pool_size(2,2)Flatten把三维特征图拉平成一维向量无参数Dropout训练时随机丢弃部分神经元防止全连接过拟合rate0.5Dense对特征做加权组合输出类别概率units1或2对应代码from tensorflow.keras import Sequential, layers model Sequential([ layers.Conv2D(32, (3, 3), activationrelu, input_shape(150, 150, 3)), layers.MaxPooling2D(2, 2), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D(2, 2), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D(2, 2), layers.Flatten(), layers.Dense(512, activationrelu), layers.Dropout(0.5), layers.Dense(1, activationsigmoid) ]) model.summary()逻辑说明输入是150x150x3的RGB图。第一层32个3x3卷积核输出150x150x32的特征图池化之后变75x75x32第二层64个卷积核到38x38x64第三层128个卷积核到18x18x128。Flatten后约41万个特征值经过Dense(512)映射到512维最后由1个节点输出dog的概率。参数说明filters从32翻倍到128是常见模式每次翻倍伴随特征图尺寸减半、通道数翻倍信息容量不降。Dropout(0.5)放在唯一的大全连接层前面是这里最关键的防过拟合参数。model.summary()用来核对参数量如果训练集只有几千张总参数量控制在1000万以内比较稳妥超了就先删一层或把128改64。3.2 输入尺寸怎么定最后为什么输出1个节点而不是2个为什么是150x150而不是原始图片大小原始图片可能一张500x500、一张640x480CNN需要固定输入尺寸才能成batch。150x150是速度和精度的折中点再大到224x224可以更清晰但训练时间基本翻倍期末作业没必要。尺寸由ImageDataGenerator的target_size决定模型里的input_shape(150,150,3)必须和它一致不一致时predict会直接报维度不匹配。最后一个全连接层二分类有两种写法很多人在这里抄错# 写法Asigmoid单节点配套binary_crossentropy推荐 layers.Dense(1, activationsigmoid) # model.compile(lossbinary_crossentropy, ...) # 写法Bsoftmax双节点配套categorical_crossentropy # layers.Dense(2, activationsoftmax) # model.compile(losscategorical_crossentropy, ...) # flow_from_directory 的 class_mode 要改成 categorical逻辑说明猫和狗互斥一张图不是猫就是狗sigmoid用一个浮点数表示是dog的概率接近0是猫、接近1是狗简单直接。softmax双节点是针对互斥多分类的通用形态之后改成三分类猫狗鸟时要用它。参数说明用了写法Bclass_mode必须同步改成categoricalpredict返回的是二维向量argmax之后才能拿到类别。到底是0代表猫还是1代表猫以train_gen.class_indices打印结果为准别靠“我觉得”直接看字典。4. 训练、调参与模型保存把准确率从0.5拉进0.9的每个细节4.1 fit还是fit_generator旧教程的坑与新写法很多旧教程会在训练时写model.fit_generator(train_gen, ...)TensorFlow 2.x里这个方法已经移除直接换成model.fit即可。fit能自动识别生成器作为输入不需要关心底层线程处理。这里要先确认框架版本不要拿老代码硬试。EPOCHS 30 history model.fit( train_gen, steps_per_epochtrain_gen.samples // BATCH_SIZE, epochsEPOCHS, validation_dataval_gen, validation_stepsval_gen.samples // BATCH_SIZE, callbackscallbacks, verbose1 )逻辑说明train_gen.samples是生成器统计到的训练图片总数除以BATCH_SIZE得到每个epoch需要跑几步。模型每走完一个epoch会看到一遍所有训练图但每张图是随机增广后的新版本所以本质上训练集被“放大”了好几倍。参数说明epochs30只是上限后面配了EarlyStopping会提前收不要傻跑30个。validation_steps也显式指定不然它会尝试把验证集完整扫一遍在小数据集上拖时间。verbose1让每轮打印loss和准确率训练时能实时看到曲线有没有翻车。4.2 三个回调把“玄学调参”变成机制早停、检查点与学习率衰减训练过程最怕两种状态loss降不下去卡在0.5附近或者降得太狠直接过拟合。两个问题都不靠肉眼盯着解决靠回调。from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks [ EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), ModelCheckpoint( models/best_cat_dog.h5, monitorval_loss, save_best_onlyTrue ), ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6 ) ]逻辑说明三个回调都盯着val_loss。EarlyStopping发现验证集loss连续5个epoch不创新低就停restore_best_weights把模型权重回滚到历史最优等于自带后悔药ModelCheckpoint把最优状态存成h5文件ReduceLROnPlateau在loss停滞3个epoch后把学习率减半给模型一次“慢下来继续找”的机会让卡在0.5的模型有机会脱困。参数说明patience5比较温和验证集噪声大时太小的patience会误停。factor0.5是减半min_lr设1e-6避免降到0。训练结束时models/best_cat_dog.h5就是GUI要用的模型文件。训练完顺手看一眼准确率曲线是最快的验证方式import matplotlib.pyplot as plt plt.plot(history.history[accuracy], labeltrain_acc) plt.plot(history.history[val_accuracy], labelval_acc) plt.legend() plt.show()如果val_acc曲线在后期明显掉头向下说明过拟合回到3.1把Dense改小或Dropout调高如果train_acc和val_acc一起横盘在0.5附近先看ReduceLROnPlateau有没有触发、验证集是不是从正确目录读进来的别急着改网络。5. 猫狗识别避坑手册跑通之前必看的4个坎这几条是学生交作业前最容易翻车的点按命中概率从高到低排每一条都见过不止一次。5.1 训练集准确率接近100%验证集是65%过拟合怎么收现象history曲线里train_acc一路冲到0.98val_acc在0.65附近震荡两者之间差了一条鸿沟。原因模型参数远多于样本Dense(512)对两千多张训练图来说过于奢侈或者val_datagen里写进了rotation_range、horizontal_flip验证分布被弄脏。Dropout没加或加在错误位置也会让全连接层记住图片噪声。解决把Dense从512降到256Dropout提高到0.5检查val_datagen只保留rescaleEarlyStopping的patience调成5。这三步做完val_acc通常能回到0.85以上。如果还不行就把Conv第三层的128改64减少整体参数。我给学生的建议是先看模型参数量参数量超过训练图片数就有过拟合风险别急着堆层数。5.2 GUI里点“识别”按钮卡死或无响应现象界面能打开选完图片后程序直接转圈点哪都没反应过几分钟弹“无响应”。原因model.predict在Tkinter的主线程里同步执行模型加载和预处理占用了消息循环界面事件无法被处理。另一个常见问题是用了cv2.imread读中文路径返回None界面拿不到图片数据。解决把预测放到daemon线程里主线程只负责更新Label。读图一律用PIL的Image.open对中文路径更宽容核心写法是threading.Thread(target..., daemonTrue).start()和root.after(0, update_ui)两行。这条坎几乎每届都有人踩交作业前一定要用文件对话框多选几张图实测一遍。5.3 模型训练很准GUI预测却永远输出同一个类现象训练acc 0.98但界面预测猫图输出dog狗图也输出dog感觉模型没生效。原因预测预处理和训练不一致。训练时图片被resize到150并除以255而GUI预测时直接读取原图矩阵尺寸、数值范围都不对。CNN对输入分布极其敏感0-255和0-1的分布差100倍输出大概率被压到某一侧。解决把所有预处理收敛到一个函数里训练和预测共用同一套逻辑别再各写各的import numpy as np from PIL import Image def preprocess_image(path): img Image.open(path).convert(RGB).resize((150, 150)) arr np.asarray(img, dtypenp.float32) / 255.0 return arr[np.newaxis, ...] # 变成 (1, 150, 150, 3)逻辑说明convert(RGB)防止四通道PNG报错resize和训练target_size一致/255.0对应训练时的rescalenewaxis补上batch维度。训练和预测同一段代码至少能消灭一半问题。参数说明如果predict时报shape mismatch多半是input_shape这和resize的尺寸没对齐统一改成150即可。5.4 找不到图片或类别目录0 images与标签顺序反了现象flow_from_directory打印Found 0 images或打印出的class_indices是{cat: 1, dog: 0}而不是期待的{cat: 0, dog: 1}。原因数据目录多包了一层data/train/cat/2024/xxx.jpg会让Keras认为2024才是类别目录类别顺序按目录名的字母序排cat先读到就是0和你的直觉无关。解决重新整理目录让train/cat下面直接放jpg。标签顺序以打印出的train_gen.class_indices为准GUI判断概率时也读这个映射。最稳的办法是拿到class_indices后拿一张猫图、一张狗图分别验证一次不要假设顺序。提示路径里不要用中文文件夹名Windows下OpenCV的imread会直接失败TensorFlow扫描也会有各种诡异问题。项目从建目录开始就全用英文。6. 给期末大作业做一个能交的GUITkinter布局与打包成exe6.1 文件选择、图片预览与结果展示12行核心逻辑GUI部分用Tkinter不依赖额外界面库Python自带的就能交差。先加载第4章保存的模型再写一个独立的recognize方法预测放子线程UI更新放主循环。import threading import tkinter as tk from tkinter import filedialog from PIL import Image, ImageTk import numpy as np from tensorflow.keras.models import load_model model load_model(models/best_cat_dog.h5) class App: def __init__(self): self.root tk.Tk() self.root.title(猫狗识别 - 期末大作业) tk.Button(self.root, text选择图片, commandself.select).pack(pady10) self.lbl_img tk.Label(self.root, text暂无图片) self.lbl_img.pack(pady10) self.lbl_res tk.Label(self.root, text预测结果) self.lbl_res.pack(pady10) def select(self): path filedialog.askopenfilename( filetypes[(图片, *.jpg *.jpeg *.png)]) if not path: return img Image.open(path).resize((200, 200)) self.tk_img ImageTk.PhotoImage(img) self.lbl_img.config(imageself.tk_img) threading.Thread(targetself.recognize, args(path,), daemonTrue).start() def recognize(self, path): arr preprocess_image(path) prob float(model.predict(arr)[0][0]) label dog if prob 0.5 else cat self.root.after(0, lambda: self.lbl_res.config( textf预测结果{label}dog概率 {prob:.2f})) app App() app.root.mainloop()逻辑说明select里先选文件、再预览、最后启动子线程推理。ImageTk.PhotoImage必须赋值给self.tk_img否则局部变量被回收后图片不显示。预测结果通过after(0, ...)排回主线程更新避免直接在子线程改Tkinter控件。参数说明daemonTrue保证窗口关闭时子线程立即终止不会拖住进程prob 0.5对应sigmoid单节点的阈值如果改了模型输出结构这里也要同步改。6.2 用PyInstaller打包成exe一条命令但留出两个耐心点交作业通常要求能演示不能只给.py和.h5。常见做法是用PyInstaller打包pip install pyinstaller pyinstaller -F -w --name cat_dog_gui gui.py-F把程序压成单个exe-w不出现命令行黑窗--name指定输出名。打包后把models/best_cat_dog.h5放到exe同目录代码里写相对路径就能找到。要留两个耐心点第一TensorFlow被打进去后exe体积很大首次启动要解压临时文件等待十几秒是正常现象第二如果exe一闪而过先用去掉-w的命令重新打包在终端里看Traceback多半是缺了某个数据文件用--add-data补进去。如果还要交一份文档说明建议只写三样训练曲线截图、三个核心参数为什么这么设、GUI使用步骤老师看的是思路不是代码量。我自己的习惯是先把GUI在解释器里完整跑通一遍确认模型加载路径和预测结果再打包顺序反了很多时候“打包的锅”实际上是“预测预处理不一致”的锅。希望帮到你。本文还有配套的精品资源点击获取
返回列表