简介:面向高校电子信息、计算机等相关专业学生,尤其是正在开展图像识别与深度学习课题研究的人,提供一份可直接参考的本科毕业设计论文《基于深度学习的生活垃圾分类识别算法的研究》。论文针对城市化背景下的垃圾分类需求,提出基于图像处理的分类方法,系统阐述了带标记数据收集、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_ok=True) 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_ok=True) 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) # 宽高128,RGB三通道 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, dtype=np.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", encoding="utf-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_size=0.2, random_state=42, stratify=y ) print(f"训练集: {X_train.shape} {y_train.shape}") print(f"测试集: {X_test.shape} {y_test.shape}")train_test_split是sklearn里最常用的划分函数,三个关键参数都有讲究。test_size=0.2表示拿20%数据当测试集,剩下80%训练,这是小数据集最常见的配比,测试集太小评估不靠谱,训练集太少模型学不到位。random_state=42固定随机种子,保证每次运行划分结果一致,实验结果可以复现,这是论文实验的基本要求。
最容易忽略的是stratify=y。垃圾数据通常是不平衡的,比如饮料瓶拍了500张、废纸只有50张,不做分层采样,随机划分出来的测试集可能恰好没有废纸,模型在废纸上的表现完全没被检验,准确率虚高还不自知。stratify=y会让训练集和测试集里每个类别的占比跟原始数据保持一致,这个参数在类别不平衡时几乎是必选项。
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), activation="relu", input_shape=(128, 128, 3)), layers.MaxPooling2D((2, 2)), layers.Conv2D(64, (3, 3), activation="relu"), layers.MaxPooling2D((2, 2)), layers.Conv2D(128, (3, 3), activation="relu"), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(128, activation="relu"), layers.Dropout(0.5), layers.Dense(num_classes, activation="softmax") ]) model.compile( optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"] ) history = model.fit( X_train, y_train, batch_size=32, epochs=30, validation_split=0.1, callbacks=[tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)] ) 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设了patience=5,验证集损失连续5个epoch不降就提前停,restore_best_weights保证最后保存的是验证集上最优的那组权重,防止过拟合后的烂模型被存下来。epochs=30只是上限,实际大概率会被EarlyStopping中途截断,这是好事。batch_size=32是经验和显存的折中,显存小就降到16,速度快就升到64,32是多数小项目的最稳起点。
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, dtype=np.float32) / 255.0 arr = np.expand_dims(arr, axis=0) # 补batch维度,变成(1,128,128,3) pred = model.predict(arr, verbose=0) 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里的verbose=0是给新版本tensorflow准备的,不写的话每次预测都会打印进度条,在脚本和自动化服务里刷得很难看。
4. 训练避坑记录:从翻车到93%准确率的五次关键排障
4.1 过拟合:训练集准确率99%,测试集只有75%
这个现象在训练初期几乎必然出现。网络结构偏深、训练轮数一多,模型就开始「背题」,把训练集里的噪声细节也当成规律记下来,测试集一上立马露馅。
原因很直接:垃圾图像数据集通常不大,几千张图对三层卷积网络来说信息量不够,参数有余而数据不足,模型自然转向死记硬背。解决思路是给网络加约束。EarlyStopping盯住验证集loss,连续几个epoch不降就停;Dropout层在训练时随机屏蔽一半神经元,强迫网络学到冗余特征,而不是只依赖某几个「尖子生」神经元。这两个机制同时用,过拟合基本能压住。如果还想更稳,可以做数据增强,把图片随机旋转、翻转、调亮度,等于白赚了好几倍的训练样本。
4.2 梯度消失:加了层数,loss反而不降了
第一次往网络里加层,以为层数越多越强,结果loss从头到尾几乎一条直线,训练了十几个epoch纹丝不动。这是把大网络思路直接搬过来的翻车现场。
原因就是正文里说的梯度弥散:深层网络反向传播时梯度逐层相乘,越靠近输入层梯度越小,参数几乎得不到更新。解决办法是换激活函数。sigmoid在深层网络中会把梯度压到接近0,ReLU在正区间的梯度恒为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( weights="imagenet", include_top=False, input_shape=(128, 128, 3) ) base.trainable = False # 冻结骨干网络,只训练分类头 model = models.Sequential([ base, layers.GlobalAveragePooling2D(), layers.Dense(128, activation="relu"), layers.Dropout(0.5), layers.Dense(num_classes, activation="softmax") ])input_shape仍然保持128×128,数据预处理完全不用重做。trainable=False冻结基座后,训练参数量从天量降到只有最后一小截,CPU也能在十几分钟内跑完。这一步跑通、准确率稳定之后,再解冻最后几层做微调,学习率调低到1e-5,效果还能再涨一截。
5.3 换验证方式:混淆矩阵比准确率诚实得多
二次开发里最容易忽略的是验证方式。准确率只是一个平均值,类别一多就会被高样本量类别带偏。建议每次训练完都打一张混淆矩阵,看看哪个类别的召回率最低,往往能发现模型把「纸箱」和「书本」这类形状接近的类别搞混。这也是我在第4章踩过数据不平衡那个坑之后养成的习惯。从那以后我每次训练图像模型都会先跑一遍小批量基线,确认loss曲线正常下降,再开全量训练,避免在错误配置上浪费几个小时。希望帮到你。
本文还有配套的精品资源,点击获取