树莓派垃圾分类识别:TensorFlow神经网络端到端实战 简介面向树莓派与嵌入式AI实践者的一份完整垃圾分类识别项目基于Python与TensorFlow/Keras搭建卷积神经网络覆盖数据预处理、模型训练、评估与部署全流程。资源共131个文件压缩包约112.45MB核心包含py源码与ipynb示例、训练好的tflite模型、垃圾分类数据集图片png/jpg、前端交互页面js/vue/html及配套说明文档md/json便于按模块对照学习。已有2062人学习适合想要从零实现图像分类并落地到树莓派的开发者。项目中不仅给出神经网络各层设计与优化技巧还包含模型量化剪枝等边缘端部署思路可借此掌握从数据集标注到实时识别的完整工程路径。1. 树莓派上跑垃圾分类识别一块小板子上的神经网络实战把一袋垃圾照片丢给一块巴掌大的树莓派一两秒内返回「可回收 / 厨余 / 有害 / 其他」的识别结果——这就是这个树莓派垃圾分类识别项目在做的事。它不是把图片传到云端而是用 TensorFlow 搭出来的神经网络在嵌入式设备本地完成图像识别和分类从数据准备、模型训练到模型保存的完整链路都收在压缩包里。对刚开始折腾「树莓派 Python 垃圾识别」的人来说这是一份少见的端到端样例对已经会跑分类模型的开发者它又是一个能直接换数据集、改网络结构复用的基础工程。2. 项目架构与技术选型从摄像头图像到分类结果的完整链路2.1 为什么是树莓派 Python TensorFlow树莓派本身就是一个 Linux 单板计算机功耗低、体积小可以放在垃圾投放点或者社区场景里 7×24 小时跑。相比在 PC 上做演示树莓派方案的真正价值在于验证「神经网络能不能在有限算力下做出实时推理」这是从玩具走向产品必须跨过的一步。Python 是这个场景下的首选原因很直白数据预处理、模型训练、模型转换、串口或摄像头驱动全都有现成库不需要像 C 那样自己拼装整个推理管线。TensorFlow 在这里承担两层工作训练阶段用它的 Keras 高层 API 快速搭建和训练网络部署阶段再把训练好的模型转成轻量格式跑在树莓派上。项目摘要里专门提到的 Keras 就是运行在 TensorFlow 之上的高级接口写网络结构时只需要关注「有哪些层、按什么顺序堆叠」底层的反向传播和张量运算由框架处理。2.2 目录结构与数据流这套工程从压缩包解压出来之后目录里既有 Python 端的 TensorFlow 代码也保留了交互页面相关的文件。其中.babelrc、index.ejs、icon.icns这类文件看起来像是一个桌面外壳工程Electron 系的打包模板而predict.html是预测结果展示页。实际数据流是这样的图片从文件或者摄像头进来交给 Python 推理脚本加载模型做预测最后把分类结果和置信度传给predict.html展示出来。整个链路如下图片文件/摄像头帧 --(预处理)-- 模型推理脚本 --(加载 SavedModel)-- 分类结果置信度 --(HTTP/本地)-- predict.html 展示我把关键文件整理成了一张表解压后对号入座即可文件角色说明predict.html预测前端页面展示识别结果与置信度index.ejs桌面外壳的页面模板用于加载预测页.babelrcJavaScript 转译配置属于前端工程部分.gitignore/.eslintignore版本控制与代码检查忽略规则icon.icnsMac 平台的应用图标文件数据集与 tf 代码分类图片目录 模型构建、训练脚本2.3 数据集的摆放与标签设计垃圾分类数据集是这个项目里最容易被低估的部分。模型能不能收敛、准确率能不能上去一半取决于网络结构另一半取决于数据怎么组织。这里的常见做法是按类别建目录每一类垃圾图片放在一个文件夹里文件夹名就是标签。比如按国内通用的四分法来分data/ ├── train/ │ ├── 可回收/ │ ├── 厨余/ │ ├── 有害/ │ └── 其他/ └── test/ ├── 可回收/ ├── 厨余/ ├── 有害/ └── 其他/目录结构决定了标签的生成方式。用 TensorFlow 的数据加载接口时它默认扫描子目录名并自动映射成整数标签不需要手写数据标注文件。这里有一个容易忽略的坑标签的顺序是按目录名的字典序排序的也就是说「可回收」可能索引是 1 而不是 0。后面算混淆矩阵的时候一定要从class_names属性里取真实的类别顺序不能拍脑袋写死。3. 核心模块拆解数据读取、网络搭建与训练配置3.1 数据读取一行代码把图片目录变成训练流在 TensorFlow 2.x 里最省事的图片数据读取方式是image_dataset_from_directory。它会把目录结构扫描一遍自动处理标签、乱序、批大小和图像尺寸缩放不需要自己写循环去cv2.imread。这个项目里一定绕不开这一步代码如下import tensorflow as tf # 从目录读取训练图像按子目录名自动生成标签 train_ds tf.keras.preprocessing.image_dataset_from_directory( data/train, # 训练集根目录子目录名为类别名 validation_split0.2, # 从训练集里切 20% 做验证 subsettraining, # 当前取的是训练子集 seed123, # 随机种子保证切分可复现 image_size(150, 150), # 统一缩放到 150x150 batch_size32, # 每个 batch 32 张图 label_modeint # 标签为整数索引配合 SparseCategoricalCrossentropy ) val_ds tf.keras.preprocessing.image_dataset_from_directory( data/train, validation_split0.2, subsetvalidation, # 当前取的是验证子集 seed123, image_size(150, 150), batch_size32, label_modeint )这段代码的关键逻辑是validation_split和subset的配对使用第一次调用取训练子集第二次取验证子集两次的seed必须一致否则切分结果对不上验证集会混进训练过的图片评估结果直接失真。image_size这里我设成 150×150这是树莓派场景下常见的折中——太小丢特征太大内存吃紧。如果在纯 PC 上训练可以放宽到 224×224精度会好一些。3.2 网络结构卷积层、池化层、Dropout 怎么堆项目摘要里提到的卷积层、池化层、全连接层和激活函数在这个工程里组合成了一个典型的轻量分类网络。模型主体用 Keras 的Sequential顺序模型来搭按「卷积提特征 → 池化降维 → 全连接分类」的套路写model tf.keras.Sequential([ # 像素值归一化到 0~1避免数值过大导致训练不稳定 tf.keras.layers.Rescaling(1./255, input_shape(150, 150, 3)), # 第一组卷积32 个 3x3 卷积核提取边缘、纹理等低级特征 tf.keras.layers.Conv2D(32, (3, 3), activationrelu, paddingsame), tf.keras.layers.MaxPooling2D(2, 2), # 2x2 池化空间尺寸减半 # 第二组卷积64 个卷积核提取组合特征 tf.keras.layers.Conv2D(64, (3, 3), activationrelu, paddingsame), tf.keras.layers.MaxPooling2D(2, 2), # 第三组卷积128 个卷积核特征更抽象 tf.keras.layers.Conv2D(128, (3, 3), activationrelu, paddingsame), tf.keras.layers.MaxPooling2D(2, 2), # 把二维特征图拉平成向量交给全连接层 tf.keras.layers.Flatten(), tf.keras.layers.Dense(128, activationrelu), # 批量归一化 Dropout稳定训练、抑制过拟合 tf.keras.layers.BatchNormalization(), tf.keras.layers.Dropout(0.5), # 输出层4 个类别softmax 归一化成概率 tf.keras.layers.Dense(4, activationsoftmax) ])卷积层的参数需要说清楚Conv2D第一个参数是卷积核数量数量越多网络容量越大但也越容易过拟合(3, 3)是卷积核尺寸视觉任务里 3×3 是最常用的选择paddingsame表示边缘补零让输出尺寸和输入一致。Dropout(0.5)的意思是每次训练随机丢弃 50% 的神经元连接它只在训练时生效推理时自动关闭这是防止过拟合最有效的一招。最后的Dense(4, activationsoftmax)输出 4 个类别的概率值概率最大的就是预测结果。3.3 训练配置优化器、损失函数与回调函数模型编译这一步决定了网络用什么方式学习。项目摘要里提到的 Adam 优化器和交叉熵损失正好都能在 Keras 里直接指定。Adam 是实际项目里最常用的优化器自带自适应学习率比 SGD 好调得多损失函数用SparseCategoricalCrossentropy因为前面读取数据时label_modeint标签是整数而不是 one-hot 向量model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-4), losstf.keras.losses.SparseCategoricalCrossentropy(), metrics[accuracy] ) callbacks [ # 每轮都在验证集上评估只保存验证准确率最好的权重 tf.keras.callbacks.ModelCheckpoint( best_model.h5, monitorval_accuracy, save_best_onlyTrue ), # 连续 10 轮验证准确率不提升提前终止训练 tf.keras.callbacks.EarlyStopping(patience10, restore_best_weightsTrue), # 验证损失停滞 5 轮学习率减半 tf.keras.callbacks.ReduceLROnPlateau(patience5, factor0.5) ]这里我把 Adam 的学习率手动降到了1e-4而不是用框架默认的1e-3。原因是数据集规模不算大默认学习率容易在训练后期震荡收敛不稳定。如果你的数据集更大、样本更丰富可以试着调回默认值看速度。三个回调函数是训练最重要的兜底ModelCheckpoint保证全程最优权重不丢EarlyStopping省时间ReduceLROnPlateau在损失曲面平缓时自动缩小步长帮模型钻过局部最优点。这组配置在大多数图像分类任务里都能直接复用。4. 训练与评估从损失曲线到混淆矩阵4.1 训练流程fit 配合回调函数模型编译完成后真正的训练就一行model.fit。这里把train_ds和val_ds直接喂进去整个训练过程中几个回调函数会自动工作每轮结束评估验证集、保存最优模型、触发早停就终止。50 轮的epochs上限只是个保险丝实际上一旦验证准确率连续 10 轮不涨EarlyStopping会自动截断不会白白跑满history model.fit( train_ds, # 训练数据流 validation_dataval_ds, # 验证数据流 epochs50, # 训练轮数上限 callbackscallbacks # 模型保存、早停、学习率衰减 ) # 训练结束后把历史指标画出来确认没有欠拟合或过拟合 import matplotlib.pyplot as plt plt.plot(history.history[accuracy], labeltrain_acc) plt.plot(history.history[val_accuracy], labelval_acc) plt.legend() plt.savefig(training_curve.png)检查训练曲线是这一步最关键的验收动作如果训练准确率一路走高验证准确率却掉头向下说明模型过拟合了需要加大Dropout或者扩增数据如果两者都很低说明欠拟合需要加深网络或者增加训练轮数。树莓派上用 CPU 训练小数据集时几十轮通常几分钟能跑完不需要急着上 GPU。训练完best_model.h5就是验证集上表现最好的那个权重文件。4.2 用混淆矩阵评估准确率高不等于分类靠谱单纯看准确率很容易被误导尤其是垃圾分类这种类别样本数可能不平衡的任务。比如「其他垃圾」图片特别多模型就算把小类全分错准确率也可能很高。这时候要看混淆矩阵和每个类别的精确率、召回率。用测试集跑一轮预测和真实标签做对比import numpy as np from sklearn.metrics import confusion_matrix, classification_report # 对测试集做预测得到每个 batch 的标签 preds model.predict(test_ds) pred_classes np.argmax(preds, axis1) true_classes np.concatenate([y for _, y in test_ds], axis0) # 把目录名和标签索引对应起来便于阅读 class_names test_ds.class_names print(classification_report( true_classes, pred_classes, target_namesclass_names )) print(confusion_matrix(true_classes, pred_classes))classification_report会输出每个类别的精确率、召回率、F1 值confusion_matrix则直观展示哪些类别容易被混淆。实战里常见的结果是「厨余」和「其他」互相认错因为这两类外观本身就接近——厨余垃圾里也常常混着纸巾、塑料袋。如果发现某个类别召回率明显偏低优先检查这一类图片数量是不是太少、场景是不是太单一而不是一头扎进调网络结构。4.3 模型导出从 h5 到 SavedModel训练好的.h5文件只适合继续调参用真正上树莓派部署建议导出成SavedModel格式。SavedModel 目录结构完整包含网络结构和权重TensorFlow 的各类部署工具包括转成 TFLite都优先认这个格式# 导出成 SavedModel部署时加载更稳定 model.save(saved_model/garbage_model) # 单独写一个推理脚本加载模型并预测单张图片 loaded tf.keras.models.load_model(saved_model/garbage_model) img tf.keras.utils.load_img(test_photo.jpg, target_size(150, 150)) img_arr tf.keras.utils.img_to_array(img) / 255.0 img_arr np.expand_dims(img_arr, axis0) # 增加 batch 维度 result loaded.predict(img_arr) class_index np.argmax(result[0]) print(f预测类别: {class_names[class_index]}, 置信度: {result[0][class_index]:.2f})这里有个最容易被忽略的细节推理时的target_size必须是模型训练时用过的image_size值比如前面的 150×150预处理也要保持一致训练用了Rescaling(1./255)推理时就要手动除 255。如果训练用1./255、推理却不归一化网络输入的分布就变了精度直接蒸发而且这类错误很难从报错信息里看出来只能凭结果反推。5. 树莓派部署避坑指南模型跑不起来的常见问题排查5.1 训练或推理时直接 OOM进程被杀掉现象树莓派上跑到一半终端提示Killed或者 Python 进程无故消失。原因树莓派动辄只有 2GB4GB 内存TensorFlow 构建图和加载模型都要吃内存再加上图片解码很容易触顶。内核的内存杀手会直接把进程干掉不留异常栈。解决先降batch_size从 32 降到 8 或 4 试一轮再把image_size从 224 降到 150 或 128。训练完在树莓派上只做推理的话不要加载完整 TensorFlow改用tflite-runtime内存占用能压到原来的一半以下。临时开 swap 能续命但别依赖SD 卡扛不住长期高频写入。5.2 import tensorflow 报 Illegal instruction或者找不到 so 文件现象树莓派上明明装好了 TensorFlowimport tensorflow直接崩报Illegal instruction (core dumped)。原因树莓派 CPU 架构特殊部分预编译 wheel 依赖较新的指令集或较新的 glibc 版本。如果系统镜像版本太老装上去的包和系统底层库对不上跑起来就翻车。解决优先用官方为 ARM 架构发布的 wheel 包不要从默认 PyPI 源随便下带armv7l字样的包。如果只是要推理直接装tflite-runtime轻量且少踩底层兼容性的坑。装完先跑一行import tensorflow as tf做冒烟测试过了再继续。5.3 转成 TFLite 量化模型后准确率断崖式下跌现象原模型测试准确率 85% 以上转换 int8 量化模型后在树莓派上一测只剩 60%。原因量化是把浮点权重压成低精度整数信息丢失是必然的。如果校准数据集选得太少或者图片风格和真实场景不一致量化器找不到合适的动态范围精度损失会被放大。解决做量化时representative_dataset一定要用验证集里随机抽的几百张真实图片不能只用几张开盲盒。优先试「动态范围量化」而不是全 int8 量化前者精度损失小后者才需要做量化感知训练兜底。每次转换完先在 PC 上用测试集评估一轮确认精度衰减在可接受范围内再部署。5.4 摄像头推理画面卡顿帧率只有 12 FPS现象树莓派接上摄像头做实时识别画面像幻灯片点一次识别要好几秒。原因推理脚本每次预测都重新加载模型、初始化会话或者把所有图像处理都阻塞在主线程里。树莓派 CPU 算力本身就有限这种写法等于每次都在重启一套管线。解决模型全局只加载一次放进一个常驻进程里接收图片并返回结果摄像头图像采集和推理放进两个线程中间用队列缓冲。再把送入模型的帧缩到 150×150推理耗时能压到一两秒的可用区间。6. 二次开发换自己的数据集、加速部署改造6.1 换数据集改目录、改参数、改输出层这套工程最值钱的地方是你完全可以不碰网络结构只换数据就迁移到新任务。把data/train下的 4 个类别目录换成自己的分类目录比如水果识别、零件缺陷识别。需要同步改两处一是最后一层Dense(4, activationsoftmax)的 4 改成你的类别数二是如果新数据集图片尺寸和 150×150 差别很大重算image_size。训练流程不用动image_dataset_from_directory会自动扫描新目录并生成标签。6.2 部署提速转 TFLite 量化模型树莓派上真正能提速的是把模型转成 TFLite再配上tflite-runtime推理。转换时用验证集抽 100 张图做校准避免精度暴跌# 转 TFLite带代表性数据集做量化校准 def representative_gen(): for img, _ in val_ds.take(100): yield [img] converter tf.lite.TFLiteConverter.from_saved_model(saved_model/garbage_model) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_gen tflite_model converter.convert() with open(garbage_model.tflite, wb) as f: f.write(tflite_model)转完之后的推理代码要从model.predict换成Interpreter的invoke方式输入输出都是 NumPy 数组调用模式完全不同。我自己的习惯是每次换数据集之后都强制把整套流程从头到尾走一遍训练、混淆矩阵评估、导出 SavedModel、转 TFLite、树莓派实机跑一轮测试图片。这个习惯帮我避免过太多次「在电脑上一切正常、上设备就翻车」的尴尬——先自己翻一次车总比到现场翻车便宜。希望这个项目的坑和路径能帮你少走几段弯路把垃圾分类识别从「能跑通」推进到「跑得稳」。本文还有配套的精品资源点击获取