尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于深度学习的水果识别系统:从CNN训练到Gradio部署的完整毕业设计实战

基于深度学习的水果识别系统:从CNN训练到Gradio部署的完整毕业设计实战 简介这份资源是面向计算机相关专业学生与项目实战学习者的深度学习水果识别系统完整项目可直接用于毕业设计、课程大作业或算法练手。项目经导师指导并通过评审源码在本地编译运行无误难度适中适合作为入门到进阶的深度学习实践案例。压缩包共277个文件约17.53MB其中8个Python文件承载模型训练与识别核心逻辑114个js、26个css与7个html构成前端交互界面另有84个gif、14个jpg、10个png等图片素材及字体文件配套数据集与模型一并提供目录结构清晰便于按模块查阅与二次开发。目前已有141人学习下载。读者可从中获得一套可复现的完整方案涵盖数据准备、模型构建、训练调参与界面展示全流程既能对照源码理解深度学习落地细节也能在此基础上替换数据集或调整网络结构快速完成自己的设计任务。1. 从零搭一套水果识别系统毕业设计里最容易被低估的工程细节很多人做水果识别毕业设计第一反应是打开 Python 装个深度学习框架找一份水果数据集跑一个 CNN 就交差。真到答辩现场被问「模型部署在哪、推理一张图要多久、换一种水果还能不能认」往往答不上来。这套「基于深度学习的水果识别系统 Python 源代码 数据集模型完整项目」真正要解决的不是训练一个分类器而是把数据采集、标注、训练、评估、推理封装成一条能跑通、能演示、能讲清楚的链路。它适合正在做毕业设计的学生也适合想用一个小而完整的视觉项目入门深度学习 CNN 的开发者。下面我按自己实际搭过几套的经验把这条链路拆开讲重点放在能复现的命令、参数和踩过的坑上。2. 水果识别系统的技术选型为什么是 CNN 而不是传统特征2.1 从 HOGSVM 到 CNN差在哪早期水果识别常用手工特征加分类器比如提取颜色直方图、HOG 梯度特征再喂给 SVM。这条路在背景干净、光照固定的数据集上能到 80% 左右但一旦水果有遮挡、旋转、光照变化准确率掉得很快。原因是手工特征描述的是「人认为重要」的边缘和颜色分布而水果的判别信息往往藏在纹理和局部形状的组合里人很难穷举。CNN 的优势在于卷积核自动学习局部特征浅层学边缘和颜色深层学形状和语义。对水果这种类间差异中等、类内差异较大的目标CNN 的泛化能力明显更强。常见做法是用迁移学习拿在 ImageNet 上预训练过的骨干网络替换最后的全连接层用水果数据集微调。这样即使你的数据集只有几千张也能拿到 95% 以上的验证准确率。选型上毕业设计推荐两条路线一是轻量级自定义 CNN参数量小、训练快、代码透明适合讲清楚每一层在干什么二是迁移学习用 ResNet18 或 MobileNetV3精度高、训练轮数少适合追求指标。我一般会两个都跑用自定义 CNN 讲原理用迁移学习出最终指标。2.2 数据集怎么组织才不返工数据集是这类项目最容易翻车的地方。常见的水果数据集有 Fruit-360、Kaggle 上的 Fruits-360类别从几十到上百不等。但直接下载的数据集往往目录结构混乱有的按类别分文件夹有的所有图混在一起配一个 CSV 标签。训练前必须统一成ImageFolder能读的结构dataset/ ├── train/ │ ├── apple/ │ │ ├── 001.jpg │ │ └── ... │ ├── banana/ │ └── ... ├── val/ │ ├── apple/ │ └── ...划分比例建议 train:val 8:2如果类别样本不均衡按类别分层抽样别直接随机切。我见过有人随机切完验证集里某个类别只有两张图评估指标完全不可信。提示划分脚本要固定随机种子否则每次运行切分结果不同实验无法复现。import os, shutil, random from pathlib import Path random.seed(42) # 固定种子保证切分可复现 src Path(raw_dataset) dst Path(dataset) classes [d.name for d in src.iterdir() if d.is_dir()] for cls in classes: imgs list((src / cls).glob(*.jpg)) random.shuffle(imgs) split int(len(imgs) * 0.8) # 8:2 划分 for phase, subset in [(train, imgs[:split]), (val, imgs[split:])]: out dst / phase / cls out.mkdir(parentsTrue, exist_okTrue) for img in subset: shutil.copy(img, out / img.name)这段脚本做三件事遍历原始类别目录、按类别打乱、按 8:2 复制到 train 和 val。random.seed(42)是关键保证每次切分一致。split用整数截断样本少的类别要检查 val 是否为空。复制而不是移动是为了保留原始数据方便换划分比例重跑。2.3 环境依赖与版本对齐Python 环境建议用 conda 单独建一个避免和系统包冲突。核心依赖就四个torch、torchvision、numpy、pillow。安装命令conda create -n fruit python3.9 -y conda activate fruit pip install torch torchvision numpy pillow matplotlib版本上torch 1.13 到 2.x 都能跑torchvision 要和 torch 对应。如果要用 GPU先确认 CUDA 版本再装对应 wheel。CPU 也能训只是慢小数据集 20 轮大概十几分钟。matplotlib用来画训练曲线答辩时放 loss 和 accuracy 曲线很加分。3. 训练一个能用的水果分类模型代码、参数与评估3.1 自定义 CNN 的最小实现先给一个结构清晰的自定义 CNN适合讲原理。三层卷积每层后接 ReLU 和最大池化最后全连接输出类别数。import torch import torch.nn as nn class FruitCNN(nn.Module): def __init__(self, num_classes): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 224-112 nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 112-56 nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), # 56-28 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): return self.classifier(self.features(x))padding1保证卷积后尺寸不变池化负责降采样。三次池化把 224 降到 28特征图 128 通道。全连接前加 Dropout 0.5 抑制过拟合这是小数据集上的常规操作。num_classes按你的数据集类别数传比如 10 类水果就传 10。3.2 数据增强与 DataLoader 参数小数据集必须做增强否则训练集准确率 99%、验证集 70% 的过拟合很常见。用 torchvision 的 transformsfrom torchvision import transforms, datasets from torch.utils.data import DataLoader train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.RandomRotation(15), # 随机旋转 ±15 度 transforms.ColorJitter(0.2, 0.2, 0.2), # 亮度/对比度/饱和度扰动 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(dataset/train, transformtrain_tf) val_ds datasets.ImageFolder(dataset/val, transformval_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4)Normalize 用的均值方差是 ImageNet 的统计值迁移学习和从头训练都建议沿用保证输入分布一致。batch_size32是显存和稳定性的折中显存小就降到 16。num_workers4在 Windows 上有时会报错改成 0 即可。验证集不做增强只做 Resize 和归一化否则评估指标会失真。3.3 训练循环与关键超参训练循环要记录每轮 loss 和 accuracy方便画曲线。device torch.device(cuda if torch.cuda.is_available() else cpu) model FruitCNN(num_classeslen(train_ds.classes)).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) for epoch in range(30): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() scheduler.step() # 每 10 轮学习率减半 # 验证阶段省略见完整代码lr1e-3是 Adam 的常用起点weight_decay1e-4做 L2 正则。StepLR每 10 轮把学习率乘 0.5后期收敛更稳。轮数 30 对小数据集够用如果验证 loss 连续 5 轮不降可以早停。训练时把模型切到model.train()验证时切model.eval()并加torch.no_grad()这是新手最容易漏的两步。3.4 评估指标别只看准确率准确率在类别均衡时够用但水果数据集往往某几类样本多。要补一个混淆矩阵和每类 precision/recall。from sklearn.metrics import classification_report, confusion_matrix model.eval() preds, labels [], [] with torch.no_grad(): for x, y in val_loader: out model(x.to(device)) preds.extend(out.argmax(1).cpu().numpy()) labels.extend(y.numpy()) print(classification_report(labels, preds, target_namesval_ds.classes)) print(confusion_matrix(labels, preds))classification_report直接给出每类的 precision、recall、f1。如果某一类 recall 特别低说明模型把它和别的类混了看混淆矩阵定位是哪两类。常见原因是这两类外观接近比如青苹果和梨需要更多样本或更强增强。4. 推理封装与界面演示让答辩现场能跑起来4.1 单张图片推理函数训练完要能对任意一张图输出类别和置信度。from PIL import Image import torch.nn.functional as F def predict(img_path, model, class_names, device): model.eval() img Image.open(img_path).convert(RGB) x val_tf(img).unsqueeze(0).to(device) # 加 batch 维度 with torch.no_grad(): prob F.softmax(model(x), dim1) conf, idx prob.max(1) return class_names[idx.item()], conf.item() # 用法 name, conf predict(test.jpg, model, train_ds.classes, device) print(f{name} {conf:.2%})unsqueeze(0)把单张图变成[1,3,224,224]因为模型 forward 期望有 batch 维。softmax把 logits 转成概率max取最大概率和对应类别。置信度低于 0.6 时建议提示「不确定」避免硬答。4.2 用 Gradio 快速搭演示界面答辩现场用命令行不够直观Gradio 几行代码就能出网页界面。import gradio as gr def classify(img): name, conf predict(img, model, train_ds.classes, device) return {name: conf} gr.Interface(fnclassify, inputsgr.Image(typefilepath), outputsgr.Label(num_top_classes3), title水果识别系统).launch()gr.Image(typefilepath)传文件路径给 predictgr.Label显示 top3 类别和概率。launch()默认起本地服务答辩时浏览器打开就能演示。注意模型和 class_names 要在全局加载一次别每次推理都重新加载。4.3 模型保存与加载的坑保存用torch.save(model.state_dict(), fruit.pth)只存参数不存结构。加载时要先实例化同结构模型再load_state_dict。torch.save(model.state_dict(), fruit.pth) # 加载 model FruitCNN(num_classeslen(train_ds.classes)).to(device) model.load_state_dict(torch.load(fruit.pth, map_locationdevice)) model.eval()map_location很重要GPU 上训的模型在 CPU 机器上加载不加这个参数会报错。类别数必须和训练时一致否则最后一层维度对不上。建议把 class_names 一起存成 json加载时读回来避免顺序错乱。5. 避坑与排查水果识别项目里最常见的 5 个翻车点5.1 验证准确率远低于训练准确率现象训练集 99%验证集 70% 上下。原因过拟合数据增强太弱或模型太大。解决加强增强加 RandomResizedCrop、ColorJitter加 Dropout 和 weight_decay或者换更小的模型。如果数据集本身很小考虑迁移学习而不是从头训。5.2 损失变成 nan现象训练几轮后 loss 显示 nan。原因学习率太大或者输入没归一化。解决把 lr 降到 1e-4确认 transforms 里有 Normalize检查数据里有没有损坏图片。损坏图片用 PIL 打开会抛异常写个脚本遍历一遍过滤掉。5.3 类别索引和名称对不上现象预测结果张冠李戴明明输入苹果输出香蕉。原因ImageFolder按文件夹名排序生成类别索引推理时 class_names 顺序和训练时不一致。解决训练完把train_ds.classes存成 json推理时读同一个文件别手写类别列表。5.4 DataLoader 在 Windows 上报 BrokenPipe现象num_workers0时 Windows 报错或卡死。原因Windows 多进程 spawn 机制和 Linux 不同。解决把num_workers设为 0或者把训练代码放在if __name__ __main__:里。这是血泪经验调半天以为是代码问题其实是平台差异。5.5 推理速度慢到无法演示现象单张图推理要好几秒。原因每次推理都重新加载模型或者没切 eval 模式。解决模型全局加载一次推理前model.eval()并包torch.no_grad()。如果还慢检查是不是在 CPU 上跑了大模型换 MobileNetV3 会快很多。6. 把项目做扎实的进阶技巧迁移学习与可复现实验自定义 CNN 跑通后想让指标更好看迁移学习是最划算的一步。用 torchvision 自带的预训练 ResNet18只改最后一层from torchvision import models import torch.nn as nn model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) model.fc nn.Linear(model.fc.in_features, len(train_ds.classes)) model model.to(device) # 只微调最后几层前面冻结 for name, param in model.named_parameters(): if fc not in name: param.requires_grad FalseweightsDEFAULT加载 ImageNet 预训练权重model.fc.in_features是 ResNet18 最后一层输入维度 512。冻结前面层只训 fc第一轮就能到 90% 以上再解冻全部微调几轮通常能到 97% 以上。学习率要调小微调阶段用 1e-4别用 1e-3 把预训练权重冲掉。可复现实验的习惯每次训练把超参、数据集划分、随机种子写进一个 config 字典连同最终指标存成 json。答辩被问「你这个结果怎么来的」直接翻记录。我一般还会固定torch.manual_seed(42)和torch.cuda.manual_seed_all(42)保证同一份代码两次运行结果一致。这些细节看起来琐碎但正是毕业设计里区分「跑通」和「做扎实」的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表