ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

智能垃圾分类系统实战:MobileNetV3端侧识别与树莓派部署

智能垃圾分类系统实战:MobileNetV3端侧识别与树莓派部署 简介这是一份面向计算机、人工智能方向学生与开发者的智能垃圾分类系统项目资料可作为毕业设计或课程作业的完整参考。项目围绕计算机视觉、机器学习与大数据分析展开涵盖图像预处理、特征提取、CNN分类模型训练、数据集构建与标注、超参数调优及模型融合等关键环节并延伸至垃圾分类大数据平台的趋势预测与资源分配思路。压缩包共20个文件约35.12MB以7个Python源码为主另含6个数据集压缩包、3个UI界面文件、1个C测试文件及图片、说明文档等目录结构清晰便于按模块查阅。目前已有121人学习。读者可从中获取系统设计文档、算法实现细节、训练与测试数据、界面代码及测试报告等资料适合通过复现项目巩固计算机视觉与深度学习知识提升编程与工程实践能力。1. 智能垃圾分类系统从一份课程作业到能跑通的识别流水线很多同学拿到“智能垃圾分类系统”这个题目时第一反应是找个开源模型套个界面就交差结果答辩时被问“为什么不用轻量网络”“摄像头帧率掉到 5 帧怎么办”就卡住了。这个标题背后其实是一条完整的端侧识别链路图像采集、预处理、分类模型推理、结果映射到四类垃圾桶、再通过界面或串口输出。它适合课程作业和毕设场景因为硬件门槛低——一台带摄像头的笔记本或一块树莓派就能跑通全流程同时又能延伸出模型量化、数据增强、误判分析等加分项。我见过太多人把精力花在 UI 动画上却让核心分类器在真实光照下翻车这篇就把我踩过的坑和能直接抄的代码一次讲清。2. 先定技术路线为什么选 MobileNetV3 而不是 ResNet502.1 课程作业的算力边界决定模型选型大部分毕设环境是笔记本 CPU 或树莓派 4B没有独立显卡。ResNet50 参数量 25M 左右单张推理在树莓派上要 1.2 秒以上加上摄像头读取和界面刷新帧率直接掉到个位数。MobileNetV3-Small 参数量约 2.5M输入 224×224 时树莓派 4B 单张推理能压到 80 毫秒以内配合多线程可以做到 810 帧的准实时效果。这不是说 ResNet 不好而是课程作业的验收场景通常只看演示流畅度和分类准确率没必要为了“看起来高级”牺牲可运行性。选型时还要看数据集规模。垃圾分类公开数据集常见的有 TrashNet约 2500 张和华为云垃圾分类数据集约 1.5 万张。TrashNet 类别少、背景干净MobileNetV3 微调后验证集准确率能到 92% 以上如果自己拍的数据背景杂乱建议先用 MobileNetV3-Large 试准确率不够再考虑换 backbone而不是一上来就堆大模型。2.2 四分类映射与数据集目录规范垃圾分类系统最终要输出“可回收物、厨余垃圾、有害垃圾、其他垃圾”四类。但公开数据集往往按纸板、玻璃、金属、塑料等细类标注需要做一层映射。我一般会在项目根目录建dataset/文件夹按最终四类建子目录再用脚本把细类图片复制或软链接过去。这样训练时ImageFolder直接读不用改 DataLoader。import os import shutil # 细类到四分类的映射表按自己数据集调整 CATEGORY_MAP { cardboard: recyclable, glass: recyclable, metal: recyclable, paper: recyclable, plastic: recyclable, trash: other, battery: hazardous, food: kitchen, } SRC_ROOT raw_dataset DST_ROOT dataset for split in [train, val]: src_split os.path.join(SRC_ROOT, split) for fine_cls in os.listdir(src_split): fine_path os.path.join(src_split, fine_cls) if not os.path.isdir(fine_path): continue coarse_cls CATEGORY_MAP.get(fine_cls.lower()) if coarse_cls is None: print(f跳过未映射类别: {fine_cls}) continue dst_dir os.path.join(DST_ROOT, split, coarse_cls) os.makedirs(dst_dir, exist_okTrue) for img_name in os.listdir(fine_path): shutil.copy( os.path.join(fine_path, img_name), os.path.join(dst_dir, img_name) ) print(映射完成检查 dataset/train 下四个子目录是否都有图片)这段脚本的关键是CATEGORY_MAP你需要根据自己下载的数据集类别名逐条核对。常见坑是类别名大小写不一致比如Plastic和plastic同时出现所以统一用.lower()处理。复制完成后务必人工抽查每个子目录避免某个类别只有几张图导致训练时类别不平衡。2.3 训练脚本与三个必调参数训练用 PyTorch 的 torchvision 即可不需要额外框架。下面是最小可运行训练脚本重点看BATCH_SIZE、LR、EPOCHS三个参数。import torch import torch.nn as nn from torchvision import datasets, transforms, models from torch.utils.data import DataLoader # 数据增强训练集用随机翻转和裁剪验证集只做缩放 train_tf transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), 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(256), transforms.CenterCrop(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) # BATCH_SIZE树莓派训练建议 16笔记本 GPU 可以 32 train_loader DataLoader(train_ds, batch_size16, shuffleTrue, num_workers2) val_loader DataLoader(val_ds, batch_size16, shuffleFalse, num_workers2) # 加载预训练 MobileNetV3-Small替换最后一层为四分类 model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.DEFAULT) model.classifier[3] nn.Linear(model.classifier[3].in_features, 4) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # LR微调场景 1e-3 起步太大容易震荡太小收敛慢 criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) # EPOCHSTrashNet 级别 1520 轮足够自建大数据集可到 30 for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() # 每轮验证记录准确率 model.eval() correct total 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels imgs.to(device), labels.to(device) pred model(imgs).argmax(1) correct (pred labels).sum().item() total labels.size(0) print(fEpoch {epoch1}, val_acc{correct/total:.4f}) torch.save(model.state_dict(), garbage_mobilenetv3.pth)BATCH_SIZE设 16 是因为树莓派内存有限太大容易 OOM笔记本有 GPU 可以翻倍。LR用 1e-3 是微调的常见起点如果 loss 在前几轮就飙到 NaN降到 1e-4。EPOCHS不是越多越好验证准确率连续 3 轮不升就可以停否则过拟合。训练完保存的.pth文件后面部署直接加载。3. 推理部署把模型塞进摄像头循环里3.1 OpenCV 读取摄像头与预处理对齐训练时的预处理必须和推理时完全一致否则准确率会莫名其妙掉十几个点。常见错误是训练用了RandomResizedCrop推理时直接Resize(224)尺度不一致导致模型“不认识”输入。推理阶段应该用和验证集一样的Resize(256) CenterCrop(224)。import cv2 import torch from torchvision import transforms, models import torch.nn as nn # 推理预处理必须和训练时验证集一致 infer_tf transforms.Compose([ transforms.ToPILImage(), transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) CLASS_NAMES [recyclable, kitchen, hazardous, other] 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() cap cv2.VideoCapture(0) # 0 表示默认摄像头 if not cap.isOpened(): raise RuntimeError(摄像头打开失败检查是否被其他程序占用) while True: ret, frame cap.read() if not ret: break # BGR 转 RGB因为训练用的是 RGB rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) tensor infer_tf(rgb).unsqueeze(0) with torch.no_grad(): logits model(tensor) prob torch.softmax(logits, dim1) conf, pred prob.max(1) label CLASS_NAMES[pred.item()] text f{label} {conf.item():.2f} cv2.putText(frame, text, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Garbage Classification, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里cv2.cvtColor那一步最容易被忽略。OpenCV 默认 BGR而 torchvision 预训练模型期望 RGB不转的话颜色通道错位准确率直接崩。另外map_locationcpu是为了在没有 GPU 的机器上也能加载。如果要做串口输出到 Arduino 控制舵机开盖把label通过serial.write发出去即可波特率一般 9600 或 115200。3.2 帧率优化多线程与跳帧策略单线程读摄像头加推理在树莓派上通常只有 35 帧演示时肉眼可见卡顿。我一般用两个办法一是把推理放到单独线程主线程只负责显示二是跳帧每 3 帧做一次推理中间帧沿用上次结果。这样显示帧率能回到 15 帧以上观感流畅很多。import threading import queue # 推理线程从队列取帧结果放回另一个队列 def infer_worker(in_q, out_q, model, tf): while True: frame in_q.get() if frame is None: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) tensor tf(rgb).unsqueeze(0) with torch.no_grad(): prob torch.softmax(model(tensor), dim1) conf, pred prob.max(1) out_q.put((pred.item(), conf.item())) in_q queue.Queue(maxsize2) out_q queue.Queue(maxsize2) worker threading.Thread(targetinfer_worker, args(in_q, out_q, model, infer_tf), daemonTrue) worker.start() frame_count 0 last_label, last_conf 0, 0.0 while True: ret, frame cap.read() if not ret: break frame_count 1 # 每 3 帧送一次推理队列满就跳过避免堆积 if frame_count % 3 0 and not in_q.full(): in_q.put(frame.copy()) if not out_q.empty(): last_label, last_conf out_q.get() text f{CLASS_NAMES[last_label]} {last_conf:.2f} cv2.putText(frame, text, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(Garbage Classification, frame) if cv2.waitKey(1) 0xFF ord(q): break队列设maxsize2是关键防止推理慢时帧堆积导致延迟越来越大。跳帧策略下如果物体移动快标签会滞后所以演示时建议把物体在摄像头前停 1 秒再移开。3.3 模型量化让树莓派推理再快一倍如果树莓派上还是觉得慢可以做动态量化把权重从 float32 转成 int8模型体积缩小约 4 倍推理速度提升 30%50%准确率通常只掉 12 个点。import torch.quantization # 动态量化只需在保存的模型上操作 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() quantized_model torch.quantization.quantize_dynamic( model, {nn.Linear}, dtypetorch.qint8 ) torch.save(quantized_model.state_dict(), garbage_quantized.pth) print(量化完成加载时同样用 quantize_dynamic 包装后再 load_state_dict)注意量化后的模型加载时也要先做同样的quantize_dynamic包装不能直接load_state_dict到原始模型。另外卷积层动态量化支持有限主要加速来自全连接层MobileNetV3 的全连接层占比不大所以提升没有想象中夸张但体积缩小对存储紧张的树莓派很实用。4. 避坑与排查那些答辩前夜才发现的翻车点4.1 现象训练准确率 99%摄像头前完全乱判原因通常是训练集和推理输入分布不一致。检查三处训练是否用了ColorJitter而推理没有对应归一化摄像头是否自动白平衡导致色温偏移数据集里同一类图片背景过于单一模型学到了背景而不是物体。解决办法是采集一批摄像头实拍图加入训练集或者在推理时固定摄像头曝光和白平衡。4.2 现象树莓派上运行报 “Illegal instruction”原因多是 PyTorch 版本与树莓派 CPU 指令集不匹配常见于在 x86 上 pip 下载的 wheel 直接拷到 ARM 板子。解决是在树莓派上重新安装对应架构的 PyTorch或者用 ONNX Runtime 替代先导出 ONNX 再在树莓派上推理。4.3 现象摄像头打开成功但画面全黑原因可能是摄像头排线接触不良或者cv2.VideoCapture(0)的索引不对。树莓派上有时 CSI 摄像头对应索引是 0USB 摄像头是 1 或 2。解决是遍历索引 03 试或者用v4l2-ctl --list-devices查看设备节点。4.4 现象验证集准确率震荡一会 80% 一会 95%原因通常是BATCH_SIZE太小导致 BatchNorm 统计量不稳定或者学习率太大。解决是把BATCH_SIZE提到 32内存允许的话学习率降到 1e-4并加torch.optim.lr_scheduler.StepLR每 5 轮衰减一次。4.5 现象量化后模型加载报错 “Missing key(s)”原因是保存的是量化模型的state_dict但加载时没有先构建量化结构。解决是加载前先执行quantize_dynamic得到空壳再load_state_dict。如果还是报错直接保存整个模型torch.save(quantized_model, ...)而不是只存 state_dict。5. 进阶技巧用混淆矩阵定位“最容易被误判的那一类”课程作业答辩时老师最爱问“哪两类最容易混”。与其临场编不如训练完直接画混淆矩阵。下面这段代码在验证集上跑一遍输出 4×4 矩阵能清楚看到比如“厨余”被大量判成“其他”。import numpy as np from sklearn.metrics import confusion_matrix, classification_report model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in val_loader: preds model(imgs).argmax(1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵行真实列预测:) print(cm) print(classification_report(all_labels, all_preds, target_namesCLASS_NAMES))拿到矩阵后如果发现“厨余→其他”误判多就专门补这类难样本去食堂拍一批剩饭剩菜或者用数据增强模拟汤汁反光。我一般会针对混淆最严重的两类做 200300 张补充采集再微调 5 轮准确率通常能拉回 35 个点。另一个技巧是把 softmax 置信度低于 0.6 的样本单独存下来人工复核后加入训练集这比盲目加数据有效得多。最后说个血泪经验答辩前一定要用真实摄像头在答辩现场的光照下跑一遍别只在实验室白炽灯下测。我见过太多模型在实验室 95%到教室投影灯下直接掉到 60%。提前准备一个“演示模式”把置信度阈值调低宁可多显示“其他”也别频繁跳错类。希望帮到你。本文还有配套的精品资源点击获取
返回列表