资讯动态

基于Python的智能垃圾分类系统:从数据清洗到Docker部署全流程

发布时间:2026/10/8 16:58:19 来源:尧图企业网站定制
简介这份资源是一套基于Python开发的智能垃圾分类系统完整源码与部署指南源自本科毕业设计课题适合计算机视觉入门者、毕业设计选题学生及需要期末大作业参考的学习者。系统通过卷积神经网络与迁移学习实现可回收物、厨余垃圾、有害垃圾及其他垃圾四大类别的识别分类涵盖图像采集、数据预处理、模型推理与可视化界面等模块。压缩包共24个文件约55.76MB包含7个py源码文件、7个zip数据集与备份包、3个ui界面文件、3个zbak备份文件以及cpp、jpg、md、png等辅助资料代码采用模块化设计并配有详尽注释。资源内附环境配置、依赖库清单、模型训练与部署流程说明便于快速复现与二次开发。目前已有57人学习可为教学研究和技术开发提供完整参考。1. 从一堆垃圾到一条数据智能垃圾分类系统到底在做什么小区楼下四个桶厨余、可回收、有害、其他督导员站在旁边盯着你手里的塑料袋。这个场景催生了一个很实际的需求能不能让摄像头或一张照片自动判断垃圾类别把结果推给投放口或者小程序基于 Python 的智能垃圾分类系统本质就是一套「图像输入 → 类别输出 → 部署上线」的完整链路核心是图像分类模型外围是数据采集、推理服务、前端交互和部署运维。它适合两类人一类是想拿一个完整项目练手 Python 工程化的开发者另一类是想在社区、园区、学校做小规模试点的实施者。源码和部署指南这两个词之所以被反复搜是因为大多数人卡在「模型跑通了但服务起不来」这一步。下面按数据、模型、服务、部署、排错的顺序把这条链路拆开讲清楚。2. 数据从哪来垃圾分类数据集的采集与清洗2.1 公开数据集与自采数据的取舍做垃圾分类第一道坎不是模型是数据。公开数据集里常见的是 TrashNet 这类大约两千多张分六类纸板、玻璃、金属、纸、塑料、其他。它的优点是干净、标注规范缺点是背景单一全是白底摆拍直接拿去识别真实场景里的垃圾袋会翻车。我的做法是公开数据集打底再自采补充。自采渠道有三个手机在小区投放点拍、从监控视频抽帧、网上爬取带标注的图片。自采数据一定要覆盖真实光照和遮挡否则模型上线后准确率会掉得很难看。数据量上每类至少准备 500 张起步四分类厨余、可回收、有害、其他建议每类 800 到 1500 张。类别不均衡是常态厨余和其他往往最多有害垃圾最少。处理不均衡有两个办法一是对少数类做数据增强二是训练时用类别权重。我一般两个一起上。2.2 用 Python 做数据清洗和增强的实操清洗的核心是去重、去模糊、修正错标。下面这段脚本做三件事用感知哈希找重复图、用拉普拉斯方差筛模糊图、按类别统计数量。import os import cv2 import imagehash from PIL import Image from collections import defaultdict DATA_DIR dataset # 结构dataset/类别名/图片 def find_duplicates(root, hash_size8): 用感知哈希找出重复或高度相似的图片 hashes defaultdict(list) for cls in os.listdir(root): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): path os.path.join(cls_dir, fname) try: h imagehash.phash(Image.open(path), hash_sizehash_size) hashes[str(h)].append(path) except Exception as e: print(f读取失败 {path}: {e}) # 同一哈希下多于一张视为重复 return {k: v for k, v in hashes.items() if len(v) 1} def find_blurry(root, threshold100.0): 拉普拉斯方差低于阈值判为模糊 blurry [] for cls in os.listdir(root): cls_dir os.path.join(root, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): path os.path.join(cls_dir, fname) img cv2.imread(path) if img is None: continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) score cv2.Laplacian(gray, cv2.CV_64F).var() if score threshold: blurry.append((path, round(score, 2))) return blurry def count_by_class(root): stat {} for cls in os.listdir(root): cls_dir os.path.join(root, cls) if os.path.isdir(cls_dir): stat[cls] len(os.listdir(cls_dir)) return stat if __name__ __main__: print(重复图:, find_duplicates(DATA_DIR)) print(模糊图:, find_blurry(DATA_DIR)) print(类别分布:, count_by_class(DATA_DIR))逻辑说明imagehash.phash把图片压成一个 64 位指纹内容相近的图指纹接近适合找翻拍和重复。cv2.Laplacian的方差反映图像边缘强度方差越低越模糊阈值 100 是经验值手机拍摄的垃圾图可以放宽到 60。参数说明hash_size越大越敏感8 是通用值threshold要根据你的拍摄设备调先用一批人工确认的清晰图跑一遍取最低分的 80% 作为阈值。增强用torchvision.transforms或albumentations都行。垃圾分类场景我推荐albumentations因为它对随机裁剪、旋转、亮度调整的组合更灵活。注意增强只加在训练集验证集和测试集保持原样否则评估结果会虚高。提示清洗完一定要人工抽检 50 张机器筛出来的模糊图里往往混着正常图直接删会丢样本。3. 模型怎么选从 MobileNet 到迁移学习的落地路径3.1 为什么垃圾分类不适合从零训练垃圾分类的类别少、类间差异大玻璃瓶和纸板差别明显但类内差异也大不同形状的塑料瓶。从零训练一个 CNN 需要几十万张图才能收敛个人和小团队没有这个数据量。迁移学习是标准答案拿 ImageNet 上预训练好的骨干网络换掉最后的全连接层用你的数据微调。骨干网络选型看部署环境服务器端可以用 ResNet50 或 EfficientNet-B3边缘设备或手机端用 MobileNetV3 或 ShuffleNetV2。我一般先用 MobileNetV3-Small 跑通全流程准确率不够再换大的。3.2 用 PyTorch 微调 MobileNetV3 的完整代码下面这段是训练脚本的核心部分包含数据加载、模型改造、训练循环和验证。import torch import torch.nn as nn from torch.utils.data import DataLoader from torchvision import datasets, transforms, models DEVICE torch.device(cuda if torch.cuda.is_available() else cpu) BATCH_SIZE 32 EPOCHS 20 LR 1e-3 NUM_CLASSES 4 # 厨余、可回收、有害、其他 # 训练集增强验证集只做缩放和归一化 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.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_sizeBATCH_SIZE, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_sizeBATCH_SIZE, shuffleFalse, num_workers4) # 加载预训练模型替换分类头 model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.DEFAULT) model.classifier[3] nn.Linear(model.classifier[3].in_features, NUM_CLASSES) model model.to(DEVICE) # 类别不均衡时给少数类更高权重 class_weights torch.tensor([1.0, 1.0, 2.0, 1.0]).to(DEVICE) criterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.AdamW(model.parameters(), lrLR, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxEPOCHS) for epoch in range(EPOCHS): 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 outputs.argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fEpoch {epoch1}/{EPOCHS} loss{running_loss/len(train_loader):.4f} val_acc{acc:.4f}) torch.save(model.state_dict(), garbage_mobilenetv3.pth)逻辑说明models.mobilenet_v3_small的classifier是一个 Sequential索引 3 是最后的 Linear 层替换成你的类别数。class_weights里给有害垃圾假设索引 2加了 2 倍权重缓解样本少的问题。CosineAnnealingLR让学习率按余弦曲线下降比固定学习率更容易收敛到好的局部最优。参数说明BATCH_SIZE在显存够的情况下越大越稳8G 显存跑 224 分辨率可以到 64LR用 1e-3 是微调的常用起点如果 loss 震荡就降到 3e-4EPOCHS看验证准确率一般 15 到 30 轮就饱和。训练完看混淆矩阵如果厨余和其他经常混说明这两类在视觉上确实接近需要补充更多区分度高的样本或者考虑加一个二级分类器。4. 服务怎么起用 FastAPI 把模型包成接口4.1 推理服务的接口设计模型训练完只是半成品要能用起来必须包成 HTTP 接口。选 FastAPI 的理由是异步支持好、自带文档、部署简单。接口设计上一个/predict接收图片返回类别和置信度一个/health做健康检查。图片传输用 multipart 表单返回 JSON。注意推理时要加torch.no_grad()并且把模型设为 eval 模式否则 BatchNorm 和 Dropout 会导致结果不稳定。4.2 FastAPI 推理服务代码与启动命令import io import torch import torch.nn as nn from fastapi import FastAPI, File, UploadFile from PIL import Image from torchvision import transforms, models app FastAPI(title垃圾分类推理服务) DEVICE torch.device(cuda if torch.cuda.is_available() else cpu) CLASSES [厨余垃圾, 可回收物, 有害垃圾, 其他垃圾] # 与训练时保持一致的预处理 tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def load_model(): model models.mobilenet_v3_small(weightsNone) model.classifier[3] nn.Linear(model.classifier[3].in_features, len(CLASSES)) model.load_state_dict(torch.load(garbage_mobilenetv3.pth, map_locationDEVICE)) model.to(DEVICE) model.eval() return model model load_model() app.get(/health) def health(): return {status: ok} app.post(/predict) async def predict(file: UploadFile File(...)): raw await file.read() img Image.open(io.BytesIO(raw)).convert(RGB) tensor tf(img).unsqueeze(0).to(DEVICE) with torch.no_grad(): logits model(tensor) probs torch.softmax(logits, dim1)[0] idx int(probs.argmax()) return { class: CLASSES[idx], confidence: round(float(probs[idx]), 4), all_probs: {CLASSES[i]: round(float(probs[i]), 4) for i in range(len(CLASSES))} }逻辑说明load_model里weightsNone是因为我们要加载自己训练好的权重不需要再下载预训练参数。map_locationDEVICE保证在 CPU 和 GPU 之间切换时不会报错。unsqueeze(0)把单张图变成 batch 维度为 1 的张量。返回all_probs是为了前端能做置信度展示也方便排查误判。启动命令uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2参数说明--workers 2表示起两个进程适合多核 CPU如果模型在 GPU 上多 worker 会各自加载一份模型显存要够。生产环境建议用gunicorn配合uvicorn.workers.UvicornWorker并加--timeout 60防止大图推理超时。注意预处理必须和训练时完全一致包括归一化的均值和方差。我见过有人推理时忘了 Normalize准确率直接掉 30 个点排查了半天。5. 部署怎么落地Docker 打包与边缘设备适配5.1 用 Docker 把服务打包成镜像部署最省心的方式是 Docker。基础镜像选python:3.10-slim把依赖、模型文件、代码一起打进去。注意 PyTorch 的 CPU 版本和 GPU 版本镜像不同边缘设备用 CPU 版服务器用 GPU 版。下面是一个可用的 Dockerfile。FROM python:3.10-slim WORKDIR /app # 先装系统依赖opencv 需要 libgl RUN apt-get update apt-get install -y --no-install-recommends \ libgl1 libglib2.0-0 rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py garbage_mobilenetv3.pth ./ EXPOSE 8000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]requirements.txt里写fastapi0.110.0 uvicorn[standard]0.29.0 torch2.2.0 torchvision0.17.0 pillow10.2.0 python-multipart0.0.9构建和运行docker build -t garbage-cls:1.0 . docker run -d --name garbage -p 8000:8000 garbage-cls:1.0参数说明--no-install-recommends减少镜像体积python-multipart是 FastAPI 接收文件上传的必需依赖漏了会报 422。镜像大小 CPU 版大约 1.5GGPU 版 5G 以上边缘设备建议用 ONNX Runtime 替代 PyTorch能把体积压到 200M 以内。5.2 边缘设备上的模型转换与性能取舍如果部署在树莓派、Jetson 或安卓设备上PyTorch 太重转 ONNX 或 NCNN 是常见做法。转 ONNX 的代码import torch import torch.nn as nn from torchvision import models model models.mobilenet_v3_small(weightsNone) model.classifier[3] nn.Linear(model.classifier[3].in_features, 4) model.load_state_dict(torch.load(garbage_mobilenetv3.pth, map_locationcpu)) model.eval() dummy torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy, garbage.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version12 )逻辑说明dynamic_axes让 batch 维度可变方便批量推理。opset_version12兼容性较好Jetson 上的 TensorRT 支持这个版本。转完后用onnxruntime验证输出和 PyTorch 一致误差在 1e-4 以内算正常。性能取舍上MobileNetV3-Small 在树莓派 4B 上单张推理约 80 到 120 毫秒够用如果要更快可以量化成 INT8速度提升 2 到 3 倍准确率掉 1 到 2 个点。量化用onnxruntime.quantization的quantize_dynamic就行。6. 避坑与排查垃圾分类系统上线前后的 5 个血泪教训现象一本地准确率 95%部署后用户反馈一半识别错。原因训练数据是白底摆拍真实场景是复杂背景和遮挡域偏移导致模型失效。 解决自采至少 30% 的真实场景图加入训练集推理前加一个简单的目标检测或裁剪把垃圾主体从背景里抠出来再分类。现象二服务跑一段时间后内存持续上涨最后 OOM。原因FastAPI 默认不限制上传大小有人传了 10M 的大图PIL 解码后占大量内存另外模型没有做单例每次请求都重新加载。 解决在load_model外用模块级变量保证只加载一次加UploadFile大小校验超过 5M 直接返回 413用--limit-max-requests让 worker 定期重启。现象三GPU 显存够但推理报 CUDA out of memory。原因多个 uvicorn worker 各自加载一份模型显存翻倍。 解决GPU 部署时--workers 1用批处理提高吞吐或者用torch.cuda.empty_cache()在请求间隙释放缓存但更推荐单 worker 加异步队列。现象四同一张图两次请求返回不同类别。原因模型忘了eval()Dropout 和 BatchNorm 在训练模式下随机性导致输出波动。 解决加载模型后立刻model.eval()推理包在torch.no_grad()里。这个坑很隐蔽本地测试时如果只跑一次不会发现。现象五Docker 镜像构建成功但容器启动报找不到模型文件。原因.dockerignore里把.pth排除了或者COPY路径写错。 解决检查.dockerignore模型文件单独COPY并确认路径用docker run --rm -it garbage-cls:1.0 ls /app进容器看文件在不在。提示上线前一定要做一轮「脏数据测试」故意传纯色图、文字截图、超大图看服务会不会崩。这些边界情况在真实使用中一定会遇到。7. 把准确率再往上推置信度阈值与二级分类的实战技巧模型上线后你会发现大部分误判集中在置信度 0.5 到 0.7 这个区间。我的做法是设一个阈值低于 0.7 的结果不直接返回而是走「人工确认」或「二级分类」。二级分类针对容易混的类别对比如厨余和其他单独训一个小模型只在这两类之间做区分。这个小模型可以用更细粒度的特征比如颜色直方图和纹理甚至不需要深度学习用 SVM 加 HOG 特征就能到 85% 以上。另一个技巧是测试时增强TTA。对同一张图做水平翻转和轻微裁剪各推理一次把 softmax 概率平均。这个操作能让准确率涨 1 到 2 个点代价是推理时间翻倍。边缘设备上不划算服务器端可以开。def predict_with_tta(model, img_tensor, n_aug3): TTA原图 水平翻转 轻微裁剪概率平均 model.eval() probs torch.zeros(1, len(CLASSES)).to(DEVICE) with torch.no_grad(): probs torch.softmax(model(img_tensor), dim1) probs torch.softmax(model(torch.flip(img_tensor, dims[3])), dim1) # 中心裁剪再缩放 crop transforms.CenterCrop(200)(img_tensor) crop transforms.Resize((224, 224))(crop) probs torch.softmax(model(crop), dim1) return (probs / n_aug).argmax(dim1)参数说明n_aug是增强次数3 次是精度和耗时的平衡点CenterCrop(200)的 200 要根据你的主体占比调主体小就裁大一点。TTA 对旋转不变的类别比如玻璃瓶提升明显对方向敏感的类别要慎用。最后说一个我自己的习惯每次改完模型或预处理一定用同一批 100 张固定测试图跑一遍记录准确率和混淆矩阵和上一版对比。没有这个基线你根本不知道改动是变好还是变坏。垃圾分类这个方向数据质量比模型结构重要得多把清洗和自采做扎实比换十个骨干网络都管用。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