☰
基于PyQt5与OpenCV的水果识别系统:源码解析与HSV调试实战
2026/10/9 6:39:48 网站建设 项目流程

简介:这份基于PyQt5、OpenCV与Python实现的水果识别系统源码,完整包含GUI界面与详细代码注释,主要面向计算机相关专业学生及开发者。项目覆盖了图像采集、预处理、特征提取与识别等基础流程,可直接用于毕业设计、课程设计或大作业,也适合作为项目初期演示与二次开发的原型。压缩包内共55个文件,以16个Python脚本为核心,同时提供2个UI界面设计文件、2个qrc资源文件,以及多种水果样本图片(如苹果、香蕉、西瓜、猕猴桃等)和说明文档,整体仅4.63MB,轻量易部署。源码中还包含图像去模糊、图像处理与识别切换等具体实现模块,配合注释与readme说明,便于理解脉络和按需改造。目前已有439人参与学习下载,所有功能经验证可稳定运行,适合希望快速搭建图像识别项目、研习OpenCV与PyQt5整合开发的中高级读者。

1. 基于pyqt5+opencv+python的水果识别:这套源码能解决什么

做课程设计或毕设时,最怕的不是没有思路,而是拿到一套代码却跑不起来、看不懂、改不动。这个基于PyQt5+OpenCV+Python的水果识别系统源码,正好把这三件事一起解决了:它自带GUI界面、包含详细中文注释,识别主链路不依赖外部网络,双击运行就能看到界面、选图、出结果。对计科、人工智能、物联网这些专业的学生来说,它既是课设大作业的完整交付物,也是后续做二次开发时一个不错的起点。我拿到压缩包后把它完整跑通、拆了一遍,下面直接给你讲清楚里面每个文件是干嘛的、主程序怎么运转、有哪些坑要提前避开。

2. 源码结构与识别原理:从文件清单到HSV颜色阈值

2.1 压缩包里有哪些文件:先按职责分组

解压之后你会发现文件不算少,但真正影响主流程的其实就几个。大多数毕设源码包都喜欢把所有脚本平铺在根目录,显得内容多,实际效仿一下就能看出分工:

文件/目录职责使用优先级
fruit_recognition.py主程序入口,负责加载Qt界面、绑定按钮事件、调用识别函数必看
fruit_recognition.uiQt Designer绘制的界面文件,双击可以继续编辑必看
pics_ui.qrc / pics_ui_rc.py资源文件(图标、背景图)及其编译产物改界面时用
imagedeal图像处理模块,预处理和颜色分割相关操作集中在这里核心
main_image_process.py把图像处理流程封装成可复用函数,供主程序调用核心
switch.py页面切换或结果显示逻辑,通常与界面交互绑定辅助
baidufruit.py在线图像识别接口封装(需要联网token),离线环境可不启用可选
deblur.py图像去模糊/清晰度增强脚本,给质量较差的输入图做预处理辅助
text.py / text2.py文字或界面上字体显示相关的调试脚本辅助
source_imagedata6类水果示例图:苹果、西瓜、山竹、橙子、香蕉、猕猴桃数据集
readme.txt / 介绍.md运行说明和提交说明先看

把文件分组之后,主线就很清楚了:主程序读UI文件,用户选一张水果图片,程序调用imagedeal里的图像处理函数做识别,再把结果文字和框画回界面上。baidufruit.py这类联网脚本并不是主链路的一部分,属于额外补充。

2.2 识别原理:为什么颜色特征在这个场景够用

水果识别的难点在于同一种水果可能有不同成熟度、不同光照表现、甚至有遮挡和杂乱背景。如果上深度学习模型,就需要准备大量标注图片、训练时间和机器资源,对于课程设计来说属于杀鸡用牛刀。这个项目采用的方案是经典计算机视觉里的颜色特征识别,在HSV颜色空间做阈值分割,然后用轮廓筛选锁定目标区域。

选择HSV而不是RGB是有原因的。RGB三个通道对光照变化极为敏感,同一颗苹果在亮光和暗光下RGB值能差出一个数量级;而HSV把色相(H)、饱和度(S)、亮度(V)拆开,识别时主要依靠H分量,S和V作为辅助过滤条件。这样只要把水果的色相区间找对,识别稳定性会明显好于RGB阈值分割。用OpenCV做这件事特别顺手,cv2.cvtColor一行就能完成BGR到HSV的转换。

各水果的HSV阈值也是这套源码的灵魂所在。源码里大概率是针对每种水果写了对应的lower和upper数组,参考区间大致如下:

水果H范围S范围V范围备注
红苹果0~10 和 156~18043~25546~255红色在OpenCV的H轴上首尾相连,需要双区间
橙子11~2543~25546~255与苹果红边界相邻,光照偏黄时易混
香蕉26~3443~25546~255青色香蕉H会跑到40左右
猕猴桃35~7743~25546~255果肉与果皮绿色有差异
山竹125~15543~25546~255紫红色果皮,深紫色H值偏高
西瓜0~10 或 35~8543~25546~255红瓤或绿皮都可用,关键是选对你要识别的部分

2.3 关键代码拆解:从BGR到HSV再到轮廓筛选

我拆源码的习惯是先找核心图像处理部分,再反向理解整个流程。这个项目里的imagedeal模块,对应到通用实现是下面这样的套路,代码逻辑基本能对上:

import cv2 import numpy as np def recognize_fruit_by_color(image_path, lower, upper, name): # 读图,OpenCV默认返回BGR顺序 img = cv2.imread(image_path) if img is None: return None, "图像读取失败,请检查路径是否有中文或文件是否存在" # 高斯模糊减少传感器噪点,让阈值分割更干净 blurred = cv2.GaussianBlur(img, (5, 5), 0) # BGR转HSV,H范围在OpenCV里是0~180而非0~360 hsv = cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) # inRange会把落在[lower, upper]区间内的像素置为255,其余为0 mask = cv2.inRange(hsv, lower, upper) # 找轮廓,去噪后再筛选面积 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) best_area = 0 for cnt in contours: area = cv2.contourArea(cnt) if area > best_area: best_area = area x, y, w, h = cv2.boundingRect(cnt) if best_area < 500: return None, "未检测到目标水果,请调整HSV阈值或光照条件" # 在原图上画框标注识别结果 cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(img, name, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2) return img, "识别成功:" + name

这里inRange的lower和upper是两个numpy数组,顺序必须是[H, S, V]。有个最常见的翻车点:把RGB的值直接填进来,或者把H写成0~360的度数。OpenCV的H是0~180,如果你从其他资料抄来一个0~360的H区间,整个分割结果会完全错乱。另外findContours在不同OpenCV版本里返回值结构不同,4.x版本返回两个值,3.x版本返回三个值,写代码时要注意。

# 调用示例,识别图片中的红苹果 lower_red = np.array([0, 43, 46]) upper_red = np.array([10, 255, 255]) result_img, message = recognize_fruit_by_color("apple.png", lower_red, upper_red, "苹果") if result_img is not None: cv2.imshow("result", result_img) cv2.waitKey(0)

识别面积阈值500是经验值,如果图片分辨率很高,水果占比很大,这个阈值可以调高到2000甚至5000,避免把背景里偶然出现的同色噪点判定成水果。实际项目里识别失败时弹出提示框,用的就是这个返回到None的逻辑。

3. 跑通整套系统:环境配置、启动命令与GUI操作路径

3.1 运行前的环境准备:Python 3.8、PyQt5与OpenCV的安装

从压缩包里的__pycache__能看到cpython-38的痕迹,说明作者大概率用的Python 3.8。我建议你也用3.8或3.9,而不是直接上Python 3.12,原因是PyQt5和OpenCV的wheel在不同版本下兼容性有差异,高版本Python容易出现不必要的报错。安装依赖用pip一条命令就能解决:

pip install pyqt5 pyqt5-tools opencv-python numpy

如果你的机器已经装过其他版本,最好先检查一下版本再决定是否升级:

python -m pip show pyqt5 opencv-python numpy

pyqt5-tools不是必须的,但它自带pyrcc5这个工具,后面修改pics_ui.qrc资源文件重新编译时要用到。OpenCV强烈建议装opencv-python这个发行版,不要装opencv-contrib-python,除非你确定需要用SIFT、KAZE这类扩展模块,否则只会多出不少体积和潜在冲突。

我一般还会顺手装一个Pillow,用来做图片格式转换辅助。虽然不是项目硬依赖,但在答疑课上同学问得最多的“为什么我的jpg识别不出来”往往就是图片格式本身的坑,Pillow可以快速帮你转格式排查。

