• opencv 结合pyqt5 编写简单的图像处理GUI程序


    实验目标

    实验目标
    我们学的内容都是跑在命令行中的,并没有界面,那么”脚本语言”Python如何搭建GUI界面呢?
    其实Python支持多种图形界面库,如Tk(Tkinter)、wxPython、PyQt等,虽然Python自带Tkinter,无需额外安装包,但我更推荐使用PyQt,一是因为它完全基于Qt,跨平台,功能强大,有助于了解Qt的语法,二是Qt提供了Designer设计工具,界面设计上可以拖控件搞定,非常方便,大大节省时间。

    pyqt5

    安装pyqt5:
    pip install pyqt5
    我推荐使用Qt Designer来设计界面,如果你装的是Anaconda的话,就已经自带了designer.exe,例如我的是在:D:ProgramDataAnaconda3Libraryin,如果是普通的Python环境,则需要自行安装:
    pip install pyqt5-tools
    安装完成后,designer.exe在Python安装目录下:xxxLibsite-packagespyqt5_tools。
    可以使用下面的代码生成一个简单的界面:

    import sys
    from PyQt5.QtWidgets import QApplication, QWidget
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
    
        window = QWidget()
        window.setWindowTitle('Hello World!')
        window.show()
    
        sys.exit(app.exec_())
    

    上述代码输出结果

    界面设计

    根据我们的挑战内容,解决思路是使用Qt Designer来设计界面,使用Python完成代码逻辑。打开designer.exe,会弹出创建新窗体的窗口,我们直接点击“create”:
    创建图形用户界面
    界面的左侧是Qt的常用控件”Widget Box”,右侧有一个控件属性窗口”Property Editor”,其余暂时用不到。本例中我们只用到了”Push Button”控件和”Label”控件:最上面的三个Label控件用于显示图片,可以在属性窗口调整它的大小,我们统一调整到150×150:
    拖动widget实现实验目标的效果
    调整label的宽和高
    另外,控件上显示的文字 text 属性和控件的名字 objectName 属性需要修改,便于显示和代码调用。可以按照下面我推荐的命名:
    控件属性设置
    这样大致界面就出来了,很简单:
    调整部件属性后的图形用户界面

    按钮事件

    我们知道GUI是通过事件驱动的,什么意思呢?比如前面我们已经设计好了界面,接下来就需要实现”打开摄像头”到”阈值分割”这5个按钮的功能,也就是给每个按钮指定一个”函数”,逻辑代码写在这个函数里面。这种函数就称为事件,Qt中称为槽连接。
    点击Designer工具栏的”Edit Signals/Slots”按钮,进入槽函数编辑界面,点击旁边的”Edit Widgets”可以恢复正常视图:
    点击,进入槽函数编辑界面
    然后点击按钮并拖动,当产生类似于电路中的接地符号时释放鼠标,参看下面动图:
    图解按钮绑定事件1
    在弹出的配置窗口中,可以看到左侧是按钮的常用事件,我们选择点击事件”clicked()”,然后添加一个名为”btnOpenCamera_Clicked()”的槽函数:
    图解按钮绑定事件2
    重复上面的步骤,给五个按钮添加五个槽函数,最终结果如下:
    添加按钮事件后结果
    到此,我们就完成了界面设计的所有工作,按下Ctrl+S保存当前窗口为.ui文件。.ui文件其实是按照XML格式标记的内容,可以用文本编辑器将.ui文件打开看看。

    ui文件转py代码

    因为我们是用Designer工具设计出的界面,并不是用Python代码敲出来的,所以要想真正运行,需要使用pyuic5将ui文件转成py文件。pyuic5.exe默认在%Scripts下,比如我的是在:D:ProgramDataAnaconda3Scripts。
    打开cmd命令行,切换到ui文件的保存目录。Windows下有个小技巧,可以在目录的地址栏输入cmd,一步切换到当前目录:
    然后执行这条指令:
    pyuic5 -o mainForm.py using_pyqt_create_ui.ui
    如果出现pyuic5不是内部命令的错误,说明pyuic5的路径没有在环境变量里,添加下就好了。执行正常的话,就会生成mainForm.py文件,里面应该包含一个名为”Ui_MainWindow”的类。

    编写逻辑代码

    在同一工作目录下新建一个”mainEntry.py”的文件,存放逻辑代码。代码中的每部分我都写得比较独立,没有封装成函数,便于理解。代码看上去很长,但很简单,可以每个模块单独看。

    import sys
    import cv2 as cv
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import QFileDialog, QMainWindow
    
    from mainForm import Ui_MainWindow
    
    
    class PyQtMainEntry(QMainWindow, Ui_MainWindow):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
    
            self.camera = cv.VideoCapture(0)
            self.is_camera_opened = False  # 摄像头有没有打开标记
    
            # 定时器:30ms捕获一帧
            self._timer = QtCore.QTimer(self)
            self._timer.timeout.connect(self._queryFrame)
            self._timer.setInterval(30)
    
        def btnOpenCamera_Clicked(self):
            '''
            打开和关闭摄像头
            '''
            self.is_camera_opened = ~self.is_camera_opened
            if self.is_camera_opened:
                self.btnOpenCamera.setText("关闭摄像头")
                self._timer.start()
            else:
                self.btnOpenCamera.setText("打开摄像头")
                self._timer.stop()
    
        def btnCapture_Clicked(self):
            '''
            捕获图片
            '''
            # 摄像头未打开,不执行任何操作
            if not self.is_camera_opened:
                return
    
            self.captured = self.frame
    
            # 后面这几行代码几乎都一样,可以尝试封装成一个函数
            rows, cols, channels = self.captured.shape
            bytesPerLine = channels * cols
            # Qt显示图片时,需要先转换成QImgage类型
            QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
            self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
    
        def btnReadImage_Clicked(self):
            '''
            从本地读取图片
            '''
            # 打开文件选取对话框
            filename,  _ = QFileDialog.getOpenFileName(self, '打开图片')
            if filename:
                self.captured = cv.imread(str(filename))
                # OpenCV图像以BGR通道存储,显示时需要从BGR转到RGB
                self.captured = cv.cvtColor(self.captured, cv.COLOR_BGR2RGB)
    
                rows, cols, channels = self.captured.shape
                bytesPerLine = channels * cols
                QImg = QImage(self.captured.data, cols, rows, bytesPerLine, QImage.Format_RGB888)
                self.labelCapture.setPixmap(QPixmap.fromImage(QImg).scaled(
                    self.labelCapture.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
    
        def btnGray_Clicked(self):
            '''
            灰度化
            '''
            # 如果没有捕获图片,则不执行操作
            if not hasattr(self, "captured"):
                return
    
            self.cpatured = cv.cvtColor(self.captured, cv.COLOR_RGB2GRAY)
    
            rows, columns = self.cpatured.shape
            bytesPerLine = columns
            # 灰度图是单通道,所以需要用Format_Indexed8
            QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
            self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
    
        def btnThreshold_Clicked(self):
            '''
            Otsu自动阈值分割
            '''
            if not hasattr(self, "captured"):
                return
    
            _, self.cpatured = cv.threshold(
                self.cpatured, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
    
            rows, columns = self.cpatured.shape
            bytesPerLine = columns
            # 阈值分割图也是单通道,也需要用Format_Indexed8
            QImg = QImage(self.cpatured.data, columns, rows, bytesPerLine, QImage.Format_Indexed8)
            self.labelResult.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.labelResult.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
    
        @QtCore.pyqtSlot()
        def _queryFrame(self):
            '''
            循环捕获图片
            '''
            ret, self.frame = self.camera.read()
    
            img_rows, img_cols, channels = self.frame.shape
            bytesPerLine = channels * img_cols
    
            cv.cvtColor(self.frame, cv.COLOR_BGR2RGB, self.frame)
            QImg = QImage(self.frame.data, img_cols, img_rows, bytesPerLine, QImage.Format_RGB888)
            self.labelCamera.setPixmap(QPixmap.fromImage(QImg).scaled(
                self.labelCamera.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        window = PyQtMainEntry()
        window.show()
        sys.exit(app.exec_())
    

    程序运行情况

    运行GUI程序

  • 相关阅读:
    AntDesign(React)学习-9 Dva model reducer实践
    AntDesign(React)学习-8 Menu使用 切换框架页内容页面
    AntDesign(React)学习-7 Menu添加事件
    AntDesign(React)学习-6 Menu展示数据
    AntDesign(React)学习-5 路由及使用Layout布局
    AntDesign(React)学习-4 登录页面提交数据简单实现
    AntDesign(React)学习-3 React基础
    AntDesign(React)学习-2 第一个页面
    AntDesign(React)学习-1 创建环境
    正则表达式分组捕获非捕获的示例理解
  • 原文地址:https://www.cnblogs.com/wojianxin/p/12629085.html
一二三 - 开发者的网上家园