简介:这份资源面向图像分类入门者与深度学习实践者,提供一套基于简单垃圾分类数据集的完整智能分类方案,帮助读者理解从数据准备到模型预测的全流程。包内共1046个文件,以1041张jpg图片构成训练与测试数据集,另含2个Python脚本分别负责训练与预测、1个h5模型文件、1个txt说明及1段mp4演示视频,压缩包约824.68MB。已有1332人学习下载,适合作为图像分类模板参考。读者可借助train.py完成模型训练,通过predict.py对输入图片进行推理,并在图片上以中文标注干垃圾、湿垃圾、可回收垃圾、有害垃圾四类结果,同时了解数据集制作、TensorFlow与OpenCV结合使用的具体做法,快速搭建自己的分类实验环境。
1. 从一张垃圾桶照片说起:这套垃圾分类模型到底能跑出什么结果
你拍一张外卖盒的照片丢给模型,它告诉你这是「干垃圾」还是「可回收物」——这件事听起来像是个玩具 demo,但真正动手做一遍,你会发现它把图像分类的完整链路全串起来了:数据采集、标注、增强、搭网络、训练、调参、预测、可视化。这套资源就是干这个的,核心文件是my_model.h5和一组测试图(img_11548.jpg、img_11351.jpg、img_4491.jpg等),配套train.py负责训练、predict.py负责推理,输出四个类别:干垃圾、湿垃圾、可回收垃圾、有害垃圾。它不追求 SOTA 精度,定位是「图像分类模板」——你可以把数据集换成自己的,网络结构不动,跑通整条流水线。适合刚接触 TensorFlow 和 OpenCV 的从业者拿来练手,也适合需要快速搭一个分类 baseline 的人直接改。
2. 拆开这个 h5 文件:模型结构、数据流与四分类逻辑
2.1 为什么是 TensorFlow + OpenCV 这套组合
垃圾分类本质上是细粒度图像分类的一个简化版。干垃圾和湿垃圾在视觉上的差异往往集中在纹理和边缘——比如纸巾的纤维感 vs 果皮的湿润反光。OpenCV 在这里承担的是预处理角色:读图、缩放、颜色空间转换、直方图均衡,这些操作比直接用 PIL 更可控,尤其是当你需要做形态学处理(膨胀、腐蚀)来强化边缘特征时,OpenCV 的cv2.dilate和cv2.erode是现成的。
TensorFlow 这边,my_model.h5是一个已经保存权重的 Keras 模型文件。h5 格式的好处是结构和权重打包在一起,tf.keras.models.load_model('my_model.h5')一行就能恢复整个网络,不需要你重新定义层。常见做法是底层用几个卷积块提特征,后面接全局平均池化再连全连接层输出四类 softmax。这种结构参数量小,在几千张图的规模上不容易过拟合。
选 TensorFlow 而不是 PyTorch 的理由很实际:h5 格式在 Keras 生态里加载最省事,而且predict.py里用model.predict()拿到概率向量后直接np.argmax就能出类别索引,再映射到中文标签。如果你习惯 PyTorch,也可以把 h5 转成 ONNX 再加载,但那是另一条路,这套资源没走。
2.2 数据从哪来、怎么进网络
垃圾分类数据集通常按文件夹组织,每个类别一个子目录:
dataset/ ├── gan/ │ ├── img_001.jpg │ └── ... ├── shi/ │ ├── img_002.jpg │ └── ... ├── kehuishou/ │ └── ... └── youhai/ └── ...train.py里一般用ImageDataGenerator做流式读取,好处是不用一次性把几千张图全塞进内存。核心参数这么设:
from tensorflow.keras.preprocessing.image import ImageDataGenerator train_datagen = ImageDataGenerator( rescale=1./255, # 像素归一化到 [0,1] rotation_range=20, # 随机旋转 ±20 度 width_shift_range=0.1, # 水平平移 10% height_shift_range=0.1, # 垂直平移 10% horizontal_flip=True, # 水平翻转 validation_split=0.2 # 划出 20% 做验证 ) train_generator = train_datagen.flow_from_directory( 'dataset/', target_size=(224, 224), # 统一缩放到 224x224 batch_size=32, class_mode='categorical', subset='training' )rescale必须做,否则像素值在 0-255 之间,梯度更新会不稳定。target_size设成 224×224 是因为大多数预训练骨干网络(比如 MobileNetV2)的默认输入就是这个尺寸,即使你从零搭网络,这个尺寸在精度和显存之间也比较平衡。validation_split划出的验证集不参与梯度更新,只用来监控过拟合。
flow_from_directory会自动根据子目录名生成类别索引,顺序是字母序。所以gan、kehuishou、shi、youhai对应的索引可能是 0、1、2、3,但具体顺序要在训练后打印train_generator.class_indices确认,否则预测时标签会错位。
2.3 训练脚本里几个不能省的步骤
train.py的骨架大致是这样:
import tensorflow as tf from tensorflow.keras import layers, models model = models.Sequential([ layers.Conv2D(32, (3,3), activation='relu', input_shape=(224,224,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.GlobalAveragePooling2D(), layers.Dense(128, activation='relu'), layers.Dropout(0.5), layers.Dense(4, activation='softmax') # 四个类别 ]) model.compile( optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'] ) history = model.fit( train_generator, epochs=30, validation_data=val_generator ) model.save('my_model.h5')GlobalAveragePooling2D替代Flatten是为了减少参数量,降低过拟合风险。Dropout(0.5)在全连接层前随机丢弃一半神经元,也是防过拟合的常规操作。categorical_crossentropy对应 one-hot 标签,如果你用class_mode='sparse'就得换成sparse_categorical_crossentropy,这两个不能混。
epochs=30是个经验值,实际跑的时候要看验证集准确率什么时候不再上升。如果验证 loss 开始往上走而训练 loss 还在降,就是过拟合了,该早停。可以加EarlyStopping回调:
callbacks = [ tf.keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True) ]patience=5表示验证 loss 连续 5 个 epoch 不改善就停,restore_best_weights把权重回滚到最好的那一轮。
2.4 预测脚本怎么把中文标签画到图上
predict.py要做三件事:加载模型、读图推理、把结果写到图片上。中文显示是个坑,OpenCV 的cv2.putText不支持中文,直接写会变成问号。常见做法是用 PIL 画字:
import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFont from tensorflow.keras.models import load_model model = load_model('my_model.h5') class_names = ['干垃圾', '湿垃圾', '可回收垃圾', '有害垃圾'] img = cv2.imread('img_11548.jpg') img_resized = cv2.resize(img, (224, 224)) img_array = img_resized / 255.0 img_array = np.expand_dims(img_array, axis=0) preds = model.predict(img_array) idx = np.argmax(preds) label = class_names[idx] confidence = preds[0][idx] img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(img_rgb) draw = ImageDraw.Draw(pil_img) font = ImageFont.truetype('simhei.ttf', 36) # 需要中文字体文件 draw.text((10, 10), f'{label} {confidence:.2f}', font=font, fill=(255, 0, 0)) result = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) cv2.imwrite('result.jpg', result)simhei.ttf是黑体字体文件,Windows 系统在C:/Windows/Fonts/下能找到,Linux 上需要自己拷一份或者用fc-list找可用中文字体。ImageFont.truetype的第二个参数是字号,图片分辨率高的时候要相应调大,否则字太小看不清。
np.expand_dims那一步不能省,model.predict要求输入是 batch 形式,即使你只预测一张图,也得凑出一个维度。preds[0]才是这张图的四类概率,np.argmax返回最大概率的索引,和class_names的顺序对应。
3. 从零跑通训练到预测:环境、命令与参数调整
3.1 环境装什么、版本怎么选
TensorFlow 2.x 是必须的,因为 h5 加载和 Keras API 都依赖它。Python 版本建议 3.8 到 3.10,太新的版本可能和 TensorFlow 的 wheel 不兼容。一条命令装齐:
pip install tensorflow opencv-python pillow numpy matplotlib如果你有 NVIDIA 显卡,可以装tensorflow-gpu,但要注意 CUDA 和 cuDNN 版本必须和 TensorFlow 版本匹配。常见坑是 TensorFlow 2.10 之后 GPU 支持合并进了主包,不再单独发tensorflow-gpu,装错了会报Could not find cudart64_*.dll。不确定的话先用 CPU 版跑通流程,再折腾 GPU。
验证安装:
import tensorflow as tf print(tf.__version__) print(tf.config.list_physical_devices('GPU'))如果 GPU 列表为空但你有显卡,检查 CUDA 路径是否加进了系统环境变量。
3.2 训练命令与日志解读
假设数据集已经按类别分好文件夹,训练直接跑:
python train.py --data_dir dataset/ --epochs 30 --batch_size 32 --output my_model.h5如果你的train.py没写 argparse,那就改脚本里的硬编码路径。训练过程中终端会打印每个 epoch 的 loss 和 accuracy:
Epoch 1/30 100/100 [==============================] - 45s 450ms/step - loss: 1.2345 - accuracy: 0.4567 - val_loss: 1.0123 - val_accuracy: 0.5234重点看val_accuracy和val_loss。如果val_accuracy在 0.7 左右就上不去了,可能是数据量不够或者类别不平衡。垃圾分类数据集里「可回收垃圾」的样本往往最多,「有害垃圾」最少,这会导致模型偏向多数类。解决办法是给flow_from_directory加class_weight参数,或者对少数类做过采样。
3.3 预测单张图和批量预测
单张预测:
python predict.py --image img_11548.jpg --model my_model.h5批量预测就是把测试图全跑一遍:
import os test_images = ['img_11548.jpg', 'img_11351.jpg', 'img_4491.jpg', 'img_11382.jpg', 'img_11376.jpg', 'img_48.jpg', 'img_4343.jpg', 'img_4276.jpg', 'img_11566.jpg'] for img_path in test_images: result = predict_single(img_path) print(f'{img_path}: {result}')批量跑的时候注意内存,如果图片很多,别一次性全读进来,用循环逐张处理。另外预测前要确保图片路径正确,cv2.imread读不到文件不会报错,会返回None,后面 resize 就会崩。加个判断:
if img is None: print(f'无法读取 {img_path}') continue3.4 参数怎么调:学习率、batch size、输入尺寸
学习率是影响最大的超参数。Adam 默认 0.001,如果训练 loss 震荡厉害,降到 0.0001;如果 loss 下降太慢,可以试 0.005 但别更高,否则容易发散。batch size 受显存限制,CPU 训练用 16 或 32 都行,GPU 显存 4G 以上可以上 64。输入尺寸 224×224 是标准值,如果你要识别更细的纹理(比如区分不同材质的塑料),可以提到 256×256 或 299×299,但训练时间会成倍增加。
数据增强的强度也要看情况。rotation_range=20对垃圾分类是合理的,因为垃圾不会总是正着放。但如果你做的是工业质检类的图像分类,旋转增强可能反而不合理,因为产品方向是固定的。horizontal_flip=True对大多数自然图像没问题,但如果是文字相关的分类,翻转会改变语义,得关掉。
4. 避坑与排查:中文乱码、标签错位、过拟合
4.1 预测结果中文显示成方块或问号
现象:predict.py跑完,图片上只有方框或者???,看不到「干垃圾」这些字。
原因:OpenCV 的cv2.putText只支持 ASCII 字符,中文字符不在它的字体渲染范围内。即使你传了中文字符串,它也会用默认字体渲染成乱码。
解决:改用 PIL 的ImageDraw.text,并且指定一个支持中文的 TTF 字体文件。ImageFont.truetype('simhei.ttf', 36)里的字体路径要写对,Linux 上可能是/usr/share/fonts/truetype/...,Windows 上是C:/Windows/Fonts/simhei.ttf。如果找不到字体,用fc-list :lang=zh查一下系统里有哪些中文字体。
4.2 预测标签和实际类别对不上
现象:模型预测「可回收垃圾」的图,输出却是「有害垃圾」,但置信度还挺高。
原因:class_names列表的顺序和训练时flow_from_directory生成的class_indices不一致。flow_from_directory按文件夹名的字母序分配索引,比如gan=0, kehuishou=1, shi=2, youhai=3,但你在predict.py里写的class_names = ['干垃圾', '湿垃圾', '可回收垃圾', '有害垃圾']对应的索引是干=0, 湿=1, 可回收=2, 有害=3,完全错位了。
解决:训练完打印train_generator.class_indices,把那个字典保存下来,预测时按同样的映射关系取标签。或者直接在predict.py里用class_indices的反向映射:
class_indices = {'gan': 0, 'kehuishou': 1, 'shi': 2, 'youhai': 3} idx_to_class = {v: k for k, v in class_indices.items()} label_en = idx_to_class[idx] label_cn = {'gan': '干垃圾', 'shi': '湿垃圾', 'kehuishou': '可回收垃圾', 'youhai': '有害垃圾'}[label_en]4.3 训练准确率很高但预测一塌糊涂
现象:训练集 accuracy 到 0.98,验证集也有 0.95,但拿新图片预测,结果随机跳。
原因:过拟合加上数据泄漏。如果验证集是从训练集里随机切的,而训练集里同一张图的不同增强版本同时出现在训练和验证中,验证准确率就是虚高的。另外,如果数据集里同一类别的图片高度相似(比如都是从同一个视频里截的帧),模型学到的是背景而不是物体本身。
解决:确保验证集和训练集在图片级别完全隔离,最好按拍摄批次或来源划分。增强操作只在训练集上做,验证集只做rescale。如果数据量太少,考虑用预训练权重做迁移学习,把 MobileNetV2 的前面层冻结,只训练最后的分类层。
4.4 h5 模型加载报错
现象:load_model('my_model.h5')抛出ValueError: Unknown layer或OSError: Unable to open file。
原因:第一种情况是模型里用了自定义层或自定义损失函数,加载时没有传custom_objects。第二种是文件路径不对或者文件损坏。
解决:如果是自定义层,加载时加custom_objects={'MyLayer': MyLayer}。如果是文件问题,检查文件大小是否正常,训练保存时是否完整写入了。另外,TensorFlow 2.15 之后推荐用.keras格式替代 h5,如果你用的是新版本,可以另存为.keras再加载。
4.5 显存不够导致训练中断
现象:训练到一半报ResourceExhaustedError: OOM when allocating tensor。
原因:batch size 太大,或者输入尺寸太大,超出了 GPU 显存。
解决:先把 batch size 减半,如果还不行就减输入尺寸。另外可以在训练前设置显存按需增长:
gpus = tf.config.experimental.list_physical_devices('GPU') if gpus: for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True)这样 TensorFlow 不会一次性占满所有显存,而是按需分配。
5. 把模板变成你自己的分类器:换数据、调网络、验效果
这套资源最大的价值不是垃圾分类本身,而是它提供了一个可复用的图像分类骨架。你只需要替换dataset/下的文件夹和图片,改一下class_names和最后Dense层的输出单元数,就能迁移到其他四分类甚至 N 分类任务。比如做塑料瓶、玻璃瓶、纸盒、金属罐的材质分类,流程完全一样。
换数据时要注意几点。第一,每个类别的图片数量尽量均衡,差距不要超过 3 倍,否则用class_weight补偿。第二,图片的拍摄条件要一致,如果训练集全是白底产品图,预测时丢一张复杂背景的图,模型大概率翻车。第三,类别定义要互斥,别出现一张图既像干垃圾又像可回收垃圾的情况,标注时就要定好规则。
网络结构也可以按需调整。如果类别之间差异很细微(比如不同种类的塑料),可以把卷积层加深,或者把Dense(128)改成Dense(256)。如果数据量只有几百张,反而要把网络变浅,否则过拟合压不住。一个实用的技巧是先用预训练模型提特征,把MobileNetV2(weights='imagenet', include_top=False)的输出接一个全局池化,再连自己的分类层,只训练最后几层。这样即使数据少,也能拿到不错的精度。
验证模型效果不能只看准确率。做一个混淆矩阵,看看哪两个类别最容易混:
from sklearn.metrics import confusion_matrix import seaborn as sns y_pred = model.predict(val_generator) y_pred_classes = np.argmax(y_pred, axis=1) y_true = val_generator.classes cm = confusion_matrix(y_true, y_pred_classes) sns.heatmap(cm, annot=True, fmt='d', xticklabels=class_names, yticklabels=class_names)如果「干垃圾」和「湿垃圾」之间的误判特别多,说明这两个类别的视觉特征区分度不够,要么加更多有区分力的样本,要么在预处理阶段强化纹理特征(比如用 OpenCV 做局部二值模式或者边缘检测,把结果作为额外通道输入)。
我自己的习惯是每次换数据集后,先跑 5 个 epoch 看验证准确率能不能到 0.6 以上。如果 5 轮下来还在 0.3 左右,说明数据或标签有问题,别急着调参,先回去检查文件夹结构和图片内容。从那以后我每次拿到新数据都强制走一遍「随机抽 20 张肉眼过一遍」的流程,省得训练半天才发现标签错了。希望帮到你。
本文还有配套的精品资源,点击获取