3.2 启动GUI:从命令到界面之间的完整链路

环境准备好之后,不要急着双击运行。先把压缩包解压到一个没有中文的路径下,比如D:\fruit_project,解压后进入目录执行:

python fruit_recognition.py

主程序会先加载fruit_recognition.ui文件,初始化主窗口,然后进入事件循环等待你操作。如果你看到的报错是ModuleNotFoundError: No module named 'cv2'或者No module named 'PyQt5',那就是环境没装好,回到上一步把依赖补上。还有一种情况是主程序用了loadUi动态加载UI文件,没找到fruit_recognition.ui就会崩溃,报错信息一般是找不到文件,这时候检查当前工作目录是不是项目根目录。

界面启动后,操作路径通常是:点击“打开图片”按钮选择source_imagedata里的图,界面上会显示原始图像,再点击“开始识别”按钮,程序调用图像处理模块返回结果,在GUI上刷新识别文字和标注图像。部分版本会直接集成定时器实现批量识别文件夹里的图片,遇到这种情况看readme.txt里有没有对应说明。

3.3 主程序三段式结构:把识别结果绑定到界面上

fruit_recognition.py看起来内容多,本质上就三个段落。第一段是创建QApplication和加载UI:

import sys from PyQt5 import QtWidgets, uic app = QtWidgets.QApplication(sys.argv) window = uic.loadUi("fruit_recognition.ui") window.show() sys.exit(app.exec_())

uic.loadUi的好处是界面改动后不需要重新生成Python代码,Qt Designer里保存一下,重启程序就生效。如果作者用的是pyuic5把ui转换成了Python类,那你改完ui文件就得重新转换,两种方式的维护成本差别在二次开发时特别明显。

第二段是绑定按钮事件。PyQt5里常用的做法是window.btn_open.clicked.connect(callback),把“打开图片”按钮对应到选择图片的函数,“开始识别”按钮对应到调用识别函数的函数。这里有个值得注意的地方:如果你在Qt Designer里改了按钮的objectName,连接代码里的名字也要同步改,不然按钮点了没反应。

第三段是识别结果回填。处理完的图像通过setPixmap显示到QLabel上,识别文字用setText写进结果标签。很多同学改代码时只改了识别函数,忘了更新界面刷新部分,结果算法已经识别对了,界面上还显示上一次的结果,这种问题排查起来很折磨人,但其实代码只需要顺着三段结构找就能定位。

4. 改GUI、换水果、调阈值:面向二次开发的改造思路

4.1 用Qt Designer改界面:改按钮、换图片、重新编译资源

如果你想在界面里增加一个“清空图片”按钮,或者把窗口标题改成自己的毕设题目,最好的方式是用Qt Designer打开fruit_recognition.ui直接改。安装pyqt5-tools后,在Python安装目录的Scripts文件夹下能找到designer.exe,或者直接在命令行执行:

pyqt5-tools designer

界面的视觉资源全部由pics_ui.qrc管理。你如果往项目目录里放了一张新的背景图或图标,需要把它加进qrc文件,然后执行以下命令重新生成pics_ui_rc.py:

pyrcc5 -o pics_ui_rc.py pics_ui.qrc

这一步忘了做的话,界面运行时还是引用旧的资源模块,新图不会显示。而且要注意pics_ui_rc.py必须生成在项目根目录,和主程序保持同级,否则import时找不到。改完UI保存后,如果你用的是loadUi方式,直接重启程序就能看到效果;如果是pyuic转换方式,还需要额外跑一遍pyuic5命令,具体看readme.txt里怎么写的。

4.2 新增一种水果的完整流程:从HSV定位到字典扩展

项目自带的六类水果如果不够用,想加草莓、柠檬、葡萄这类新类别,参考这个固定套路十拿九稳。核心做法是把每种水果的颜色范围维护在一个字典里,而不是散落在各个if分支中:

fruit_ranges = { "苹果": ([0, 43, 46], [10, 255, 255]), "红苹果_补": ([156, 43, 46], [180, 255, 255]), "橙子": ([11, 43, 46], [25, 255, 255]), "香蕉": ([26, 43, 46], [34, 255, 255]), "猕猴桃": ([35, 43, 46], [77, 255, 255]), "山竹": ([125, 43, 46], [155, 255, 255]) }

