news 2026/9/28 6:17:28

基于CNN的垃圾识别分类系统:从数据集到部署的完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于CNN的垃圾识别分类系统:从数据集到部署的完整实战

简介:这份资源是面向高校学生与深度学习入门者的垃圾识别分类课程设计完整项目,基于卷积神经网络实现图像分类,可直接用于期末大作业或课程设计答辩。压缩包共约2000个文件,以1196张jpg与789张jpeg图像构成训练与测试数据集,另有13个Python源码文件负责模型搭建、训练与推理,并附带json配置与md说明文档,整体约564.69MB,下载后无需修改即可运行。项目已获导师指导并通过,取得97分的高分评价,涵盖数据预处理、CNN模型构建、训练调参到分类预测的完整流程,目录结构清晰,便于按模块阅读与二次开发。目前已有263人学习下载,适合希望快速掌握图像分类实战、需要现成数据集与模型参考的读者,也可作为进一步优化网络结构与提升识别精度的起点。

1. 垃圾识别分类系统:从一张照片到四个桶,CNN 到底做对了什么

你拍一张外卖餐盒的照片,系统告诉你「这属于其他垃圾」——听起来简单,但背后要跑通一条完整的链路:数据采集、图像预处理、CNN 模型搭建、训练调参、推理部署。这套「基于深度学习卷积神经网络实现垃圾识别分类系统」的课程设计,核心就是用 Python 把这条链路串起来。它适合正在做课程设计的学生、想入门深度学习 CNN 的开发者,以及需要一套可复现图像分类模板的工程师。热搜里「深度学习入门」「cnn卷积神经网络」「python深度学习教程」这些词,恰好对应了这套系统涉及的三个层面:框架选型、网络结构、训练流程。我见过太多人卡在环境配置或数据集格式上,模型还没跑起来就放弃了。这篇笔记按「先立住原理、再动手复现、最后避坑」的顺序展开,每一步都给出可抄的代码和参数说明。

2. 数据集准备与 CNN 输入管线的搭建

2.1 垃圾图像数据集的来源与目录结构

常见做法是使用公开的垃圾分类数据集,比如 TrashNet 或华为云垃圾分类大赛的数据集,通常包含可回收物、厨余垃圾、有害垃圾、其他垃圾四大类,部分版本会细分为玻璃、纸张、塑料、金属等子类。我一般会先确认三件事:类别数量、每类样本量、图像分辨率。如果某类样本少于 200 张,训练时很容易过拟合,需要做数据增强。

目录结构建议按dataset/train/类别名/图片和dataset/val/类别名/图片组织,这样后续用ImageFolder或flow_from_directory可以直接读取,不用自己写标签映射。下面是一个典型的目录树:

dataset/ ├── train/ │ ├── recyclable/ │ ├── kitchen_waste/ │ ├── hazardous/ │ └── other/ └── val/ ├── recyclable/ ├── kitchen_waste/ ├── hazardous/ └── other/

划分比例一般按 8:2 或 7:3,如果原始数据没有验证集,用splitfolders库一行命令切分:

pip install splitfolders splitfolders --ratio 0.8 0.2 --group-prefix dataset/raw dataset/split

--ratio 0.8 0.2表示训练集占 80%、验证集占 20%,--group-prefix会按类别子目录自动分组,避免随机切分导致某类全落在训练集里。切分后检查一下每个子目录的文件数,确认没有空目录。

2.2 用 torchvision 构建预处理与增强管线

CNN 对输入尺寸和归一化很敏感。常见做法是把图像统一缩放到 224×224(ResNet 系列的标准输入),再按 ImageNet 的均值和标准差做归一化。训练阶段加入随机翻转、随机裁剪、颜色抖动等增强,验证阶段只做缩放和归一化。

import torch from torchvision import transforms, datasets from torch.utils.data import DataLoader # 训练集增强管线 train_tf = transforms.Compose([ transforms.Resize((256, 256)), # 先放大再裁剪,保留更多信息 transforms.RandomResizedCrop(224), # 随机裁剪到 224 transforms.RandomHorizontalFlip(p=0.5), # 水平翻转 transforms.ColorJitter(0.2, 0.2, 0.2), # 亮度/对比度/饱和度扰动 transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 验证集只做确定性变换 val_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) train_ds = datasets.ImageFolder('dataset/train', transform=train_tf) val_ds = datasets.ImageFolder('dataset/val', transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) print(train_ds.classes) # 输出类别名列表

