尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于ResNet-50与Jaccard双决策的垃圾分类系统设计与部署

发布时间:2026/9/19 15:55:26

资讯中心
01
ARTICLE

基于ResNet-50与Jaccard双决策的垃圾分类系统设计与部署

基于ResNet-50与Jaccard双决策的垃圾分类系统设计与部署
简介这份基于ResNet与Jaccard算法的垃圾分类系统设计文档面向计算机视觉、深度学习方向的开发者与毕业设计人员用于解决生活垃圾自动识别与分类效率低的问题覆盖从需求分析、系统设计到测试实现的完整流程。压缩包内为1个docx文件大小2.6MB包含绪论、相关技术、需求分析、系统设计与实现、系统测试等章节结合CNN、ResNet-50残差网络、Jaccard相似系数及Softmax、ReLU等激活函数系统介绍了垃圾分类识别模型的构建方法。方案采用TensorFlow与sklearn搭建模型引入Bottleneck残差学习单元并通过旋转、缩放等数据增广提升泛化能力在垃圾图像数据集上达到87%的准确率具备拍照识别、文字识别、分类指南等实用功能。已有1285人学习下载文档对卷积神经网络原理、实验数据预处理、模型训练与功能测试均给出了具体说明可直接作为课程设计、论文撰写、毕业答辩或系统开发的参考资料。1. 从 ResNet-50 到 Jaccard这套垃圾分类系统为什么需要双决策城市垃圾处理最烧钱的环节不是运输而是分拣。一个回收厂每天处理几百吨混装垃圾逐件靠人工挑玻璃、塑料、金属、纸张不仅费人力分错一件后面整条再生链路都会受影响。基于 ResNet-50 的图像分类能回答“这是什么材质”但实际跑起来会出现一个尴尬场景玻璃瓶和塑料瓶、纸盒和薄塑料膜在照片里轮廓和纹理高度接近网络输出的概率往往卡在 0.60.8 之间硬选最大概率很容易翻车。这套系统的思路是让 ResNet-50 负责深层特征提取再引入 Jaccard 算法对分类结果做相似度二次校验最后在自建垃圾图像数据集上拿到了 87% 的分类准确性。适合想用深度学习做可落地小产品或者需要完整复现“模型训练 决策优化 小程序部署”全流程的读者。2. ResNet-50 模型构建残差块、激活函数与数据增强2.1 选型原理为什么是 ResNet-50 而不是 VGG 或 MobileNet垃圾分类图像和 ImageNet 场景有一个显著差异同一材质在不同光照、拍摄角度、遮挡程度下的外观差异非常大而不同材质玻璃和透明塑料在特征空间里又可能离得很近。VGG 结构简单、易于理解但 16 层以上梯度回传很不稳定训练时损失会随层数加深先降后升这就是论文里常说的“退化问题”。ResNet 用残差学习单元把输入输出做恒等映射梯度可以跨层直接回传网络能安全地堆深到 50 层以上。选择 ResNet-50 而非 MobileNet 的考虑是部署场景。这个系统的小程序端只负责采集图片、展示结果推理在服务端完成不要求模型压缩到几 MB。ResNet-50 在服务端 CPU 上单张推理约 80120 ms识别精度明显优于轻量网络速度和准确率比较平衡。如果后续要迁移到手机端离线推理再考虑 MobileNetV3 剪枝不迟。2.2 bottleneck 残差学习单元与 ResNet-50 的结构ResNet-50 的核心不是普通残差块而是 bottleneck 结构。它把 3×3 卷积夹在 1×1 降维和 1×1 升维卷积之间先降维再做卷积参数量和计算量大幅缩减。下面用 TensorFlow 实现两种基本残差单元。import tensorflow as tf from tensorflow.keras import layers, Model def conv_bn_relu(x, filters, kernel_size1, strides1): x layers.Conv2D(filters, kernel_size, stridesstrides, paddingsame, kernel_initializerhe_normal)(x) x layers.BatchNormalization()(x) x layers.ReLU()(x) return x def conv_block(x, filters, strides2): # 跨层残差下采样并调整通道数用于每个阶段的入口 f1, f2, f3 filters shortcut layers.Conv2D(f3, 1, stridesstrides)(x) shortcut layers.BatchNormalization()(shortcut) x conv_bn_relu(x, f1, 1, 1) x conv_bn_relu(x, f2, 3, strides) x layers.Conv2D(f3, 1)(x) x layers.BatchNormalization()(x) x layers.Add()([x, shortcut]) x layers.ReLU()(x) return x def identity_block(x, filters): # 恒等残差输入输出尺寸一致直接做加法 f1, f2, f3 filters shortcut x x conv_bn_relu(x, f1, 1, 1) x conv_bn_relu(x, f2, 3, 1) x layers.Conv2D(f3, 1)(x) x layers.BatchNormalization()(x) x layers.Add()([x, shortcut]) x layers.ReLU()(x) return xconv_block 与 identity_block 的区别在于 shortcut 路径。conv_block 用 1×1 卷积对输入做跨层下采样和通道对齐用于每个 stage 的入口identity_block 保持输入输出形状一致单纯堆叠网络深度。残差连接保证了即使层数加深梯度也能从输出端直线回传到浅层避免梯度消失。然后按 ResNet-50 标准配置组装网络。def build_resnet50(input_shape(224, 224, 3), num_classes4): inputs layers.Input(shapeinput_shape) x layers.Conv2D(64, 7, strides2, paddingsame)(inputs) x layers.BatchNormalization()(x) x layers.ReLU()(x) x layers.MaxPooling2D(3, strides2, paddingsame)(x) x conv_block(x, [64, 64, 256], strides1) x identity_block(x, [64, 64, 256]) x identity_block(x, [64, 64, 256]) x conv_block(x, [128, 128, 512], strides2) x identity_block(x, [128, 128, 512]) x identity_block(x, [128, 128, 512]) x identity_block(x, [128, 128, 512]) x conv_block(x, [256, 256, 1024], strides2) x identity_block(x, [256, 256, 1024]) x identity_block(x, [256, 256, 1024]) x identity_block(x, [256, 256, 1024]) x identity_block(x, [256, 256, 1024]) x identity_block(x, [256, 256, 1024]) x conv_block(x, [512, 512, 2048], strides2) x identity_block(x, [512, 512, 2048]) x identity_block(x, [512, 512, 2048]) x layers.GlobalAveragePooling2D()(x) outputs layers.Dense(num_classes, activationsoftmax)(x) return Model(inputs, outputs)各阶段重复次数与论文对齐conv2_x 重复 3 次、conv3_x 重复 4 次、conv4_x 重复 6 次、conv5_x 重复 3 次。这里省略了中间重复块完整搭建时需要补齐。he_normal初始化配合 BatchNormalization 能有效避免训练初期激活值振荡。2.3 relu、softmax、sigmoid 的搭配边界模型里三个激活函数各有明确的职责。ReLU 用在隐藏层解决线性模型的表达能力不足计算量低且能缓解梯度饱和最后一个全连接层用 softmax把四个类别的 logits 归一化成和为 1 的概率分布保证类间互斥。sigmoid 在这个系统里不放在输出主分类上而是用在辅助的多标签判断。实际识别中一张图中可能出现纸箱上有塑料胶带、玻璃瓶内有金属瓶盖主任务输出“纸张”时辅助分支会单独判断“是否存在塑料/金属”。这个辅助分支就是二分类 sigmoid 输出每个类别独立计算置信度不像 softmax 那样互相压制。2.4 数据预处理与增广策略from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rescale1.0 / 255, rotation_range20, width_shift_range0.1, height_shift_range0.1, zoom_range0.2, horizontal_flipTrue, fill_modenearest, validation_split0.2 ) train_gen datagen.flow_from_directory( garbage_data/train, target_size(224, 224), batch_size32, class_modecategorical, subsettraining ) val_gen datagen.flow_from_directory( garbage_data/train, target_size(224, 224), batch_size32, class_modecategorical, subsetvalidation )预处理的关键点有两个一是rescale1.0/255把像素从 0255 映射到 01配合 BN 层稳定前向传播二是增广参数上限不能过大。rotation_range 取 20 而不是 45是因为垃圾图像本身是用户随手拍的角度偏移通常在 20 度以内过大的旋转会引入倒置物品等现实中很少出现的样本反而拉低验证集表现。下表列出实测中比较适合垃圾图像任务的增广参数范围直接照抄可能会过拟合或欠拟合需要按数据集规模微调。参数取值作用调参建议rotation_range20模拟手持拍摄角度偏差数据量小于 1 万张时不要超过 30width_shift_range0.1模拟物体偏离画面中心配合 fill_modenearest 使用height_shift_range0.1模拟俯拍高度变化与 width_shift 保持一致zoom_range0.2模拟远近拍摄过大会裁掉垃圾主体horizontal_flipTrue左右镜像增加样本多样性玻璃瓶翻转后语义不变可以开brightness_range(0.8, 1.2)模拟室内外光线差异可新增原方案未提但很实用数据量本身是约束条件。爬虫拿到的图片存在大量相似帧网上抓的图还可能带了水印和背景杂物先用 pandas 和 numpy 做一轮去重和像素方差筛选方差过低的纯色图直接删掉避免模型学到“背景”而不是“物品”。2.5 训练配置与损失曲线观察模型编译时选 Adam 优化器、初始学习率 0.001配合余弦退火调度让学习率在训练后段平滑下降。batch_size 设为 32类别不均衡时给少数类加 class_weight。训练 50 到 80 轮观察验证损失是否在 10 轮内明显反弹如果反弹就降低学习率到 0.0003 或加大数据增广强度网络加深带来的过拟合在小型数据集上会比想象中来得更快。3. Jaccard 算法在分类决策层的二次校验3.1 Jaccard 相似系数解决什么问题ResNet-50 输出的 softmax 概率在类间相似时会变得不可靠。透明玻璃瓶和透明塑料瓶的分界线本来就很模糊模型可能会给出“玻璃 0.52、塑料 0.41、金属 0.07”的输出。这时如果强行取最大概率就等于把置信度不到六成的判断当成正确答案。Jaccard 算法在这里不是用来比较图像的像素相似度而是比较“模型预测结果的集合”与“候选样本标签集合”的重叠程度。它的数学定义是交集大小除以并集大小数值落在 0 到 1 之间。对于两个标签集合 A 和 BJaccard 系数的含义是两个集合共同包含的类别占全部类别的比例。3.2 从概率输出到可比较的集合要把 Jaccard 用起来第一步是把模型输出的连续概率转成集合。常见做法是取前 k 个高概率类别构成预测集合k 一般取 3 到 5。比如模型的输出是“玻璃 0.52、塑料 0.41、金属 0.07”取 top-3 就得到集合 {玻璃, 塑料, 金属}。这个集合不只看最终胜者还保留了模型认为“长得像谁”这是 Jaccard 能起作用的关键。3.3 Jaccard 匹配器的 Python 实现import numpy as np class JaccardMatcher: def __init__(self, class_names, k3): self.class_names class_names self.k k def top_k_set(self, prob): # 取概率最高的 k 个类别作为预测集合 indices np.argsort(prob)[::-1][:self.k] return set(self.class_names[i] for i in indices) def jaccard(self, prob, sample_label): pred_set self.top_k_set(prob) label_set set(sample_label) inter len(pred_set label_set) union len(pred_set | label_set) return inter / union if union 0 else 0top_k_set用argsort对概率降序排列后取前 k 个索引再映射成类别名集合。jaccard方法接收模型概率向量和候选样本的标注集合计算两者交集和并集比值。注意 sample_label 用 set 传入因为一个训练样本在多标签场景下可能有多个标注比如“玻璃瓶贴着纸标签”。举例说明计算过程预测集合 {玻璃, 塑料, 金属}候选样本 A 标注为 {玻璃}交集大小 1并集大小 3J 0.33候选样本 B 标注为 {塑料, 金属}交集 2、并集 3J 0.67。虽然概率上“玻璃”排第一但 Jaccard 排序会让 B 更靠前说明模型内部其实激活了塑料和金属相关特征此时 B 的更可能是真实类别。3.4 与 ResNet-50 的决策融合策略把两组判断合并需要确定谁在什么情况下说了算。实际操作中先在验证集上统计模型预测概率的分布找到一个阈值当最大概率高于 0.9 时ResNet-50 的分类可信度很高直接输出 argmax 结果当最大概率在 0.750.9 之间时模型存在明显摇摆需要借助 Jaccard 排序结果做加权投票低于 0.75 时直接以 Jaccard 结果为主。def final_decision(prob, matcher, sample_db, cnn_threshold0.9): max_prob np.max(prob) if max_prob cnn_threshold: return matcher.class_names[int(np.argmax(prob))] # 与数据库中的候选样本逐一计算 Jaccard 分数 scores [] for sample_id, sample_label in sample_db.items(): scores.append((sample_id, matcher.jaccard(prob, sample_label))) scores.sort(keylambda x: x[1], reverseTrue) best_id, best_score scores[0] # 最高 Jaccard 分数过低说明预测集合与任何已知样本都不匹配 if best_score 0.34: return unknown return sample_db[best_id][category]阈值 0.34 是经验值top-3 集合与单标签集合的交集最大只能是 1理论上 J 最大 0.33如果候选样本是多标签集合J 可以更高。这个系统里训练图片的标注以单标签为主0.34 意味着预测集合中至少有一个类别与样本完全匹配如果低于这个值说明模型看到的物体不在训练类别分布内。给定最后一套系统在数据集上达到 87% 的准确率其中 Jaccard 校验解决的主要就是玻璃、塑料、金属这类高混淆类别。4. 微信小程序端与 Flask 服务端的联动实现4.1 功能模块划分这个垃圾分类系统的最终载体是微信小程序整体基于 Uni-app 框架开发服务端用 Flask 提供推理接口。功能拆分得很清晰拍照识别负责接收摄像头或相册图片文字识别负责解析垃圾袋、包装盒上的印刷文字作为辅助判断依据分类指南则是一个可检索的规则库用户输入物品名直接查归属。三个功能共用同一个 Flask 服务端只是走不同的路由。拍照识别走 /predict 接口文字识别走 /ocr 接口分类指南走 /guide 接口。小程序端不需要关心模型细节只需封装统一的请求方法。4.2 拍照识别的小程序端实现export default { data() { return { label: , confidence: 0, errorMsg: }; }, methods: { chooseAndUpload() { uni.chooseImage({ count: 1, sourceType: [camera, album], // 允许拍照或从相册选择 success: (res) { this.upload(res.tempFilePaths[0]); } }); }, upload(filePath) { uni.uploadFile({ url: http://127.0.0.1:5000/predict, filePath: filePath, name: image, // 必须与后端 request.files[image] 对应 success: (res) { const data JSON.parse(res.data); if (data.error) { this.errorMsg data.error; return; } this.label data.label; this.confidence data.confidence; } }); } } }chooseImage的sourceType指定为[camera, album]用户可直接调用摄像头拍摄也可以从相册选历史照片。uploadFile的 name 参数必须和服务端 Flask 的request.files.get(image)对应传错名字服务端会拿到空文件。4.3 Flask 服务端推理接口from flask import Flask, request, jsonify from PIL import Image import numpy as np app Flask(__name__) model load_model(resnet50_garbage.h5) CLASS_NAMES [glass, metal, paper, plastic] app.route(/predict, methods[POST]) def predict(): f request.files.get(image) if f is None: return jsonify({error: no image}), 400 image Image.open(f.stream).convert(RGB).resize((224, 224)) # 与训练时保持一致先 resize 再归一化 arr np.expand_dims(np.array(image) / 255.0, axis0) prob model.predict(arr, verbose0)[0] label CLASS_NAMES[int(np.argmax(prob))] return jsonify({ label: label, confidence: float(np.max(prob)) }) if __name__ __main__: app.run(host0.0.0.0, port5000)image.convert(RGB)是为了处理 PNG 带 Alpha 通道的情况垃圾照片经常来自各种渠道直接丢给模型会报维度错误。resize((224, 224))和训练时target_size(224, 224)保持一致这一步不一致是部署阶段最隐蔽的坑常见现象是本地准确率 87%、接口准确率掉到 70%问题出在缩放算法上。4.4 文字识别与分类指南文字识别功能面向的是“包装袋上印着可回收物字样”的场景。服务端可以先用 OpenCV 做灰度化和边缘检测框出文字区域再用 Tesseract 提取文本把文本交给一个简单的关键词规则表去匹配。匹配结果只作为参考信息返回不会直接覆盖图像分类结论因为印刷内容和实物可能不一致。分类指南功能实现起来最简单本质上是一个前端可检索的静态数据表。按可回收物、厨余垃圾、有害垃圾、其他垃圾四个大类建索引每类下列举常见物品。小程序端通过 uni.request 调用 /guide 接口传入物品名关键字服务端在 SQLite 表里模糊匹配。物品大类常见混淆正确投放矿泉水瓶可回收物带标签易误判为纸张压扁后投放外卖餐盒可回收物残剩食物属厨余清洗后投放干电池有害垃圾无汞碱性电池可作其他按包装标识投放上表只是演示数据字典的字段结构真实系统里一张表放几百条常见物品即可覆盖绝大多数用户查询。这个模块的价值不只是查询更是在用户不确定时提供“为什么不这么分”的理由降低误投率。5. 模型收敛与识别准确率的验证技巧5.1 用混淆矩阵定位易混类别训练完成后不要只看总准确率画一张混淆矩阵能快速暴露类别分布问题。用 sklearn 的confusion_matrix加上ConfusionMatrixDisplay可视化四个大类之间的互相错分情况。实际测试中出现最多的错分对是玻璃→塑料其次是纸→塑料。观察这些错分对能确认 Jaccard 决策是否真的在起作用如果混淆矩阵里玻璃和塑料行仍然密集说明决策层的 top-k 集合需要调整。5.2 阈值与集合大小的配合调整top_k参数和置信度阈值必须联动调整不能单独调一个。top_k 从 3 调到 5 时预测集合里混入低概率类别的可能性变大Jaccard 分数整体抬高最终决策会更依赖候选样本集合而弱化 CNN 主输出。这时要把 cnn_threshold 适当下调到 0.85让更多低置信度样本进入 Jaccard 决策。验证集上做一个 3×3 的参数网格搜索对比不同组合的微平均 F1 分数比凭感觉设参更可靠。5.3 部署前的预处理一致性检查上线前最容易踩的坑就是服务端推理和训练时预处理不一致常见做法是写一个断言函数把一张已知类别的图片分别走训练 pipeline 和 Flask 接口对比两个预处理后的像素矩阵是否完全一致。这里要特别注意Image.open().resize()的插值方式如果训练时用 TensorFlow 的tf.image.resize默认双线性服务端用 PIL 的默认Image.LANCZOS最终像素差异会在三到五个像素级别对深层网络来说已经足够翻转分类结果。统一改为 PIL 的Image.BILINEAR就能消除这个偏移。最后一个实际建议把 ResNet-50 换成预训练模型加载冻结前面几层的 BN 参数只微调后面两个 stage 和分类头在这个数据规模下收敛速度和最终准确率通常比从头训练高一截。预训练模型权重只用于初始化自己的数据增广和 Jaccard 决策模块保持不动整套系统结构调整成本很低。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。