
简介这是一份面向Python初学者与计算机视觉入门者的图像识别实践资源聚焦垃圾分类这一典型多分类任务手把手指导如何基于深度残差网络ResNet构建端到端图像识别系统。资源共7个文件包含3个Jupyter Notebook分别覆盖数据处理、模型训练与测试全流程、1个Python核心模型脚本model.py、1个XMind流程图清晰呈现程序逻辑架构、1个说明文档txt及1个编译缓存文件pyc整体压缩包仅415KB轻量易部署。已有21056人学习下载体现其在教学实践与课程设计中的广泛认可。读者可直接复现完整训练流程获得结构清晰的代码组织方式、可调参的ResNet实现、标准化的数据加载与评估模块以及关键环节的注释说明与调试提示特别适合用于课程实验、毕业设计或AI入门项目实战。1. 图像识别做垃圾分类不是调个 pretrain 模型就完事真实场景下准确率掉到 62% 的血泪复现笔记去年在社区智能回收站项目里我拿 ResNet50 ImageNet 权重直接 finetune 垃圾图片训练集准确率 98.3%测试集 94.7%结果一放到现场摄像头里——识别塑料瓶成功率不到 65%铝罐常被判成“其他垃圾”湿纸巾和干纸巾分不清连垃圾桶边沿反光都触发误检。后来拆开数据、重跑 pipeline、逐帧看预测热力图才发现图像识别的垃圾分类系统本质是“光照-遮挡-容器形变-标签噪声”四重干扰下的鲁棒性工程不是算法竞赛排行榜。这份代码分享不是教你怎么抄论文而是把我在三个真实落地点高校宿舍楼、社区驿站、环卫中转站反复打磨出的完整闭环——从手机拍图预处理、自建 12 类细粒度垃圾数据集含易混淆样本、轻量 CNNAttention 双分支结构、到部署到 Jetson Nano 的量化推理链——全量开源。适合 Python 中级、熟悉 PyTorch 基础、想用真实数据跑通端到端流程的工程师或毕设同学。Anaconda 环境已锁定依赖版本所有脚本带中文注释关键参数有实测对比表。2. 数据构建与增强为什么你下载的公开垃圾数据集根本不能直接训2.1 公开数据集的四大硬伤从 VOC2012 到 TrashNet 的实测对比我横向测试了当前主流 5 个公开垃圾数据集TrashNet、Garbage Classification、CVPR2022-Waste、UAV-Garbage、Chinese-Garbage结论很残酷没有一个能直接用于真实部署。问题不在数量而在采集逻辑数据集样本数主要问题实测验证集 dropTrashNet2527单一白底正面平铺无遮挡/角度/光照变化-38.2%现场图Garbage Classification15,360多数为网络爬取图含大量 PS 合成、水印、文字遮挡-41.7%手机实拍CVPR2022-Waste42,800无人机俯拍为主垃圾堆叠严重单类目标占比15%-29.5%桶内识别UAV-Garbage8,900仅含 4 类可回收/厨余/有害/其他无细分如PET瓶 vs HDPE瓶不适用需12类Chinese-Garbage36,500标签混乱同一张图中“西瓜皮”标为厨余“西瓜皮塑料袋”标为其他标签错误率 12.3%提示别迷信“大数据”。我们最终采用30% 自采 40% 清洗公开数据 30% 合成增强的混合策略。自采部分覆盖 12 类PET瓶、HDPE瓶、PP餐盒、PS泡沫、PVC管材、铝罐、铁罐、废纸板、报纸、厨余果皮、厨余剩饭、其他垃圾含湿纸巾、烟头、尘土。每类不少于 2000 张且强制包含侧拍/俯拍/斜角、强光/背光/阴天、半遮挡手/桶沿/其他垃圾、模糊/运动拖影。2.2 手机实拍数据清洗流水线用 OpenCV 写的 5 行核心过滤逻辑手机拍图质量极差必须前置清洗。我们不用第三方 SDK纯 OpenCV NumPy 实现轻量过滤单图耗时 80msi5-8250Uimport cv2 import numpy as np def clean_mobile_image(img_path): img cv2.imread(img_path) # 1. 去模糊拉普拉斯方差 50 视为过糊实测阈值 lap_var cv2.Laplacian(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), cv2.CV_64F).var() if lap_var 50: return None # 模糊图丢弃 # 2. 去过曝亮区像素占比 85% 且平均亮度 220 hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) bright_mask hsv[:, :, 2] 220 if np.sum(bright_mask) / img.size 0.85: return None # 3. 去纯色背景计算颜色直方图熵太低说明是白墙/桌面 hist cv2.calcHist([img], [0,1,2], None, [8,8,8], [0,256,0,256,0,256]) entropy -np.sum((hist / hist.sum()) * np.log2(hist / hist.sum() 1e-7)) if entropy 3.2: # 实测白底图熵集中在 2.1~2.8 return None # 4. 裁剪有效区域去掉顶部 15%常为手/天空和底部 10%常为桶沿阴影 h, w img.shape[:2] img img[int(h*0.15):int(h*0.9), :] # 保留中间 75% # 5. 自动白平衡灰度世界假设法比 simpleCLAHE 更稳 b, g, r cv2.split(img) avg_b, avg_g, avg_r np.mean(b), np.mean(g), np.mean(r) avg_gray (avg_b avg_g avg_r) / 3 b np.clip(b * (avg_gray / (avg_b 1e-7)), 0, 255).astype(np.uint8) g np.clip(g * (avg_gray / (avg_g 1e-7)), 0, 255).astype(np.uint8) r np.clip(r * (avg_gray / (avg_r 1e-7)), 0, 255).astype(np.uint8) return cv2.merge([b, g, r]) # 使用示例 cleaned clean_mobile_image(raw/IMG_20230812_1422.jpg) if cleaned is not None: cv2.imwrite(cleaned/IMG_20230812_1422.jpg, cleaned)这段代码的核心价值在于它不追求“美化”只做“保真过滤”。比如白平衡不用 CLAHE会放大噪声而用灰度世界法裁剪不靠检测框手机图无稳定 ROI而用固定比例——因为实测发现92% 的有效垃圾区域集中在画面中下部。所有阈值均来自 3000 张现场图的统计分布不是拍脑袋定的。2.3 针对垃圾特性的合成增强不是加高斯噪声而是模拟“桶内物理”常规增强旋转/裁剪/色彩抖动对垃圾识别提升有限。我们设计了三类物理驱动增强堆叠遮挡增强用真实垃圾掩膜mask叠加到目标图上模拟桶内垃圾堆叠。掩膜来自 TrashNet 的分割标注但做了形态学膨胀cv2.dilate(mask, kernel, iterations3)模拟边缘模糊。材质反射增强对金属罐、塑料瓶区域用cv2.GaussianBlur生成高光斑点sigma1.5叠加到原图 HSV 的 V 通道强度按材质查表铝罐 0.35PET瓶 0.22纸板 0.08。运动模糊增强用cv2.filter2D施加方向性模糊核长度 7px角度随机专治手机拍摄时手抖导致的拖影——这是现场误检主因之一把拖影当异物。增强后模型在未见过的“湿纸巾塑料袋”组合图上F1-score 从 0.51 提升到 0.79。关键不是加得多而是加得准每种增强都对应一个真实失效场景。3. 模型架构与训练为什么不用 ViT而选 CNNAttention 双分支3.1 选型依据算力、延迟、可解释性的三角平衡ViT 在 ImageNet 上吊打 CNN但在垃圾识别场景它有三个致命短板显存爆炸ViT-Base 输入 224x224 时单 batch16 显存占用 11.2GBRTX3090而我们的 Jetson Nano 只有 4GB小目标失效垃圾图中单个瓶子常只占画面 5%~15%ViT 的 patch embedding 会稀释其特征黑匣子难调试当模型把“沾油的 pizza 盒”判成“其他垃圾”而非“厨余”ViT 的 attention map 完全无法定位是油渍干扰还是盒子材质干扰。我们最终采用CNN 主干 局部 Attention 分支的双路径结构代码见model/garbage_net.pyCNN 主干用修改版 MobileNetV3-Small删去最后两层输出 576 维特征负责提取全局形状、纹理Attention 分支在 CNN 第 3 个 bottleneck 后接一个轻量 spatial attention 模块nn.Conv2d(40, 1, 1) sigmoid生成 28x28 注意力图引导模型聚焦瓶身标签区、罐体压痕等判别性局部区域特征融合将 attention 图上采样到 56x56与 CNN 中间层特征56x56x40加权相乘再送入后续层。这样做的好处参数量仅 2.1MViT-Base 86M推理延迟 17msJetson Nano且 attention 图可直接可视化——调试时一眼看出模型是否在看正确位置。3.2 训练策略Focal Loss Label Smoothing 渐进式解冻垃圾类别存在严重长尾PET瓶 3200 张而 PVC管材仅 890 张更麻烦的是标签噪声如“泡面盒”该属“其他”还是“厨余”不同城市标准不同。我们放弃 CrossEntropy改用三重加固# focal loss with alpha-balancing (alpha0.25 for rare classes) class FocalLoss(nn.Module): def __init__(self, alpha1, gamma2, reductionmean): super().__init__() self.alpha alpha self.gamma gamma self.reduction reduction def forward(self, inputs, targets): ce_loss F.cross_entropy(inputs, targets, reductionnone) pt torch.exp(-ce_loss) focal_weight (1 - pt) ** self.gamma loss self.alpha * focal_weight * ce_loss if self.reduction mean: return loss.mean() return loss # label smoothing: 0.1 smoothing factor criterion LabelSmoothingCrossEntropy(smoothing0.1) # 渐进式解冻先冻 backbone训 head再解冻最后 2 个 block optimizer torch.optim.AdamW([ {params: model.classifier.parameters(), lr: 1e-3}, {params: model.features[-2:].parameters(), lr: 1e-4}, # 后两层慢学习 {params: model.attention.parameters(), lr: 1e-3} ])实测效果Focal Loss 将 PVC管材的召回率从 63.2% 提升至 78.5%Label Smoothing 使模型对“模糊标签”如油污纸盒的预测置信度更合理不再死磕 0.99渐进式解冻让收敛更稳避免早期 backbone 被噪声标签带偏。3.3 避坑训练过程中的五个典型翻车点及修复方案现象 → 原因 → 解决全是现场踩出来的坑现象训练 loss 快速下降但 val_acc 停滞在 72%且 confusion matrix 显示“厨余果皮”和“厨余剩饭”混淆率达 65%原因两类样本视觉差异极小都是棕色不规则块状但原始数据集中未做区分增强模型学不到纹理细节解决在厨余类中加入微纹理增强—— 对“果皮”样本用cv2.ximgproc.anisotropicDiffusion()加强纤维感对“剩饭”样本用cv2.bilateralFilter()保留颗粒感增强后混淆率降至 29%现象验证集准确率 94%但部署后手机端识别率仅 68%且大量“其他垃圾”被误判为“可回收”原因训练用图是 224x224 中心裁剪而手机图常为 4:3 或 16:9中心裁剪切掉了关键判别区如瓶底回收标志解决训练时改用adaptive center crop—— 先用轻量 YOLOv5s 检测垃圾大致位置再以检测框为中心裁剪代码见data/augment.py的AdaptiveCenterCrop现象模型对“透明 PET 瓶”识别率仅 51%但对“绿色 PET 瓶”达 92%原因数据集中透明瓶多为白底图模型学到“白底透明瓶”的虚假关联而非瓶身特征解决强制对透明瓶样本做背景替换增强—— 用 GrabCut 提取前景随机贴到 10 种真实背景水泥地、木桌、绿植、瓷砖等透明瓶识别率升至 86%现象训练后期 loss 波动剧烈val_acc 出现周期性震荡每 12 epoch 一次原因batch_size32 时每个 epoch 恰好包含 12 个“铝罐”样本长尾类模型在铝罐 batch 上过拟合下一 batch 又跌解决改用weighted random sampler按类别频率倒数加权确保每 batch 铝罐样本数稳定在 3~5 个震荡消失现象模型在 Jetson Nano 上推理结果与 PC 端不一致同一张图 PC 输出 [0.1, 0.85, 0.05]Nano 输出 [0.08, 0.72, 0.2]原因PC 用 float32Nano 用 TensorRT int8 量化时未对 attention 分支的 sigmoid 输出做特殊校准导致权重截断失真解决在量化前对 attention 分支输出加clamp(0.01, 0.99)并重训Nano 端输出与 PC 差异 0.034. 部署与推理从 PyTorch 到 TensorRT 的零拷贝优化4.1 Jetson Nano 部署全流程为什么不用 ONNX而直走 TensorRTONNX 是通用中间表示但 Jetson Nano 的 GPU 架构Maxwell对某些算子支持不佳如torch.nn.functional.interpolate(modebilinear)在 ONNX 中会转成低效的 resize layer。我们绕过 ONNX用 PyTorch 的 TorchScript 导出再由 TensorRT 8.4 直接解析# 1. 导出 TorchScript注意必须用 tracing非 scripting python export_torchscript.py --model-path weights/best.pth \ --input-size 224 \ --output-path model/garbage_net.ts # 2. TensorRT 构建引擎关键参数fp16 dynamic batch trtexec --onnxmodel/garbage_net.onnx \ # 注此处需先用 torch.onnx.export 转一次仅作格式桥接 --saveEnginemodel/garbage_net.engine \ --fp16 \ --minShapesinput:1x3x224x224 \ --optShapesinput:8x3x224x224 \ --maxShapesinput:16x3x224x224 \ --workspace2048注意trtexec命令中--fp16是必须的Nano 的 FP16 性能是 FP32 的 2.3 倍--dynamicShapes让引擎支持 batch1~16适配单图识别和视频流多帧并行。4.2 零拷贝推理内存带宽才是 Nano 的瓶颈Nano 的内存带宽仅 25.6 GB/s频繁 CPU-GPU 拷贝会吃掉 40% 推理时间。我们用CUDA Unified Memory实现零拷贝// inference_trt.cpp 关键片段 cudaMallocManaged(d_input, batch_size * 3 * 224 * 224 * sizeof(float)); cudaMallocManaged(d_output, batch_size * 12 * sizeof(float)); // 12类 // 推理时CPU 端直接写 d_inputGPU 端直接读无需 memcpy for (int i 0; i batch_size; i) { preprocess_cpu_to_gpu(input_imgs[i], d_input i * 3 * 224 * 224); // 预处理在 CPU但写入 unified mem } context-executeV2(bindings); // bindings 指向 d_input/d_output cudaDeviceSynchronize(); // 等待 GPU 完成 // 此时 d_output 已含结果CPU 可直接读实测零拷贝使单图推理从 28ms 降至 17ms提升 39%。这不是玄学优化是 Nano 架构决定的刚需。4.3 实时视频流 pipeline如何把 30fps 摄像头喂给 17ms 模型摄像头输出 30fps模型单帧 17ms理论可撑 58fps但实际卡顿。问题出在OpenCV 的 cv2.VideoCapture 默认使用 V4L2 缓冲区会累积 3~4 帧延迟。解决方案是手动控制缓冲区import cv2 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关键设为 1 帧缓冲 cap.set(cv2.CAP_PROP_FPS, 30) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # 双线程读帧线程 推理线程 frame_queue queue.Queue(maxsize1) # 只存最新一帧 def read_frames(): while True: ret, frame cap.read() if not ret: break if not frame_queue.full(): frame_queue.put(frame) else: frame_queue.get() # 强制丢弃旧帧保最新 time.sleep(0.001) # 防 CPU 占满 def infer_loop(): while True: if not frame_queue.empty(): frame frame_queue.get() # 预处理 TRT 推理17ms result trt_engine.infer(frame) draw_result(frame, result) # 绘制结果 cv2.imshow(Garbage Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break这套 pipeline 在 Nano 上稳定跑 28fps延迟 120ms从成像到显示满足实时交互需求。5. 效果验证与边界测试用这 7 类图检验你的模型是否真能落地5.1 不是看 top-1 accuracy而是测这 7 类边界场景公开 benchmark 只报整体 acc但落地要看模型在“刁钻场景”下的鲁棒性。我们定义 7 类边界测试集每类 200 张全部现场采集必须全部 ≥85% 才算合格边界类型示例描述检验目的合格线弱光桶内黄昏下垃圾桶内部仅靠 LED 补光信噪比 8dB检验低照度特征提取能力≥85%强反光瓶身阳光直射 PET 瓶出现大面积镜面高光检验材质干扰抵抗≥82%半遮挡组合铝罐被湿纸巾半盖住仅露罐顶红标检验局部判别能力≥79%相似材质混淆HDPE奶瓶 vs PP餐盒同为白色不透明塑料检验细粒度分类≥80%运动模糊拖影手持手机拍摄晃动瓶身出现 5px 拖影检验动态场景适应≥75%极端角度俯拍从 75° 俯角拍桶内垃圾呈椭圆压缩检验几何形变鲁棒性≥83%多类密集堆叠12 类垃圾混装单图含 15 个目标检验拥挤场景分割能力≥70%mAP0.5提示这些测试集已打包在data/boundary_test/下运行test_boundary.py可一键生成报告。不要跳过——我见过太多模型在标准 test set 上 94%在“弱光桶内”上直接崩到 41%。5.2 可视化诊断用 Grad-CAM 定位模型到底在看哪准确率数字是假象必须看到模型“思考过程”。我们用 Grad-CAM 生成热力图重点看三处是否聚焦判别区PET瓶应关注瓶底三角标铝罐应关注罐顶拉环纸板应关注纤维纹路是否受干扰区影响若热力图集中在背景反光区或手部说明模型学到了虚假特征是否覆盖完整目标厨余果皮若只亮起边缘腐烂处而忽略主体说明特征提取不全。# gradcam.py 核心兼容我们的双分支结构 class GradCAM: def __init__(self, model, target_layerfeatures.12.conv.3): # MobileNetV3 的最后一个 conv self.model model self.target_layer target_layer self.gradients None self.activations None def forward_hook(self, module, input, output): self.activations output.detach() def backward_hook(self, module, grad_in, grad_out): self.gradients grad_out[0].detach() def generate_cam(self, input_img, class_idx): # 注册 hook target_module dict(self.model.named_modules())[self.target_layer] target_module.register_forward_hook(self.forward_hook) target_module.register_backward_hook(self.backward_hook) # 前向 反向 output self.model(input_img) self.model.zero_grad() output[0, class_idx].backward() # 只对目标类反向 # 计算 CAM weights torch.mean(self.gradients, dim(2, 3), keepdimTrue) cam torch.relu(torch.sum(weights * self.activations, dim1)) cam F.interpolate(cam, size(224, 224), modebilinear) return cam.squeeze().cpu().numpy() # 使用 cam_gen GradCAM(model) cam cam_gen.generate_cam(test_tensor, class_idx0) # PET瓶 plt.imshow(test_img) plt.imshow(cam, cmapjet, alpha0.5) plt.title(fGrad-CAM for PET bottle (class {class_idx})) plt.show()这张热力图就是你的“后悔药”——当模型判错时它告诉你错在哪。从那以后我每次调参都强制走一遍 Grad-CAM看热力图是否符合物理常识。如果 PET 瓶的热力图亮在瓶身反光区而不是三角标宁可降低准确率也要加针对性增强。希望帮到你。本文还有配套的精品资源点击获取