RandomResizedCrop(224)的默认缩放范围是 0.08~1.0,如果垃圾图像主体占比很小,可以改成RandomResizedCrop(224, scale=(0.5, 1.0)),避免裁掉关键区域。ColorJitter的参数不要设太大,0.2 左右足够,否则颜色失真会让模型学到错误的纹理特征。num_workers在 Windows 上如果报错,改成 0 用主进程加载。

注意:ImageFolder要求每个类别一个子目录,且目录名就是类别标签。如果数据集里混入了非图片文件(如 .DS_Store、Thumbs.db),加载时会直接报错,先清理干净。

3. 卷积神经网络选型与迁移学习策略

3.1 从零搭一个轻量 CNN 还是直接用预训练模型

课程设计里常见的两种路线:一是自己堆几层 Conv-BN-ReLU-Pool 从零训练,二是用 ResNet18/MobileNetV3 做迁移学习。从零训练的好处是结构透明、便于写进论文,但垃圾图像类间差异小(比如塑料瓶和玻璃瓶),从零训练往往需要更多数据和更长的训练周期。我一般会先用 ResNet18 预训练权重跑一版 baseline,再决定要不要自己搭。

下面是一个从零搭建的四层 CNN,适合理解卷积、池化、全连接的作用:

import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), # 224 -> 112 nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(2), # 112 -> 56 nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(), nn.MaxPool2d(2), # 56 -> 28 nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(), nn.AdaptiveAvgPool2d(1) # 全局平均池化 ) self.classifier = nn.Linear(256, num_classes) def forward(self, x): x = self.features(x) x = x.flatten(1) return self.classifier(x)

AdaptiveAvgPool2d(1)把任意空间尺寸压成 1×1,避免全连接层参数量爆炸。BatchNorm2d放在卷积和激活之间,能加速收敛。如果验证集准确率卡在 60% 上不去,优先检查数据增强是否过强、学习率是否太大。

3.2 迁移学习:冻结与微调的参数怎么设

用预训练 ResNet18 时,常见做法是先把 backbone 冻结,只训练最后的全连接层,等 loss 稳定后再解冻最后几个 block 做微调。这样能在小数据集上快速拿到一个不错的起点。

import torchvision.models as models import torch.nn as nn model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) # 阶段一:冻结 backbone for param in model.parameters(): param.requires_grad = False # 替换分类头 num_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(num_features, 4) ) # 阶段二:解冻 layer4 做微调(可选) # for param in model.layer4.parameters(): # param.requires_grad = True

weights=models.ResNet18_Weights.DEFAULT会自动下载 ImageNet 预训练权重。Dropout(0.3)在全连接前加正则,如果训练集很小可以调到 0.5。解冻layer4时学习率要调小,一般设为基础学习率的 1/10,否则预训练权重会被快速破坏。

优化器选 AdamW,学习率 1e-3(冻结阶段)或 1e-4(微调阶段),权重衰减 1e-4。损失函数用CrossEntropyLoss,如果类别不平衡可以加weight参数。

import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3, weight_decay=1e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20)

filter(lambda p: p.requires_grad, ...)只把需要更新的参数传给优化器,冻结的层不会浪费计算。CosineAnnealingLR让学习率按余弦曲线下降,比 StepLR 更平滑。

4. 训练循环、评估指标与模型导出

4.1 一个可复用的训练与验证循环

训练循环要记录 loss 和准确率,每个 epoch 结束后在验证集上评估,保存最佳模型。下面是一个最小可用的模板:

import torch from tqdm import tqdm device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) best_acc = 0.0 for epoch in range(30): # 训练阶段 model.train() running_loss = 0.0 for imgs, labels in tqdm(train_loader, desc=f'Epoch {epoch+1} train'): 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() * imgs.size(0) scheduler.step() train_loss = running_loss / len(train_ds) # 验证阶段 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(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) val_acc = correct / total print(f'Epoch {epoch+1}: train_loss={train_loss:.4f}, val_acc={val_acc:.4f}') if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'best_model.pth') print(f' -> saved best model (acc={best_acc:.4f})')

model.train()和model.eval()必须成对出现,前者启用 Dropout 和 BatchNorm 的训练模式,后者切换到推理模式。torch.no_grad()在验证时关闭梯度计算,节省显存。scheduler.step()放在 epoch 结束后调用,如果按 batch 更新则放在 batch 循环里。

4.2 混淆矩阵与分类报告:看清模型到底错在哪

准确率只能看整体,垃圾识别里更关心「有害垃圾有没有被误判成其他垃圾」。用sklearn的classification_report和confusion_matrix能定位问题类别。

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) preds = model(imgs).argmax(dim=1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_names=val_ds.classes, digits=4)) print(confusion_matrix(all_labels, all_preds))

