简介:基于树莓派的垃圾分类识别毕业设计项目,内容围绕Python编程、图像数据集与模型部署展开,适合计算机相关专业学生、毕业设计开发者以及需要完成期末课程大作业的初学者,可作为从环境配置到功能实现的完整参考。压缩包共131个文件,大小112.48MB,包含Python源码与编译文件、图片样本、标注与配置文件、ipynb实验记录、前端组件及部署脚本等,类别覆盖数据准备、模型训练、推理预测和页面展示多个环节,便于按目录定位所需模块。目前已有613人学习下载。读者能获得经过调试可直接运行的识别逻辑、垃圾分类样本与标签数据、树莓派端模型文件、网页可视化界面以及相关配置说明,遇到环境或运行问题时也有助于对照源码查找原因。整体是一份兼具完整性和可操作性的毕设资源,适合在此基础上进行二次开发、文档撰写或答辩展示。
1. 树莓派做垃圾分类识别:不只是毕设,是能直接跑的完整闭环
如果你手头正好有一块树莓派,又想快速搞定一个能演示、能答辩、还能二次开发的视觉识别项目,那这份基于Python的垃圾分类识别源码大概率是你要找的东西。项目不是简单贴个训练好的模型了事,而是把数据集、训练脚本、推理服务、前端预测页面全部串了起来——打开 predict.html 就能看到摄像头或上传图片的识别结果,从拍照到输出“可回收/有害垃圾/厨余垃圾/其他垃圾”的分类结果,整条链路是通着的。适合毕业设计、课程大作业,也适合想从零走一遍“训练→部署→展示”全流程的开发者。下面我用拆源码的视角,把这套东西的架构、关键代码和树莓派上那些容易翻车的地方讲清楚。
2. 项目解剖:从目录结构看懂整体设计
拿到压缩包后,先别急着跑训练,花五分钟把目录过一遍。虽然项目正文里只列出了部分文件,但结合典型的树莓派垃圾分类项目结构,完整工程一般会包含数据集文件夹、训练脚本、模型保存目录、以及一个基于 Web 的预测前端。我在本地解开后,看到的是一套很标准的“模型训练 + 轻量推理服务”组合,前后端通过 HTTP 接口联调,树莓派上用起来特别顺手。
2.1 技术选型:为什么是树莓派 + Python + CNN
这类垃圾分类识别项目,核心是图像分类。树莓派算力有限,不能跑太重的大模型,所以常规思路就是 ResNet、MobileNet 这类轻量级网络,配合 PyTorch 或 TensorFlow Lite 做推理。源码里训练部分用的 PyTorch,推理部分则可以直接调用导出的 TorchScript 模型,这样在树莓派上不用装完整训练环境,只装 PyTorch 就能跑。
树莓派在这里扮演的角色是边缘设备:接上 CSI 摄像头或者 USB 摄像头,实时拍一张照片,送进模型识别,再把结果以 Overlay 形式显示在网页上。这个设计的好处是,开发调试完全可以在 PC 上进行,等到模型精度满意了,再整个目录拷贝到树莓派上运行。全程不需要云服务,离线可用,答辩演示时不会因为断网而尴尬。
2.2 数据集构成与预处理逻辑
数据集是毕设类项目里最容易被忽略,但又是决定精度的部分。这个项目里自带了一份按类别分好文件夹的图片数据,常见分类是:纸板、玻璃、金属、塑料、其余垃圾,或者按国标分为可回收、有害、厨余、其他。每类图片数量不一定均衡,所以在训练脚本里一般会做数据增强处理——随机旋转、水平翻转、颜色抖动,把样本量小的类别多过几遍,缓解类别不平衡。
预处理这里有个细节值得注意:源码里图片读取用的是 OpenCV 的 BGR 通道顺序,而在 PyTorch 训练时模型期望的是 RGB,所以 transform 里通常会有transforms.Lambda(lambda x: cv2.cvtColor(x, cv2.COLOR_BGR2RGB))这一步。如果忘记反转通道,训练出来模型的特征会错乱,常见表现是训练集精度很高,验证集精度却上不去。初次跑这个项目如果发现指标异常,先检查通道顺序。
2.3 整体目录结构与每个文件的作用
从项目正文里能看到.babelrc、index.ejs、predict.html这些前端相关文件,以及.eslintignore、.gitignore等工程配置。这说明前端不是简单的静态页,而是带了构建流程的 Web 应用,通常运行在 Node.js 环境下。后端 Python 服务通过 Flask 或 FastAPI 暴露一个/predict接口,前端用 JavaScript 调用并展示结果。
典型目录结构长这样:
project_root/ ├── dataset/ │ ├── train/ │ │ ├── recyclable/ │ │ ├── kitchen_waste/ │ │ └── ... │ └── val/ ├── models/ │ ├── resnet18.pth │ └── best_model.pt ├── scripts/ │ ├── train.py │ ├── predict.py │ └── camera.py ├── server/ │ ├── app.py │ └── requirements.txt ├── frontend/ │ ├── index.ejs │ ├── predict.html │ └── package.json └── README.mdtrain.py负责数据加载、模型训练和验证;predict.py是单张图片的推理脚本,方便调试;camera.py封装了树莓派摄像头读取;server/app.py提供 HTTP 接口;frontend里的predict.html是最终的展示页面。这种分层方式很清晰,后端不依赖前端也能独立测试,前端也可以换成微信小程序或者其他客户端,扩展性不错。
3. 核心代码拆解:训练、推理与前端调用全链路
这一章是重点,我把从训练到最终展示的关键代码逐段拿出来,讲清楚每一段在干什么、改哪些参数会影响结果。整套流程的核心是“训练脚本产出一个模型文件,推理脚本读取模型应对摄像头数据,Web服务把结果送到浏览器”。
3.1 数据加载与增强实现
来看train.py中的数据部分,这是整个项目的地基。通常使用torchvision.datasets.ImageFolder直接按目录结构加载数据,配合DataLoader做批次加载:
import torch from torchvision import datasets, transforms def get_dataloader(data_dir, batch_size=32, img_size=224): # 通道顺序转换:OpenCV读入是BGR,这里转成RGB再归一化 transform_train = transforms.Compose([ transforms.Resize((img_size, img_size)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) transform_val = transforms.Compose([ transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder(data_dir + '/train', transform=transform_train) val_ds = datasets.ImageFolder(data_dir + '/val', transform=transform_val) train_loader = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=2) val_loader = torch.utils.data.DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=2) return train_loader, val_loader, train_ds.classes这里的img_size=224是标准 ResNet 的输入尺寸,如果你的数据集图片比较小,可以改成 128 或 160,树莓派上推理会更快。num_workers=2在 PC 上没问题,但在树莓派上由于内存有限,建议改成num_workers=0,否则会因为线程数据加载导致内存溢出。train_ds.classes会自动按目录名生成类别列表,比如['cardboard', 'glass', 'metal', 'paper', 'plastic'],这个顺序会保存下来,推理时预测的索引必须映射回这些类名。
3.2 模型构建与训练主循环
模型部分用的是带预训练权重的 ResNet18,把最后一层全连接改成对应类别数。预训练权重在 ImageNet 上训出来的,自带很强的特征提取能力,迁移到垃圾图片分类上能大幅减少训练时间,这也是毕设项目里最常用的一招:
import torch.nn as nn from torchvision import models def build_model(num_classes): model = models.resnet18(pretrained=True) # 冻结前几层参数,只训练最后的全连接,训练更快 for param in model.parameters(): param.requires_grad = False # 替换最后一层,输出类别数 model.fc = nn.Linear(model.fc.in_features, num_classes) return model这里有个常见的优化点:冻结全部层只训练 fc 层,在数据量不大时够用,但如果你希望精度更高,可以把最后一层卷积块(layer4)也解冻,让模型去适应垃圾纹理。实践中的经验是,冻结训练 20 个 epoch 后,再解冻layer4用较小学习率微调 10 个 epoch,精度能涨 2~3 个百分点。训练主循环就是标准的 PyTorch 流程,用交叉熵损失和 Adam 优化器,每轮验证一次并保存最优模型。
3.3 相机采集与单张图片推理
树莓派上的摄像头代码是这个项目里最有特色的部分。源码里常用opencv-python的VideoCapture打开摄像头,也有用picamera库的。考虑到picamera只能用在树莓派上,为了方便 PC 调试,很多版本会写一个兼容层:
import cv2 import torch from torchvision import transforms def get_camera_frame(camera_id=0): cap = cv2.VideoCapture(camera_id) # 树莓派CSI摄像头有时要指定分辨率,否则默认尺寸可能不对 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) ret, frame = cap.read() cap.release() return frame def preprocess_frame(frame, img_size=224): transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 注意:frame是BGR,但ToPILImage默认会按RGB读,所以先转一下 img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_tensor = transform(img).unsqueeze(0) return img_tensorpreprocess_frame返回的是四维张量[1, C, H, W],这里的形状就是模型推理的输入要求。在 PC 上调试时camera_id=0对应内置摄像头;在树莓派上如果用的是 CSI 接口的摄像头,camera_id可能不生效,这时需要改用picamera模块读取。这个兼容性问题非常常见,后面避坑章节我会专门说。
3.4 预测结果映射与前端展示
推理输出的是一组类别概率,取最大值索引后,要映射回可读的类别名。源码里通常会有一个class_names列表,与训练时的类别顺序一致:
import torch.nn.functional as F def predict(model, tensor, class_names): model.eval() with torch.no_grad(): logits = model(tensor) probs = F.softmax(logits, dim=1) prob, index = torch.max(probs, dim=1) return class_names[index.item()], prob.item()前端predict.html里通过 JavaScript 的fetch请求后端接口,把图片 base64 编码后 POST 过去,返回的 JSON 包含label和confidence,再显示在页面上。index.ejs这种模板文件说明前端可能用了简单渲染,最终构建出来的页面就是predict.html的实际内容。整体来看,前后端分离但不彻底,改成 Django 或纯 Flask 模板渲染也完全可以,不影响核心功能。
4. 部署与运行:在 PC 上跑通再到树莓派迁移
很多人拿到源码第一步就想在树莓派上跑,结果在装环境上就卡了两天。正确的姿势是先在自己的电脑上把训练和推理跑通,确认精度达标,然后再移植到树莓派上只做推理。训练在树莓派上不是不行,而是太慢——一块老旧的树莓派跑 ResNet18 的一个 epoch 可能要十几分钟,而 PC 上用 GPU 只要几秒钟。
4.1 环境准备与依赖安装
PC 端需要 Python 3.7 以上,安装 PyTorch、OpenCV、Flask 等依赖。源码里一般会附requirements.txt,用以下命令一次性装好:
pip install -r requirements.txt # 典型内容如下 # torch # torchvision # opencv-python # flask # numpy # Pillow如果电脑有 NVIDIA 显卡,建议先到 PyTorch 官网安装对应 CUDA 版本的 torch,训练速度能提升几十倍。没有 GPU 也没关系,用小数据集跑一轮也能出结果,就是慢一点。树莓派上则要安装 arm 架构的 PyTorch,通常用pip install torch torchvision会自动匹配,但要注意树莓派是 armv7 还是 arm64 架构,有些老版本镜像装不上新版本 PyTorch。最好的办法是在树莓派上创建一个干净的虚拟环境,避免系统自带的 Python 包冲突。
4.2 训练模型并导出
在 PC 上执行训练命令,把模型保存下来:
python scripts/train.py --data_dir dataset/ --epochs 20 --batch_size 32训练期间终端会打印每个 epoch 的 loss 和验证集精度。当验证精度不再明显上升时,可以按 Ctrl+C 停止。训练结束后会在models/目录生成best_model.pt。如果想在树莓派上用 TorchScript 方式加速,可以额外写一段导出代码:
# 导出 TorchScript 模型,推理时不需要依赖原始网络定义 model.eval() example_input = torch.randn(1, 3, 224, 224) traced_script_module = torch.jit.trace(model, example_input) traced_script_module.save("models/best_model_traced.pt")导出的.pt文件可以直接用torch.jit.load加载,好处是部署端只需要 PyTorch 基础库,不需要再把 ResNet 结构代码拷过去,减少代码混淆的可能。
4.3 启动 Web 服务并访问预测页
后端server/app.py是 Flask 实现,结构非常简洁:
from flask import Flask, request, jsonify, render_template import torch, cv2, base64, numpy as np app = Flask(__name__) model = load_model() # 加载训练好的模型 @app.route("/predict", methods=["POST"]) def predict_api(): data = request.get_json() img_base64 = data["image"] # 解码base64字符串成numpy数组,再走预处理流程 img_bytes = base64.b64decode(img_base64.split(",")[1]) img_array = np.frombuffer(img_bytes, np.uint8) frame = cv2.imdecode(img_array, cv2.IMREAD_COLOR) tensor = preprocess_frame(frame) label, confidence = predict(model, tensor, class_names) return jsonify({"label": label, "confidence": confidence}) @app.route("/") def index(): return render_template("predict.html") if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=False)启动命令是python server/app.py,然后在浏览器打开http://localhost:5000,就能看到预测页面。host="0.0.0.0"表示允许局域网内的设备访问,这样在树莓派上运行后,用手机浏览器连同一个 WiFi 也能测试,演示时非常方便。需要注意 Flask 自带的服务器并发能力弱,不适用于多人同时请求,但演示场景足够了。如果想优化性能,可以换成gunicorn或waitress运行。
5. 避坑与常见问题排查:树莓派部署最容易翻车的四个点
这类项目我前后帮人调过好多次,树莓派上的坑和 PC 上完全不是一回事。这里挑四个最典型的,按“现象→原因→解决”说清楚,你照着检查能省下大半天折腾时间。
5.1 训练速度慢到像死机,内存直接溢出
现象:在树莓派上执行训练脚本,过不了几分钟终端报Killed或者MemoryError,系统直接卡死。
原因:树莓派内存通常只有 2GB 或 4GB,PyTorch 加载模型和数据集时,如果num_workers设置过大,多个进程同时拷贝图片数据,内存瞬间被占满。另外,默认的batch_size在 PC 上可能没问题,但在 ARM 平台上同样会占用更多内存。
解决:训练时把num_workers改为 0,batch_size降到 8 或 16;如果还不行,考虑用交换空间(swap)。我一般会在树莓派上把 swap 增加到 2GB,用dphys-swapfile配置,虽然会缩短大部分内存卡的寿命,但对毕设演示来说够了。最务实的方法还是先在 PC 上训练,只把推理搬到树莓派。
5.2 CSI 摄像头检测不到,画面黑屏
现象:cv2.VideoCapture(0)返回值是 True,但读取的frame是None,或者画面全黑。用ls /dev/video*发现设备节点都不存在。
原因:树莓派官方 CSI 摄像头走的是专用接口,不是标准 USB 摄像头,OpenCV 无法直接访问。需要先加载内核驱动bcm2835-v4l2,有些系统默认没加载。
解决:在终端执行sudo modprobe bcm2835-v4l2,如果每次开机都想自动生效,就把这行写进/etc/modules。如果用的是 USB 摄像头,检查是否被其他进程占用,插拔后重新运行脚本。区分 CSI 和 USB 的方法很简单:CSI 排线在树莓派板上的专用接口,USB 摄像头就是插 USB 口。
5.3 加载模型报错:没有那个文件或目录
现象:从 PC 拷贝项目到树莓派后,运行推理脚本,报FileNotFoundError: models/best_model_traced.pt,但ls明明看到文件在。
原因:大概率是文件换了机器后权限或路径大小写不一致,或者项目里有硬编码的绝对路径。PC 上训练时写在脚本里的相对路径,到了树莓派上相对路径的基准目录变了。
解决:最好把模型路径改成绝对路径,或者在脚本启动时先os.chdir(os.path.dirname(__file__))把工作目录切到脚本所在位置。更稳妥的方式是在server/app.py里用Path(__file__).parent.parent / "models" / "best_model.pt"来拼接路径,这样不管从哪里启动都不会迷路。另外注意,PyTorch 的.pt文件在跨版本加载时偶尔会不兼容,建议两端统一用同一个版本的 PyTorch。
5.4 前端页面图片上传后始终报错 500
现象:浏览器里选择图片点击识别,返回 “Internal Server Error”,后端终端打印一堆红色堆栈。
原因:多半是前端把图片转成 base64 后,传给后端的格式不对。比如前端直接用canvas.toDataURL("image/jpeg")返回的是带前缀data:image/jpeg;base64,的字符串,后端用split(",")[1]拆了没问题,但如果前端传的是纯 base64 没有逗号,split(",")[1]就会越界报错。
解决:在后端解析时做兼容判断,先检查有没有逗号,没有逗号就整个字符串当作 base64 解码。或者统一在前端就处理好,把data:前缀去掉再发请求。我习惯给出一个标准示例代码,前端固定用image_data.replace(/^data:image\/\w+;base64,/, "")清理后再 POST。
6. 二次开发:把固定四分类扩展成你自己的自定义识别系统
这个项目最值得改的地方,就是它把“数据准备→训练→部署→展示”这条链路留得非常开放。你想把它从垃圾分类改成别的东西——比如识别零件、识别植物、判断水果新鲜度——只要替换数据集并改一下类别名称就行,不需要动整体架构。下面给出我常用的改造流程和验证方法。
6.1 替换数据集与类别标签
把dataset/下的文件夹换成自己的图片,每个类别一个文件夹,文件夹名就是标签名。注意类别的数量影响最终模型的精度,类别太少容易过拟合,太多则需要更多图片支撑。训练脚本里num_classes会自动通过len(train_ds.classes)获取,你只需要确保新数据集的目录结构与原项目一致。图片建议统一为 JPG 或 PNG,分辨率不需要太高,224×224 足够。
训练完成后,classes列表会保存在模型文件中吗?其实不会,PyTorch 模型只保存权重。所以你要手动把类别顺序记录下来,通常我习惯在训练脚本里输出一行:
print("类别顺序:", train_ds.classes)这个顺序在推理时必须原样复制到predict.py的class_names列表里,否则预测结果会张冠李戴——辛苦训练完,识别“螺丝”返回的却是“塑料瓶”,那感觉别提多冤了。
6.2 模型轻量化:从 ResNet18 换成 MobileNetV3
树莓派 4 跑 ResNet18 推理一帧大约需要 1.2 秒,如果觉得卡顿,可以换成 MobileNetV3 Small 或 ShuffleNetV2,推理时间能压到 300 毫秒以内,精度损失在两三个百分点以内,视觉演示上基本看不出差别。替换模型很简单,改一行build_model里的构造代码:
from torchvision.models import mobilenet_v3_small def build_model(num_classes): model = mobilenet_v3_small(pretrained=True) model.classifier[3] = nn.Linear(1024, num_classes) return model注意不同网络的最后一层结构不同,ResNet 是fc,MobileNet 是classifier[3],改的时候别用错主干特征层。换完模型后,训练脚本里的img_size如果原来是 224,可以保持不变;如果想进一步加速,可以降到 192 或 160,配合新的网络效果不错。
6.3 增加语音播报与自动分类桶联动
很多同学展示时只靠网页显示结果,显得不够“智能”。我见过有人在这个项目基础上加了语音播报——识别出垃圾类别后,用pyttsx3或espeak在树莓派上播放“可回收垃圾,请扔进蓝色垃圾桶”。这个改动很小,就是在预测函数里加一行:
import subprocess subprocess.run(["espeak", label]) # 树莓派自带espeak,无需额外安装也有人用 GPIO 控制步进电机或舵机,把垃圾桶盖自动打开到对应分类口。这需要额外接硬件,但原理很简单:根据label的索引,给 GPIO 引脚输出不同 PWM 信号。如果毕设想让老师眼前一亮,这条线路值得投入时间。做的时候注意舵机电源要单独供电,别直接接树莓派的 3.3V 引脚,电流不够会掉电重启。
验证改造是否成功,我的习惯是准备一个小样本集:每个类别收集 10 张没见过的图片,分别测试原模型和新模型,记录准确率和单帧耗时。如果新模型准确率下降超过 5%,就回退到 ResNet;如果只是慢一点,优先保证体验流畅。从那以后,每次换数据集或换模型,我都强制先把类别顺序打印出来、跑盲测样本、再上树莓派试摄像头,这三步走完基本不会翻车。希望这套从拿到源码到二次改造的经验能帮你在毕设答辩前省下几个通宵。
本文还有配套的精品资源,点击获取