PyTorch CNN猫狗识别实战:从模型训练到GUI与摄像头部署
简介这是一份面向Python课程设计、深度学习入门与期末大作业场景的猫狗识别项目源码基于CNN卷积神经网络构建完整的图像分类方案附带GUI图形界面、模型训练与预测脚本以及README文档说明。资源包共6个文件其中5个为Python脚本、1个为Markdown文档整体压缩包仅7KB但麻雀虽小五脏俱全。代码按模型训练、图像预处理、单张预测和界面交互等模块拆分注释清晰友好零基础读者也能快速看懂数据加载、卷积层搭建、损失计算和推理结果展示等关键环节。目前已有244人学习下载特别适合需要工具脚本、演示项目或高分范例的在校生。项目经严格调试可直接运行系统功能完善、界面美观可直接用于毕业设计、期末大作业或课程设计答辩节省自行搭建与排错的时间整体性价比很高。1. 期末大作业选猫狗识别为什么这个 CNN 项目值得直接复用期末大作业选“猫狗识别”的人很多但能同时拿出“能训练的代码 能演示的界面 能写进报告的原理说明”的并不多。这个基于 Python CNN 模型的猫狗识别项目恰好把三件事打包在一起源码里既有 train.py / train0.py 两个训练入口也有 pred.py 单张预测脚本还带一个完整的 GUI 界面代码注释覆盖了主要逻辑新手跟一遍就能说清“图像怎么进网络、特征怎么提、类别怎么出”。对正在做课程设计、期末大作业或者想快速跑通一个深度学习 Demo 的人来说它最大的价值不是算法多前沿而是结构完整、能跑、能讲、能展示。2. CNN 模型与数据集组织先读懂 train.py 和 train0.py 的分工2.1 为什么猫狗识别是 CNN 的入门级考点猫狗识别本质是一个二分类问题输入一张图片输出“猫”或“狗”的概率。传统做法是提取颜色直方图、边缘特征、HOG 等手工特征再丢给 SVM但这类方法对背景变化、光照差异、拍摄角度非常敏感换一组图片准确率就掉得厉害。CNN 的核心优势在于卷积层能自动学习不同层级的特征浅层卷积学边缘、纹理深层卷积学耳朵形状、眼睛位置这类语义特征不需要人工设计特征算子。一个典型的 CatDogCNN 网络会按这样的链路处理一张图输入 (3, 128, 128) - Conv2d - ReLU - MaxPool2d - ... - Flatten - Dense - Softmax以输入 128×128 的 RGB 图片为例经过 3 组“卷积 激活 池化”后特征图从 128×128 缩小到 16×16通道数从 3 增加到 128。最后展平成一个一维向量接两个全连接层输出 2 个 logits再用 Softmax 转成概率。Pooling 层每次把尺寸减半既减少计算量也让特征对平移更鲁棒这是 CNN 能“扛住”真实图片中猫狗位置不固定的关键。实际项目里网络结构不一定完全一样但整体套路是固定的。你在 train.py 里大概率会看到类似 SmallCNN、CatDogCNN 这样的自定义类内部就是nn.Sequential堆叠卷积层和全连接层。理解了这条链路后面改网络层数、改输入尺寸都不会慌。2.2 数据集的目录规范与 DataLoader 读取逻辑CNN 训练第一步不是写网络而是把数据组织对。PyTorch 里最省事的做法是用torchvision.datasets.ImageFolder它要求数据目录按类别分文件夹存放目录名就是类别名。这个项目的数据集我通常这样组织data/ ├── train/ │ ├── cat/ # 放猫的图片 │ └── dog/ # 放狗的图片 ├── val/ │ ├── cat/ │ └── dog/ └── test/ # 测试用不放标签对应数据加载代码from torch.utils.data import DataLoader from torchvision import datasets, transforms transform_train transforms.Compose([ transforms.Resize((128, 128)), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_dataset datasets.ImageFolder(data/train, transformtransform_train) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers0)这段代码有两个关键参数。shuffleTrue保证每个 epoch 训练样本顺序被打乱避免模型学到样本顺序相关的假规律batch_size32是显存与梯度稳定性的折中显存不够可以降到 16 或 8。Normalize 的 mean 和 std 用的是 ImageNet 统计值因为预训练模型和很多公开网络的初始化都基于这套分布常用做法是直接沿用。如果训练时发现收敛很慢可以尝试去掉 Normalize 或改成自己数据集的均值方差但一般不建议动。在num_workers设置上Windows 下经常遇到BrokenPipeError这是多进程数据加载的经典坑。家用电脑训练图片数量不大时直接设 0 最省心让主进程同步加载数据。2.3 定义模型与训练超参torch.nn 搭一个能跑的 CatDogCNN网络模型是训练脚本的核心。这个项目里的模型定义大致结构如下import torch.nn as nn class CatDogCNN(nn.Module): def __init__(self): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 16 * 16, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, 2) ) def forward(self, x): return self.classifier(self.features(x))第一层卷积输入 3 通道输出 32 个特征图padding1保证卷积后尺寸不缩水每个卷积之后接 ReLU 增加非线性再接 MaxPool2d 把空间尺寸减半。三层之后128×128 的输入变成 128 通道、16×16 的特征图。展平后是 128×25632768 个特征经过 128 维的中间层Dropout(0.5)随机丢弃一半神经元防止过拟合最后输出 2 个类别分数。训练循环的骨架也很固定import torch from torch.optim import Adam model CatDogCNN() criterion nn.CrossEntropyLoss() optimizer Adam(model.parameters(), lr0.001) for epoch in range(20): running_loss 0.0 correct 0 total 0 for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() _, predicted torch.max(outputs, dim1) total labels.size(0) correct (predicted labels).sum().item() print(fEpoch {epoch1:02d} | Loss: {running_loss/len(train_loader):.4f} | Acc: {correct/total:.4f})optimizer.zero_grad()清空上一次迭代的梯度loss.backward()反向传播计算梯度optimizer.step()更新权重。Adam 一般比 SGD 好调lr0.001是常见启动值如果 Loss 震荡厉害就降到 0.0001如果收敛太慢就适当加大到 0.003。epoch 不用太多20 轮在 CPU 上大概半小时左右能看到效果训练中途 Loss 降到 0.3 以下基本就能用了。2.4 train.py 与 train0.py 两个入口怎么选项目文件里有 train.py 和 train0.py 两个训练脚本很多人第一次打开会困惑该跑哪一个。我看了下这种项目的普遍设计思路train0.py 通常是“从零开始训练”的完整流程脚本网络初始化随机权重训练结束后保存catdog_model.pthtrain.py 则是在此基础上加了更细的验证逻辑比如每个 epoch 结束后在验证集上计算准确率、保存最佳模型适合用来对比不同超参效果。实际操作建议是第一次跑先用 train0.py 验证数据加载和模型前向传播没问题能跑完一个 epoch 再切到 train.py 看完整训练曲线。如果 train.py 里加了 TensorBoard 或日志记录之类的增强逻辑那就以它为准。两个脚本共用同一个模型定义文件训练产物都是同一个权重文件名后面 pred.py 和 GUI 加载都认这个名字所以不用纠结跑通一个即可。3. 单张图片预测从 pred.py 看权重加载、预处理到输出类别的完整链路3.1 加载权重时最容易翻车的 device 与 map_location训练完成后预测脚本要加载模型权重。这里最容易踩的坑是设备不一致在一台 GPU 机器上训练的权重拿到只有 CPU 的电脑上直接torch.load(catdog_model.pth)会报RuntimeError: Attempting to deserialize object on a CUDA device。正确做法是预测脚本里统一用 CPU 或自动选择设备import torch def load_model(model_pathcatdog_model.pth): device torch.device(cuda if torch.cuda.is_available() else cpu) model CatDogCNN() model.load_state_dict( torch.load(model_path, map_locationdevice) ) model.to(device) model.eval() return model, devicemap_locationcpu可以把权重张量全部映射到 CPU 上即使原模型是在 GPU 上训练的也能加载成功model.eval()必须调用它会关闭 Dropout 和 BatchNorm 的训练语义否则同一张图片每次预测的概率都可能不同这在推理阶段是致命的逻辑错误。3.2 图片预处理与推理PIL 打开、Resize、Normalize 顺序不能乱预测时图片预处理必须和训练保持一致差别只在于不需要数据增强。比如训练时用了RandomHorizontalFlip预测时就别加训练时 Resize 到 128×128预测时也要 Resize 到同样尺寸。pred.py 里的核心逻辑通常是这样的from PIL import Image from torchvision import transforms import torch transform_pred transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def predict_one_image(model, img_path, device): img Image.open(img_path).convert(RGB) tensor transform_pred(img).unsqueeze(0).to(device) with torch.no_grad(): logits model(tensor) prob torch.softmax(logits, dim1) pred_idx torch.argmax(prob, dim1).item() confidence prob[0, pred_idx].item() label dog if pred_idx 1 else cat return label, confidence这里三个细节容易忽略。第一.convert(RGB)把灰度图或带透明通道的图片统一成三通道避免通道数不匹配第二ToTensor()会把像素值从 0-255 缩放到 0-1Normalize再按通道减均值除方差如果漏掉 Normalize网络输入的分布和训练时不一致预测结果基本不可信第三torch.no_grad()关闭梯度计算推理时显存占用和速度都会好很多。3.3 预测脚本怎么加断点看中间结果如果你想知道模型到底学到了什么或者排查某张图片为什么识别错可以在 forward 里临时返回中间特征图def forward_with_features(self, x): feat self.features(x) # 比如输出形状 (1, 128, 16, 16) out self.classifier(feat) return out, feat然后把特征图可视化看模型关注的是猫耳朵还是背景。这种方法在做实训报告时特别好用能直接截图放进“模型原理分析”章节。特征图可视化太深奥这里只要记住在预测脚本里临时打印feat.shape确认卷积层输出尺寸是否和分类层输入匹配大多数 “size mismatch” 都是这一步先发现的。4. GUI 界面把模型推理装进图形化应用答辩时更有说服力4.1 GUI 框架取舍tkinter 为什么是默认选项课程设计项目里GUI 一方面是为了演示方便另一方面也是答辩时的加分项。Python 的 GUI 方案无非 tkinter、PyQt5、wxPython 三种主流选择。这个项目用的 tkinter原因很实际它是 Python 标准库自带组件不需要额外安装依赖代码量少写一个“选图 → 显示 → 识别”的界面几十行就够。PyQt5 虽然界面更现代但引入额外包并且信号槽机制对没接触过 GUI 编程的同学有学习成本。对期末大作业来说tkinter 的稳定性比美观更重要答辩投影时界面能正常响应就是成功。4.2 从 GUI 到模型的调用链选图、推理、回显gui.py 的逻辑分三段用户选择图片文件、调用模型推理、把结果显示到界面上。核心结构如下import tkinter as tk from tkinter import filedialog from PIL import ImageTk, Image class CatDogApp: def __init__(self, root): self.root root self.root.title(猫狗识别系统) self.root.geometry(500x600) self.model, self.device load_model(catdog_model.pth) self.tip_label tk.Label(root, text点击选择图片) self.tip_label.pack(pady10) self.img_label tk.Label(root) self.img_label.pack() self.result_label tk.Label(root, text, font(Arial, 16)) self.result_label.pack(pady10) btn tk.Button(root, text选择图片, commandself.select_and_predict) btn.pack() def select_and_predict(self): path filedialog.askopenfilename( filetypes[(Image Files, *.jpg *.png *.jpeg)] ) if not path: return img Image.open(path).convert(RGB) img.thumbnail((300, 300)) photo ImageTk.PhotoImage(img) self.img_label.config(imagephoto) self.img_label.image photo label, confidence predict_one_image(self.model, path, self.device) self.result_label.config( textf识别结果{label} 置信度{confidence:.2%} )这段代码的逻辑很直白够选图片按钮触发select_and_predict先用 PIL 打开图片并生成缩略图显示在界面上再调用预测函数把结果写到标签里。有一个小坑需要注意self.img_label.image photo这行必须保留。tkinter 的引用机制会在函数结束后回收 PhotoImage 对象不保存引用的话图片会直接消失。这个坑几乎每个写 tkinter 图片显示的人都会踩一次。4.3 推理放后台线程让 GUI 不变成“未响应”GUI 程序有个原则耗时操作不能放在主线程。模型加载和推理本身是 CPU/GPU 密集计算图片稍大就可能造成界面冻结。Windows 下表现为窗口标题出现“未响应”答辩演示时非常尴尬。改善方式是用threading在后台执行推理import threading def select_and_predict(self): path filedialog.askopenfilename() if not path: return threading.Thread(targetself._predict_worker, args(path,), daemonTrue).start() def _predict_worker(self, path): label, confidence predict_one_image(self.model, path, self.device) self.result_label.config( textf识别结果{label} 置信度{confidence:.2%} )注意daemonTrue让后台线程随主窗口退出而结束避免关掉窗口后 Python 进程还在后台挂着。如果你的 GUI 代码里有进度条或者实时更新图片预览建议把“显示图片”放在主线程“模型推理”放后台线程这样用户体验最稳。5. 猫狗识别常见踩坑训练不收敛、界面卡死、误识别高的排查清单5.1 训练 Loss 停在 0.693 附近震荡现象Loss 从一开始就在 0.69 左右来回波动几十个 epoch 后没有明显下降。原因二分类任务随机猜测的概率是 0.5交叉熵损失对应-ln(0.5) ≈ 0.693。Loss 一直停在这里说明网络完全没有学习到有效特征最常见原因是数据没有 Normalize或者学习率设置不当。解决先确认ToTensor()和Normalize都加上了。再把学习率调到 0.0001 试一个 epoch如果 Loss 开始下降说明之前学习率太大如果还是不动检查数据集目录里是否真的有两个类别的图片以及标签是否在[0, 1]范围内。5.2 预测阶段报 size mismatch现象模型加载正常但输入图片后报RuntimeError: size mismatch, m1: [1 x 4096], m2: [64 x 128]之类的错误。原因训练时图片 Resize 到 128×128预测时用了不同尺寸比如 PIL 读入原图后没有 Resize导致展平后的特征数量与全连接层输入维度对不上。解决检查 predict 脚本里的transforms.Resize((128, 128))是否和训练脚本完全一致。如果改过网络结构还要同步更新nn.Linear(128 * 16 * 16, 128)的输入维度手动算一下输入尺寸每经过一次 MaxPool2d 减半三层池化后除以 8。5.3 验证集准确率 95%换张真实照片就乱猜现象验证集上准确率很高但用自己的手机照片测试结果完全不对。原因验证集图片和训练集来自同一数据源背景、光照、拍摄角度高度相似模型学到的是“这个数据集的特征”而不是泛化的“猫狗特征”。另外手机照片常有 EXIF 旋转信息直接读取会得到旋转 90 度的图片。解决测试时优先选背景简单的正脸照片在预处理里加入ImageOps.exif_transpose(img)修正旋转如果项目允许做简单的中心裁剪或数据增强增加泛化性。5.4 Windows 中文路径导致数据集读取失败现象ImageFolder加载数据时报错或者 OSError但路径明明存在。原因Windows 下 Python 默认编码和文件系统编码不一致中文目录名或中文文件名容易触发 Unicode 解码问题。解决最直接的办法是数据集路径全用英文data/train/cat这种结构最安全。如果你的训练脚本里用os.listdir自己写数据加载添加encodingutf-8参数或者直接用pathlib.Path替代字符串拼接。5.5 GUI 点击预测后窗口无响应现象点击按钮后整个界面卡住过几秒才恢复甚至一直转圈。原因推理在主线程执行期间 tkinter 的消息循环被阻塞无法响应刷新和点击事件。解决用threading.Thread(targetself._predict_worker, args(path,), daemonTrue).start()把推理放到后台线程。如果模型加载也在 GUI 启动时执行考虑把模型加载也放到后台任务避免启动界面卡顿。6. 把模型接入摄像头实时识别验证 CNN 泛化能力的最快方式训练完模型、跑通 GUI 之后建议再做一步用 OpenCV 打开摄像头实时识别画面中的猫狗。这一步批量测试多张真实环境图片也比 GUI 单张识别更有演示冲击力。核心思路和 GUI 一样只是把图片来源从文件换成摄像头帧import cv2 import torch from PIL import Image from torchvision import transforms device torch.device(cuda if torch.cuda.is_available() else cpu) model CatDogCNN().to(device) model.load_state_dict(torch.load(catdog_model.pth, map_locationdevice)) model.eval() transform transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img Image.fromarray(rgb) tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): prob torch.softmax(model(tensor), dim1) pred torch.argmax(prob, dim1).item() label Dog if pred 1 else Cat color (0, 255, 0) if pred 1 else (0, 0, 255) cv2.putText(frame, f{label} {prob[0, pred].item():.2f}, (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 2) cv2.imshow(CatDog Real-Time, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码把 OpenCV 的 BGR 帧转成 RGB再用 PIL 封装后走训练时的同一套预处理。实际测试中你会立刻发现模型对模糊画面和近距离大脸很敏感这类场景下置信度会明显下降。如果画面里同时出现猫和狗模型大概率只输出其中一类这是单分类网络的天然局限用来写进报告的“不足与改进”很合适。从那以后我每次拿到一个识别模型都会先跑一遍摄像头实测再决定要不要上报告里的准确率数字这比在验证集上刷多少次准确率都更有说服力。希望这个项目的源码和这份复现笔记能帮你在期末答辩时少走几条弯路。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →