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:\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_())

上述代碼輸出結果

界面設計

根據我們的挑戰內容,解決思路是使用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:\ProgramData\Anaconda3\Scripts\。
打開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程序


免責聲明!

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



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