如果某一类的 recall 明显偏低,说明该类样本太少或特征不明显,优先补充该类数据或调整CrossEntropyLoss的weight参数。混淆矩阵里如果「有害垃圾」大量被预测成「其他垃圾」,检查一下两类图像在颜色和形状上是否过于相似。

4.3 导出 ONNX 与推理脚本

训练完的.pth只能在 PyTorch 环境里用,导出 ONNX 后可以跨框架部署,也方便集成到 Web 服务或桌面应用里。

import torch model.load_state_dict(torch.load('best_model.pth', map_location='cpu')) model.eval() dummy = torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy, 'garbage_cnn.onnx', input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch'}, 'output': {0: 'batch'}}, opset_version=11 )

dynamic_axes让 batch 维度可变,推理时可以一次传多张图。opset_version=11兼容性较好,如果部署环境支持更高版本可以调到 13 或 17。导出后用onnxruntime跑一遍验证输出是否一致:

import onnxruntime as ort import numpy as np sess = ort.InferenceSession('garbage_cnn.onnx') dummy_np = dummy.numpy() out = sess.run(None, {'input': dummy_np}) print(out[0].shape) # (1, 4)

5. 避坑与排查:垃圾识别 CNN 训练中最容易翻车的 5 个点

5.1 现象:训练 loss 不下降,准确率始终在 25% 左右

原因:学习率过大导致梯度爆炸,或者数据标签和目录名没有对上。ImageFolder按目录名排序生成标签,如果训练集和验证集的类别顺序不一致,验证准确率会随机波动。

解决:先把学习率降到 1e-4 试一轮,确认 loss 有下降趋势。然后打印train_ds.classes和val_ds.classes,确认两者完全一致。如果用了自定义 Dataset,检查__getitem__返回的 label 是否和类别列表对应。

5.2 现象:验证集准确率远高于训练集准确率

原因:验证集太小或分布和训练集差异大,也可能是 Dropout 在验证时没关闭。另一种常见情况是验证集里混入了训练集的图片,导致「作弊」。

解决:检查model.eval()是否在验证前调用。用splitfolders重新切分数据,确保同一张图不会同时出现在训练集和验证集。如果验证集少于 100 张,考虑做交叉验证或扩充验证集。

5.3 现象:GPU 显存溢出,报 CUDA out of memory

原因:batch_size 太大,或者没有用torch.no_grad()导致验证阶段也计算梯度。图像分辨率从 224 提到 448 时显存占用会翻四倍。

解决:先把 batch_size 降到 16 或 8,验证阶段加上with torch.no_grad():。如果还不够,用torch.cuda.empty_cache()清理缓存,或者把模型换成 MobileNetV3 这类轻量结构。

5.4 现象:模型在测试图上表现很好,但实际拍照识别一塌糊涂

原因:训练数据是白底商品图,实际场景是复杂背景、光照不均、角度倾斜。数据分布不一致是图像分类落地最大的坑。

解决:在训练集里加入实际场景拍摄的图片,至少每类 50 张。增强管线里加入RandomRotation(15)、RandomAffine和更强的ColorJitter。如果条件允许,用手机拍一批测试图做一次「真实场景评估」,别只看验证集数字。

5.5 现象:ONNX 导出成功但推理结果和 PyTorch 不一致

原因:导出时模型没有切换到eval()模式,Dropout 和 BatchNorm 仍在训练状态。或者输入数据的预处理方式和训练时不一致,比如忘了归一化。

解决:导出前务必执行model.eval()。推理时确认输入张量的 shape 是(N, 3, 224, 224),且归一化参数和训练时完全相同。用同一张图分别跑 PyTorch 和 ONNX,对比输出向量的余弦相似度,低于 0.99 就说明有问题。

6. 把模型塞进实际系统:从单张推理到批量分类的一个技巧

训练完模型只是第一步,课程设计通常还要求做一个可交互的界面或脚本。我一般会写一个predict.py,支持单张图片和整个文件夹的批量推理,输出类别名和置信度。这样演示时不用每次都开 Jupyter Notebook。

