简介:这是一套基于PyQt5开发的轻量级水果识别系统Python源码,面向计算机、电子信息及人工智能方向的本科生与初学者,适用于课程设计、期末大作业或毕业设计参考,帮助理解图像识别基础流程与GUI交互实现。资源共27个文件,包含8个核心Python模块(如main_image_process.py、fruit_recognition.py、deblur.py等)、1个Qt Designer生成的UI界面文件、1个资源编译配置qrc文件、10张测试与示例图片(png/jpg),以及README.md和说明文档txt,整体压缩包仅1.54MB,结构清晰、依赖简洁,便于快速部署与二次开发。已有405人学习下载,代码涵盖图像预处理、特征提取、简易分类逻辑及界面信号槽绑定等关键环节,配套注释较完整,适合边读边调试,是入门CV+GUI融合项目的实用范例。 我在做图形界面应用时,最常被问到的一个问题是:“你写的是个命令行工具,能不能套个界面给我点着用?”水果识别系统就是冲着这个需求去的。项目本身用的是Python加PyQt5,把深度学习模型封装成一个带图形界面的桌面应用,用户不用敲一行命令,打开程序、选一张图片、点一下按钮,就能看到识别结果和置信度。整套源码打包成了zip,里面包含界面代码、模型推理脚本、训练记录和依赖清单,适合正在学PyQt5的初学者,也适合想把模型快速落地成演示项目的开发者。
这个系统的核心价值在于,它完整演示了“训练好的模型如何变成普通人能用的软件”这件事。很多教程都在讲怎么训练模型,但很少有人讲怎么把模型跑起来、怎么在界面上显示图片、怎么把推理结果格式化输出。这篇文章我会从整体设计思路讲起,再拆解界面实现和识别模块的对接方式,最后把我在实际开发中踩过的坑、排查问题的方法都整理出来。
1. 系统整体设计与技术选型
1.1 为什么选PyQt5做界面层
做桌面界面,Python生态里其实有几个选择:Tkinter、PyQt5/PySide2、Kivy、wxPython。我最终选了PyQt5,主要有三个原因。
第一,PyQt5的控件风格比较接近原生桌面应用。Tkinter做出来的界面总有一种“上世纪软件”的感觉,按钮和布局的精细度不够。PyQt5默认的Fusion风格或者叠加上QSS样式表之后,界面观感能提升一个档次,用户接受度更高。
第二,PyQt5对图片的处理非常方便。水果识别系统核心功能是让用户上传图片,PyQt5自带的QPixmap可以直接加载并显示常见格式的图片,配合QLabel就能做图片预览区域,不需要额外引入图像显示库。如果需要缩放,QPixmap.scaled一步搞定,这在Tkinter里要绕不少弯子。
第三,PyQt5的信号槽机制特别适合界面和业务逻辑解耦。用户点击按钮触发一个信号,界面线程调用识别函数,识别完成后通过信号把结果传回界面线程更新显示。这种异步的思维方式和深度学习的推理流程能很自然地结合。
如果你是完全没接触过PyQt5的新手,建议先跑通一个最简单的窗口程序,再慢慢往里面加控件。直接上手整个项目可能有点吃力,但拆开看其实每个部分都不复杂。
1.2 识别方案选型:深度学习模型的选择逻辑
水果识别的核心是图像分类任务,可选的方案大致分三类。
第一类是传统图像处理:提取颜色直方图、纹理特征、形状特征,再用SVM或随机森林分类。优点是计算量小、不用装深度学习框架,缺点是对相似外观的水果(比如红苹果和红番茄)区分能力很弱,泛化性能差。
第二类是自训练CNN模型:用PyTorch或TensorFlow自己搭一个卷积神经网络,从零训练。优点是可控性强,缺点是需要大量标注数据,训练周期长,容易过拟合。
第三类是迁移学习:用在大规模数据集上预训练好的模型(如ResNet50、MobileNetV3、EfficientNet),替换掉最后的全连接层,只微调最后几层,在自定义水果数据集上重新训练。
我选的是第三类,具体用的是ResNet50在ImageNet上的预训练权重,因为它的参数量适中、成熟度很高,在分类任务上有非常多的现成案例可以参考,出了问题也容易在网上找到解决方案。如果部署的机器性能较差,可以考虑换成MobileNetV3,但ResNet50作为通用选择,兼容性和稳定性都更好。
1.3 系统模块划分
整个系统的源码结构我整理成了这样:
fruit_recognition_system/ ├── main.py # 程序入口,启动PyQt5界面 ├── ui/ │ ├── main_window.py # 主窗口界面定义 │ └── styles.qss # 界面样式表 ├── model/ │ ├── classifier.py # 模型加载与推理封装 │ └── labels.json # 类别名称映射文件 ├── train/ │ ├── train.py # 模型训练脚本 │ └── dataset.py # 数据集加载与预处理 ├── utils/ │ └── image_utils.py # 图片读取、格式转换等工具 ├── models/ │ └── fruit_model.pth # 训练好的权重文件 ├── requirements.txt # 项目依赖 └── README.md # 项目说明文档UI层、模型层、工具层分离,是这套代码里我认为做得最值的地方。界面完全不知道模型内部用什么框架实现的,模型层也不关心界面怎么显示。你在扩展功能的时候,可以只改模型层的推理逻辑,界面和工具代码都不用动。
2. PyQt5界面设计与核心交互逻辑
2.1 主窗口布局思路
主界面的布局我设计成左右两栏结构。左侧是图片预览区,用一个QLabel作为展示区域,设置固定最小尺寸,同时开启了setScaledContents属性,这样图片可以自动缩放填充整个区域。右侧是操作区和结果展示区,包含“选择图片”按钮、“开始识别”按钮、识别结果标签和置信度进度条。
布局用QVBoxLayout和QHBoxLayout组合实现。左侧一个垂直布局,右侧一个垂直布局,外层用一个QHBoxLayout把左右拼起来。这种嵌套布局的好处是窗口拉伸时控件能自适应变化,不会出现控件位置漂移的问题。
我建议你尽量不要用setGeometry方式去绝对定位控件,因为不同分辨率下界面会乱掉。用布局管理器虽然写起来稍微绕一点,但适配性和后期维护性都更好。
下面是我界面核心部分的精简代码:
from PyQt5.QtWidgets import (QMainWindow, QWidget, QLabel, QPushButton, QFileDialog, QVBoxLayout, QHBoxLayout, QProgressBar, QMessageBox) from PyQt5.QtGui import QPixmap from PyQt5.QtCore import Qt class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("水果识别系统") self.setMinimumSize(800, 600) # 左侧图片显示区域 self.image_label = QLabel("请选择图片") self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet("border: 2px dashed #aaa; border-radius: 8px;") self.image_label.setMinimumSize(400, 400) # 右侧按钮和结果展示 self.select_btn = QPushButton("选择图片") self.recognize_btn = QPushButton("开始识别") self.result_label = QLabel("识别结果:等待输入") self.confidence_bar = QProgressBar() self.confidence_bar.setRange(0, 100) self.confidence_bar.setValue(0) # 右侧布局 right_layout = QVBoxLayout() right_layout.addWidget(self.select_btn) right_layout.addWidget(self.recognize_btn) right_layout.addWidget(self.result_label) right_layout.addWidget(self.confidence_bar) right_layout.addStretch() # 左右拼接 main_layout = QHBoxLayout() main_layout.addWidget(self.image_label, stretch=3) main_layout.addLayout(right_layout, stretch=1) container = QWidget() container.setLayout(main_layout) self.setCentralWidget(container) # 绑定事件 self.select_btn.clicked.connect(self.select_image) self.recognize_btn.clicked.connect(self.recognize_image)这段代码里有个小细节:setMinimumSize(800, 600)保证了窗口不会缩得太小导致布局挤压。右侧布局addStretch()让按钮组和结果区整体靠上,底部留白,看起来更舒服。
2.2 图片选择与预览的实现细节
选图功能用的是QFileDialog.getOpenFileName,这个方法是PyQt5封装好的原生文件对话框,不需要额外处理跨平台兼容性。
有个我认为比较关键的细节:在显示图片之前,一定要先判断用户是否真的选了文件。getOpenFileName在未取消选择时返回文件路径,取消时返回空字符串,如果不做判断直接加载,程序会因为文件路径为空而抛出异常。
还有一个常见问题是图片尺寸过大的情况。摄像头的图片动辄几千像素宽,如果直接把原图塞进QLabel,界面会卡顿甚至崩溃。这里我用QPixmap加载后强制缩放到标签尺寸,同时保持宽高比:
def select_image(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.jpg *.jpeg *.png *.bmp)" ) if not file_path: return self.current_image_path = file_path pixmap = QPixmap(file_path) # 缩放,保持宽高比,平滑处理 scaled_pixmap = pixmap.scaled( self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.image_label.setPixmap(scaled_pixmap)这里的Qt.KeepAspectRatio是我想特别强调的。如果不用这个参数,图片会被强行拉伸填满标签区域,出现变形,苹果变成长条的,用户一看就觉得不专业。Qt.SmoothTransformation则是让缩小后的图片边缘更平滑,避免出现锯齿。
2.3 识别按钮的交互逻辑
识别按钮的点击处理是整个界面逻辑的核心。完整流程是:检查是否已经选择了图片,未选择就弹窗提示;已选择就禁用按钮,防止重复点击;调用模型推理,把推理结果更新到界面上。
这里有个我在新手阶段踩过的坑:直接把模型推理放在按钮的clicked信号处理函数里。如果模型推理耗时较长,界面会进入“假死”状态,用户拖动窗口、点击其他地方都没反应,体验非常差。
原因是PyQt5的主线程是GUI线程,所有耗时操作如果直接在主线程执行,会阻塞事件循环,界面就卡住了。解决办法是把推理放到QThread子线程。
下面是我实际用的线程处理方案:
from PyQt5.QtCore import QThread, pyqtSignal class RecognizeWorker(QThread): result_ready = pyqtSignal(str, float) # 类别, 置信度 error_occurred = pyqtSignal(str) def __init__(self, image_path, classifier): super().__init__() self.image_path = image_path self.classifier = classifier def run(self): try: label, confidence = self.classifier.predict(self.image_path) self.result_ready.emit(label, confidence) except Exception as e: self.error_occurred.emit(str(e))在主窗口里这样调用:
def recognize_image(self): if not hasattr(self, 'current_image_path'): QMessageBox.warning(self, "提示", "请先选择图片") return self.recognize_btn.setEnabled(False) self.result_label.setText("正在识别中...") self.worker = RecognizeWorker(self.current_image_path, self.classifier) self.worker.result_ready.connect(self.on_result_ready) self.worker.error_occurred.connect(self.on_error) self.worker.start() def on_result_ready(self, label, confidence): self.result_label.setText(f"识别结果:{label}") self.confidence_bar.setValue(int(confidence * 100)) self.recognize_btn.setEnabled(True) def on_error(self, error_msg): QMessageBox.critical(self, "错误", f"识别失败:{error_msg}") self.recognize_btn.setEnabled(True)这样处理后,推理过程中界面依然可以正常响应,用户不会因为点击后界面卡死而重复点击或关闭程序。信号槽机制在这里起到了关键作用,子线程不能直接操作界面控件,必须通过信号把数据传给主线程再更新。
3. 水果识别模型训练与接口封装
3.1 数据集准备与预处理策略
模型要识别得准,首先数据集要对。我用的水果数据集包含了苹果、香蕉、橙子、葡萄、梨、西瓜等常见品类,每类图片数量在500到1000张之间。数据来源包括公开数据集和自己拍摄的补充照片。
训练前必须做数据清洗,把模糊的、有明显遮挡的、标签错误的图片剔除。这一步很多人会忽略,但恰恰是影响模型上限的关键。喂给模型一万张带噪声的图,不如给五千张干净的图。
数据预处理我做了三件事。第一是统一尺寸,所有图片resize到224x224,这是ResNet50标准输入尺寸;第二是归一化,像素值除以255后,再用ImageNet数据集的均值方差做标准化;第三是数据增强,包括随机水平翻转、随机旋转、随机亮度对比度变化,目的是增强模型对拍摄角度和光照变化的适应能力。
预处理代码如下:
from torchvision import transforms train_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.3, contrast=0.3), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_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]) ])这里要特别注意训练集和验证集的预处理差异。验证集不需要随机翻转和旋转,因为验证是为了评估模型在真实场景下的表现,不能人为制造额外的不确定性。
3.2 迁移学习训练细节
模型加载部分我用了PyTorch的torchvision模块,加载在ImageNet上预训练的ResNet50,然后替换最后的全连接层为新的分类层:
import torch import torch.nn as nn from torchvision import models def create_model(num_classes): model = models.resnet50(pretrained=True) # 冻结前面所有层 for param in model.parameters(): param.requires_grad = False # 替换最后一层全连接 in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) return model冻结前面层的原因是预训练模型已经学会了通用的图像特征提取能力(边缘、纹理、形状等基础特征),这些特征对水果识别同样适用,不需要重新训练。只需要训练最后新增的分类层,让模型学会把通用特征映射到特定的水果类别即可。这样训练速度大幅提升,而且需要的数据量也不用太多。
训练参数配置我放在这里做参考:
batch_size = 32 learning_rate = 0.001 epochs = 20 optimizer = torch.optim.Adam(model.fc.parameters(), lr=learning_rate) loss_fn = nn.CrossEntropyLoss()学习率设0.001是Adam优化器的常用默认值,经验上在这个量级比较稳。如果训练过程中loss震荡严重,可以试着降低到0.0001;如果收敛太慢,可以适当增大到0.01,但要注意过拟合风险。
训练过程中我每轮都会记录训练集准确率和验证集准确率。早停策略也很重要,当验证集准确率连续三轮没有提升时提前终止训练,能有效避免过拟合。我的最终模型在验证集上达到了大约93%的准确率,对常见水果的识别已经够用。
3.3 推理接口的设计与封装
为了让界面层调用方便,我把模型加载和推理过程封装成了一个独立的类。这个类的设计要点是:初始化时加载模型权重到内存,predict方法接收图片文件路径,返回类别标签和置信度。
import json import torch from PIL import Image from torchvision import transforms class FruitClassifier: def __init__(self, model_path, labels_path): # 加载类别标签 with open(labels_path, 'r', encoding='utf-8') as f: self.labels = json.load(f) # 加载模型,使用CPU推理,降低部署门槛 self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.model = models.resnet50(pretrained=False) self.model.fc = nn.Linear(self.model.fc.in_features, len(self.labels)) self.model.load_state_dict(torch.load(model_path, map_location=self.device)) self.model.to(self.device) self.model.eval() def predict(self, image_path): # 图片预处理 image = Image.open(image_path).convert('RGB') 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]) ]) tensor = transform(image).unsqueeze(0).to(self.device) # 推理 with torch.no_grad(): outputs = self.model(tensor) probabilities = torch.softmax(outputs, dim=1) confidence, predicted = torch.max(probabilities, 1) label = self.labels[str(predicted.item())] return label, confidence.item()这段代码里有几个细节值得注意。
第一是map_location=self.device,这样即使模型是在GPU机器上训练的,也能加载到没有GPU的机器上用CPU运行,兼容性更强。
第二是model.eval(),这个模式切换很关键。推理时必须确保模型处于eval模式而非train模式,否则BatchNorm层和Dropout层的行为差异会导致推理结果不稳定。
第三是torch.no_grad(),推理阶段不需要计算梯度,使用no_grad可以显著减少内存占用和计算开销。
4. 环境搭建与完整运行流程
4.1 开发环境与依赖安装
在开始跑项目之前,建议先创建一个独立的Python虚拟环境,避免依赖冲突污染系统Python。我用的是Python 3.9版本,搭配PyQt5 5.15系列,这个组合在Windows和Linux下都表现稳定。
requirements.txt里的核心依赖如下:
PyQt5==5.15.9 torch==1.13.1 torchvision==0.14.1 pillow==9.5.0 numpy==1.24.3 opencv-python==4.8.0.74安装步骤很简单:
# 创建虚拟环境 python -m venv fruit_env # 激活虚拟环境(Windows) fruit_env\Scripts\activate # 激活虚拟环境(Linux/macOS) source fruit_env/bin/activate # 安装依赖 pip install -r requirements.txt如果你不想为PyTorch的CPU版本占用太多磁盘空间,可以在官网选择适合你机器的安装命令,CPU版本安装包相对小很多。对于这个水果识别项目,CPU版本推理速度完全够用。
最后启动系统:
python main.py如果一切正常,就会弹出主窗口。
4.2 PyQt5安装的常见问题
PyQt5安装过程中最容易出的问题是pip下载超时或安装报错。我推荐用国内镜像源来加速:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple如果安装后运行报错,提示缺少Qt平台插件,多半是PyQt5的插件路径没有正确加载。最常见的报错是:
Could not load the Qt platform plugin "xcb" or "windows"这种问题一般是PyQt5核心库和系统库之间的兼容性问题。干净环境下重新安装PyQt5通常能解决,不要在这种报错上花太多时间纠结。
还有一个我在Windows上遇到过的坑:运行程序后窗口打开又立即闪退,没有任何报错信息。排查后发现是环境变量QT_QPA_PLATFORM被设置成了不存在的平台。解决方法是在代码最前面强制指定:
import os os.environ["QT_QPA_PLATFORM"] = "windows"这段放在import PyQt5之前,能避免很多环境层面的坑。
4.3 从启动到识别完成的完整流程
程序启动后的完整流程图解(文字版)是这样的:
- Python解释器执行main.py,创建QApplication对象。
- 加载FruitClassifier模型,此时界面可能短暂白屏,因为模型权重加载需要时间。
- 创建主窗口对象,设置布局和连接信号槽。
- 调用window.show()显示主界面,进入Qt事件循环。
- 用户点击“选择图片”,在文件对话框中选择本地图片。
- 图片显示在预览区域,路径保存到内存变量。
- 用户点击“开始识别”,界面禁用按钮并显示“识别中”。
- 后台线程加载图片、预处理、模型推理,返回类别和置信度。
- 主线程通过信号槽更新界面,显示结果和进度条。
在第2步加载模型时,如果模型文件比较大(ResNet50的权重大约100MB左右),加载可能需要几秒钟。为了避免启动时闪退,我给main.py加了简单的启动画面逻辑,先显示“正在加载模型...”的提示窗口,等模型加载完成再显示主界面。
这个细节虽然简单,但显著提升了程序的整体体验,用户不会以为程序死了。
5. 常见问题与排查技巧实录
5.1 界面卡死与无响应
症状:点击“开始识别”后,整个窗口无法拖动,标题栏显示“未响应”。
原因:模型推理放在了主线程执行,阻塞了Qt事件循环。
解决方法:参考本文2.3节,把推理放到QThread子线程中执行。核心原则是:所有耗时操作(网络请求、文件读写、模型推理、大数据处理)都必须离开GUI主线程。
这块我专门补充一个排查思路。当程序卡死时,可以点击窗口标题栏右键,选择系统菜单,观察窗口是否能正常响应。如果系统菜单能打开,说明界面进程没死,只是事件循环被阻塞;如果连系统菜单都打不开,那就是Python解释器被某些死循环或死锁卡住了。前者多半是线程问题,后者可能是C扩展库的问题,排查方向完全不一样。
5.2 识别的置信度异常高但结果错误
症状:模型对每张图片都输出接近100%的置信度,但识别结果是错误的。
原因:模型严重过拟合了训练集,或者训练集和测试集的数据分布差异太大。置信度欺骗性很高,模型虽然“笃定”,但它的确没见过类似的图。
解决方法:使用独立的验证集评估真实准确率,不要只依赖置信度判断模型好坏。增加数据增强的强度和种类,让模型见过更多形态的图片。收集更多真实场景的测试图片,验证模型的泛化性能。
我实际测试中发现,这个项目如果只用公开数据集训练,对自拍照片的识别准确率会明显下降。因为公开数据集的图片大多是购物网站的商品图,背景干净、光照均匀、颜色鲜艳,而自拍照片有桌面纹理、手指遮挡、窗户反光等干扰。把自拍照片加入训练集后,准确率提升非常明显。
5.3 打包exe后运行报错
症状:用PyInstaller打包成exe,换一台电脑运行时报错,提示找不到模型文件。
原因:PyInstaller打包时没有把模型权重和标签JSON文件包含进可执行文件。
解决方法:把资源文件作为数据文件打包,并在代码里使用相对可靠的路径解析逻辑:
import sys import os def resource_path(relative_path): """根据程序运行环境获取资源文件的绝对路径""" base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path)使用PyInstaller打包时,在spec文件里追加:
datas=[('models/fruit_model.pth', 'models'), ('model/labels.json', 'model')]打包命令可以这样写:
pyinstaller --windowed --onefile --add-data "models/fruit_model.pth;models" --add-data "model/labels.json;model" main.py注意Windows的add-data参数用分号分隔源路径和目标路径,Linux和macOS用冒号。这个坑我踩过不止一次。
5.4 数据集很小的情况下如何提升准确率
症状:每一类水果只有几十张图片,训练出来的模型准确率只有百分之六七十。
解决方法:少样本场景下有几个实用技巧。一是数据增强,把每张训练图扩增出翻转变体、旋转变体、色彩抖动变体,相当于人工扩大数据集。二是更激进的冻结策略,只训练最后一层分类器,让前层特征提取完全使用ImageNet预训练的能力。三是更换更小的模型,MobileNetV3在小数据集上往往比ResNet50表现更好,因为参数量少,过拟合风险低。
我试过最多把六十张训练图扩增到接近三百张,配合数据增强,最终准确率从68%提升到了85%以上,虽然不算很高,但对于演示项目已经够用。
6. 性能优化与扩展方向
6.1 模型推理延迟优化
如果你的机器CPU性能一般,ResNet50推理单张图片可能需要几百毫秒到一秒不等,界面上会感觉到明显的延迟。
优化手段有几个方向。一是换成更轻量的模型,比如MobileNetV3 Small,推理速度可能提升三到五倍,准确率下降在可接受范围内。二是使用ONNX Runtime加速,把PyTorch模型导出为ONNX格式,ONNX Runtime的CPU推理性能通常比PyTorch原生的CPU推理高一截。三是使用OpenVINO工具套件,它在Intel家的CPU上优化效果尤为明显。
ONNX导出示例:
import torch import torch.onnx model = torch.load('fruit_model.pth', map_location='cpu') dummy_input = torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, 'fruit_model.onnx', input_names=['input'], output_names=['output'])6.2 从单张图片识别扩展到实时视频识别
这个系统目前是单图片识别,但很多人都会问一个同样的问题:能不能用摄像头实时识别水果?
可以。基于现有代码做扩展,思路是:用OpenCV的VideoCapture打开摄像头,逐帧读取,每帧先经过同样的预处理(resize到224x224、归一化),再丢给模型推理。为了降低计算压力,可以跳帧处理,每隔两三帧识别一次,识别结果叠加显示在视频帧上。
在PyQt5界面里做实时识别,视频帧获取放到QTimer定时器里,每30毫秒触发一次。注意还是不能把推理放到主线程,否则视频预览会卡顿。理想方案是视频采集和推理都在子线程,界面只负责显示最新的帧和识别结果。
6.3 源码的二次开发建议
拿到这套源码之后,如果你想自己改造,我的建议是不要直接从界面代码开始看。先从main.py开始,看程序如何初始化;然后看model/classifier.py,弄清楚预测接口的输入输出;最后再看ui/main_window.py,理解界面如何调用模型。这样从主到次、从核心到外围,理解速度会快很多。
如果你想支持自己的数据集,只需要做三件事:换掉model/labels.json里的类别名称,在train目录下准备好和类别一一对应的图片文件夹,重新运行train.py训练脚本。训练完成后替换models/fruit_model.pth即可,界面代码不用动。
我在实际使用中发现,这个系统后续最有价值的扩展方向是加一个“识别历史记录”功能,把每次识别的图片和结果保存到本地SQLite数据库里。这样不仅能追溯历史记录,还能统计哪些水果识别的准确率偏低,反过来指导训练数据的补充。
最后再分享一个小技巧:在开发调试阶段,可以在main.py里加一个--debug命令行参数,开启调试模式后把所有异常堆栈打印到终端。这样上线后用户遇到问题,只需要让他用调试模式跑一遍,把终端日志发给你,就能快速定位问题根因。我在多个桌面应用项目里都用这个方法,排查问题效率高了不少。
本文还有配套的精品资源,点击获取