OpenCV-Python入門教程7-PyQt編寫GUI界面


前面一直都是使用命令行運行代碼,不夠人性化。這篇用Python編寫一個GUI界面,使用PyQt5編寫圖像處理程序。包括:打開、關閉攝像頭,捕獲圖片,讀取本地圖片,灰度化和Otsu自動閾值分割的功能。

使用Qt Designer來設計界面。而anaconda里自帶了designer.exe,我使用的就是這個。designer.exe的路徑:D:\ProgramData\Anaconda3\Library\bin\,如果是普通的Python環境,則需要自行安裝

pip install pyqt5-tools

安裝完成后,designer.exe應該在Python的安裝目錄下:xxx\Lib\site-packages\pyqt5_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_())

一、界面設計

在D:\ProgramData\Anaconda3\Library\bin\下打開designer.exe,會彈出創建新窗體的窗口,我們直接點擊"創建"(英文版是create)

界面左側是Qt的常用控件"Widget Box",右側有一個控件屬性窗口"Property Editor"。本例中我們只用到了"Push Button"控件和"Label"控件,可以在屬性窗口調整它的大小150x150(可以根據自己的需求適當調大或者縮小):

控件上顯示的文字"text"屬性和控件的名字"objectName"屬性需要修改,便於顯示和代碼調用,可以按照下面的表格命名:

控件 顯示內容text 控件名objectName
PushButton 打開攝像頭 btnOpenCamera
PushButton 捕獲圖片 btnCapture
PushButton 打開圖片 btnReadImage
PushButton 灰度化 btnGray
PushButton 閾值分割 btnThreshold
Label 攝像頭 labelCamera
Label 捕獲圖 labelCapture
Label 結果圖 labelResult

前面設計好了界面,接下來就是實現"打開攝像頭"到"閾值分割"這五個按鈕的功能,也就是給每個按鈕指定一個函數,邏輯代碼寫在這個函數里面。這個函數就稱事件,Qt中稱為槽連接

點擊Designer工具欄的Edit Signals/Slots按鈕,進入槽函數編輯界面,點擊旁邊的"Edit Widgets"可以恢復正常視圖:

在彈出的配置窗口中,可以看到左側是按鈕的常用事件,我們選擇點擊事件”clicked()”,然后添加一個名為”btnOpenCamera_Clicked()”的槽函數:

重復上面的步驟,給五個按鈕添加五個槽函數,最終結果如下:

Ctrl + S保存.ui文件。我們需要將ui轉py代碼。

打開cmd命令行,切換到ui文件的保存目錄。Windows下有個小技巧,可以在目錄的地址欄輸入cmd,一步切換到當前目錄:

執行這條指令

pyuic5 -o mainForm.py using_pyqt_create_ui.ui

生成mainForm.py文件,里面包含一個名為”Ui_MainWindow”的類。

二、編寫邏輯代碼

mainForm.py是根據ui文件生成的,也就是說,一旦ui文件有所改變,需要重新生成覆蓋原來的文件。

新建一個mainEntry.py存放邏輯代碼,代碼雖然很長,但是很簡單並不難懂。有些部分有所重復,並沒有將其封裝成一個函數(博主能力有限),感興趣的可以試一下

import sys
import cv2
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 = cv2.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 = cv2.imread(str(filename))
            # OpenCV圖像以BGR通道存儲,顯示時需要從BGR轉到RGB
            self.captured = cv2.cvtColor(self.captured, cv2.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 = cv2.cvtColor(self.captured, cv2.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 = cv2.threshold(
            self.cpatured, 0, 255, cv2.THRESH_BINARY + cv2.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

        cv2.cvtColor(self.frame, cv2.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_())

 參考網址:https://tianchi.aliyun.com/course/courseConsole?courseId=40992&chapterIndex=1&sectionIndex=16


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM