
简介面向嵌入式环保应用开发者与Python深度学习初学者这份资源围绕树莓派平台构建了一套完整的垃圾分类识别方案基于TensorFlow与Keras实现神经网络图像分类覆盖数据预处理、网络搭建、训练评估到模型部署等关键环节。压缩包共131个文件约112.45MB主体包含Python源程序.py/.pyc、垃圾分类数据集图片.png/.jpg/.jpeg、可直接使用的TFLite模型文件以及配套的Vue/JS前端展示代码和配置文件便于按模块快速定位并复现整个项目流程。目前已有2062人浏览学习适合希望从零入门嵌入式视觉识别或需要参考完整实战代码的开发者。资源中自带标注好的垃圾图片数据集、训练脚本和前端交互示例可帮助读者理解图像预处理、卷积神经网络结构、模型量化与树莓派部署思路在实际项目中快速迁移应用。1. 树莓派跑垃圾分类识别这套Pythontf代码能直接复现到什么程度树莓派的垃圾分类识别很多刚接触神经网络的人卡在同一个地方数据集不好找训练代码和部署代码对不上跑通了一个环节下一个环节又报错。这个包含garbage-raspi-master压缩包的项目把数据集、tf训练代码、网页预测页面predict.html一次性放到了一起目标是让你在树莓派上完成从图像输入到垃圾类别输出的完整链路。它适合手里有树莓派、想用Python和神经网络做图像分类的开发者也适合学校环保创新项目里需要快速做demo的场景。拿到手就能从数据预处理开始走通不用东拼西凑。2. 部署前的软硬件准备型号怎么选、tf版本怎么锁、系统怎么装2.1 树莓派型号怎么选4GB内存起步训练机和部署机分开项目代码的设计逻辑是PC上训练、树莓派上推理但不少人在第一步就容易翻车。拿到代码后不管三七二十一想在树莓派上直接运行训练脚本结果跑起来就卡死或者内存溢出。看模型输入尺寸就明白了常用的224×224分辨率一个批次32张图在内存里占用的空间远超树莓派2GB版本能轻松承受的范围更别提还有数据增强时的中间变量。选型号时按这个标准判断比较稳妥设备内存在本项目中的定位树莓派3B1GB不推荐推理都吃力树莓派4B 2GB2GB推理勉强可用训练直接放弃树莓派4B 4GB4GB推荐推理流畅能做简单微调树莓派5 4GB/8GB4GB/8GB算力更强但tflite库兼容性要额外验证树莓派4B 4GB是甜点位内存余量够日常使用。5代虽然性能翻倍但新系统镜像里TensorFlow相关依赖容易踩兼容坑为了跑这个不算大的分类模型没必要折腾一轮系统适配。另外要注意树莓派只是推理端训练还是建议在PC上完成把训练好的模型文件传到树莓派上做预测。2.2 系统镜像与Python环境64位系统、虚拟环境、依赖锁版本系统镜像没有太多选择余地直接装64位的Raspberry Pi OSPython 3.9或3.10都可以。但有个关键步骤不能省建虚拟环境。树莓派自带的系统Python被很多系统工具依赖直接pip install tensorflow会把系统环境搅乱。最常见的坑是装完TensorFlow后numpy版本被升级系统某个自带工具突然import失败最后只能重刷镜像。实操步骤# 1. 更新系统源索引并升级现有包 sudo apt update sudo apt upgrade -y # 2. 安装虚拟环境工具和Python开发头文件 sudo apt install -y python3-venv python3-dev # 3. 创建项目专用虚拟环境 python3 -m venv ~/garbage-env source ~/garbage-env/bin/activate # 4. 安装TensorFlow和Keras锁定版本 pip install tensorflow2.10.0 keras2.10.0 # 5. 安装图像处理与数据科学依赖 pip install numpy1.24.3 opencv-python-headless pillow matplotlib这段命令完成了三件事更新系统基础环境、创建隔离的Python环境、安装指定版本的深度学习依赖。锁定TensorFlow 2.10不是强迫症是树莓派64位系统上pip直接能装到的稳定版本线之后的版本对ARM平台的预编译包支持一直在变容易出现pip install成功但import时报so库加载错误。numpy锁1.24.3是因为新版numpy和TensorFlow 2.10的二进制接口不完全兼容。装完后验证一下环境是否可用python -c import tensorflow as tf; print(tf.__version__)能看到版本号说明环境正常。看不到就把检查点沿三个方向排查虚拟环境是否激活、装的是不是32位系统的armv7版本、Python版本是否匹配。PC端的训练环境同样建议锁版本常见做法是建独立虚拟环境安装和树莓派同一系列的TensorFlow两个设备用同一版本能省掉很多跨平台兼容性问题。3. 垃圾分类数据集处理目录结构、标签核对与数据增强参数3.1 数据集目录结构分析与标签映射关系解压garbage-raspi-master后先找数据集目录。常见结构是garbage_classification或者dataset里面按垃圾类别分子文件夹类别通常覆盖塑料plastic、玻璃glass、纸类paper、金属metal、其他other有的版本还会把厨余垃圾和有害垃圾单独分出来。类别数量直接决定模型最后一个全连接层的神经元个数动手前先数清楚。典型目录结构garbage_classification/ ├── train/ │ ├── plastic/ │ │ ├── img001.jpg │ │ ├── img002.jpg │ │ └── ... │ ├── glass/ │ │ └── ... │ ├── paper/ │ ├── metal/ │ └── other/ └── test/ ├── plastic/ └── ...这里train和test是Keras的flow_from_directory标准输入格式它会自动按文件夹名生成标签不需要手动写csv。但有个隐蔽的坑标签顺序是按字母排序的不是按你心里想的顺序。比如文件夹名是glass, metal, other, paper, plastic那生成的标签编号就是glass0, metal1, other2, paper3, plastic4。训练前必须把这个映射关系打出来核对否则预测阶段类别和中文名对不上玻璃识别成塑料这种低级错误就是这么来的。from tensorflow.keras.preprocessing.image import ImageDataGenerator # 实例化生成器先只做归一化不急着加增强 datagen ImageDataGenerator(rescale1./255) # 加载训练集自动按文件夹生成标签 train_gen datagen.flow_from_directory( garbage_classification/train, target_size(224, 224), batch_size32, class_modecategorical ) # 打印标签映射核对文件夹名和编号的对应关系 print(train_gen.class_indices)class_modecategorical表示做多分类标签转换为one-hot编码格式。class_indices属性返回字典例如{glass: 0, metal: 1, other: 2, paper: 3, plastic: 4}。每次改动数据目录后都要重新执行这段代码确认映射关系这是整个流程里成本最低的预防措施。3.2 数据划分与增强参数小样本场景下的增广策略这类垃圾分类数据集每类图片少则几十张多则两三百张属于典型的小样本图像分类。样本量不充足的类别不加数据增强几乎必然过拟合。具体表现是训练准确率快速升到95%以上但验证准确率卡在70%左右上下抖动这是模型把训练集的背景、光照等噪声特征背下来了没有学到可泛化的类别特征。数据增强的力度按每个类别的图片数量调整增强参数默认值小样本建议值作用说明rotation_range2040模拟垃圾任意摆放的角度width_shift_range0.20.3模拟目标不在画面中心height_shift_range0.20.3同上zoom_range0.20.3模拟不同拍摄距离horizontal_flipTrueTrue镜像翻转增加样本多样性brightness_rangeNone[0.8, 1.2]模拟不同光照条件这些参数只加在训练集生成器上验证集和测试集只能用rescale归一化不能做任何随机增强。验证集如果也被随机旋转缩放过评估结果会虚高上线后真实场景的推理效果对不上测试数据。数据划分建议按7:2:1切训练、验证、测试三个部分。flow_from_directory没有内置划分功能常见做法是用split_folders库一键划分# 一键按比例划分数据集固定随机种子保证可复现 import split_folders split_folders.ratio( inputgarbage_classification_raw, # 原始数据目录 outputgarbage_classification, # 划分后的目录 seed42, # 固定种子重复执行结果一致 ratio(0.7, 0.2, 0.1) # 训练:验证:测试 )split_folders是第三方库pip install split-folders后即可使用。seed参数很关键确定之后每次划分的图片分配结果都一样后续对比不同网络结构的实验时数据分布不变比较才有意义。有的场景下数据集里类别数量很不均衡比如其他类有500张图片而金属类只有80张。项目代码如果直接拿原始数据训练模型会对样本量大的类别产生倾向性。处理方式一般是先统计每个类别的图片数量差距超过3倍时用类别加权法或者过采样/欠采样。在Keras中可以用class_weight参数直接指定每个类别的权重效果等同于让模型更加重视小样本类别。4. CNN模型构建与训练卷积层/池化层/全连接层的参数实测4.1 轻量卷积网络结构设计从输入层到softmax的完整配置项目使用的神经网络结构是典型的轻量CNN没有套预训练大模型原因是预训练模型动辄上百MB树莓派4B的内存和算力都吃紧。自己从头构建一个3组卷积池化的网络参数量在几百万级别模型文件大小通常不超过20MB在树莓派上跑推理帧率能保持在可用范围。import tensorflow as tf from tensorflow.keras import layers, models def build_garbage_cnn(num_classes, img_size224): 轻量CNN3组卷积池化 Dropout 全连接分类 model models.Sequential([ # 第一组提取边缘、颜色等低级特征 layers.Conv2D(32, (3, 3), activationrelu, input_shape(img_size, img_size, 3)), layers.MaxPooling2D(pool_size(2, 2)), # 第二组提取纹理、局部形状等中级特征 layers.Conv2D(64, (3, 3), activationrelu), layers.MaxPooling2D(pool_size(2, 2)), # 第三组组合前层特征形成类别相关的抽象表示 layers.Conv2D(128, (3, 3), activationrelu), layers.MaxPooling2D(pool_size(2, 2)), # 展平特征图随机丢弃神经元防止过拟合 layers.Flatten(), layers.Dropout(0.5), layers.Dense(128, activationrelu), # 输出层softmax把得分转成各类别概率 layers.Dense(num_classes, activationsoftmax) ]) return model结构上有几个值得注意的细节。Conv2D用3×3小卷积核两层3×3叠加等效于5×5的感受野但参数量更少计算效率更高。每个卷积层后面接MaxPooling2D池化把特征图尺寸减半同时保留最显著的特征响应让模型对目标位置有一定容忍度。Dropout放在全连接层之前训练时以50%概率随机屏蔽神经元迫使模型学习冗余特征这是对抗小数据集过拟合最经济的手段。最后两层Dense分别把特征映射到128维和类别数量维度softmax保证所有类别的输出概率总和为1。也可以把Conv2D的padding参数从valid改成same让卷积输出尺寸不缩水但这会增加计算量。如果类别间的外观差异比如塑料瓶和玻璃瓶足够明显用valid模式让特征图逐步收缩即可网络能学到判别信息。4.2 训练配置优化器、损失函数、早停与模型保存策略模型搭好之后进入训练阶段。这个阶段的坑往往不在结构上而在超参数和回调设置上。learning_rate直接套用默认的0.001是可以的Adam优化器对这种规模的数据集收敛稳定。但有一个细节值得注意训练轮数不要盲目设大配合早停回调让训练在验证损失停止下降时自动终止既省时间又防过拟合。# 构建模型并打印各层参数量 model build_garbage_cnn(num_classeslen(train_gen.class_indices)) model.summary() # 编译Adam优化器 多分类交叉熵损失 model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), losscategorical_crossentropy, metrics[accuracy] ) # 早停与模型保存回调 callbacks [ tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), tf.keras.callbacks.ModelCheckpoint( garbage_best.h5, monitorval_accuracy, save_best_onlyTrue, verbose1 ) ] # 执行训练 history model.fit( train_gen, steps_per_epochtrain_gen.samples // 32, epochs50, validation_dataval_gen, validation_stepsval_gen.samples // 32, callbackscallbacks )交叉熵损失categorical_crossentropy是多分类任务的标准选择配合softmax输出层梯度回传信号稳定。EarlyStopping的patience5表示连续5轮验证损失不下降就停止训练restore_best_weightsTrue让模型回滚到验证指标最好的那一轮权重。ModelCheckpoint的save_best_onlyTrue保证磁盘上只保留验证准确率最高的模型版本避免最后一轮训练跑偏把好权重覆盖掉。训练过程中重点盯两个值val_loss的走向和训练准确率与验证准确率的差距。如果两者差距超过15个百分点说明过拟合已经比较严重回到数据增强章节把增广力度调大。如果val_loss连续10轮都没动静可以尝试把learning_rate降到0.0003让模型在小梯度下做更精细的收敛。4.3 训练效果评估混淆矩阵比准确率更能说明问题项目摘要里特别提醒了准确率和混淆矩阵是用来评估模型性能的指标。二分类看准确率还有一定参考价值多分类场景下准确率容易被样本量大的类别拉高。比如其他类占了40%的测试样本模型把所有图片都判成其他也能有40%准确率但这个模型没有实用价值。评估时建议打混淆矩阵import numpy as np from sklearn.metrics import confusion_matrix, classification_report # 收集真实标签和预测标签 y_true val_gen.classes y_pred [] for i in range(len(val_gen)): batch_img, batch_label val_gen[i] preds model.predict(batch_img, verbose0) y_pred.extend(np.argmax(preds, axis1)) if len(y_pred) len(y_true): break # 打印每个类别的精确率、召回率、F1分数 print(classification_report(y_true, y_pred, target_nameslist(train_gen.class_indices.keys()))) # 打印混淆矩阵看哪些类别互相混淆 print(confusion_matrix(y_true, y_pred))classification_report给出每类的精确率、召回率和F1值能定位到具体哪些类别学得不好。混淆矩阵可以看到实际的错误模式——比如玻璃杯反光被误判成塑料瓶或者金属罐头和纸盒的纹理混淆。根据这些信息有针对性地补充对应类别的训练图片或者调整数据增强参数比盲目增加网络层数有效得多。5. 树莓派端部署避坑tflite转换、内存不足与推理异常排查5.1 训练模型转tflite量化方式决定体积与精度的平衡PC上训练好的h5模型不能直接搬到树莓派上用。规模小的模型虽然可以加载但推理速度慢内存占用高。TensorFlow Lite是专门为端侧推理设计的格式体积小、启动快是树莓派部署的主流选择。import tensorflow as tf # 加载训练好的模型 model tf.keras.models.load_model(garbage_best.h5) # 动态范围量化转换 converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] tflite_model converter.convert() # 保存为tflite文件 with open(garbage_model.tflite, wb) as f: f.write(tflite_model)DEFAULT优化会把部分权重从32位浮点压缩到8位整数模型体积通常能缩小到原来的1/3左右。但这里有一个隐蔽的坑动态范围量化是训练后量化不需要校准数据压缩效果有限精度也会有一点损失。如果想让体积更小就得用int8全整数量化但需要传入代表性数据集进行校准import numpy as np def representative_dataset(): 从训练集生成器取100批次图片作为量化校准数据 for i in range(100): img, _ train_gen[i % len(train_gen)] yield [img.astype(np.float32)] converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_dataset converter.target_spec.supported_ops [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type tf.int8 converter.inference_output_type tf.int8 tflite_model converter.convert()加了代表性数据集校准后int8量化模型体积能压缩到原模型的1/4但精度平均要掉13个百分点。如果垃圾类别之间外观差异足够大比如塑料瓶和玻璃瓶轮廓明显不同这点精度损失可以接受。如果类别长得像比如不同颜色的纸盒分类建议用float16量化或者干脆不量化。先试DEFAULT测试集准确率下降在2个百分点以内就用它否则退回float16或者原模型。5.2 树莓派推理脚本tflite解释器调用与置信度输出模型转换完成后把tflite文件传到树莓派上接下来的推理脚本是所有部署工作的核心。这里用tf.lite.Interpreter接口加载模型避免在树莓派上重新导入Keras模型。import tensorflow as tf import numpy as np from PIL import Image # 加载tflite模型并分配张量内存 interpreter tf.lite.Interpreter(model_pathgarbage_model.tflite) interpreter.allocate_tensors() # 获取输入输出张量信息 input_details interpreter.get_input_details() output_details interpreter.get_output_details() def preprocess_image(image_path, size(224, 224)): 加载图片并转换为模型输入格式 img Image.open(image_path).convert(RGB) img img.resize(size) # 归一化到[0,1]增加batch维度 img_array np.array(img, dtypenp.float32) / 255.0 img_array np.expand_dims(img_array, axis0) return img_array def predict_image(image_path): 推理单张图片返回类别索引和置信度 img_array preprocess_image(image_path) interpreter.set_tensor(input_details[0][index], img_array) interpreter.invoke() output interpreter.get_tensor(output_details[0][index]) class_idx np.argmax(output[0]) confidence output[0][class_idx] return class_idx, confidencepreprocess_image做的三件事值得逐条说明convert(RGB)把图片统一成三通道避免遇到带透明通道的PNG或者灰度图时报维度错误resize((224, 224))把图像压缩到模型输入尺寸归一化除以255是把像素值从0255缩放到01必须和训练时的rescale参数保持一致。推理时interpreter.set_tensor把预处理后的数据写入输入张量invoke执行一次前向计算get_tensor取出输出张量argmax取概率最大的类别作为预测结果。5.3 常见问题排查现象、原因、解决部署过程中有几个问题出现频率很高每条都值得记下来问题一推理时程序直接退出提示Killed或者内存不足现象tflite模型加载成功但推理时进程被杀终端只显示Killed。原因树莓派内存不足触发OOM Killer机制。解决优先确认swap是否开启把swap文件扩大至2GB如果用的是2GB内存版本启动推理脚本前关闭桌面环境释放内存或者换用int8量化后的模型减小内存占用。问题二推理速度明显偏慢一帧画面要好几秒现象单张224×224图片推理耗时超过3秒。原因模型参数量太大或者树莓派没有启用GPU加速。解决树莓派推理主要靠CPU检查模型是否已做量化压缩再用numpy优化预处理逻辑避免在Python循环里逐像素操作。这个项目的轻量CNN配合量化和合理的预处理单张推理控制在1秒以内是可以接受的。问题三部署后准确率比训练时明显下降现象训练时测试集准确率92%树莓派上推理同一批图片只有80%。原因量化精度损失或者图片预处理流程和训练时不一致。解决先跑一遍5.2的predict_image用训练时的验证图片逐张对比看预处理是否完全一致。确认无误再用float16量化替换int8量化重新测试优先保住精度。问题四tflite模型加载报错File not found现象Interpreter(model_pathgarbage_model.tflite)抛异常提示找不到文件。原因模型文件路径不对。解决树莓派上运行脚本时工作目录和模型文件的相对路径很容易踩坑。建议用绝对路径把tflite文件放在脚本同目录先跑ls确认文件名大小写再用os.path.abspath打印实际加载路径。问题五摄像头实时识别画面卡顿画面和识别结果不同步现象用摄像头连续抓帧做实时分类视频预览明显掉帧。原因摄像头采集、预处理、推理三个环节在同一个Python循环里串行执行单帧总耗时高于帧间隔。解决把推理放到单独的线程主线程只负责采集和显示或者降低摄像头分辨率到480p减少预处理耗时的同时保证分类效果。6. 让推理结果更可信置信度阈值与拒识设计模型训练完、tflite部署好、树莓派能跑通识别这只能算demo完成。实际用起来会发现一个之前没预料到的问题模型对训练集之外的物体也很有自信。测试阶段用验证集图片时每张图都来自已知类别输出概率自然比较高。但真实场景里摄像头扫到的可能是随意一个物体、桌面上的笔、盆栽的叶子甚至是一个空空的背景——模型没见过这些东西却依然会硬给一个类别并给出不低的置信度。这是分类模型的黑匣子效应在作怪。背后的原因是softmax输出的概率分布是数学上必然求和为1的无论如何模型都会挑一个得分最高的类别作为答案哪怕这个类别只是相对其他类别稍微没那么差。如果直接把这个结果用于垃圾分类执行机构就会出现塑料瓶误判成金属后执行机构把塑料投进了金属回收仓的尴尬操作。解决这个问题的标准做法是给推理结果加一道置信度门槛低于阈值的输出归为未识别# 类别名列表顺序和训练时class_indices一致 CLASS_NAMES [glass, metal, other, paper, plastic] def classify_with_threshold(image_path, threshold0.7): 分类并应用置信度阈值低于阈值返回未识别 class_idx, confidence predict_image(image_path) if confidence threshold: return -1, confidence # -1表示未识别 return class_idx, confidence # 用法示例 result, conf classify_with_threshold(test_photo.jpg, threshold0.7) if result -1: print(f未识别置信度仅{conf:.2f}) else: print(f识别为{CLASS_NAMES[result]}置信度{conf:.2f})threshold这个参数不要照搬先用验证集跑一遍所有预测结果的置信度分布画个直方图看哪些样本被分给了错误类别但置信度依然很高。我实际操作中发现0.7能过滤掉大部分误判但有两个类别相似度特别高正确样本的置信度本身就在0.65左右徘徊这时阈值调到0.7会把不少正确样本误杀。最终在0.6到0.75之间用一个网格搜索选出最适合当前数据集的阈值判定标准是未识别率和错误识别率两个指标同时尽量低。还有一点容易遗漏阈值设好之后要在树上提前挂一张完全不相关的图片比如纯色纸片走一遍完整流程确认它确实被拒识。这一步能验证预处理、推理、阈值的整个链路是否真的对未知输入有效而不是某个环节漏了什么bug。关于这个阈值设计有一个印象很深的教训。某次项目交付前一整套流程在验证集上表现很好模型直接拿去做演示。现场一个同学用手机拍了一张照片画面里出现的是垃圾桶旁边的文字标签而不是垃圾本体模型硬生生把贴有文字标签的白色塑料桶识别成了纸类置信度高达0.86系统把误差投进纸类回收仓。原因就是那个白色桶的外观和训练集中的白色纸盒太接近模型被背景颜色和桶身形状误导了。从那以后我每次部署完都会强制走一遍未知样本拒识测试从几十张完全不相关的图片里随机抽5张确认全部被拒识后才算通过。这个习惯救了我好几次希望帮到你。本文还有配套的精品资源点击获取