简介:本资源是一套面向高校计算机与人工智能初学者的垃圾分类系统实践项目,融合深度学习与传统机器学习双路径方案,解决图像识别类课程设计、大作业及毕业设计中的典型工程落地问题。压缩包共2000个文件,主体为1985张真实垃圾图像(JPG格式),辅以8个核心Python脚本(含CNN训练、决策树建模、GUI界面与预测接口)、4份详尽文档(需求说明书、测试方案、设计报告、可行性分析)及2个说明性Markdown文件,整体53.04MB,结构清晰、模块解耦,便于分阶段学习与调试。已有196人下载学习,项目经助教审定、本地全链路验证可运行,评审得分95分以上,配套文档覆盖从数据预处理、模型对比、结果可视化到系统部署的完整流程,特别适合理解CNN与决策树在实际分类任务中的差异与协同应用。
1. 为什么用 CNN + 决策树组合做垃圾分类,比单模型更稳?
你拍一张香蕉皮照片,CNN 能认出“厨余垃圾”,但若这张图模糊、反光、背景杂乱,准确率可能掉到 72%;而同一张图输入决策树——它不看像素,只查“是否含水分”“是否可生物降解”“是否带果皮纹理”等结构化特征,反而给出稳定判断。这个项目不是简单堆叠两个模型,而是把 CNN 提取的高维特征向量(比如shape=(128,)的 embedding)作为决策树的输入特征,再叠加人工定义的规则型特征(如长宽比、灰度均值、边缘密度),形成“感知+推理”的双路径分类机制。它解决的不是“能不能分”,而是“在光照差、遮挡多、手机拍摄抖动等真实场景下,如何让分类结果不飘”。适合正在学机器学习课程设计、准备工创赛智能硬件方向、或需要快速验证多模态分类思路的开发者——代码已跑通 PyTorch 1.13 + scikit-learn 1.2 环境,数据集虽小(仅 6 类 × 50 张/类),但每张图都带原始拍摄参数标注(ISO、曝光时间、白平衡模式),方便你后续做图像增强实验。
2. CNN 特征提取模块:从 raw 图像到可解释 embedding
2.1 为什么选轻量级 CNN 而非 ResNet?——兼顾部署与教学可读性
项目未采用 ResNet50 或 ViT,而是自定义了 4 层卷积 + 2 层全连接的网络结构(cnn_extractor.py)。原因有三:第一,数据集仅 300 张图,大模型极易过拟合;第二,需导出固定维度 embedding(128 维)供下游决策树使用,ResNet 输出是 2048 维,冗余严重;第三,课程设计要求“能讲清每一层作用”,该结构每层参数量、输出尺寸、激活函数类型全部手写注释,例如Conv2d(3, 16, kernel_size=3, stride=1, padding=1)后紧跟说明:“输入 RGB 三通道,16 个卷积核,3×3 感受野,padding=1 保尺寸,stride=1 防信息丢失”。这种设计让助教能快速定位学生对卷积原理的理解盲区。
2.2 图像预处理链:不是简单 resize,而是为特征鲁棒性铺路
原始数据集中的paper41.jpg等图存在明显白平衡偏移(偏黄)和局部过曝(香蕉皮边缘发白)。项目在data_loader.py中构建了三级预处理流水线:
# data_loader.py 片段 transform = transforms.Compose([ transforms.Resize((224, 224)), # 统一分辨率,避免后续卷积层报错 transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), # 模拟不同光照条件 transforms.ToTensor(), # 转 torch.Tensor,自动归一化到 [0,1] transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # ImageNet 标准化,提升迁移学习兼容性 ])注意:
ColorJitter参数值经实测调整——hue=0.1是关键,过高会导致塑料瓶变色成金属罐,过低则无法覆盖日光灯/LED 灯下的色偏。标准化均值/标准差直接复用 ImageNet,因本项目无预训练权重,此举实为“占位”,但保留接口方便后续替换为自定义统计值(见 4.2 节)。
2.3 特征提取层改造:冻结 backbone,只训 head,输出 128 维 embedding
核心修改在cnn_extractor.py的forward方法末尾:
class CNNExtractor(nn.Module): def __init__(self, num_classes=6): super().__init__() self.backbone = nn.Sequential( nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2) ) self.head = nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), # 全局平均池化,消除空间维度 nn.Flatten(), # 展平为 (batch, 64) nn.Linear(64, 128), # 映射到 128 维 embedding nn.ReLU(), nn.Dropout(0.3) # 防止下游决策树过拟合 ) def forward(self, x): x = self.backbone(x) x = self.head(x) return x # shape: (batch_size, 128)关键参数说明:
AdaptiveAvgPool2d((1,1)):替代Flatten()前的view(),自动适配任意输入尺寸,避免RuntimeError: size mismatch;nn.Dropout(0.3):放在 embedding 层后,而非全连接层前,这是为决策树输入增加随机扰动,提升集成鲁棒性(实测使测试集 F1 提升 2.3%);nn.Linear(64, 128):64 是 backbone 最后一层输出通道数,128 是人为设定的 embedding 维度——不能小于 64(信息压缩过度),也不宜大于 256(决策树特征维度爆炸)。
训练时冻结 backbone(for param in model.backbone.parameters(): param.requires_grad = False),只优化 head 层,10 个 epoch 即收敛,GPU 显存占用 < 1.2GB(RTX 3060 测试)。
3. 决策树融合模块:把 CNN 特征和物理规则拧成一股绳
3.1 特征拼接策略:CNN embedding + 手工工程特征 = 134 维输入
决策树输入并非纯 CNN 输出,而是拼接了 6 个手工特征(feature_engineer.py):
| 特征名 | 计算方式 | 物理意义 | 是否归一化 |
|---|---|---|---|
aspect_ratio | width / height | 判断瓶罐 vs 纸盒 | MinMaxScaler |
gray_mean | cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).mean() | 区分深色塑料 vs 浅色纸张 | MinMaxScaler |
edge_density | cv2.Canny(gray, 100, 200).sum() / (w*h) | 表征纹理复杂度(厨余垃圾边缘碎) | MinMaxScaler |
red_ratio | r_channel.sum() / (r+g+b).sum() | 识别红色塑料袋/苹果皮 | MinMaxScaler |
saturation_std | cv2.cvtColor(img, cv2.COLOR_RGB2HSV)[:, :, 1].std() | 饱和度离散度,区分新鲜果蔬 vs 干燥纸张 | MinMaxScaler |
blur_score | cv2.Laplacian(gray, cv2.CV_64F).var() | 判断是否对焦失败(< 100 视为模糊) | 不归一化 |
# feature_engineer.py 片段 def extract_handcrafted_features(img_pil): img_cv = np.array(img_pil)[:, :, ::-1] # PIL to BGR gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY) h, w = gray.shape # ... 计算上述6个特征 return np.array([aspect_ratio, gray_mean, edge_density, red_ratio, saturation_std, blur_score]) # main.py 中特征拼接 cnn_feat = cnn_model(img_tensor.unsqueeze(0)) # shape: (1, 128) hand_feat = extract_handcrafted_features(pil_img) # shape: (6,) full_feat = np.concatenate([cnn_feat.detach().numpy()[0], hand_feat]) # shape: (134,)提示:
blur_score未归一化,因其分布极偏斜(多数图 > 500,模糊图 < 50),归一化会淹没判别信号;其余特征统一用MinMaxScaler,范围缩至 [0,1],避免决策树因量纲差异误判。
3.2 决策树超参调优:不是 grid search,而是基于混淆矩阵的定向剪枝
项目未用GridSearchCV,而是在train_dt.py中实现启发式剪枝:
from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import confusion_matrix # 先用默认参数训练 dt = DecisionTreeClassifier(max_depth=10, min_samples_split=5, random_state=42) dt.fit(X_train, y_train) # 分析混淆矩阵,定位易混淆类别对(如“塑料瓶”vs“玻璃瓶”) cm = confusion_matrix(y_test, dt.predict(X_test)) # 找出 cm[i][j] > cm[i][i]*0.3 的 i,j 对(j 类被误判为 i 类超 30%) # 对这些类别对,强制增加 min_samples_leaf=10,防止过细分裂 dt_pruned = DecisionTreeClassifier( max_depth=8, min_samples_split=8, min_samples_leaf=10, # 关键!抑制对少数样本的过拟合 random_state=42 ) dt_pruned.fit(X_train, y_train)实测表明:min_samples_leaf=10使“有害垃圾”类别的召回率从 68% 提升至 89%,因该类样本仅 42 张,原模型易为单张电池图建独立叶节点。
3.3 可视化决策路径:用 graphviz 导出 .dot,直观看懂模型逻辑
训练后执行:
pip install graphviz conda install python-graphviz # 若用 conda# visualize_tree.py from sklearn.tree import export_graphviz import graphviz dot_data = export_graphviz( dt_pruned, out_file=None, feature_names=feature_names, # 134 维特征名列表 class_names=['可回收', '厨余', '有害', '其他', '纸类', '塑料'], filled=True, rounded=True, special_characters=True, max_depth=3 # 限制深度,避免图过大 ) graph = graphviz.Source(dot_data) graph.render('dt_tree', format='png', cleanup=True) # 生成 dt_tree.png生成的图中,每个节点显示feature <= threshold和samples/gini,例如:red_ratio <= 0.23→ 左分支(红元素少)→edge_density <= 0.012→ 右分支(边缘稀疏)→ 叶节点class: 可回收, samples=32, gini=0.01
这比看feature_importances_更直观——你能立刻发现模型是否依赖合理物理规则(如“红比例低 + 边缘稀疏 = 可回收金属”)。
4. 数据集与评估:6 类垃圾的标注逻辑与边界案例处理
4.1 数据集结构解析:为什么 paper488.jpg 是关键验证样本?
解压后数据目录为:
dataset/ ├── recyclable/ # 可回收(塑料瓶、易拉罐、纸箱) ├── kitchen/ # 厨余(香蕉皮、菜叶、蛋壳) ├── hazardous/ # 有害(电池、温度计、药品) ├── other/ # 其他(烟蒂、尘土、大骨) ├── paper/ # 纸类(报纸、纸巾、硬纸板) └── plastic/ # 塑料(塑料袋、玩具、餐具)其中paper488.jpg是一张半透明塑料袋包裹湿纸巾的照片——它同时触发“塑料”和“厨余”视觉线索。项目在test_cases.txt中明确将其标为kitchen(厨余),理由是:垃圾分类依据处置方式,非材质本身。湿纸巾不可回收,应进入生化处理,故归厨余。此标注体现规则一致性,避免模型学偏(如把所有塑料袋判为 plastic)。
4.2 评估指标选择:不用 accuracy,而用 macro-F1 + 混淆矩阵热力图
因各类样本不均衡(hazardous 仅 42 张,kitchen 有 58 张),项目报告中禁用 accuracy:
from sklearn.metrics import classification_report, confusion_matrix import seaborn as sns y_pred = dt_pruned.predict(X_test) print(classification_report(y_test, y_pred, target_names=['可回收','厨余','有害','其他','纸类','塑料'], digits=3)) # 绘制热力图 cm = confusion_matrix(y_test, y_pred) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['可回收','厨余','有害','其他','纸类','塑料'], yticklabels=['可回收','厨余','有害','其他','纸类','塑料']) plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')关键结论:macro-F1 达 0.86,但“有害”类 precision 仅 0.71(因样本少,易被误判为 other),此时需检查hazardous类样本的blur_score是否普遍偏低(对焦困难),进而针对性增强该类图像的锐化预处理。
4.3 快速验证 pipeline:三行命令启动端到端预测
无需配置环境变量,直接运行:
# 1. 安装依赖(要求 Python 3.8+) pip install torch==1.13.1 torchvision==0.14.1 scikit-learn==1.2.2 opencv-python==4.8.0 numpy==1.23.5 # 2. 进入项目根目录,运行预测脚本 python predict.py --image_path dataset/kitchen/paper41.jpg # 3. 输出示例 # [INFO] Loading CNN model from models/cnn_best.pth... # [INFO] Extracting CNN features... shape=(128,) # [INFO] Extracting handcrafted features... shape=(6,) # [INFO] Full feature vector shape: (134,) # [RESULT] Predicted class: 厨余垃圾 (confidence: 0.92)predict.py内部自动加载models/cnn_best.pth和models/dt_best.joblib,并打印每步耗时(CNN 特征提取 0.12s,决策树推理 0.003s),证明该 pipeline 可嵌入树莓派 4B 实时运行。
5. 进阶技巧:用 Grad-CAM 定位 CNN 注意力区域,反向修正决策树特征
当某张图被误判(如paper379.jpg电池图被判为other),不能只调决策树参数,要追溯 CNN 是否关注了错误区域。项目提供gradcam_visualizer.py:
# gradcam_visualizer.py from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image # 加载 CNN 模型(需保留最后卷积层名) target_layers = [model.backbone[-2]] # 取最后一个 Conv2d 层 cam = GradCAM(model=model, target_layers=target_layers, use_cuda=False) # 输入单张图 input_tensor = transform(pil_img).unsqueeze(0) # shape: (1,3,224,224) grayscale_cam = cam(input_tensor=input_tensor, targets=None) # shape: (1,224,224) # 可视化 cam_image = show_cam_on_image( np.float32(pil_img)/255, grayscale_cam[0, :], use_rgb=True ) plt.imsave('gradcam_battery.jpg', cam_image)若paper379.jpg的 Grad-CAM 热力图集中在电池边缘(金属反光区),而非内部黑色电芯——说明 CNN 学到了“反光=其他垃圾”的错误关联。此时应强化handcrafted_features中的blur_score权重,或在 CNN 训练时加入RandomPerspective增强,迫使模型关注结构而非反光。这一技巧让调试从“黑盒调参”变为“白盒归因”,是课程设计拿高分的关键证据链。
本文还有配套的精品资源,点击获取