import torch from torchvision import transforms from PIL import Image import sys, os, json # 加载模型结构(以 ResNet18 为例) import torchvision.models as models import torch.nn as nn def load_model(ckpt='best_model.pth', num_classes=4): model = models.resnet18(weights=None) model.fc = nn.Sequential(nn.Dropout(0.3), nn.Linear(model.fc.in_features, num_classes)) model.load_state_dict(torch.load(ckpt, map_location='cpu')) model.eval() return model # 与训练一致的预处理 infer_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) CLASSES = ['hazardous', 'kitchen_waste', 'other', 'recyclable'] def predict_image(model, img_path): img = Image.open(img_path).convert('RGB') tensor = infer_tf(img).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): logits = model(tensor) probs = torch.softmax(logits, dim=1)[0] idx = probs.argmax().item() return CLASSES[idx], probs[idx].item() if __name__ == '__main__': model = load_model() target = sys.argv[1] if os.path.isdir(target): results = {} for fname in os.listdir(target): if fname.lower().endswith(('.jpg', '.png', '.jpeg')): label, conf = predict_image(model, os.path.join(target, fname)) results[fname] = {'label': label, 'confidence': round(conf, 4)} print(json.dumps(results, ensure_ascii=False, indent=2)) else: label, conf = predict_image(model, target) print(f'{target} -> {label} ({conf:.2%})')

CLASSES的顺序必须和训练时ImageFolder的classes一致,否则标签会错位。unsqueeze(0)把单张图的(3, 224, 224)变成(1, 3, 224, 224),因为模型要求输入有 batch 维度。批量推理时用json.dumps输出结构化结果,方便后续接入 Web 接口或写入数据库。

这个脚本我一般会再包一层argparse,加上--topk参数输出前三个类别及概率,演示时更有说服力。如果要做成 Web 服务,用 FastAPI 包一下predict_image函数,接收上传的图片文件,返回 JSON 即可。实际部署时注意图片大小限制和并发数,CPU 推理单张 224×224 大约 50~100ms,GPU 上可以做到 10ms 以内。

血泪经验是:别等到答辩前一天才把模型导出和推理脚本串起来。训练和推理的预处理必须严格一致,归一化参数、图像尺寸、通道顺序,任何一个对不上,结果就是玄学。我习惯在训练脚本里把预处理参数写进一个config.json,推理脚本直接读同一个文件,省得来回改。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 6:17:27

基于CNN的垃圾识别分类系统:Python源码与数据集实战

简介:这份资源是面向高校学生与深度学习入门者的垃圾识别分类课程设计完整项目,基于卷积神经网络实现图像分类,可直接用于课程设计或期末大作业,无需二次修改即可运行。压缩包共约2000个文件,以1196张jpg与789张jpeg图…

作者头像 李华
网站建设 2026/9/28 6:16:54

Java集成支付宝扫码支付全链路实战:从沙箱到回调验签与幂等

简介:这份资源面向需要在Java应用中接入支付宝支付能力的开发者,尤其适合电商、O2O场景下希望快速跑通扫码支付流程的中级Java工程师。项目围绕支付宝SDK展开,涵盖扫码支付、订单处理、异步回调、appid与密钥配置、前端二维码展示页面以及API…

作者头像 李华
网站建设 2026/9/28 6:16:53

S7-1200 PUT/GET通讯避坑指南:DB块配置与自动连接5大关键点

1. 为什么PUT/GET通讯总在DB块上栽跟头1.1 一个让无数工程师抓狂的现场S7-1200做PUT/GET通讯,连接组态好了,硬件也下载了,一触发读写就报错。错误代码五花八门,有时候是16#05,有时候是16#0A,有时候干脆连接…

作者头像 李华
网站建设 2026/9/28 6:16:22

陀螺匠企业助手:把战略规划从PPT变成落地执行

1. 陀螺匠企业助手:先搞懂它到底解决什么事我第一次拿到“陀螺匠企业助手”这个战略规划工具时,第一反应是这名字怎么这么像养生用品。但真把它跑完一轮,我才意识到它其实是个挺上头的管理框架:把企业战略规划这件事,从…

作者头像 李华
网站建设 2026/9/28 6:16:03

情感戏写作:如何把“信赖”从结果改写成过程

1. 这一章到底在写什么:先把信赖的层次拆清楚写“莹姐的信赖”这个章节之前,我花了整整两天时间想一个问题:信赖到底是一个结果,还是一个过程?很多人写情感戏,习惯把信赖当成一个可以瞬间达成的结果——主角…

作者头像 李华
网站建设 2026/9/28 6:15:58

智慧城市与可持续发展EI会议投稿全攻略:从选题到检索避坑指南

1. 先把这个会议标题拆开看:每个关键词都在传递信号做学术的人看到这种会议宣传,第一反应往往是既心动又警惕。心动的是"EI检索"几个字,警惕的也是这仨字。我在学术圈子里混了十几年,既投过稿也审过稿,对这种…

作者头像 李华