新增水果时先对着实物拍一张光照正常的照片,用后面第六节我会讲的滑条工具确定H/S/V上下限,然后往字典里加一组键值对,最后在识别结果映射表里补一个中文显示名。三个步骤分别对应数据、算法、界面三层,结构清晰,答辩被问到时也容易讲明白。

注意红色是一个特殊颜色,它在HSV色相环上跨越0和180两端,所以表格里苹果要写两个区间。很多初次做颜色识别的人只写了0~10,结果暗红色苹果直接漏检。处理方式是让mask做一次按位或运算:

mask = cv2.inRange(hsv, lower_red_1, upper_red_1) | cv2.inRange(hsv, lower_red_2, upper_red_2)

这就是为什么源码里苹果的识别逻辑通常比香蕉复杂一点。

4.3 与在线识别接口的关系:baidufruit.py和它对应的边界

压缩包里的baidufruit.py是调在线图像识别API的封装脚本,需要联网和用户自己的access_token才能运行。它的定位是作为离线颜色识别方案的补充对比,不适合作为答辩演示的主链路。为什么这么说?因为在线接口依赖网络环境,如果评审现场的Wi-Fi不稳定或者token过期,演示就会卡死在那里。我建议答辩时默认跑离线识别,把baidufruit.py作为“系统支持扩展接入云端接口”的架构亮点在PPT里提一嘴就行,真要现场演示也要提前把token和网络确认好。

# baidufruit.py内部会用到类似这样的请求逻辑(简化版) import requests def baidu_recognize(image_path, access_token): url = "https://aip.baidubce.com/rest/2.0/image-classify/v1/classify" params = {"access_token": access_token} with open(image_path, "rb") as f: image_data = base64.b64encode(f.read()) resp = requests.post(url, params=params, headers={"Content-Type": "application/x-www-form-urlencoded"}, data={"image": image_data}) return resp.json()

在线识别能识别的水果种类远多于颜色阈值方案,但它在整个项目架构里只是锦上添花。你把离线识别比作“看颜色猜水果”,在线识别比作“云端图库比对”,两个放在一起刚好能讲出一个清晰的技术演进逻辑,这也是答辩加分的关键点之一。

5. 避坑清单:中文路径、OpenCV报错与不稳定的识别结果

5.1 中文路径导致的“解析不了”

现象:解压后直接运行,程序报错打不开文件,或者Qt加载UI时提示解析失败,但文件明明存在。

原因:Qt内部对中文路径处理不够稳定,OpenCV的imread函数更古老,传入带中文的路径时直接返回None,不给任何报错信息。如果主程序又把加载失败的结果当成正常返回值继续处理,后续操作就会连锁崩溃。

解决:压缩包介绍里已经写了,项目路径和项目名不要用中文。解压后重命名为fruit_system或类似纯英文目录,同时确保source_imagedata里的图片文件名也保持英文,比如apple.png而不是苹果.png。遇到历史遗留数据,可以在程序入口做一个路径重命名提示,或使用cv2.imdecode配合np.fromfile读取中文路径图片。

import cv2 import numpy as np def imread_unicode(path): # 绕开OpenCV对中文路径支持不好的问题 data = np.fromfile(path, dtype=np.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)

5.2 安装OpenCV后import报错(cv2.error 4.4.0 pip-req-build)

现象:pip install opencv-python结束后,执行import cv2直接崩溃,报错信息里出现cv2.error: OpenCV(4.4.0) C:\Users\appveyor\AppData\Local\Temp\1\pip-req-buil...,看起来像是OpenCV官方在Windows上编译打包时留下的报错痕迹。

原因:这是典型的OpenCV wheel与当前numpy版本不兼容。OpenCV 4.4.0版本的二进制是当时基于特定numpy ABI编译的,新版本numpy改了底层结构后,旧版OpenCV import时就会出现这种底层报错。

解决:两种做法。一是降级numpy到1.21或1.20版本,pip install numpy==1.21.2;二是把OpenCV升级到4.5.5以上,新版对numpy 1.24+的支持好很多。我建议直接升级OpenCV,毕竟新版本修复了不少findContours的边界问题。

5.3 光照一变识别就翻车

现象:白天拍的一张苹果照片识别正常,晚上在室内灯光下拍同样一颗苹果,识别成橙子或者直接报未检测到。

