ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于PyQt5与OpenCV的水果识别系统:源码解析与HSV调试实战

基于PyQt5与OpenCV的水果识别系统:源码解析与HSV调试实战 简介这份基于PyQt5、OpenCV与Python实现的水果识别系统源码完整包含GUI界面与详细代码注释主要面向计算机相关专业学生及开发者。项目覆盖了图像采集、预处理、特征提取与识别等基础流程可直接用于毕业设计、课程设计或大作业也适合作为项目初期演示与二次开发的原型。压缩包内共55个文件以16个Python脚本为核心同时提供2个UI界面设计文件、2个qrc资源文件以及多种水果样本图片如苹果、香蕉、西瓜、猕猴桃等和说明文档整体仅4.63MB轻量易部署。源码中还包含图像去模糊、图像处理与识别切换等具体实现模块配合注释与readme说明便于理解脉络和按需改造。目前已有439人参与学习下载所有功能经验证可稳定运行适合希望快速搭建图像识别项目、研习OpenCV与PyQt5整合开发的中高级读者。1. 基于pyqt5opencvpython的水果识别这套源码能解决什么做课程设计或毕设时最怕的不是没有思路而是拿到一套代码却跑不起来、看不懂、改不动。这个基于PyQt5OpenCVPython的水果识别系统源码正好把这三件事一起解决了它自带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转HSVH范围在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 numpypyqt5-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和加载UIimport 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.pypyrcc5 -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, paramsparams, 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, dtypenp.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 numpy1.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上下限再填回代码里去。这套流程用了无数次基本能把误识别率压到最低。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进