资讯动态

基于PyQt5与OpenCV的水果识别系统:带GUI界面与详细注释的完整源码实现

发布时间:2026/10/11 11:50:37 来源:尧图企业网站定制
简介这是一套面向计算机相关专业在校学生与教师的Python水果识别系统源码采用PyQt5搭建GUI界面、OpenCV完成图像处理与识别适合作为毕业设计、课程设计或大作业的完整参考方案。压缩包共55个文件约4.63MB包含16个py源码文件、20个png与2个jpg水果样本图、2个ui界面文件、2个qrc资源文件及若干pyc缓存与md说明文档源码带详细注释目录涵盖图像处理、百度识别接口调用、界面切换与识别主逻辑等模块。已有439人学习下载可帮助读者快速理解图像预处理、去模糊、目标裁剪与识别流程并支持在此基础上二次开发或DIY其他识别功能。运行前建议将项目路径重命名为英文避免中文路径导致解析异常。1. 水果识别系统从一张货架照片到可点击的 GUI 到底要几步超市结账台前收银员把一堆苹果、橙子、香蕉往摄像头底下一推屏幕自动框出每个水果并标上名字——这个场景听起来像大厂方案其实用 PyQt5 OpenCV Python 就能在本地跑通。标题里的「水果识别系统源码带 GUI 界面 详细注释」本质是一套桌面端图像分类应用OpenCV 负责读图、预处理、轮廓分割Python 做特征提取或调用轻量分类逻辑PyQt5 把整个流程包成有按钮、有预览区、有结果输出的窗口程序。它适合三类人想拿一个完整项目练手的 Python 入门者、需要给课程设计交作业的学生、以及想理解「图像处理 桌面 GUI」怎么串起来的初中级开发者。热词里 pyqt5 安装、opencv 图像处理、python 安装教程反复出现说明卡点从来不在算法本身而在环境能不能跑起来、界面能不能显示、识别结果能不能稳定输出。这篇笔记就按「先跑通、再调参、最后避坑」的顺序把这条链路拆开讲清楚。2. 环境搭建与最小可运行骨架先让窗口弹出来再谈识别2.1 为什么选 PyQt5 OpenCV 而不是 Web 或命令行很多人第一反应是用 Flask 做个网页上传图片或者干脆写个命令行脚本。但水果识别这个场景有个硬需求实时预览。收银台或分拣线上操作员需要看到摄像头画面里被框出的水果而不是等三秒后返回一个 JSON。PyQt5 的 QLabel 配合 QTimer 可以做到毫秒级刷新OpenCV 的 VideoCapture 直接读摄像头帧两者通过 numpy 数组无缝衔接——OpenCV 读出来是 BGR ndarray转成 QImage 只需要改一下通道顺序。命令行方案调试算法方便但交付给非技术用户就是灾难Web 方案要处理上传延迟、跨域、前端渲染对「识别」这个核心逻辑反而是干扰。所以标题里 GUI 界面不是装饰它是这个项目能落地的关键组件。另一个选型理由是依赖轻。PyQt5 是纯 Python 绑定pip 就能装OpenCV 的 Python 包 opencv-python 自带常用图像处理函数不需要编译整个 OpenCV 源码。热词里有人搜「opencv 3.4.1 mingw64 下载」「编译 opencv」那是 C 开发者的路径做 Python 水果识别完全用不上。你只需要确认 Python 版本在 3.7 到 3.10 之间太新或太旧都可能遇到 PyQt5 轮子缺失的问题。2.2 三条命令装完依赖验证 cv2 和 PyQt5 都能导入先建虚拟环境避免和系统里其他包打架。Windows 下用 python -m venv venvLinux 或 macOS 用 python3 -m venv venv然后激活。激活后依次执行pip install opencv-python4.8.1.78 pip install PyQt55.15.9 pip install numpy1.24.3版本号不是随便写的。opencv-python 4.8.x 对 numpy 1.24 兼容稳定PyQt5 5.15.9 是最后一个支持 Python 3.10 的成熟版本。装完后用下面这段代码验证不要跳过import cv2 import numpy as np from PyQt5.QtWidgets import QApplication, QLabel import sys print(OpenCV version:, cv2.__version__) print(NumPy version:, np.__version__) app QApplication(sys.argv) label QLabel(环境 OK) label.show() # 不进入事件循环只验证能否创建窗口对象 print(PyQt5 window created)如果报ModuleNotFoundError: No module named cv2说明 opencv-python 没装进当前虚拟环境检查 pip 路径是否指向 venv。如果报DLL load failed通常是 Visual C Redistributable 缺失装一下 2015-2022 的 x64 版本即可。Linux 下如果报cannot open shared object file: libGL.so.1执行sudo apt install libgl1-mesa-glx。这些报错在热词里对应「modulenotfounderror: no module named opencv」和「ubuntu 配置 opencv」本质都是环境隔离没做好。2.3 最小 GUI 骨架一个按钮、一个预览区、一个结果标签不要一上来就写完整识别逻辑先把界面骨架搭出来确认信号槽能连通。下面这段代码创建一个 800x600 的窗口左边是图像预览 QLabel右边是按钮和结果文本import sys import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog) from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtCore import Qt class FruitGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(水果识别系统) self.resize(900, 600) central QWidget() self.setCentralWidget(central) main_layout QHBoxLayout(central) # 左侧预览区 self.image_label QLabel(等待加载图片...) self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setStyleSheet(border: 2px solid gray;) self.image_label.setFixedSize(600, 500) main_layout.addWidget(self.image_label) # 右侧控制区 right_layout QVBoxLayout() self.btn_load QPushButton(加载图片) self.btn_load.clicked.connect(self.load_image) self.result_label QLabel(识别结果--) self.result_label.setWordWrap(True) right_layout.addWidget(self.btn_load) right_layout.addWidget(self.result_label) right_layout.addStretch() main_layout.addLayout(right_layout) self.cv_image None # 保存原始 OpenCV 图像 def load_image(self): path, _ QFileDialog.getOpenFileName(self, 选择图片, , Images (*.jpg *.png *.jpeg)) if not path: return # OpenCV 读取注意中文路径要用 np.fromfile self.cv_image cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR) self.show_image(self.cv_image) self.result_label.setText(图片已加载等待识别...) def show_image(self, img): # BGR 转 RGB rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w qimg QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimg).scaled( self.image_label.width(), self.image_label.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.image_label.setPixmap(pixmap) if __name__ __main__: app QApplication(sys.argv) window FruitGUI() window.show() sys.exit(app.exec_())这段代码的关键点有三个。第一cv2.imdecode配合np.fromfile是为了解决 OpenCV 在 Windows 下读中文路径失败的问题直接cv2.imread遇到中文目录会返回 None。第二show_image里做了 BGR 到 RGB 的转换因为 Qt 的 QImage 期望 RGB 顺序不转的话红色和蓝色会互换苹果看起来像蓝莓。第三scaled时用Qt.KeepAspectRatio保持比例否则水果会被拉扁。跑通这一步你已经有了一个能加载并显示图片的 GUI接下来才是识别逻辑。3. 水果识别核心链路从颜色阈值到轮廓筛选的完整实现3.1 为什么先用 HSV 颜色空间做分割而不是直接上深度学习标题里没有指定识别算法但「水果识别系统源码」这个描述通常对应两类实现一类是传统图像处理靠颜色、形状、纹理特征做分类另一类是轻量 CNN 推理。对于带 GUI 的桌面项目我一般推荐先用 HSV 颜色阈值 轮廓分析跑通全流程原因很实际不需要训练数据、不需要 GPU、代码可读性强、调试直观。你打开一张苹果照片调一调 H 通道范围就能看到掩膜变化这种即时反馈对理解图像处理帮助极大。深度学习方案虽然精度高但模型文件动辄几十兆推理还要装 onnxruntime 或 torch对只想跑通 GUI 的人门槛太高。HSV 相比 RGB 的优势在于颜色分离更稳定。RGB 三个通道都受光照影响而 HSV 把色调H、饱和度S、亮度V拆开水果的颜色主要由 H 决定光照变化主要影响 V。比如红色苹果的 H 值在 0 到 10 和 170 到 180 两个区间OpenCV 里 H 范围是 0-179你只需要框定 H 范围再对 S 和 V 设一个最低阈值过滤掉灰白背景就能得到比较干净的水果区域掩膜。3.2 颜色阈值分割的代码实现与参数调节下面这段代码对加载的图片做 HSV 转换、阈值分割、形态学去噪最终输出二值掩膜def segment_fruit(self, img): # 高斯模糊降噪kernel 必须是奇数 blurred cv2.GaussianBlur(img, (7, 7), 0) hsv cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) # 红色范围苹果、草莓等H 跨 0 需要两段 lower_red1 np.array([0, 80, 80]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 80, 80]) upper_red2 np.array([180, 255, 255]) mask_red cv2.inRange(hsv, lower_red1, upper_red1) | \ cv2.inRange(hsv, lower_red2, upper_red2) # 橙色范围橙子、橘子 lower_orange np.array([11, 100, 100]) upper_orange np.array([25, 255, 255]) mask_orange cv2.inRange(hsv, lower_orange, upper_orange) # 黄色范围香蕉、柠檬 lower_yellow np.array([26, 100, 100]) upper_yellow np.array([35, 255, 255]) mask_yellow cv2.inRange(hsv, lower_yellow, upper_yellow) # 合并掩膜 mask mask_red | mask_orange | mask_yellow # 形态学开运算去噪闭运算填洞 kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations2) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations2) return mask参数说明GaussianBlur的 (7,7) 是模糊核大小越大越模糊对噪声多的图片可以加到 (9,9) 或 (11,11)但太大会把水果边缘也糊掉。lower_red1里的 80 是饱和度下限如果水果颜色偏淡比如粉红苹果可以降到 50如果背景有红色干扰就升到 100 以上。morphologyEx的 iterations2 表示做两次开运算对细小噪点很有效但如果水果本身很小比如樱桃iterations 要降到 1否则整个水果会被腐蚀掉。这些参数没有万能值必须对着你的实际图片调调参时把 mask 用cv2.imshow单独显示出来看效果比盲猜快得多。3.3 轮廓提取与水果计数findContours 的三个关键过滤条件拿到掩膜后用cv2.findContours找轮廓再按面积、宽高比、圆形度过滤掉噪声和背景碎片def count_fruits(self, mask, original): contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) result_img original.copy() count 0 for cnt in contours: area cv2.contourArea(cnt) if area 800: # 过滤小噪点 continue x, y, w, h cv2.boundingRect(cnt) aspect_ratio w / float(h) if aspect_ratio 0.5 or aspect_ratio 2.0: # 过滤细长形状 continue # 圆形度4π*面积 / 周长^2越接近 1 越圆 perimeter cv2.arcLength(cnt, True) if perimeter 0: continue circularity 4 * np.pi * area / (perimeter * perimeter) if circularity 0.4: # 太不规则的不要 continue count 1 cv2.rectangle(result_img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(result_img, fFruit {count}, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return result_img, countRETR_EXTERNAL只取最外层轮廓避免水果内部纹理产生嵌套轮廓。面积阈值 800 是针对 640x480 以上分辨率图片的经验值如果你的图片很小按比例降到 200 左右。宽高比过滤能去掉长条形的背景边缘圆形度过滤能去掉不规则碎片。这三个条件叠加后误检率会明显下降。但要注意香蕉的宽高比可能达到 3:1 甚至更高如果你要识别香蕉宽高比上限要放宽到 3.5或者干脆对香蕉单独走一套形状判断逻辑。3.4 把识别结果嵌回 PyQt5 界面信号槽与图像刷新识别函数写好后在 GUI 里加一个「开始识别」按钮点击后调用 segment 和 count再把画了框的结果图显示到 QLabel 上def recognize(self): if self.cv_image is None: self.result_label.setText(请先加载图片) return mask self.segment_fruit(self.cv_image) result_img, count self.count_fruits(mask, self.cv_image) self.show_image(result_img) self.result_label.setText(f识别结果共检测到 {count} 个水果)这里有个容易翻车的点show_image里用了self.image_label.width()和height()如果 QLabel 还没完成布局拿到的可能是默认值。稳妥做法是在__init__里给 QLabel 设固定尺寸或者用setScaledContents(True)让 QLabel 自己缩放。另外OpenCV 的putText不支持中文如果想把「苹果」「橙子」显示在图上需要用 PIL 的 ImageDraw 或者 Qt 的 QPainter 在 QPixmap 上画字这是很多源码里没写清楚的地方。4. 避坑与排查水果识别系统最常见的 5 个翻车现场4.1 现象窗口一闪而过报 Process finished with exit code -1073740791原因PyQt5 和 OpenCV 的 Qt 库冲突。opencv-python 自带一套 Qt 运行时PyQt5 也带一套两者版本不一致时 Windows 下会直接崩溃。解决在 import cv2 之前先 import PyQt5或者设置环境变量os.environ[QT_QPA_PLATFORM_PLUGIN_PATH]指向 PyQt5 的 plugins 目录。更彻底的办法是装opencv-python-headless它不带 GUI 模块不会和 PyQt5 抢 Qt 库但代价是不能用cv2.imshow调试只能靠保存图片到磁盘看中间结果。4.2 现象识别结果框出一堆小方块水果没框住原因HSV 阈值范围太窄或者形态学开运算 iterations 太多把水果区域腐蚀碎了。解决先把 mask 单独显示出来如果 mask 里水果区域是碎的降低开运算 iterations 到 1或者把 kernel 从 (5,5) 改成 (3,3)。如果 mask 里水果区域根本不亮说明 H 范围没覆盖到用取色工具读一下水果像素的 HSV 值把范围往两边各扩 5 到 10。4.3 现象加载图片后 QLabel 显示全黑或全白原因QImage 构造时数据指针指向了临时变量。cv2.cvtColor返回的 numpy 数组在函数结束后可能被回收而 QImage 只保存指针不复制数据。解决在show_image里加一句rgb rgb.copy()确保数据在 QImage 使用期间有效。另一个可能是 bytes_per_line 算错了对于连续存储的 numpy 数组ch * w是对的但如果数组不是 C 连续比如切片过的要先np.ascontiguousarray。4.4 现象pip install PyQt5 卡在下载或者装完 import 报 DLL 错误原因网络问题或 Python 版本不匹配。PyQt5 5.15.x 对 Python 3.11 以上的轮子支持不完整如果你用的是 Python 3.12pip 会尝试从源码编译大概率失败。解决确认 Python 版本是 3.8 到 3.10用python --version看。如果已经是 3.12要么降级 Python要么改用 PyQt6 或 PySide6但 PySide6 的 API 和 PyQt5 有差异源码里的 import 语句要改。热词里「python 3.8」出现频率高不是没有道理。4.5 现象识别速度慢点一下按钮要等好几秒原因图片分辨率太高或者形态学操作 kernel 太大。一张 4000x3000 的手机照片做高斯模糊和两次形态学运算CPU 要跑好几秒。解决在segment_fruit开头把图片缩放到宽度 800 以内scale 800 / img.shape[1]然后cv2.resize。识别完再把框的坐标按比例映射回原图。这样速度能提升 10 倍以上精度损失肉眼几乎看不出来。另一个优化是把GaussianBlur的 kernel 从 (7,7) 降到 (5,5)对结果影响很小。5. 进阶技巧用轮廓面积排序 颜色投票提升多水果识别准确率前面讲的流程对单张图片里颜色分明的水果已经够用但实际场景里经常遇到苹果和橙子挨在一起、香蕉被部分遮挡、背景里有类似颜色的物体。这时候单纯靠颜色阈值会翻车。我自己的习惯是加两层后处理第一层按轮廓面积从大到小排序只保留前 N 个N 根据画面里水果数量预估避免小噪点干扰第二层对每个轮廓区域内的像素做颜色投票统计 H 通道落在红、橙、黄三个区间的比例哪个比例最高就判为哪类水果。def classify_by_color(self, hsv, cnt): mask np.zeros(hsv.shape[:2], dtypenp.uint8) cv2.drawContours(mask, [cnt], -1, 255, -1) pixels hsv[mask 255] if len(pixels) 0: return unknown h_values pixels[:, 0] red_count np.sum((h_values 10) | (h_values 170)) orange_count np.sum((h_values 11) (h_values 25)) yellow_count np.sum((h_values 26) (h_values 35)) total len(h_values) ratios {red: red_count / total, orange: orange_count / total, yellow: yellow_count / total} best max(ratios, keyratios.get) if ratios[best] 0.5: return unknown return best这段代码的关键是drawContours填充轮廓生成掩膜然后只取掩膜内的 HSV 像素做统计。阈值 0.5 表示超过一半像素落在某个颜色区间才判定否则返回 unknown。这个逻辑对光照变化有一定鲁棒性因为 H 通道对亮度不敏感。但要注意如果水果表面有高光反射高光区域饱和度低H 值可能不稳定可以在统计前过滤掉 S 50 的像素。验证方法很简单找 10 张包含不同水果组合的图片跑一遍看分类结果把判错的图片单独拿出来分析 H 值分布。如果某个水果总是被判成 unknown说明它的 H 范围不在你预设的三个区间里比如绿苹果、紫葡萄、火龙果需要额外加区间。这也是传统颜色方法的边界——它只能识别你预先定义好颜色的水果遇到新品类就要改代码。想突破这个限制就得引入形状特征比如香蕉的细长、苹果的圆或者上轻量分类模型但那是另一个话题了。最后说个我踩过的坑调参时不要同时改多个参数。先把 mask 调好确认水果区域完整再调轮廓过滤确认框的位置对最后调分类投票确认标签正确。一次只动一个变量用同一张测试图对比效果。我见过太多人把 HSV 范围、kernel 大小、面积阈值一起改结果好了不知道为啥好坏了不知道为啥坏。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