原因:HSV空间虽然比RGB抗光照变化,但H分量在极端低饱和度或者色温偏移下同样会偏移。暖光灯光会让红苹果的色相往橙黄方向漂移10到15度,直接越过阈值边界。

解决:在进入inRange之前增加预处理链路。先用高斯模糊降噪,再考虑加一个简单的白平衡或直方图均衡化。如果拍摄环境固定,最直接的办法是把阈值区间放宽10%左右,并配合面积阈值过滤杂点。源码里的deblur.py和imagedeal预处理模块就是干这个用的,识别前先跑一遍它们,效果会稳定很多。

5.4 打包exe后秒退与资源文件丢失

现象:代码在命令行运行正常,用PyInstaller打包后双击exe,黑色窗口闪一下就消失,界面根本没弹出来。

原因:资源文件路径用的是相对路径“fruit_recognition.ui”和“pics_ui_rc.py”。PyInstaller打包后,程序运行在临时解包目录,UI文件没有被复制进去,loadUi自然失败。闪退是因为异常发生在事件循环开始前,没有任何窗口展示。

解决:打包时用--add-data参数把ui和qrc文件一起打进去:

pyinstaller --windowed --add-data "fruit_recognition.ui;." --add-data "pics_ui.qrc;." fruit_recognition.py

注意Windows系统add-data的分隔符是分号,Linux/Mac是冒号。打包后把图片测试文件夹放在exe同目录,避免相对路径找不到source_imagedata里面的样本图。

5.5 在线接口token过期导致演示中断

现象:答辩演示时运行baidufruit.py,返回错误提示access_token过期或invalid client_id。

原因:在线API的token一般有有效期,冷门项目临近答辩才想起测试,token早就过期了。

解决:答辩前24小时重新获取一次token并写入配置。更稳妥的做法是演示全程用离线识别,在线识别只在PPT架构图里出现,避免把不确定性带进现场。

6. 进阶:打造HSV阈值滑条调试器

前面提到阈值是颜色识别系统的灵魂,但直接改代码里的数组再运行看效果效率极低。我每次调类似项目都会先写一个滑条调试器,把lower和upper的六个值做成实时滑条,一边拖一边看mask的变化,确定好区间再填回识代码。

import cv2 import numpy as np def nothing(x): pass img = cv2.imread("source_imagedata/apple.png") hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) cv2.namedWindow("adjust") cv2.createTrackbar("H_min", "adjust", 0, 180, nothing) cv2.createTrackbar("S_min", "adjust", 0, 255, nothing) cv2.createTrackbar("V_min", "adjust", 0, 255, nothing) cv2.createTrackbar("H_max", "adjust", 180, 180, nothing) cv2.createTrackbar("S_max", "adjust", 255, 255, nothing) cv2.createTrackbar("V_max", "adjust", 255, 255, nothing) while True: h_min = cv2.getTrackbarPos("H_min", "adjust") s_min = cv2.getTrackbarPos("S_min", "adjust") v_min = cv2.getTrackbarPos("V_min", "adjust") h_max = cv2.getTrackbarPos("H_max", "adjust") s_max = cv2.getTrackbarPos("S_max", "adjust") v_max = cv2.getTrackbarPos("V_max", "adjust") lower = np.array([h_min, s_min, v_min]) upper = np.array([h_max, s_max, v_max]) mask = cv2.inRange(hsv, lower, upper) cv2.imshow("mask", mask) key = cv2.waitKey(1) & 0xFF if key == ord("q"): break cv2.destroyAllWindows()

这个脚本跑起来后,水果区域在mask窗口里会显示为白色,背景是黑色。你只需要拖动六个滑条,让白色部分刚好覆盖水果主体、背景尽量干净,读出的滑条值就是理想的阈值区间。红色水果记得分别调0到10和156到180两个区间,然后取并集。

把这套滑条逻辑和项目结合时,我一般会先在main_image_process.py里加一个debug_mode参数,为True时进入滑条模式,为False时走固定阈值正常识别。这样既保留了调试能力,又不影响最终交付时的用户界面。从那以后,我每次拿到这类颜色识别项目,不管作者原本的阈值写得准不准,都会先把这个滑条调试器跑一遍,在真实拍摄光照下重新标定每个水果的HSV上下限,再填回代码里去。这套流程用了无数次,基本能把误识别率压到最低。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询