YOLOv11垃圾检测与PyQt5智能分类系统实战全流程
做垃圾分类识别很多人的第一反应是“这不就是拿个目标检测模型跑一下嘛”。等真上手了才发现模型训练只是其中一环怎么把模型变成别人能用的工具才是真正的分水岭。我之前帮朋友搭一套社区智能垃圾桶的演示系统前前后后踩了不少坑数据集标注格式不对、训练到一半显存炸了、GUI界面一拖图片就假死、检测结果不知道怎么导出给后端……这些坑每一个都真实消耗过时间。这篇文章就完整记录我基于YOLOv11搭建垃圾检测智能分类系统的全过程从数据集处理、模型训练调优到用PyQt5从零写一个带GUI的桌面应用最后还能把推理结果保存成图片和表格。整个过程不需要你有很强的算法底子Python基础加一点耐心就能跟着做下来。文中涉及的所有关键代码、配置参数、界面布局思路我都会贴出来也把踩过的坑整理成速查表放在最后希望对正在折腾YOLOv11的朋友有帮助。1. 项目整体设计与技术选型1.1 为什么是YOLOv11模型选型的核心考量先聊一下选型。市面上能做目标检测的模型太多了YOLOv5、YOLOv8、YOLOv9、YOLOv10到现在的YOLOv11还有各种变体。为什么最终我选了YOLOv11做垃圾检测不是因为“最新就是最好”而是它在几个关键指标上确实更适合这种场景。首先是推理速度和精度的平衡。垃圾分类这个场景很有意思它既不像工业质检那样对精度有近乎苛刻的要求又不像视频监控那种需要超高清大图。更多时候它跑在边缘设备或者普通办公电脑上比如给垃圾桶装一个摄像头、或者做一个桌面端的识别工具对实时性有要求但不能牺牲太多准确率。YOLOv11在COCO上的mAP比YOLOv8提升了约1.5到2个点但推理速度基本没掉这个提升对于垃圾这类形状不规则、类间差异大的目标来说挺关键的。其次是部署和生态。YOLOv11由Ultralytics维护这个库的成熟度非常高。训练、验证、导出、推理一条命令就能跑通而且自带的数据增强策略已经调得比较合理。对于不搞算法研究、只想快速落地一个应用的人来说这是巨大的时间节省。还有一点容易被忽略垃圾检测里大量的目标是“小物体”。比如烟头、瓶盖、碎片化的纸张在摄像头画面里可能就几十个像素。YOLOv11的backbone在特征提取上做了优化尤其是浅层特征和高层特征的融合更高效对小目标的召回率比前代有一定提升。这一条我后面会展开讲单纯从选型角度说YOLOv11在这个场景下是够用的。1.2 GUI框架怎么选PyQt5、Tkinter还是Gradio模型选完了接下来是GUI方案。我见过不少人卡在这一步模型跑通了但不知道怎么给别人用。有拿Tkinter写界面的有直接丢个命令行工具出去的还有想着用Web框架做个网页版的。我的建议很简单如果是做桌面工具直接用PyQt5或PySide6如果想快速演示给非技术人员看用Gradio如果想做真正的产品原型还是Qt系列最靠谱。Tkinter为什么不太推荐它虽然Python自带、不需要额外安装但做出来的界面是真的粗糙。垃圾桶识别这种应用你要展示图片、画检测框、显示置信度、放按钮、出结果列表Tkinter的组件栈写起来又啰嗦又难看自绘能力也弱。做内部脚本可以做演示工具不行。Gradio现在很多做AI的人喜欢用因为几行代码就能出一个网页版的交互界面拖拽上传图片就能看到结果。但它的短板也很明显一是自由度低界面布局基本被框死二是摄像头实时识别这种功能实现起来很别扭三是如果你后面要接硬件比如单片机控制垃圾桶翻盖Gradio那套异步机制反而是负担。PyQt5我比较熟控件丰富、布局灵活、支持QSS样式表做界面美化视频和图片双路输入都能很好处理。虽然它也有学习曲线——信号槽机制、布局管理器、线程模型——但这些是GUI开发的通用知识学一次受益很久。而且PyQt5基于C的Qt框架性能稳定做桌面工具是正经选择。2. 环境配置与数据集处理2.1 环境配置的坑与版本搭配YOLOv11的环境配置这个热搜词榜上的常客但其实配置起来没有想象中那么难难的是版本匹配。我配置的时候用的是Python 3.10 CUDA 12.1 PyTorch 2.x整体很顺利。这里给大家一个明确的版本参考# 创建虚拟环境推荐用conda管理起来省心 conda create -n yolov11 python3.10 conda activate yolov11 # 安装PyTorch这里用CUDA 12.1的版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 # 安装ultralyticsYOLOv11的官方库 pip install ultralytics # 安装GUI相关依赖 pip install PyQt5 opencv-python pillow pandas有几个真实的坑要说一下。第一个是Python版本别用Python 3.13当前版本的PyTorch和部分依赖对3.13的支持还不稳定装起来容易报编译错误。第二个是Ultralytics版本尽量装最新的老版本可能不包含YOLOv11的模型定义如果你import之后发现没有ultralytics/models/yolo/yolov11这类文件那就先升级库版本。第三点是如果你电脑的显卡比较老CUDA版本兼容不了那就直接用CPU版垃圾检测这种场景CPU推理也能跑出能用的速度我后面会讲怎么优化。验证环境是否ok一行命令就够了yolo predict modelyolo11n.pt sourcehttps://ultralytics.com/images/bus.jpg能正常输出结果就说明环境和模型文件都通了。2.2 垃圾数据集准备从哪里找、怎么做标注数据集是垃圾检测项目里最耗时的部分很多人到这一步就放弃了。市面上公开的垃圾数据集其实不少零零散散分布在Kaggle、GitHub和一些高校的项目页上。比较常用的是TACO这个数据集它有超过1500张野外垃圾图像场景丰富类别覆盖食物包装、塑料瓶、易拉罐、纸板、玻璃、烟头等。缺点是有不少图像的标注质量不是特别好需要人工清洗。如果你要做一个更可控的分类系统我推荐自己采集加标注。也别怕垃圾检测不需要像工业质检那样追求十几万张图几千张高质量的图片足够训练出一个能用的模型。数据集的目录结构是这样的garbage_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── classes.txtlabels里面存的是YOLO格式的txt标注文件格式是class_id center_x center_y width height注意坐标是归一化的取值0到1。比如一张640x640的图里一个目标中心点在 (320, 160)宽高是 (160, 320)那对应的标注就是0 0.5 0.25 0.25 0.5标注工具我推荐用LabelImg或者AnyLabeling。LabelImg老牌但稳定AnyLabeling支持自动标注可以先用YOLOv11模型预标注再人工修正效率能提升不少。数据标注这一块有个非常容易踩的坑类别不平衡。垃圾场景里饮料瓶和纸盒的数量可能占80%烟头、玻璃碎片、电池这些危险或小件垃圾可能只有几张图。这样训练出来的模型对小类别几乎不识别。我的做法是对样本少的类别做离线数据增强比如水平翻转、旋转、亮度调整、加噪声把样本数量拉到其他类别的三分之一以上再开始训练效果好了不少。data.yaml的内容很简单train: D:/garbage_dataset/images/train val: D:/garbage_dataset/images/val nc: 5 names: [plastic, paper, glass, metal, organic]这里的类别名称、数量根据你自己的数据集来。特别注意路径别用中文也尽量不要用相对路径后面导出和推理容易出幺蛾子。3. YOLOv11训练与优化3.1 训练参数调整与命令行实操数据准备好了可以开始训练了。Ultralytics的命令行封装得很友好直接在终端跑就行yolo train modelyolo11n.pt datadata.yaml epochs100 imgsz640 batch16 device0这几个参数我展开说说里面全是经验。modelyolo11n.pt是加载预训练权重。YOLOv11提供了n/s/m/l/x这几个尺寸从轻到重。垃圾检测这种任务是中轻量应用用n或s就行。如果你用的是nano版本可以看到显存占用大概在4-6GB左右普通消费级显卡都能跑得动。epochs是我最早纠结的参数。我一开始觉得100轮不够直接跑了300轮结果到第180轮左右就开始过拟合了验证集mAP不升反降。后来改为100轮加早停机制效果反而更好。Ultralytics默认自带早停patience50所以100轮起步让模型自己决定什么时候停就好。batch16要根据显存来调整。显存不够会报CUDA out of memory一个技巧是先把batch设为2试一下能跑就把batch加倍直到显存告急。或者直接把device0改成devicecpu牺牲速度换稳定性。训练过程还会自动生成很多输出文件包括权重、图表、混淆矩阵都在runs/detect/train/目录下。关于调参还有一点很关键学习率策略和优化器不需要你手动调Ultralytics默认的SGD加上warmup效果已经很稳定了。之前有人推荐用AdamW我试过收敛快但精度容易抖动最后还是换回了默认方案。做应用的人别在炼丹上花费太多精力默认配置足够撑起一个能用的模型。3.2 看懂训练结果mAP、混淆矩阵和PR曲线训练完成后打开runs/detect/train/目录里面有模型自己生成的评估图表。很多新手只会看results.png但那只是损失曲线更关键的是后面几个文件。confusion_matrix.png是你最该看的第一张图。它显示的是每个类别之间的混淆情况。我第一版训练时plastic和paper之间就有明显的混淆——塑料瓶和纸盒在画面里颜色接近、形状又都偏长方模型经常把纸盒识别成塑料。看到这个我就知道该去检查标注质量把标注边界框画精确尤其是那些边缘不清晰的目标。第二版训练后混淆率明显下降。PR_curve.png是Precision-Recall曲线它反映的是模型在哪个置信度阈值下表现最好。如果你看到曲线右下角明显凹陷说明你有一批高质量正样本被漏掉了可以考虑适当降低置信度阈值。val_batch0_labels.jpg和val_batch0_pred.jpg这两张图更直观左边是标注的GT框右边是模型预测的框。我之前发现模型预测的框普遍偏大就是在这种对比图里看出来的然后去检查了标注工具生成框时的锚定方式。这些图表是模型性能的直接反映不能只看loss数值。我见过有人loss降到0.02就说“模型训练好了”结果在真实垃圾桶场景里完全不能用。loss低只能说明训练收敛但泛化能力要看mAP、看混淆矩阵、看实际场景验证。3.3 小目标与难样本优化思路垃圾检测尤其是摄像头拍垃圾桶的场景很多垃圾是重叠的、光线暗的、物体又小。这一类小目标检测优化我算是被折磨过很久这里给出几个行之有效的方案。第一招是提高输入分辨率。YOLOv11默认训练分辨率是640x640但如果你把它提到960甚至1280对小目标的检测效果是立竿见影的。代价是推理速度下降、显存占用增加。如果是桌面端应用这个代价可以接受yolo train modelyolo11n.pt datadata.yaml epochs100 imgsz960 batch8 device0第二招是调整anchor。Ultralytics的原理是自适应anchor可以在训练时自动通过学习得到。但如果你发现小目标完全检测不到可以在data.yaml里明确指出目标尺度anchors: - [5, 8, 10, 16, 20, 25] # P3层 - [30, 40, 60, 80] # P4层这个配置我实际试过配合960分辨率烟头这类小目标准召率提升了大概10%。要注意的是anchor的数值要根据你数据集中真实的目标尺寸来设置先用程序统计一下所有标注框的宽高分布再手动指定。第三招是数据增强里添加马赛克增强的调整。Ultralytics默认开启mosaic增强效果好但会让很多垃圾在混合图里变得太小。我自己是大幅调高了scale参数、降低了mosaic在后期训练中的概率让模型更多关注到真实的目标尺度。4. 带GUI的智能分类系统开发4.1 界面布局设计与交互逻辑训练好模型接下来就是要把模型塞进一个普通人能用的GUI界面里。我用PyQt5实现了一个完整的桌面应用界面的布局逻辑可以给你参考。主界面的结构分三部分。左侧面板是功能控制区包含“选择图片”“摄像头检测”“开始检测”“暂停”“保存结果”这几个按钮。中间大区域是图像显示区用QLabel承接QPixmap的实时画面。右侧是结果面板用一个QTableWidget显示每次检测的目标类别、置信度和坐标信息底部还有一个状态栏显示当前FPS和模型推断耗时。这个布局有三个核心设计原则。第一检测过程不能在主线程跑。PyQt5的事件循环是单线程的如果你直接在主线程里调用model.predict()一检测界面就假死了拖都拖不动。解决办法是用QThread把推理放到子线程里完成后通过信号把结果返回主线程更新界面。这个后面会给核心代码。第二图片显示需要做坐标映射。模型检测输出的边界框坐标是在输入图片分辨率下的而你在界面上显示的图片可能缩放了。所以需要在界面尺寸和图片尺寸之间做换算不然画的检测框就会偏移。这个坑我踩了很久尤其当窗口大小可调时检测框就直接飘到屏幕外面去了。后来我就是固定显示区域用一个固定缩放比例只在初始化时计算一次映射关系问题就解决了。第三结果展示的信息一定要完整。类别名称不要只显示数字下标要有一个可以随时扩充的类别映射表。我测试的时候把模型训练用的类别序号映射成中文名称在界面上直接显示“塑料瓶”“易拉罐”这种用户看得懂的名字某种程度上比检测框本身更重要。4.2 PyQt5核心代码实现下面给出核心代码创建一个带GUI的检测主窗口。主要依赖是PyQt5 ultralytics opencv-python。import sys import cv2 import numpy as np from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QTableWidget, QTableWidgetItem, QHeaderView from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class DetectThread(QThread): result_ready pyqtSignal(dict) frame_ready pyqtSignal(QImage) def __init__(self): super().__init__() self.model None self.image None self.running False def load_model(self, model_path): self.model YOLO(model_path) def set_frame(self, frame): self.image frame.copy() def run(self): if self.model is None or self.image is None: return results self.model.predict(self.image, conf0.25, verboseFalse) data self.parse_results(results) img results[0].plot() h, w, ch img.shape bytes_per_line ch * w qimg QImage(img.data, w, h, bytes_per_line, QImage.Format_RGB888).rgbSwapped() self.frame_ready.emit(qimg) self.result_ready.emit(data) def parse_results(self, results): boxes results[0].boxes items [] if boxes is not None: for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls_id int(box.cls[0]) items.append({ cls: self.model.names[cls_id], conf: round(conf, 3), bbox: [round(x1, 1), round(y1, 1), round(x2, 1), round(y2, 1)] }) return {items: items, count: len(items)} class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(YOLOv11 垃圾检测智能分类系统) self.setMinimumSize(1000, 650) self.model_path None self.current_image None self.detect_thread DetectThread() self.init_ui() self.init_slots() def init_ui(self): central QWidget() self.setCentralWidget(central) main_layout QHBoxLayout(central) left_panel QVBoxLayout() btn_load_model QPushButton(加载模型) btn_load_image QPushButton(选择图片) btn_detect QPushButton(开始检测) btn_save QPushButton(保存结果) for btn in [btn_load_model, btn_load_image, btn_detect, btn_save]: left_panel.addWidget(btn) left_panel.addStretch(1) main_layout.addLayout(left_panel, 1) self.image_label QLabel(图片显示区) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet(border: 1px solid #aaa; background: #f5f5f5;) main_layout.addWidget(self.image_label, 4) right_panel QVBoxLayout() self.table QTableWidget() self.table.setColumnCount(3) self.table.setHorizontalHeaderLabels([类别, 置信度, 坐标 (x1,y1,x2,y2)]) self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) right_panel.addWidget(self.table, 3) self.status_label QLabel(就绪) right_panel.addWidget(self.status_label, 1) main_layout.addLayout(right_panel, 2) self.btn_load_model btn_load_model self.btn_load_image btn_load_image self.btn_detect btn_detect self.btn_save btn_save def init_slots(self): self.btn_load_model.clicked.connect(self.load_model) self.btn_load_image.clicked.connect(self.load_image) self.btn_detect.clicked.connect(self.start_detect) self.btn_save.clicked.connect(self.save_result) self.detect_thread.frame_ready.connect(self.update_frame) self.detect_thread.result_ready.connect(self.update_result) def load_model(self): path, _ QFileDialog.getOpenFileName(self, 选择模型文件, , PyTorch Model (*.pt)) if path: self.model_path path self.detect_thread.load_model(path) self.status_label.setText(f模型已加载: {path}) def load_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , 图片文件 (*.jpg *.jpeg *.png *.bmp)) if path: self.current_image cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR) h, w, ch self.current_image.shape qimg QImage(self.current_image.data, w, h, ch * w, QImage.Format_BGR888) self.image_label.setPixmap(QPixmap.fromImage(qimg).scaled(self.image_label.size(), Qt.KeepAspectRatio)) def start_detect(self): if self.current_image is None: self.status_label.setText(请先选择图片) return if self.model_path is None: self.status_label.setText(请先加载模型) return self.detect_thread.set_frame(self.current_image) self.detect_thread.start() def update_frame(self, qimg): self.image_label.setPixmap(QPixmap.fromImage(qimg).scaled(self.image_label.size(), Qt.KeepAspectRatio)) def update_result(self, data): self.table.setRowCount(data[count]) for i, item in enumerate(data[items]): self.table.setItem(i, 0, QTableWidgetItem(item[cls])) self.table.setItem(i, 1, QTableWidgetItem(str(item[conf]))) self.table.setItem(i, 2, QTableWidgetItem(str(item[bbox]))) self.status_label.setText(f检测完成共识别 {data[count]} 个目标) def save_result(self): if not self.current_image is None: path, _ QFileDialog.getSaveFileName(self, 保存检测结果, , PNG图片 (*.png)) if path: pixmap self.image_label.pixmap() if pixmap: pixmap.save(path) self.status_label.setText(f结果已保存到: {path}) if __name__ __main__: app QApplication(sys.argv) win MainWindow() win.show() sys.exit(app.exec_())这段代码已经是一个可运行的基础框架了。需要注意一点就是cv2.imdecode(np.fromfile(...))这种写法是为了兼容中文路径。直接cv2.imread(中文路径.jpg)在某些版本的OpenCV里会返回None这个问题我当时排查了很久。如果你是Linux或Mac环境用cv2.imread问题不大但Windows上强烈建议用imdecode。4.3 用QThread解决界面卡顿上面的代码里核心的思路就是把推理放到DetectThread里。很多初学者犯的错误是在主窗口类里面直接写def on_detect_clicked(self): results self.model.predict(self.current_image)这样看起来没问题但是当你点击“检测”按钮后程序会卡在predict这一步窗口无法拖动、按钮无法点击严重的时候直接被操作系统判定“无响应”。原因就是PyQt5的主线程被推理占住了事件循环来不及处理界面刷新。QThread的用法是把耗时的操作放在run()方法里用信号把结果传回主线程。主线程收到信号后通过槽函数更新界面。这样推理过程在后台执行界面保持顺滑。我这里用的result_ready和frame_ready两个信号一个传结构化数据给表格一个传绘制好的图像给显示区。一个常见的进阶需求是视频流或摄像头实时检测。思路是一样的只是把set_frame改成不断读取摄像头的帧即可。注意摄像头读取和模型推理都要在线程里而且这两个操作建议拆成两个线程中间用队列接帧——不要在一个线程里又是读帧又是推理否则还是会卡。5. 推理结果保存与常见问题排查5.1 检测结果的保存与导出热搜词里不少人搜“yolov11预测后保存”和“yolov11保存推理结果”我专门说说这个。Ultralytics在命令行推理时保存结果很容易yolo predict modelbest.pt sourcetest.jpg saveTrue save_cropTrue save_txtTruesaveTrue保存带检测框的完整图片save_cropTrue把每个检测到的目标单独裁剪保存save_txtTrue把检测结果保存为YOLO格式的txt坐标文件但这三个选项在GUI程序里不太够因为GUI里我们要把检测数据反馈到界面表格中而且要按用户指定的目录保存。我在代码里加了一个完整的保存模块import csv import os def save_detection_result(self, save_dir, img_bgr, items, image_name): os.makedirs(save_dir, exist_okTrue) # 1. 保存原图带检测框 output_img_path os.path.join(save_dir, fdetected_{image_name}) cv2.imwrite(output_img_path, img_bgr) # 2. 保存裁剪目标 crop_dir os.path.join(save_dir, crops) os.makedirs(crop_dir, exist_okTrue) for i, item in enumerate(items): x1, y1, x2, y2 [int(v) for v in item[bbox]] h, w img_bgr.shape[:2] x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) crop img_bgr[y1:y2, x1:x2] crop_path os.path.join(crop_dir, f{i}_{item[cls]}_{item[conf]:.3f}.jpg) cv2.imwrite(crop_path, crop) # 3. 保存CSV结构化数据方便后续统计分析 csv_path os.path.join(save_dir, detections.csv) with open(csv_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) if os.path.getsize(csv_path) 0: writer.writerow([image, class, confidence, x1, y1, x2, y2]) for item in items: writer.writerow([image_name, item[cls], item[conf], *item[bbox]])在GUI里这个模块和“保存结果”按钮连接起来就能实现一键导出。CSV文件建议用utf-8-sig编码这样在Excel里打开中文不会乱码这个是很多教程没提到的技巧。5.2 常见问题排查表与避坑经验项目做完之后我整理了一份避坑清单基本覆盖了从环境配置到GUI运行的全部高频问题现在贴给大家参考。问题现象原因分析解决方案训练时CUDA out of memorybatch_size过大或图像分辨率太高调低batch到2或降imgsz到480模型在cpu上推理慢得离谱没有启用OpenVINO或ONNX优化导出ONNX用onnxruntime速度提升明显中文路径下cv2.imread返回NoneOpenCV不支持中文路径使用cv2.imdecodes.fromfile读图PyQt5界面一检测就无响应推理阻塞了主线程把推理放入QThread图片显示时检测框偏移界面缩放与图片尺寸未映射固定显示区域按比例映射坐标export onnx时报路径错误模型文件和导出路径含中文或空格全部改为英文路径模型预测类别全是同一个训练时类别不平衡对小类别做数据增强或扩充样本实时检测FPS过低输入分辨率过大或使用大模型换yolo11nimgsz降到480或用TensorRT这里面我想重点展开两个我反复踩过的问题。第一个是ONNX导出和C部署时模型路径不能含中文。Ultralytics在export的时候如果路径是D:\垃圾检测\best.pt这种会在解析项目名时崩掉或者生成异常文件。解决方法是路径全部转成英文即使你最终部署到Windows系统工具问题和模型文件位置都放到英文目录下。这个避坑能省一个下午。第二个是OpenCV中文路径读取问题。这个在Windows上尤其常见cv2.imread(D:\图片\test.jpg)会返回None但你看文件确实存在。这个不是文件路径写错而是OpenCV的C底层把中文路径编码处理错了。一律用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)读取问题就消失了。还有一个小技巧想分享给做GUI的朋友PyQt5里加载模型对慢的电脑来说也是个耗时操作不要放在启动窗口时同步加载。我一个做法是做了个“延迟加载”窗口先弹出来模型在后台线程加载加载完更新状态栏提示。用户体验完全不一样不会出现打开程序转圈半天的情况。写在最后个人实操体会整套系统从零搭完我自己最深的体会是这类项目真正的门槛不在于模型有多高级而在于怎么把数据、训练、界面、保存这一整条链路串起来。YOLOv11这个模型本身已经很好用了官方库封装也很完善真正费时间和精力的点往往都在边界问题——路径兼容性、界面卡顿、结果导出、小目标识别。把这些磨顺了项目就能跑了。如果你们后面想继续扩展这个项目我推荐两个方向。一个是接入摄像头做实时垃圾桶识别可以配合硬件控制垃圾桶翻盖这个对QThread和帧队列的要求更高一些。另一个是把自己的数据集扩大把更多垃圾类别加进去然后定期用小批量新数据微调模型让系统越用越准。我自己还在给社区版的垃圾分类做Web接口的对接等跑通了我再写一篇跟大家分享。