2026/10/16 15:28:30

生活垃圾分类识别实战:基于CNN与TensorFlow的128×128轻量模型

生活垃圾分类识别实战:基于CNN与TensorFlow的128×128轻量模型 简介面向高校电子信息、计算机等相关专业学生尤其是正在开展图像识别与深度学习课题研究的人提供一份可直接参考的本科毕业设计论文《基于深度学习的生活垃圾分类识别算法的研究》。论文针对城市化背景下的垃圾分类需求提出基于图像处理的分类方法系统阐述了带标记数据收集、128×128降维生成新图像、神经网络参数调整与多轮实验等核心环节最终训练出分类准确率达93%以上的模型内容覆盖从问题分析到模型评估的完整流程可帮助读者快速借鉴算法设计与实验方法。资源为单个docx文档整体仅631KB包含中英文摘要、目录、引言、研究方法、实验与结论以及附录中的核心代码结构清晰便于按章节研读。目前已有82人学习下载适合需要快速获取垃圾分类深度学习参考资料的读者用作论文写作或课题起步的重要参考。1. 生活垃圾分类识别128×128降维CNN拿到93%准确率晚上下楼扔垃圾站在四个垃圾桶前举着外卖盒犹豫这种场景几乎每周都发生。这篇论文做的就是生活垃圾分类识别用图像识别算法自动判断垃圾属于哪个类别。整套方案技术栈是tensorflow加keras先把原始垃圾图像统一压缩成128×128再扔进卷积神经网络训练最终分类准确率在93%以上。最打动我的点是它没有堆显存和服务器一台普通PC就能训完正好解决图像分类项目「算力不够就翻车」的老大难问题。适合正在做毕业设计、或者想用深度学习做一个小型图像分类落地的开发者。2. 项目定位与技术选型有标记数据、图像压缩和CNN的三角关系2.1 数据从哪里来有标记的原始图像是一切的前提整篇论文反复强调一个前提原始数据必须有标记。这其实是整个项目的地基。垃圾分类是典型的监督学习问题每一张垃圾图像必须对应一个标签模型才能学到「这种纹理加这种颜色约等于塑料瓶」的映射关系。如果数据没有标签后面所有训练、评估、预测都无从谈起。收集垃圾图像数据的常见做法有两条路第一条是直接用公开的垃圾分类数据集网上有不少按类别分好文件夹的图片集分类通常覆盖可回收物、厨余垃圾、有害垃圾、其他垃圾这几个大类第二条是自己拍把快递盒、饮料瓶、果皮、废电池各拍几十张按类别放进不同目录。这一步容易被低估我拆过不少图像分类项目数据不整理好后面准确率再高也是虚的。这个项目里虽然没有展开讲数据来源细节但从附录代码的组织方式看它就是「根目录/类别名/图片文件」这种最朴素也最不容易出错的目录结构遍历目录就能自动生成文件名和标签的对应关系比手写标注文件省事得多。2.2 128×128图像压缩算力和准确率之间的平衡点这个项目最值得借鉴的设计就是图像压缩降维。原始垃圾照片可能是几百万像素直接塞进神经网络第一个全连接层的参数量就是天文数字。正文里提到的高维数据计算量膨胀问题本质就是维度灾难相邻两层之间的计算量会指数级上涨普通PC根本扛不住。把图像统一缩到128×128像素总量变成原来的几十分之一训练速度和显存占用都大幅下降。关键在于128×128这个尺寸不是随便拍的。它是在「保留信息」和「节省算力」之间试出来的。图像缩太小塑料瓶上的纹理、纸箱的瓦楞这些判别特征全丢了模型沦为瞎猜缩太大训练时间翻倍但准确率提升有限。从实验效果看128×128在廉价PC上的训练时间和93%以上的准确率都说明这个尺寸是划算的。另外统一尺寸还有一个容易被忽略的好处模型输入维度固定训练和预测时不需要处理变长数据代码逻辑能简化不少。正文还提到将RGBA图像转成L模式灰度图来降维但在实际落地中我建议用RGB三通道因为很多垃圾的判别信息在颜色上比如可乐罐的红色和绿瓶的绿色灰度化会把这类信息丢掉。2.3 为什么选CNN而不是SVM特征和分类器端到端一起学正文里列了SVM、线性回归、逻辑回归、k-means、贝叶斯等方法最后选了卷积神经网络理由很直接图像分类场景下CNN能把特征提取和分类器端到端一起学出来。SVM这类传统方法需要先手工设计特征比如颜色直方图、纹理特征特征设计得好不好直接决定效果上限。垃圾类别之间的视觉差异极大塑料瓶、纸箱、果皮、电池的纹理和颜色完全不同手工特征很难有一套通吃的方案。CNN的局部感受野机制在这里特别契合。一张垃圾图像里瓶盖的螺纹、纸箱的瓦楞纹理往往是局部信息卷积核用一个小窗口从上到下、从左到右扫过图像天然能捕捉这种局部模式。再通过池化层降低采样率保留主要特征的同时进一步压缩计算量。正文里提到的AlexNet等经典结构证明了加深网络层数能提升图像分类效果但副作用是参数规模暴涨所以这个项目没有直接照搬大网络而是借鉴思路手搭了一个轻量级CNN。框架选型上项目用的是tensorflow加keras正文给出的理由是作者掌握且安装简单。这个选择放在今天依然合理keras的Sequential API写一个小型CNN只要十几行代码对快速验证模型思路非常友好。PyTorch虽然生态更活跃但在这个项目的低算力、快速落地场景里没有本质优势。2.4 训练流程总览从原始图片到93%模型的五个步骤整个项目的数据流可以拆成五个环节图像压缩、图像处理存储、数据集分割、模型训练、预测。前两步是数据准备把原始照片变成模型能吃的张量第三步是数据划分把样本拆成训练集和测试集第四步是训练出模型文件第五步是拿新图片走一遍推理链路。这跟常规机器学习研究步骤完全一致原始数据、预处理清洗、模型训练、模型评估、应用预测。用一句话概括这条链路原始垃圾照片 → 统一压缩到128×128 → 转RGB后归一化到0-1并存成npy文件 → 按8比2切分训练测试集 → 送入CNN训练得到h5模型 → 加载模型对新图片预测并输出类别概率。这个流程里没有复杂的工程组件每一步都是独立脚本想改哪一环就动哪个文件。如果以后想部署成HTTP服务只需要把预测脚本包成一个函数用Flask或者FastAPI暴露一个接口就行这部分后面展开。3. 核心代码拆解附录1到附录5的完整实现链路论文的代码全部放在附录1到附录5五个脚本对应五个环节正好覆盖一条完整的图像分类数据流。附录功能输入输出附录1图像压缩任意尺寸的原始图片128×128统一尺寸图片附录2图像处理存储压缩后的图片目录X.npy、y.npy、类别清单附录3数据集分割X.npy、y.npy训练集和测试集附录4模型训练训练集和测试集garbage_model.h5附录5预测新垃圾图片类别名称与置信度下面按顺序逐段拆每个脚本都会给出核心代码和参数说明。3.1 附录1图像压缩把任意尺寸统一到128×128# 图像压缩将目录下所有原始图片统一压缩为128x128 import os from PIL import Image src_dir raw_images # 原始图片目录 dst_dir compressed # 压缩后输出目录 size (128, 128) # 目标尺寸 os.makedirs(dst_dir, exist_okTrue) for root, _, files in os.walk(src_dir): for fname in files: if not fname.lower().endswith((.jpg, .jpeg, .png)): continue path os.path.join(root, fname) img Image.open(path) # 统一缩放到128x128保留图像整体结构 img img.resize(size, Image.LANCZOS) # 保持原目录结构方便后面按类别读取 rel_path os.path.relpath(root, src_dir) out_dir os.path.join(dst_dir, rel_path) os.makedirs(out_dir, exist_okTrue) img.save(os.path.join(out_dir, fname))这段代码做的事很朴素遍历原始目录把所有jpg和png图片用PIL打开resize到128×128再按原来的目录结构存到compressed文件夹。resize的插值算法用了LANCZOS这个算法在缩小图像时能保留更多边缘细节比默认的BICUBIC更锐利。如果你发现压缩后的垃圾图片边缘出现锯齿或文字模糊优先检查这一行。需要注意这里只做了尺寸压缩没有处理通道数。如果原始图像是RGBA四通道后面训练时容易报shape错误。常规做法是在resize之前加一行img img.convert(RGB)把alpha通道丢掉附录2里会看到这个处理。3.2 附录2图像处理存储归一化到0-1并序列化# 图像处理存储读取压缩后的图像归一化并保存为numpy数组 import os import numpy as np from PIL import Image data_dir compressed img_size (128, 128, 3) # 宽高128RGB三通道 X, y [], [] class_names sorted(os.listdir(data_dir)) for label, cls in enumerate(class_names): cls_dir os.path.join(data_dir, cls) if not os.path.isdir(cls_dir): continue for fname in os.listdir(cls_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue img Image.open(os.path.join(cls_dir, fname)).convert(RGB) img img.resize(img_size[:2]) arr np.asarray(img, dtypenp.float32) / 255.0 # 归一化到0-1 X.append(arr) y.append(label) X np.stack(X) y np.array(y) np.save(X.npy, X) np.save(y.npy, y) # 保存类别名预测时用来把下标映射回名称 with open(class_names.txt, w, encodingutf-8) as f: f.write(\n.join(class_names))这段代码做了三件事把图像统一转成RGB三通道把像素值从0-255缩放到0-1区间把整个数据集序列化存成npy文件。归一化这一步很关键神经网络里的激活函数对输入尺度敏感像素值如果保持0-255的大数值梯度更新容易震荡训练很难收敛。除以255之后所有数据落在0-1区间梯度下降路径平滑很多。dtype选了float32而不是float64是因为tensorflow训练时默认也是float32保持一致可以省一半内存。128×128×3的数组几千张图片也就一两GB普通PC的内存完全扛得住。如果数据量更大可以用np.memmap把npy映射到磁盘按需读取但在这个项目规模下没必要。3.3 附录3数据集分割训练集和测试集如何切分# 数据集分割按比例拆分训练集和测试集 import numpy as np from sklearn.model_selection import train_test_split X np.load(X.npy) y np.load(y.npy) # 8:2划分stratify保证每个类别在训练/测试集中的比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(f训练集: {X_train.shape} {y_train.shape}) print(f测试集: {X_test.shape} {y_test.shape})train_test_split是sklearn里最常用的划分函数三个关键参数都有讲究。test_size0.2表示拿20%数据当测试集剩下80%训练这是小数据集最常见的配比测试集太小评估不靠谱训练集太少模型学不到位。random_state42固定随机种子保证每次运行划分结果一致实验结果可以复现这是论文实验的基本要求。最容易忽略的是stratifyy。垃圾数据通常是不平衡的比如饮料瓶拍了500张、废纸只有50张不做分层采样随机划分出来的测试集可能恰好没有废纸模型在废纸上的表现完全没被检验准确率虚高还不自知。stratifyy会让训练集和测试集里每个类别的占比跟原始数据保持一致这个参数在类别不平衡时几乎是必选项。3.4 附录4模型训练轻量级CNN的核心结构# 模型训练构建CNN并训练垃圾分类模型 import tensorflow as tf from tensorflow.keras import layers, models num_classes len(open(class_names.txt).read().splitlines()) model models.Sequential([ layers.Conv2D(32, (3, 3), activationrelu, input_shape(128, 128, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ]) model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] ) history model.fit( X_train, y_train, batch_size32, epochs30, validation_split0.1, callbacks[tf.keras.callbacks.EarlyStopping(patience5, restore_best_weightsTrue)] ) model.save(garbage_model.h5)网络结构是经典的「卷积加池化」堆叠。三层Conv2D的卷积核数量从32逐层涨到128每层后面跟一个MaxPooling2D把特征图宽高减半。128×128的输入经过三次池化变成16×16但通道数从3涨到128空间信息被压缩成通道特征这正是CNN提取图像特征的典型路径。最后Flatten拉平接两个全连接层输出层用softmax把得分转成每个类别的概率分布。训练参数里有几个值得说。Adam优化器是当前图像分类的默认选择它自适应调整每个参数的学习率省去手动调学习率的大量工作。loss用的sparse_categorical_crossentropy对应整数标签y_train如果你改成one-hot编码要同步换成categorical_crossentropy这两个名字长得像用错会直接报错或训练行为诡异。EarlyStopping设了patience5验证集损失连续5个epoch不降就提前停restore_best_weights保证最后保存的是验证集上最优的那组权重防止过拟合后的烂模型被存下来。epochs30只是上限实际大概率会被EarlyStopping中途截断这是好事。batch_size32是经验和显存的折中显存小就降到16速度快就升到6432是多数小项目的最稳起点。3.5 附录5预测新垃圾图片怎么走完推理链路# 预测加载模型对单张新图进行分类 import numpy as np import tensorflow as tf from PIL import Image model tf.keras.models.load_model(garbage_model.h5) class_names open(class_names.txt).read().splitlines() img Image.open(test_can.jpg).convert(RGB) img img.resize((128, 128)) arr np.asarray(img, dtypenp.float32) / 255.0 arr np.expand_dims(arr, axis0) # 补batch维度变成(1,128,128,3) pred model.predict(arr, verbose0) idx int(np.argmax(pred[0])) conf float(pred[0][idx]) print(f预测类别: {class_names[idx]}置信度: {conf:.4f})预测代码的关键在np.expand_dims这一步。模型训练时的输入是四维张量形状是(样本数, 128, 128, 3)单张图片读进来只有三维不补一个batch维度predict会直接报shape不匹配。这是所有用过keras做推理的开发者至少踩过一次的坑。predict返回的pred形状是(1, num_classes)每个位置代表一个类别的概率np.argmax取最大值下标再映射回class_names里的类别名就是最终结果。提示model.predict里的verbose0是给新版本tensorflow准备的不写的话每次预测都会打印进度条在脚本和自动化服务里刷得很难看。4. 训练避坑记录从翻车到93%准确率的五次关键排障4.1 过拟合训练集准确率99%测试集只有75%这个现象在训练初期几乎必然出现。网络结构偏深、训练轮数一多模型就开始「背题」把训练集里的噪声细节也当成规律记下来测试集一上立马露馅。原因很直接垃圾图像数据集通常不大几千张图对三层卷积网络来说信息量不够参数有余而数据不足模型自然转向死记硬背。解决思路是给网络加约束。EarlyStopping盯住验证集loss连续几个epoch不降就停Dropout层在训练时随机屏蔽一半神经元强迫网络学到冗余特征而不是只依赖某几个「尖子生」神经元。这两个机制同时用过拟合基本能压住。如果还想更稳可以做数据增强把图片随机旋转、翻转、调亮度等于白赚了好几倍的训练样本。4.2 梯度消失加了层数loss反而不降了第一次往网络里加层以为层数越多越强结果loss从头到尾几乎一条直线训练了十几个epoch纹丝不动。这是把大网络思路直接搬过来的翻车现场。原因就是正文里说的梯度弥散深层网络反向传播时梯度逐层相乘越靠近输入层梯度越小参数几乎得不到更新。解决办法是换激活函数。sigmoid在深层网络中会把梯度压到接近0ReLU在正区间的梯度恒为1梯度能顺畅回传。正文里也明确提到经典网络结构的贡献之一就是用ReLU解决了梯度弥散问题。如果换了ReLU之后loss还是不降可以在卷积层后加BatchNormalization它对梯度流也有稳定作用。4.3 数据不平衡准确率93%但小类别全错有一轮实验准确率到了93%本来挺高兴结果翻混淆矩阵发现某个样本量很少的类别预测结果全错准确率全靠数量多的几个类别撑起来。这是典型的「准确率骗人」。原因不复杂模型把概率预算全押在常见类别上稀有类别无论给什么输入都预测成高频类整体准确率依然很高。解决有三个办法按优先级来第一是补数据把稀有类别的样本拍到和主流类别一个量级第二是做stratify划分保证测试集里稀有类别的比例不缩水附录3的代码已经覆盖第三是给少数类更高的类别权重让loss对稀有类别的错误更敏感。调权重在keras里通过class_weight参数传入一行代码的事但效果立竿见影。4.4 CPU训练太慢一个epoch半小时调参一次等一天没有GPU的机器上训练128×128的图像一个epoch十几分钟很正常调几次参就耗掉一整天。这个问题正文其实已经提前想好了图像压缩到128×128就是为低算力环境设计的。在此基础上还可以再省三步。一是把预处理结果缓存成npy文件避免每次训练都重新读图resize附录2的做法已经覆盖。二是调低epoch上限配合EarlyStopping让模型在验证集不再变好时立刻停止。三是把batch_size从32升到64减少梯度更新次数牺牲一点收敛精度换训练速度。如果还嫌慢就砍掉一层卷积网络浅一点训练快很多准确率通常只掉两三个点换来的是能跑起来的体验。4.5 预测时shape报错或图片打不开跑预测脚本最容易遇到两个报错。第一个是PIL打开图片失败原因是图片格式损坏或者路径里有中文把路径改成纯英文即可这个坑在Windows环境下尤其常见。第二个是predict时shape不匹配原因是忘了np.expand_dims补batch维度或者训练时用的是RGB三通道、预测时图片是RGBA四通道convert(RGB)能解决。这两个错误都很隐蔽报错信息也不算友好。最有效的排查方式是打印张量的shape一层一层对从图像读取、resize、归一化到expand_dims每一步都确认形状符合预期。5. 二次开发让模型适配你自己的垃圾识别场景5.1 换数据集目录结构不变改类别就行这套代码的数据流是「目录即标签」换数据集非常省事。把compressed目录下的文件夹换成自己的类别名比如「快递纸箱」「易拉罐」「剩饭」每类放几百张图直接从附录2开始跑class_names.txt会自动更新。类别数量变了模型最后的Dense层输出节点会自动对应不用改其他代码。如果类别超过十个每类样本又只有几百张一个简单CNN可能不太够这时候建议直接上迁移学习。5.2 换模型从零训练升级到迁移学习想要更快提升准确率最快的方式是换预训练模型当特征提取器。常规做法是把MobileNetV2在ImageNet上预训练好的权重拿过来冻结前面的骨干网络只训练最后的分类层。MobileNetV2比ResNet50轻量得多更契合这个项目「低算力」的定位。代码改动很小核心就是把Sequential里开头的几层Conv2D替换成一个预训练基座from tensorflow.keras.applications import MobileNetV2 base MobileNetV2( weightsimagenet, include_topFalse, input_shape(128, 128, 3) ) base.trainable False # 冻结骨干网络只训练分类头 model models.Sequential([ base, layers.GlobalAveragePooling2D(), layers.Dense(128, activationrelu), layers.Dropout(0.5), layers.Dense(num_classes, activationsoftmax) ])input_shape仍然保持128×128数据预处理完全不用重做。trainableFalse冻结基座后训练参数量从天量降到只有最后一小截CPU也能在十几分钟内跑完。这一步跑通、准确率稳定之后再解冻最后几层做微调学习率调低到1e-5效果还能再涨一截。5.3 换验证方式混淆矩阵比准确率诚实得多二次开发里最容易忽略的是验证方式。准确率只是一个平均值类别一多就会被高样本量类别带偏。建议每次训练完都打一张混淆矩阵看看哪个类别的召回率最低往往能发现模型把「纸箱」和「书本」这类形状接近的类别搞混。这也是我在第4章踩过数据不平衡那个坑之后养成的习惯。从那以后我每次训练图像模型都会先跑一遍小批量基线确认loss曲线正常下降再开全量训练避免在错误配置上浪费几个小时。希望帮到你。本文还有配套的精品资源点击获取