水果图像识别毕设全链路:从Fruit-360数据集到Gradio推理部署 简介这份PDF是一篇基于深度学习的水果图像识别系统毕业设计范文面向电子信息、计算机等专业需要完成毕业设计或论文写作的本科生与研究生尤其适合以嵌入式AI、图像分类为选题方向的同学参考。资源包共1个PDF文件约895KB内容为正式发表的学术论文包含中英文摘要、引言、系统设计、深度学习网络搭建、硬件电路设计与软件实现等完整章节可直接作为论文结构模板与答辩素材。论文核心采用稀疏化CNN网络涵盖10个卷积层、5个池化层与全连接层设计并对卷积层权重进行稀疏化以降低模型复杂度硬件部分基于TI工业派开发板通过USB、串口、网络接口与HDMI模块连接摄像头与显示屏软件部分基于TIDL API完成模型训练、导入与目标水果信息显示。目前已有365人学习读者可从中获取选题思路、章节组织方式、关键技术描述与实验验证写法快速搭建自己的毕业设计框架。1. 水果图像识别毕设从数据集到推理一条能跑通的工程链路很多同学做水果图像识别毕业设计卡住的地方根本不是模型选得不够新而是从数据到推理这条链路没打通。论文里写个 CNN 准确率 95%答辩时老师让现场跑一张手机拍的苹果照片结果分类全乱——这种翻车我见过太多次。这个方向要解决的核心问题很具体给定一张水果图片输出它属于哪一类并且要在真实拍摄条件下保持可用精度。适合正在做深度学习、计算机视觉方向毕业设计的本科生也适合想快速搭一个可演示原型的工程师。整条链路拆开就是四件事数据集准备与增强、模型选型与训练、评估与调参、部署成可交互的推理入口。下面按这个顺序讲透每一步都给能直接抄的代码和参数。2. 数据集怎么选怎么造Fruit-360 与自建数据的取舍2.1 为什么水果识别不能直接拿 ImageNet 凑数ImageNet 有 1000 类但水果只占其中零星几类且都是棚拍白底图。你的毕设场景是手机随手拍、超市货架、带枝叶的果园域差异极大。直接拿 ImageNet 预训练权重做迁移学习可以但微调数据必须换成水果专用集。常见做法是两条路一是用公开的 Fruit-360 数据集它包含 131 类水果、共 9 万余张 100x100 的图片每类都有旋转增强版本二是自建数据集用手机拍 10 到 20 类常见水果每类 200 到 500 张覆盖不同光照、角度、遮挡。Fruit-360 的优点是开箱即用、类别多缺点是分辨率低、背景单一训练出来的模型对复杂背景泛化差。自建数据集的优点是贴合你的演示场景缺点是标注成本高。我的建议是两者结合用 Fruit-360 做预训练或大类分类再拿自建数据做微调这样答辩时既有公开基准可比又能演示真实场景。2.2 用 torchvision 加载 Fruit-360 并做增强假设你已经把 Fruit-360 下载解压到data/fruits-360/目录训练集和测试集分文件夹存放。下面这段代码完成数据集加载和增强管道搭建。import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练增强随机裁剪、翻转、颜色抖动模拟真实拍摄变化 train_transform transforms.Compose([ transforms.Resize((128, 128)), # Fruit-360 原图 100x100放大到 128 便于后续 backbone 下采样 transforms.RandomHorizontalFlip(p0.5), # 水果左右翻转不改变类别 transforms.RandomRotation(15), # 模拟拍摄角度偏差 transforms.ColorJitter(brightness0.3, contrast0.3, saturation0.3), # 模拟光照变化 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], # ImageNet 统计量迁移学习通用 std[0.229, 0.224, 0.225]) ]) # 验证/测试只做 Resize 和归一化不做随机增强 val_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]) ]) train_set datasets.ImageFolder(data/fruits-360/Training, transformtrain_transform) val_set datasets.ImageFolder(data/fruits-360/Test, transformval_transform) train_loader DataLoader(train_set, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_set, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue) print(f训练集 {len(train_set)} 张{len(train_set.classes)} 类) print(f验证集 {len(val_set)} 张)逻辑说明ImageFolder要求目录结构是根目录/类别名/图片文件Fruit-360 解压后天然满足。增强只在训练时做验证和测试必须用确定性变换否则每次评估结果都在抖动你根本分不清是模型变好了还是增强随机性带来的。Normalize用的均值和方差是 ImageNet 的统计量因为后面要用预训练 backbone输入分布要对齐。参数说明batch_size64在 8GB 显存下跑 128x128 输入比较稳显存小就降到 32。num_workers4取决于你的 CPU 核数Windows 下如果报错就改成 0。RandomRotation(15)的 15 度是经验值水果识别里旋转太大会把香蕉和黄瓜转混不建议超过 20 度。2.3 自建数据集的三个硬性要求如果你决定自建这三条必须满足否则后面训练全是玄学。第一每类至少 200 张低于这个数模型根本学不到类内变化。第二训练集和测试集必须按拍摄批次划分不能随机分——同一批次拍的照片背景、光照几乎一样随机划分会导致测试集精度虚高答辩现场换一批照片就崩。第三标注文件用 CSV 或 JSON 存文件名,类别两列不要用文件夹名当标签后再改改一次就要重新整理目录。提示Fruit-360 的类别名是英文如果论文要求中文类别建一个idx_to_class.json映射表在推理时转换不要动原始目录名。3. 模型选型与训练MobileNetV3 和 ResNet18 怎么选3.1 毕设场景下 backbone 的取舍逻辑水果图像识别不是 ImageNet 那种千类细粒度任务131 类水果之间的区分度远高于猫狗品种。这意味着你不需要 ResNet50 以上的大模型参数量在 2M 到 12M 之间的轻量 backbone 足够。常见选择有三个MobileNetV3-Small、ResNet18、EfficientNet-B0。MobileNetV3-Small 参数量约 2.5M推理速度快适合部署到边缘设备或手机端演示ResNet18 参数量约 11M精度略高训练稳定适合纯服务器端答辩演示EfficientNet-B0 参数量约 5.3M精度和速度平衡但训练时对学习率更敏感。我的建议如果你的毕设要演示 Web 端或移动端实时识别选 MobileNetV3-Small如果只在笔记本上跑图片分类选 ResNet18。下面以 ResNet18 为例MobileNetV3 只需替换模型构造那一行。3.2 迁移学习训练脚本与关键参数import torch.nn as nn import torch.optim as optim from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) # 加载预训练 ResNet18替换最后的全连接层 model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_classes len(train_set.classes) model.fc nn.Linear(model.fc.in_features, num_classes) # 原 1000 类改为水果类别数 model model.to(device) # 损失函数交叉熵类别不均衡时加 weight 参数 criterion nn.CrossEntropyLoss() # 优化器只微调 fc 层时学习率可以大一些全网络微调要小 optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) # 学习率调度余弦退火训练 30 轮 scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30) best_acc 0.0 for epoch in range(30): model.train() running_loss 0.0 for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) _, preds torch.max(outputs, 1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch1:02d} | Loss {running_loss/len(train_loader):.4f} | Val Acc {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_fruit_model.pth) print(f - 保存最优模型当前最佳 {best_acc:.4f})逻辑说明weightsResNet18_Weights.IMAGENET1K_V1加载 ImageNet 预训练权重这是迁移学习的关键没有它你 30 轮根本训不到 90% 以上。替换model.fc是因为原模型输出 1000 类你的水果类别数不同。AdamW比 SGD 更容易调weight_decay1e-4是 L2 正则化的等价实现防止过拟合。余弦退火让学习率从 1e-3 平滑降到接近 0后期微调更精细。参数说明lr1e-3适合只训练 fc 层或全网络微调如果你发现 loss 震荡降到 1e-4。T_max30要和总 epoch 数一致。batch_size在 2.2 节已经设成 64如果显存不够改成 32同时学习率可以减半。验证时model.eval()和torch.no_grad()必须加否则 BatchNorm 和 Dropout 还在训练模式精度会偏低。3.3 训练过程怎么看loss 和 acc 的四种组合训练日志不是只看最后一行。前 5 轮 loss 应该快速下降如果 loss 几乎不动检查三件事学习率是不是太小、数据归一化是不是没做、标签是不是全映射错了。如果训练 acc 很高但验证 acc 很低说明过拟合加数据增强或增大 weight_decay。如果训练和验证 acc 都低说明欠拟合换更大模型或增加 epoch。如果验证 acc 波动超过 5%说明验证集太小或分布不均至少保证每类验证样本不少于 20 张。注意Fruit-360 的测试集和训练集分布接近验证精度通常比自建数据集高 5 到 10 个百分点。论文里如果只报 Fruit-360 精度答辩时老师问真实场景表现你要有自建测试集的数据兜底。4. 评估与调参混淆矩阵告诉你哪两类在互相误判4.1 除了准确率还要看什么准确率是毕设论文里最常报的指标但它掩盖了类别不均衡问题。如果 131 类里苹果有 500 张、榴莲只有 200 张模型把榴莲全预测成苹果准确率依然可能很高。必须补三个指标每类精确率、召回率、F1 分数以及混淆矩阵。混淆矩阵能直接告诉你哪两类水果在互相误判比如青苹果和绿梨、柠檬和橙子这些视觉上接近的类别是主要错误来源。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs imgs.to(device) outputs model(imgs) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) # 分类报告每类 precision/recall/f1 print(classification_report(all_labels, all_preds, target_namesval_set.classes, digits4)) # 混淆矩阵找出误判最多的类别对 cm confusion_matrix(all_labels, all_preds) # 打印误判次数最多的前 10 对 errors [] for i in range(len(cm)): for j in range(len(cm)): if i ! j and cm[i][j] 0: errors.append((cm[i][j], val_set.classes[i], val_set.classes[j])) errors.sort(reverseTrue) for count, true_cls, pred_cls in errors[:10]: print(f真实 {true_cls} 被误判为 {pred_cls}{count} 次)逻辑说明classification_report输出每类的 precision、recall、f1-score直接贴进论文表格。混淆矩阵的cm[i][j]表示真实类别 i 被预测为类别 j 的次数对角线是正确预测。遍历非对角线元素并按次数排序就能定位主要错误来源。参数说明target_names用val_set.classes保证顺序一致。如果类别数太多导致输出太长可以只打印 f1 低于 0.8 的类别。误判对分析出来后针对性地补充这两类的训练数据或者调整增强策略比如对易混类别加更多颜色抖动。4.2 学习率与 batch size 的联动调法学习率和 batch size 不是独立调的。经验规则batch size 翻倍学习率也翻倍。如果你从 64 降到 32学习率从 1e-3 降到 5e-4。如果训练 loss 出现 NaN先检查学习率是不是太大再检查数据里有没有损坏图片。如果验证精度在最后几轮还在上升说明 epoch 不够加到 50 轮。如果训练 10 轮后验证精度就饱和了说明模型容量不够或数据增强太弱。4.3 类别不均衡的两种处理自建数据集常见某类样本特别少。两种处理方式一是加WeightedRandomSampler让少样本类别被采样的概率更高二是在CrossEntropyLoss里加weight参数给少样本类别更大的损失权重。前者改数据加载后者改损失函数通常两者选一即可。如果某类样本少于 50 张建议直接合并到相似类别比如把“青苹果”和“红苹果”合并成“苹果”否则模型学到的特征不稳定。5. 避坑与排查水果识别毕设里最容易翻车的五件事5.1 现象训练精度 99%答辩演示全错原因训练集和测试集来自同一批拍摄数据背景、光照几乎一样模型学到了背景特征而不是水果特征。解决重新按拍摄批次划分数据集确保测试集里有不同背景、不同光照的照片。如果已经训完用自建测试集重新评估论文里同时报两个精度并说明差异。5.2 现象验证 loss 突然变成 NaN原因学习率太大导致梯度爆炸或者数据里有损坏图片返回全黑/全白张量。解决先把学习率降到 1e-4 重跑如果还 NaN在 DataLoader 里加一个过滤跳过像素均值接近 0 或 255 的图片。检查图片是否损坏可以用 PIL 打开每张图捕获异常并记录文件名。5.3 现象推理时单张图片预测结果和验证时不一致原因推理时忘了做和验证一样的预处理比如没 Resize 到 128x128、没做 Normalize、或者用了训练时的随机增强。解决把验证集的val_transform单独封装成一个函数推理时直接调用同一个函数保证预处理完全一致。5.4 现象模型文件保存了但加载后精度掉一半原因保存的是model.state_dict()但加载时模型结构不一致比如类别数变了、fc 层维度对不上。解决保存时同时存num_classes和classes列表加载时先重建模型结构再load_state_dict。如果换了 backbone必须重新训练不能直接加载旧权重。5.5 现象GPU 显存够但训练速度很慢原因num_workers设成 0 导致数据加载在主进程串行执行GPU 一直在等数据。解决Linux 下设成 4 或 8Windows 下如果报BrokenPipeError就设成 0 并改用pin_memoryTrue。另外检查是否忘了.to(device)如果模型在 CPU 上跑而数据在 GPU 上会频繁拷贝导致极慢。6. 从训练脚本到可演示系统Gradio 推理界面与模型导出技巧6.1 用 Gradio 十分钟搭一个答辩可用的演示页面答辩现场老师不会看你敲命令他们要的是上传一张图片、点一下按钮、出结果。Gradio 是最快的方式不需要写前端。import gradio as gr import torch from torchvision import transforms from PIL import Image from torchvision import models import torch.nn as nn import json # 加载类别映射和模型 with open(idx_to_class.json, r, encodingutf-8) as f: idx_to_class json.load(f) device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsNone) model.fc nn.Linear(model.fc.in_features, len(idx_to_class)) model.load_state_dict(torch.load(best_fruit_model.pth, map_locationdevice)) model model.to(device) model.eval() # 推理预处理必须和验证集一致 infer_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]) ]) def predict(image): img Image.fromarray(image).convert(RGB) tensor infer_transform(img).unsqueeze(0).to(device) # 增加 batch 维度 with torch.no_grad(): outputs model(tensor) probs torch.softmax(outputs, dim1)[0] # 返回 top-3 类别和置信度 top3_prob, top3_idx torch.topk(probs, 3) result {} for prob, idx in zip(top3_prob, top3_idx): result[idx_to_class[str(idx.item())]] float(prob) return result gr.Interface(fnpredict, inputsgr.Image(), outputsgr.Label(num_top_classes3), title水果图像识别系统).launch()逻辑说明model.fc重建时必须和训练时维度一致len(idx_to_class)就是类别数。unsqueeze(0)把单张图片变成[1, 3, 128, 128]的 batch。torch.softmax把 logits 转成概率torch.topk取前 3 个。Gradio 的gr.Label直接显示概率条答辩演示效果直观。参数说明map_locationdevice保证在 CPU 上也能加载 GPU 训练的权重。num_top_classes3显示前三个预测如果只显示一个老师可能觉得模型不自信。launch()默认本地端口如果需要局域网访问加server_name0.0.0.0。6.2 模型导出为 ONNX 的注意事项如果毕设要求部署到其他平台导出 ONNX 是常见做法。导出时注意两点一是输入尺寸固定为[1, 3, 128, 128]动态 batch 在部分推理引擎上支持不好二是导出后必须用onnxruntime跑一遍验证输出和 PyTorch 一致误差在 1e-4 以内算正常。import torch.onnx dummy_input torch.randn(1, 3, 128, 128).to(device) torch.onnx.export(model, dummy_input, fruit_model.onnx, input_names[input], output_names[output], opset_version11, dynamic_axes{input: {0: batch}})导出后加载验证import onnxruntime as ort import numpy as np sess ort.InferenceSession(fruit_model.onnx) onnx_out sess.run(None, {input: dummy_input.cpu().numpy()})[0] torch_out model(dummy_input).cpu().detach().numpy() print(最大误差:, np.max(np.abs(onnx_out - torch_out)))如果误差超过 1e-3检查opset_version是否和推理引擎兼容常见做法是降到 11 或升到 13。dynamic_axes只对 batch 维度开放不要对宽高开放否则部分引擎会报错。6.3 一个我踩过的坑别在答辩前一天换 backbone我见过最血泪的教训是答辩前一天把 ResNet18 换成 EfficientNet-B0结果学习率没调好训练 loss 一直震荡最后只能拿旧模型上场。换 backbone 意味着重新调学习率、重新训 30 轮、重新评估至少留出三天缓冲。如果非要换先在 5 轮内看 loss 是否稳定下降不稳定就立刻回退。毕设的核心是完整链路和可演示不是刷到最高精度。希望帮到你。本文还有配套的精品资源点击获取