基于深度学习的可回收垃圾材质识别与分类研究毕业设计--整套 C/S 架构完整方案

架构组成:

• Server 服务端:Flask + 你训练好的 ResNet18 五分类模型(后端接口)

• Client 客户端:PyQt5 桌面界面(不本地加载模型,只调接口)

• 流程:PyQt 选图 → 传给 Flask 后端 → 后端 AI 识别 → 返回类别 + 置信度 → 界面展示

安装依赖

bash 复制代码
pip install flask torch torchvision pillow requests pyqt5

一、服务端 Flask 代码(新建 server.py

放在和 garbage_resnet18_best.pth (用迁移学习resnet18跑出来的最优模型)同目录

python 复制代码
from flask import Flask, request, jsonify
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import os
import warnings
warnings.filterwarnings("ignore")

app = Flask(__name__)

DEVICE = torch.device("cpu")

# ====================== ✅ 你的官方正确类别顺序 ======================
CLASS_NAMES = ['塑料类', '玻璃类', '纸质类', '纺织类', '金属类']
# ====================================================================

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_PATH = os.path.join(BASE_DIR, "garbage_resnet18_best.pth")

transform = transforms.Compose([
    transforms.Resize([224, 224]),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

def load_model():
    model = models.resnet18(weights=None)
    num_ftrs = model.fc.in_features
    model.fc = nn.Sequential(
        nn.Dropout(0.3),
        nn.Linear(num_ftrs, 5)
    )
    state_dict = torch.load(MODEL_PATH, map_location=DEVICE)
    model.load_state_dict(state_dict)
    model.to(DEVICE)
    model.eval()
    return model

model = load_model()
print("✅ 模型加载成功,后端服务启动")

@app.route('/predict', methods=['POST'])
def predict():
    try:
        file = request.files['image']
        img = Image.open(file.stream).convert("RGB")
        img_tensor = transform(img).unsqueeze(0).to(DEVICE)

        with torch.no_grad():
            out = model(img_tensor)
            prob = torch.softmax(out, dim=1)
            conf, idx = torch.max(prob, dim=1)

        return jsonify({
            "class": CLASS_NAMES[idx.item()],
            "confidence": round(conf.item() * 100, 2)
        })
    except Exception as e:
        return jsonify({"error": str(e)})

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=False)

二、客户端 PyQt5 代码(新建 client_ui.py)

不再本地加载模型,只调用后端接口,标准 C/S 架构

python 复制代码
import sys
import requests
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import Qt

# 后端接口地址
API_URL = "http://127.0.0.1:5000/predict"

class GarbageUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("可回收垃圾智能识别系统【C/S架构】")
        self.setFixedSize(750, 650)
        self.img_path = None

        self.font = QFont("微软雅黑", 10)
        self.setFont(self.font)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(25)
        main_layout.setContentsMargins(50, 40, 50, 40)

        title = QLabel("♻️ 基于C/S架构的可回收垃圾智能识别系统")
        title.setFont(QFont("微软雅黑", 18, QFont.Bold))
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet("color: #1E9050;")

        self.img_label = QLabel()
        self.img_label.setFixedSize(600, 320)
        self.img_label.setStyleSheet("""
            QLabel{
                border: 2px dashed #888;
                border-radius: 12px;
                background-color: #ffffff;
                color: #666;
                font-size:14px;
            }
        """)
        self.img_label.setAlignment(Qt.AlignCenter)
        self.img_label.setText("🖼️ 请选择需要识别的垃圾图片")

        self.btn_choose = QPushButton("📁 选择图片")
        self.btn_predict = QPushButton("🔍 开始识别")
        btn_style = """
            QPushButton{
                font-size:14px; padding:12px 25px; border-radius:10px;
                background-color:#1E9050; color:white; font-weight:bold; min-width:180px;
            }
            QPushButton:hover{ background-color:#25A75F; }
            QPushButton:pressed{ background-color:#187A43; }
        """
        self.btn_choose.setStyleSheet(btn_style)
        self.btn_predict.setStyleSheet(btn_style)

        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        btn_layout.addWidget(self.btn_choose)
        btn_layout.addSpacing(30)
        btn_layout.addWidget(self.btn_predict)
        btn_layout.addStretch()

        self.result_label = QLabel("识别结果:等待连接服务器")
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setFont(QFont("微软雅黑", 14, QFont.Bold))
        self.result_label.setStyleSheet("color:#2C3E50; margin-top:10px;")

        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("客户端就绪,请先启动后端服务")

        main_layout.addWidget(title)
        main_layout.addWidget(self.img_label, alignment=Qt.AlignCenter)
        main_layout.addLayout(btn_layout)
        main_layout.addWidget(self.result_label)

        self.btn_choose.clicked.connect(self.choose_image)
        self.btn_predict.clicked.connect(self.run_predict)

    def choose_image(self):
        path, _ = QFileDialog.getOpenFileName(filter="图片 (*.jpg *.jpeg *.png *.bmp)")
        if path:
            self.img_path = path
            pix = QPixmap(path).scaled(580, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            self.img_label.setPixmap(pix)
            self.result_label.setText("✅ 已加载图片,点击开始识别")
            self.status_bar.showMessage(f"已加载图片:{path}")

    def run_predict(self):
        if not self.img_path:
            self.result_label.setText("❌ 请先选择图片!")
            return

        self.result_label.setText("🔍 正在请求服务器识别...")
        QApplication.processEvents()

        try:
            # 传图片给后端
            files = {"image": open(self.img_path, "rb")}
            res = requests.post(API_URL, files=files, timeout=10)
            data = res.json()

            if "error" in data:
                self.result_label.setText(f"❌ 识别失败:{data['error']}")
            else:
                self.result_label.setText(
                    f"🗑️ 识别类别:{data['class']}   置信度:{data['confidence']}%"
                )
                self.status_bar.showMessage("识别完成,服务器返回结果成功")
        except Exception as e:
            self.result_label.setText("❌ 无法连接后端服务,请先启动 server.py")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = GarbageUI()
    win.show()
    sys.exit(app.exec_())

三、运行顺序(必须按这个来)

  1. 先运行 server.py 启动后端 Flask 服务

  2. 再运行 client_ui.py 打开桌面客户端

  3. 客户端选图 → 点开始识别 → 自动调用后端 AI 模型

四、毕设 C/S 架构说明(可直接复制论文)

基于C/S(客户端 / 服务器)架构设计可回收垃圾智能识别系统,

• 服务端(Server)采用 Flask 框架搭建接口服务,部署训练完成的 ResNet18 深度学习模型,负责垃圾图像的特征提取与智能分类推理;

• 客户端(Client)基于 PyQt5 开发桌面可视化界面,仅提供图片选择、结果展示等人机交互功能,不加载深度学习模型;

• 客户端通过 HTTP 协议将待识别图片上传至服务端,服务端完成推理后将分类类别与置信度返回客户端展示。

该架构实现业务界面与 AI 模型解耦,支持多客户端共用模型服务,易维护、易扩展,满足桌面智能识别系统设计要求。

四、把后端服务 + PyQt 客户端打包成独立 EXE

打包后:电脑不用装 Python、不用装任何库,双击直接运行,答辩插上电脑就能演示。

前置准备

  1. 已经装好打包工具
python 复制代码
pip install pyinstaller

2.把我刚给你的正确版 server.py 保存好

3.把你的 garbage_resnet18_best.pth 模型文件放在同文件夹

4.两个文件路径都在:C:\本科毕业论文\garbage\

第一步:先打包后端 server.py

1. 进入项目文件夹

文件夹地址栏输入 cmd 回车,打开命令行。

2. 执行打包命令(后端专用)

python 复制代码
pyinstaller -F -c server.py

参数说明:

• -F 打包成单个 exe

• -c 保留黑框控制台(能看启动日志、识别请求,答辩好用)

3. 打包完成后

进入文件夹:garbage\dist\里面会出现:server.exe

关键一步(必做)

garbage_resnet18_best.pth 复制到 dist 文件夹里👉 exe 和模型必须在同一个文件夹,否则找不到模型!


第二步:打包 PyQt 客户端 client_ui.py

执行打包命令

复制代码
pyinstaller -F -w client_ui.py
  • -w 无黑框,纯桌面界面

打包好后 dist 里出现:client_ui.exe

客户端不用带模型,只调接口,直接能用。


第三步:答辩使用流程(以后每次都这么用)

  1. 打开 dist 文件夹
  2. 先双击运行 server.exe 看到 模型加载成功,Running on http://127.0.0.1:5000 就别动黑框
  3. 再双击运行 client_ui.exe 打开系统界面
  4. 选图片 → 开始识别,完全正常使用
  5. 关闭直接点右上角就行,不用任何环境配置

第四步:我给你规避打包必踩的坑(已经帮你做好)

  1. 我改的 server.py自动读取 exe 所在路径,不会报找不到模型
  2. 类别顺序和你训练集完全一致,识别不会错乱
  3. 打包后不依赖任何 Python 环境,任何 Windows 电脑都能跑

第五步:我把最终可直接打包的两份完整代码发给你

1. 打包专用最终版 server.py(直接覆盖旧文件)

复制代码
from flask import Flask, request, jsonify
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import os
import warnings
warnings.filterwarnings("ignore")

app = Flask(__name__)

DEVICE = torch.device("cpu")
# 和你训练集严格对齐的类别映射
CLASS_NAMES = ['塑料类', '玻璃类', '纸质类', '纺织类', '金属类']

# 自动获取exe所在目录,打包不会找不到模型
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_PATH = os.path.join(BASE_DIR, "garbage_resnet18_best.pth")

# 与训练完全一致的预处理
transform = transforms.Compose([
    transforms.Resize([224, 224]),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])

def load_model():
    model = models.resnet18(weights=None)
    num_ftrs = model.fc.in_features
    model.fc = nn.Sequential(
        nn.Dropout(0.3),
        nn.Linear(num_ftrs, 5)
    )
    state_dict = torch.load(MODEL_PATH, map_location=DEVICE)
    model.load_state_dict(state_dict)
    model.to(DEVICE)
    model.eval()
    return model

model = load_model()
print("✅ 模型加载成功,后端服务启动")

@app.route('/predict', methods=['POST'])
def predict():
    try:
        file = request.files['image']
        img = Image.open(file.stream).convert("RGB")
        img_tensor = transform(img).unsqueeze(0).to(DEVICE)

        with torch.no_grad():
            out = model(img_tensor)
            prob = torch.softmax(out, dim=1)
            conf, idx = torch.max(prob, dim=1)

        return jsonify({
            "class": CLASS_NAMES[idx.item()],
            "confidence": round(conf.item() * 100, 2)
        })
    except Exception as e:
        return jsonify({"error": str(e)})

if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=False)

2. 打包专用最终版 client_ui.py

复制代码
import sys
import requests
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPixmap, QFont
from PyQt5.QtCore import Qt

# 后端固定接口地址
API_URL = "http://127.0.0.1:5000/predict"

class GarbageUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("可回收垃圾智能识别系统【C/S架构】")
        self.setFixedSize(750, 650)
        self.img_path = None

        self.font = QFont("微软雅黑", 10)
        self.setFont(self.font)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(25)
        main_layout.setContentsMargins(50, 40, 50, 40)

        title = QLabel("♻️ 基于C/S架构的可回收垃圾智能识别系统")
        title.setFont(QFont("微软雅黑", 18, QFont.Bold))
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet("color: #1E9050;")

        self.img_label = QLabel()
        self.img_label.setFixedSize(600, 320)
        self.img_label.setStyleSheet("""
            QLabel{
                border: 2px dashed #888;
                border-radius: 12px;
                background-color: #ffffff;
                color: #666;
                font-size:14px;
            }
        """)
        self.img_label.setAlignment(Qt.AlignCenter)
        self.img_label.setText("🖼️ 请选择需要识别的垃圾图片")

        self.btn_choose = QPushButton("📁 选择图片")
        self.btn_predict = QPushButton("🔍 开始识别")
        btn_style = """
            QPushButton{
                font-size:14px; padding:12px 25px; border-radius:10px;
                background-color:#1E9050; color:white; font-weight:bold; min-width:180px;
            }
            QPushButton:hover{ background-color:#25A75F; }
            QPushButton:pressed{ background-color:#187A43; }
        """
        self.btn_choose.setStyleSheet(btn_style)
        self.btn_predict.setStyleSheet(btn_style)

        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        btn_layout.addWidget(self.btn_choose)
        btn_layout.addSpacing(30)
        btn_layout.addWidget(self.btn_predict)
        btn_layout.addStretch()

        self.result_label = QLabel("识别结果:等待连接服务器")
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setFont(QFont("微软雅黑", 14, QFont.Bold))
        self.result_label.setStyleSheet("color:#2C3E50; margin-top:10px;")

        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("客户端就绪,请先启动后端服务")

        main_layout.addWidget(title)
        main_layout.addWidget(self.img_label, alignment=Qt.AlignCenter)
        main_layout.addLayout(btn_layout)
        main_layout.addWidget(self.result_label)

        self.btn_choose.clicked.connect(self.choose_image)
        self.btn_predict.clicked.connect(self.run_predict)

    def choose_image(self):
        path, _ = QFileDialog.getOpenFileName(filter="图片 (*.jpg *.jpeg *.png *.bmp)")
        if path:
            self.img_path = path
            pix = QPixmap(path).scaled(580, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
            self.img_label.setPixmap(pix)
            self.result_label.setText("✅ 已加载图片,点击开始识别")
            self.status_bar.showMessage(f"已加载图片:{path}")

    def run_predict(self):
        if not self.img_path:
            self.result_label.setText("❌ 请先选择图片!")
            return

        self.result_label.setText("🔍 正在请求服务器识别...")
        QApplication.processEvents()

        try:
            files = {"image": open(self.img_path, "rb")}
            res = requests.post(API_URL, files=files, timeout=10)
            data = res.json()

            if "error" in data:
                self.result_label.setText(f"❌ 识别失败:{data['error']}")
            else:
                self.result_label.setText(
                    f"🗑️ 识别类别:{data['class']}   置信度:{data['confidence']}%"
                )
                self.status_bar.showMessage("识别完成,服务器返回结果成功")
        except Exception as e:
            self.result_label.setText("❌ 无法连接后端服务,请先启动 server.exe")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = GarbageUI()
    win.show()
    sys.exit(app.exec_())

你直接:

  1. 替换这两个代码文件
  2. 按我上面的命令打包
  3. 把 pth 模型放进 dist 文件夹
相关推荐
●VON12 小时前
鸿蒙NEXT视觉特效深度解析:gradientMask渐变遮罩与Material材质系统
华为·harmonyos·鸿蒙·材质·新特性
赛德传动2 天前
蜗轮蜗杆升降机丝杆材质不同对承载与耐磨度影响大吗?
制造·材质
远离UE42 天前
UE5 如何在延时渲染在材质编辑器提前使用到多灯光的数据
ue5·编辑器·材质
搁浅小泽3 天前
PFC电感各材质作用解析
材质·可靠性工程师
CG_MAGIC5 天前
PBR 材质通用套用技巧
3d·材质·效果图·建模教程·渲云渲染
CG_MAGIC8 天前
主流 3D 软件文件互通互导教程
3d·材质·效果图·建模教程·渲云渲染
吴梓穆9 天前
UE5 材质参数集
ue5·材质
阿斯加德D12 天前
我的世界生活大冒险整合包下载高版本2026最新分享
测试工具·游戏·游戏程序·生活·材质
BSD_HY13 天前
薄膜开关技术深度解析:PET与PC材质对比、工业4.0接口设计及汽车电子产品应用
汽车·人机交互·制造·材质·薄膜开关
邪修king13 天前
UE5 TA 核心修炼:材质与纹理艺术全解 —— 从 PBR 理论到工业级材质实战
c++·后端·游戏·ue5·材质