简介:这是一份面向Python初学者、课程设计与毕业设计学习者的简易版水果识别系统源码,基于Pyqt5搭建图形界面,融合图像处理与机器学习算法,帮助理解GUI开发与分类识别的基本流程。压缩包共27个文件,约1.54MB,包含8个py源码文件、10个png图片、1个ui界面文件、1个qrc资源文件及pyc缓存、说明文档等,涵盖界面逻辑、图像预处理、特征提取与分类器训练等模块,结构清晰便于按功能阅读。已有80人学习下载。读者可从中获取完整的项目目录组织方式、Pyqt5界面与业务代码的分离写法、灰度化与边缘检测等预处理思路,以及SVM或KNN分类器的训练与调用示例,适合作为课程设计参考或图像识别入门练手项目。
1. 从一张水果照片到识别结果:PyQt5 水果识别系统到底能跑出什么效果
你手头有一张手机拍的水果照片,想让它自动告诉你这是苹果、香蕉还是橙子,并且希望整个过程在一个带按钮和预览区的桌面窗口里完成——这就是基于 PyQt5 设计实现的简易版水果识别系统要解决的事。它把深度学习推理和桌面 GUI 缝在一起,用户点一下按钮选图片,窗口里显示识别结果和置信度。适合谁?正在做课程设计、想找一个能跑通的 Python 视觉项目练手的人,以及需要快速验证「分类模型 + 桌面端」这条链路的开发者。热搜里 pyqt5界面设计、python源码、水果识别系统这几个词,恰好对应了它的三个核心模块:界面、推理、数据。这一章先把整体轮廓立住,后面再拆每一步怎么落地。
2. 拆开这个系统:PyQt5 界面、推理引擎和水果数据集怎么分工
2.1 为什么选 PyQt5 而不是 Tkinter 或 Web 前端
做桌面端水果识别,界面框架的选择直接决定开发速度和最终体验。Tkinter 是 Python 自带的,零依赖,但控件样式老旧,做图片预览和动态刷新时布局很难看,而且没有内置的进度条和状态栏配合推理耗时展示。Web 前端方案(Flask + 浏览器)跨平台好,但要多起一个服务进程,对只想双击运行 exe 的课程设计场景来说太重。PyQt5 的优势在于:控件丰富、支持信号槽机制做异步更新、能用 QLabel 直接渲染 QPixmap 显示图片,而且打包成 exe 后不依赖浏览器。热搜里 pyqt5界面设计 pycharm、pyqt5安装这些词说明很多人卡在环境配置上,后面会专门讲。
选 PyQt5 还有一个实际原因:推理过程会阻塞主线程,如果不用 QThread 把模型推理放到子线程,界面会直接卡死。PyQt5 的信号槽天然适合做「子线程推理完成 → 发信号 → 主线程更新结果」这种模式,Tkinter 做同样的事要自己维护队列和轮询,代码量翻倍。
2.2 推理引擎:TensorFlow/Keras 还是 PyTorch
简易版水果识别系统通常用迁移学习,在 MobileNetV2 或 ResNet50 上微调。选 TensorFlow/Keras 的理由是:model.predict()接口简单,保存成.h5文件后加载只要一行,适合课程设计这种不需要复杂部署的场景。PyTorch 的.pth加载要手动重建模型结构,对新手不友好。如果数据集只有几千张水果图,MobileNetV2 足够,参数量小,CPU 推理单张图在 100ms 以内,不需要 GPU 也能跑。
模型输入尺寸一般固定为 224x224,因为 MobileNetV2 在 ImageNet 上就是按这个尺寸预训练的。推理前要把 PIL 读入的图片 resize 到 224x224,再转成 numpy 数组并归一化到 [0,1] 或 [-1,1](取决于预训练时的预处理方式)。这一步如果搞错,识别结果会完全乱掉,后面避坑章节会细说。
2.3 数据集:自己拍还是用公开水果数据集
公开水果数据集常见的有 Fruits-360,包含 100 多种水果、十几万张图片,但它的图片是白底居中拍摄的,和手机实拍差距大,直接训练出来的模型在真实场景下准确率会掉。我的做法是:用 Fruits-360 做预训练或直接拿预训练权重,然后自己用手机拍 5 到 10 类常见水果,每类 100 到 200 张,背景尽量多样,再微调最后几层。这样模型既有了基础特征,又能适应真实光照和背景。
如果只是课程设计演示,Fruits-360 里挑 5 类、每类 200 张就够。目录结构按train/苹果/xxx.jpg、val/苹果/xxx.jpg组织,Keras 的ImageDataGenerator.flow_from_directory能直接读。注意类别文件夹名就是最终输出的标签,不要用中文路径,否则在某些 Windows 环境下会报编码错误。
3. 从零跑通:PyQt5 环境配置、界面搭建和推理线程的完整代码
3.1 安装 PyQt5 和 TensorFlow 的避坑命令
热搜里 pyqt5安装、labelme 无法安装 pyqt5 说明环境问题很普遍。PyQt5 在 Windows 上直接用 pip 装通常没问题,但如果之前装过 PyQt6 或 PySide,会出现版本冲突。我一般先建虚拟环境,再按固定版本装:
python -m venv venv venv\Scripts\activate pip install PyQt5==5.15.9 pip install tensorflow==2.13.0 pip install pillow numpy逻辑说明:PyQt5 5.15.x 是最后一个支持 Python 3.7-3.11 的稳定大版本,和 TensorFlow 2.13 兼容性好。不要装 PyQt5 6.x,因为 6.x 的 API 有变动,网上大部分教程代码跑不通。如果 pip 装 PyQt5 报错,先升级 pip:python -m pip install --upgrade pip。labelme 无法安装 pyqt5 通常是因为 labelme 依赖的 PyQt5 版本和系统里已有的冲突,解决办法是给 labelme 单独建一个虚拟环境,不要和主项目混用。
3.2 用 Qt Designer 拖出主窗口,再转成 Python 代码
界面部分我一般用 Qt Designer 拖控件,保存成.ui文件,再用pyuic5转成.py。主窗口需要这些控件:一个 QLabel 显示图片(objectName 设为image_label),一个 QPushButton 选图片(btn_open),一个 QPushButton 开始识别(btn_predict),一个 QLabel 显示结果文字(result_label),一个 QProgressBar 显示推理进度(progress_bar)。转换命令:
pyuic5 mainwindow.ui -o ui_mainwindow.py转换后生成的ui_mainwindow.py不要手动改,每次改界面重新生成。主程序里用继承的方式加载:
from PyQt5.QtWidgets import QApplication, QMainWindow from ui_mainwindow import Ui_MainWindow class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.btn_open.clicked.connect(self.open_image) self.btn_predict.clicked.connect(self.start_predict)参数说明:setupUi是 pyuic5 生成的方法,负责创建所有控件并布局。信号槽连接放在__init__里,保证窗口一启动按钮就可用。open_image和start_predict是自定义方法,分别处理选图和推理。
3.3 把推理放进 QThread,避免界面卡死
这是整个系统最容易翻车的地方。如果直接在按钮点击槽里调model.predict(),界面会冻结几秒,用户以为程序挂了。正确做法是继承 QThread,在run里做推理,完成后发信号:
from PyQt5.QtCore import QThread, pyqtSignal import numpy as np from tensorflow.keras.models import load_model from tensorflow.keras.preprocessing import image class PredictThread(QThread): finished = pyqtSignal(str, float) def __init__(self, img_path, model_path): super().__init__() self.img_path = img_path self.model_path = model_path def run(self): model = load_model(self.model_path) img = image.load_img(self.img_path, target_size=(224, 224)) x = image.img_to_array(img) / 255.0 x = np.expand_dims(x, axis=0) preds = model.predict(x) idx = np.argmax(preds[0]) confidence = float(preds[0][idx]) class_names = ['苹果', '香蕉', '橙子', '葡萄', '西瓜'] self.finished.emit(class_names[idx], confidence)逻辑说明:load_model放在run里而不是__init__,是因为加载模型耗时,放在子线程里不阻塞界面。target_size=(224,224)必须和训练时一致。/255.0是归一化,如果训练时用的是preprocess_input,这里要换成对应的预处理函数。finished信号带两个参数:类别名和置信度,主线程收到后更新result_label。
主线程里这样用:
def start_predict(self): if not self.img_path: return self.progress_bar.setRange(0, 0) # 不确定进度,显示忙碌状态 self.thread = PredictThread(self.img_path, 'fruit_model.h5') self.thread.finished.connect(self.on_predict_done) self.thread.start() def on_predict_done(self, label, conf): self.progress_bar.setRange(0, 100) self.progress_bar.setValue(100) self.result_label.setText(f'识别结果:{label},置信度:{conf:.2%}')注意:self.thread要保存为实例属性,否则局部变量被回收后线程可能异常终止。setRange(0,0)是 Qt 里显示忙碌进度条的技巧,识别完成后恢复成 0-100 并设成 100。
3.4 模型训练脚本:用 MobileNetV2 微调 5 类水果
训练脚本独立于界面,跑一次生成fruit_model.h5即可。核心代码:
from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.layers import Dense, GlobalAveragePooling2D from tensorflow.keras.models import Model from tensorflow.keras.preprocessing.image import ImageDataGenerator base = MobileNetV2(weights='imagenet', include_top=False, input_shape=(224,224,3)) base.trainable = False x = base.output x = GlobalAveragePooling2D()(x) x = Dense(128, activation='relu')(x) preds = Dense(5, activation='softmax')(x) model = Model(inputs=base.input, outputs=preds) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=20, zoom_range=0.2, horizontal_flip=True) train_gen = train_datagen.flow_from_directory('dataset/train', target_size=(224,224), batch_size=32, class_mode='categorical') model.fit(train_gen, epochs=10) model.save('fruit_model.h5')参数说明:include_top=False去掉 ImageNet 的 1000 类分类头,换成自己的 5 类。base.trainable = False先冻结预训练权重,只训练新加的全连接层,这样小数据集也能收敛。rotation_range和horizontal_flip做数据增强,防止过拟合。epochs=10对几百张图通常够,如果 loss 还在降可以加到 20。训练完保存成.h5,界面加载的就是这个文件。
4. 避坑与排查:水果识别系统最常见的 5 个翻车现场
4.1 识别结果全是同一类,置信度还很高
现象:不管选什么图片,输出都是「苹果」,置信度 0.99。原因:训练时类别不平衡,苹果图片远多于其他类,模型学会了偷懒全猜苹果。解决:检查dataset/train下每个类别文件夹的图片数量,差距不要超过 2 倍;在flow_from_directory里加class_weight参数,给少样本类更高权重。
4.2 界面点识别后直接闪退,没有任何报错
现象:点按钮后窗口消失,命令行也没输出。原因:子线程里抛了异常,但 Qt 默认不打印线程异常,直接终止进程。解决:在PredictThread.run里用 try/except 包住所有代码,把异常信息通过信号发回主线程显示;或者在run开头加import traceback,except 里traceback.print_exc()。
4.3 图片显示变形,QLabel 拉伸得不成比例
现象:选了一张长方形图片,显示在 QLabel 里被拉成正方形。原因:直接setPixmap没有保持宽高比。解决:用QPixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation),KeepAspectRatio保证不变形,SmoothTransformation让缩放后不锯齿。
4.4 打包成 exe 后提示找不到模型文件
现象:PyCharm 里跑得好好的,用 PyInstaller 打包后双击 exe 报FileNotFoundError: fruit_model.h5。原因:PyInstaller 不会自动把.h5文件打进 exe,需要手动指定。解决:打包命令加--add-data "fruit_model.h5;.",代码里用sys._MEIPASS拼路径:
import sys, os def resource_path(relative): if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative) return os.path.join(os.path.abspath('.'), relative)4.5 安装 PyQt5 后 PyCharm 里 import 报红,但能运行
现象:from PyQt5.QtWidgets import QApplication下面有红色波浪线,但点运行又能跑。原因:PyCharm 的解释器没选对虚拟环境,或者 PyQt5 装在了系统 Python 里而不是项目 venv 里。解决:File → Settings → Project → Python Interpreter,确认选的是项目 venv 下的 python.exe;如果列表里没有,手动添加 venv/Scripts/python.exe。热搜里 vscode python环境配置、vscode配置python 也是同类问题,VS Code 里按 Ctrl+Shift+P 选解释器即可。
5. 让识别更准的进阶技巧:置信度阈值和 Top-3 输出
简易版跑通后,下一步是让结果更可信。我一般加两个东西:置信度阈值和 Top-3 候选。阈值的作用是当最高置信度低于某个值(比如 0.6)时,不直接输出类别,而是提示「不确定,请换一张清晰图片」。Top-3 则是把概率最高的三个类别都列出来,用户能看到模型犹豫在哪两类之间,对调试和演示都有用。
改PredictThread.run里的输出逻辑:
preds = model.predict(x)[0] top3 = np.argsort(preds)[-3:][::-1] results = [(class_names[i], float(preds[i])) for i in top3] if results[0][1] < 0.6: self.finished.emit('不确定', results[0][1]) else: self.finished.emit(results[0][0], results[0][1])参数说明:np.argsort(preds)[-3:][::-1]取概率最大的三个索引并降序排列。阈值 0.6 是经验值,如果模型在验证集上准确率 90% 以上,可以降到 0.5;如果只有 70%,建议提到 0.7。这个阈值没有绝对标准,用验证集跑一遍,看错误样本的置信度分布,选一个能过滤大部分错误的点。
另一个技巧是推理前做一次简单的图像质量检查:如果图片分辨率低于 100x100,或者整体亮度均值低于 30(太暗),直接提示用户重拍,不送进模型。这能避免很多「模型明明训练得不错但实拍就是错」的玄学问题。我自己的习惯是,每加一个后处理规则,都在验证集上跑一遍对比准确率,确认有提升再保留,不然就是给自己挖坑。希望帮到你。
本文还有配套的精品资源,点击获取