pyqt5 信號與槽函數簡介,利用cv2鼠標事件在圖片中繪制矩形


信號與槽函數篇轉載於:https://www.xdbcb8.com/archives/190.html

**********************************************************************************

信號與槽函數

GUI應用程序是事件驅動的。 事件主要由應用程序的用戶生成。 但它們也可以通過其他手段產生,例如:網絡連接,窗口管理器或定時器。 當我們調用應用程序的exec_()方法時,應用程序進入主循環。 主循環獲取事件並將其發送到對象。

在事件模型中,有三個參與者:

  1. 事件來源
  2. 事件對象
  3. 事件目標
    事件源是其狀態更改的對象。 它會生成事件。 事件對象(event)將狀態更改封裝在事件源中。 事件目標是要通知的對象。 事件源對象將處理事件的任務委托給事件目標。

PyQt5具有獨特的信號和插槽機制來處理事件。 信號和槽用於對象之間的通信。 發生特定事件時發出信號。 槽可以是任何Python可調用的函數。 當發射連接的信號時會調用一個槽。

簡單的信號與槽示例:

#coding=utf-8

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QLCDNumber, QDial, QApplication)

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()

    def initUi(self):
        lcd = QLCDNumber(self)
        dial = QDial(self)

        self.setGeometry(300, 300, 350, 250)
        self.setWindowTitle('學點編程吧')

        lcd.setGeometry(100,50,150,60)
        dial.setGeometry(120,120,100,100)

        dial.valueChanged.connect(lcd.display)

        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

在這個例子我們展示了一個QtGui.QLCDNumber和一個QtGui.QDial這個兩個小部件,當我們撥動QDial這個小部件的時候,LCD屏幕就會顯示出此時Dial小部件的值。

dial.valueChanged.connect(lcd.display)

這里我們將QDial這個小部件的一個valueChanged信號連接到lcd數字的顯示槽。

QDial對象發送信號。 QLCDNumber接收信號的。 槽是對信號作出反應的方法。

重新實現事件處理程序:

下面這個例子,我們就重新實現了按下按鈕后要如何處理。

#coding=utf-8

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QWidget, QApplication, QLabel)

class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUi()
    def initUi(self):
        self.setGeometry(300, 300, 350, 250)
        self.setWindowTitle('學點編程吧')

        self.lab = QLabel('方向',self)

        self.lab.setGeometry(150,100,50,50)

        self.show()

    def keyPressEvent(self, e):

        if e.key() == Qt.Key_Up:
            self.lab.setText('')
        elif e.key() == Qt.Key_Down:
            self.lab.setText('')
        elif e.key() == Qt.Key_Left:
            self.lab.setText('')
        else:
            self.lab.setText('')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

在我們的例子中,我們重新實現了keyPressEvent()事件處理程序。當我們按住上、下、左、右方向鍵的時候,窗口中依次會出現對應方位。

我們再舉一個重寫鼠標事件與繪圖事件的例子:

#coding=utf-8

import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt

class Example(QWidget):
    distance_from_center = 0
    def __init__(self):
        super().__init__()
        self.initUI()
        self.setMouseTracking(True)
    def initUI(self):
        self.setGeometry(200, 200, 1000, 500)
        self.setWindowTitle('學點編程吧')
        self.label = QLabel(self)
        self.label.resize(500, 40)
        self.show()
        self.pos = None

    def mouseMoveEvent(self, event):
        distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
        self.label.setText('坐標: ( x: %d ,y: %d )' % (event.x(), event.y()) + " 離中心點距離: " + str(distance_from_center))       
        self.pos = event.pos()
        self.update()

    def paintEvent(self, event):
        if self.pos:
            q = QPainter(self)
            q.drawLine(0, 0, self.pos.x(), self.pos.y())

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

在這個例子中我們實現了鼠標坐標(x,y)的獲取,以及繪制一條線,這條線的起點坐標在(0,0),另外一個端點隨鼠標移動而移動,同時我們還要計算鼠標坐標與中心點的距離(運用勾股定理進行計算)

self.setMouseTracking(True)

默認情況下禁用鼠標跟蹤, 如果啟用鼠標跟蹤,即使沒有按鈕被按下,小部件也會接收鼠標移動事件。當然你也可以不寫,只需要在執行的過程中按照鼠標左鍵也行。

def mouseMoveEvent(self, event):
    distance_from_center = round(((event.y() - 250)**2 + (event.x() - 500)**2)**0.5)
    self.label.setText('坐標: ( x: %d ,y: %d )' % (event.x(), event.y()) + " 離中心點距離: " + str(distance_from_center))       
    self.pos = event.pos()
    self.update()

這個函數就是捕捉鼠標移動事件了,我們把得到的坐標已經一些相關的信息顯示在label上。必須調用函數update()才能更新圖形。

def paintEvent(self, event):
    if self.pos:
        q = QPainter(self)
        q.drawLine(0, 0, self.pos.x(), self.pos.y())

繪圖的話需要重寫繪圖事件,我們生成QPainter對象,然后調用drawLine()方法繪制一條線,需要四個參數,起點的坐標,終點的坐標。后面會詳細的對繪圖進行舉例,這里只是為了配合鼠標移動事件,做一個例子。

事件發送者
有時,知道哪個窗口小部件是信號的發送者非常有用。 為此,PyQt5具有sender()方法。例如下面這個例子,我們實現了簡單的石頭、剪刀、布的小游戲。

#coding=utf-8

import sys
from PyQt5.QtWidgets import (QApplication, QMessageBox, QWidget, QPushButton)
from random import randint

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(200, 200, 300, 300)
        self.setWindowTitle('學點編程吧')

        bt1 = QPushButton('剪刀',self)
        bt1.setGeometry(30,180,50,50)

        bt2 = QPushButton('石頭',self)
        bt2.setGeometry(100,180,50,50)

        bt3 = QPushButton('',self)
        bt3.setGeometry(170,180,50,50)

        bt1.clicked.connect(self.buttonclicked)
        bt2.clicked.connect(self.buttonclicked)
        bt3.clicked.connect(self.buttonclicked)

        self.show()

    def buttonclicked(self):
        computer = randint(1,3)
        player = 0
        sender = self.sender()
        if sender.text() == '剪刀':
            player = 1
        elif sender.text() == '石頭':
            player = 2
        else:
            player = 3

        if player == computer:
            QMessageBox.about(self, '結果', '平手')
        elif player == 1 and computer == 2:
            QMessageBox.about(self, '結果', '電腦:石頭,電腦贏了!')
        elif player == 2 and computer == 3:
            QMessageBox.about(self, '結果', '電腦:布,電腦贏了!')
        elif player == 3 and computer == 1:
            QMessageBox.about(self,'結果','電腦:剪刀,電腦贏了!')
        elif computer == 1 and player == 2:
            QMessageBox.about(self,'結果','電腦:剪刀,玩家贏了!')
        elif computer == 2 and player == 3:
            QMessageBox.about(self,'結果','電腦:石頭,玩家贏了!')
        elif computer == 3 and player == 1:
            QMessageBox.about(self,'結果','電腦:布,玩家贏了!')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

我們在我們的例子中有三個按鈕,分別代表石頭、剪刀、布。 在buttonClicked()方法中,我們通過調用sender()方法來確定我們點擊了哪個按鈕。

bt1.clicked.connect(self.buttonclicked)
bt2.clicked.connect(self.buttonclicked)
bt3.clicked.connect(self.buttonclicked)

三個按鈕的clicked信號都連接到同一個槽buttonclicked

def buttonclicked(self):
    computer = randint(1,3)
    player = 0
    sender = self.sender()
    if sender.text() == '剪刀':
        player = 1
    elif sender.text() == '石頭':
        player = 2
    else:
        player = 3

    if player == computer:
        QMessageBox.about(self, '結果', '平手')
    elif player == 1 and computer == 2:
        QMessageBox.about(self, '結果', '電腦:石頭,電腦贏了!') 
    elif player == 2 and computer == 3:
        QMessageBox.about(self, '結果', '電腦:布,電腦贏了!')
    elif player == 3 and computer == 1:
        QMessageBox.about(self,'結果','電腦:剪刀,電腦贏了!')
    elif computer == 1 and player == 2:
        QMessageBox.about(self,'結果','電腦:剪刀,玩家贏了!')
    elif computer == 2 and player == 3:
        QMessageBox.about(self,'結果','電腦:石頭,玩家贏了!')
    elif computer == 3 and player == 1:
        QMessageBox.about(self,'結果','電腦:布,玩家贏了!')

我們通過調用sender()方法來確定信號源,根據信號源確定玩家究竟選擇了石頭、剪刀、布中的哪一個。 從而與電腦隨機給出的數字進行比較,判斷輸贏。

發出自定義信號:

從QObject創建的對象可以發出信號。 以下示例顯示了我們如何發出自定義信號。

#coding=utf-8

import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QMessageBox)
from PyQt5.QtCore import (pyqtSignal, QObject)

class Signal(QObject):
    showmouse = pyqtSignal()

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(200, 200, 300, 300)
        self.setWindowTitle('學點編程吧')

        self.s = Signal()
        self.s.showmouse.connect(self.about)

        self.show()
    def about(self):
        QMessageBox.about(self,'鼠標','你點鼠標了吧!')

    def mousePressEvent(self, e):
        self.s.showmouse.emit()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

在這個例子當中,當我們單擊鼠標的時候,就會彈出對話框告知我們單擊了鼠標。

我們創建一個名為showmouse的新信號。 該信號在鼠標按壓事件期間發出。 該信號連接到QMainWindow的about()的槽。

class Signal(QObject):
    showmouse = pyqtSignal()

使用pyqtSignal()作為外部Signal類的類屬性創建一個信號。

self.s = Signal()
self.s.showmouse.connect(self.about)

自定義showmouse信號連接到QMainWindow的about()的槽。

def mousePressEvent(self, e):
    self.s.showmouse.emit()

當我們用鼠標指針點擊窗口時,會發出showmouse信號,調用相應的槽函數。

***************************************************************************************************

cv2鼠標事件

import cv2
import numpy as np

cv2.namedWindow("new")
def drawxxx(event,x,y,flags,param): #鼠標事件回調函數
    #參數 (事件,x軸位置,y軸位置,標記,屬性)
    """
    event:
        EVENT_MOUSEMOVE 0            #滑動
        EVENT_LBUTTONDOWN 1          #左鍵點擊
        EVENT_RBUTTONDOWN 2          #右鍵點擊
        EVENT_MBUTTONDOWN 3          #中鍵點擊
        EVENT_LBUTTONUP 4            #左鍵放開
        EVENT_RBUTTONUP 5            #右鍵放開
        EVENT_MBUTTONUP 6            #中鍵放開
        EVENT_LBUTTONDBLCLK 7        #左鍵雙擊
        EVENT_RBUTTONDBLCLK 8        #右鍵雙擊
        EVENT_MBUTTONDBLCLK 9        #中鍵雙擊
    x,y:
        x,y,代表鼠標位於窗口的(x,y)坐標位置
    flags:
        代表鼠標的拖拽事件,以及鍵盤鼠標聯合事件
        EVENT_FLAG_LBUTTON 1       #左鍵拖曳
        EVENT_FLAG_RBUTTON 2       #右鍵拖曳
        EVENT_FLAG_MBUTTON 4       #中鍵拖曳
        EVENT_FLAG_CTRLKEY 8       #(8~15)按Ctrl不放事件
        EVENT_FLAG_SHIFTKEY 16     #(16~31)按Shift不放事件
        EVENT_FLAG_ALTKEY 32       #(32~39)按Alt不放事件
        比如:按住CTRL鍵 單擊左鍵  返回8+1=9
    :param param:不知道有什么用
    """

    if event==cv2.EVENT_LBUTTONDOWN :
        print('你單機了鼠標左鍵,鼠標坐標為:%s %s'%(x,y))
        print(flags)
        print(param)

    if event==cv2.EVENT_RBUTTONDOWN :
        print('你單機了鼠標右鍵,鼠標坐標為:%s %s'%(x,y))
        print(flags)
        print(param)

    pass

cv2.setMouseCallback("new",drawxxx)  #注冊鼠標監聽事件(窗口,回調函數)
img = 255*np.ones((240,480,3),np.uint8)
cv2.imshow('new',img)


cv2.waitKey()
cv2.destroyAllWindows()

利用cv2的鼠標事件,在圖片中繪制矩形相關代碼

import os
import cv2



# 定義標注窗口的默認名稱
WINDOW_NAME = 'Simple Bounding Box Labeling Tool'

# 定義畫面刷新的大概幀率(是否能達到取決於電腦性能)
FPS = 24

# 在圖像下方多出BAR_HEIGHT這么多像素的區域用於顯示文件名和當前標注物體等信息
BAR_HEIGHT = 16

# ESC鍵對應的cv.waitKey()的返回值
# 注意這個值根據操作系統不同有不同
KEY_ESC = 27




# 定義物體框標注工具類
class SimpleBBoxLabeling:

    def __init__(self, inage, fps=FPS, window_name=None):
        self. inage= inage
        self.fps = fps
        self.window_name = window_name if window_name else WINDOW_NAME

        # pt0是正在畫的左上角坐標,pt1是鼠標所在坐標
        self._pt0 = None
        self._pt1 = None

        # 表明當前是否正在畫框的狀態標記
        self._drawing = False

        # 當前標注物體的名稱
        self._cur_label = None

        # 當前圖像對應的所有已標注框
        self._bboxes = []


    # 鼠標回調函數
    def _mouse_ops(self, event, x, y, flags, param):

        # 按下左鍵時,坐標為左上角,同時表明開始畫框,改變drawing標記為True
        if event == cv2.EVENT_LBUTTONDOWN:
            self._drawing = True
            self._pt0 = (x, y)

        # 左鍵抬起,表明當前框畫完了,坐標記為右下角,並保存,同時改變drawing標記為False
        elif event == cv2.EVENT_LBUTTONUP:
            self._drawing = False
            self._pt1 = (x, y)
            self._bboxes.append((self._cur_label, (self._pt0, self._pt1)))

        # 實時更新右下角坐標方便畫框
        elif event == cv2.EVENT_MOUSEMOVE:
            self._pt1 = (x, y)

        # 鼠標右鍵刪除最近畫好的框
        elif event == cv2.EVENT_RBUTTONUP:
            if self._bboxes:
                self._bboxes.pop()

    # 清除所有標注框和當前狀態
    def _clean_bbox(self):
        self._pt0 = None
        self._pt1 = None
        self._drawing = False
        self._bboxes = []

    # 畫標注框和當前信息的函數
    def _draw_bbox(self, img):

        # 在圖像下方多出BAR_HEIGHT這么多像素的區域用於顯示文件名和當前標注物體等信息
        h, w = img.shape[:2]
        canvas = cv2.copyMakeBorder(img, 0, BAR_HEIGHT, 0, 0, cv2.BORDER_CONSTANT, value=(255, 0, 0))

        # 畫出已經標好的框和對應名字
        for label, (bpt0, bpt1) in self._bboxes:
            cv2.rectangle(canvas, bpt0, bpt1, (0, 0, 255), thickness=2)

        # 畫正在標注的框和對應名字
        if self._drawing:
            if self._pt1[0] >= self._pt0[0] and self._pt1[1] >= self._pt0[1]:
                cv2.rectangle(canvas, self._pt0, self._pt1, (0, 255, 0), thickness=2)

        return canvas



    # 開始OpenCV窗口循環的方法,定義了程序的主邏輯
    def start(self):

        # 定義窗口和鼠標回調
        cv2.namedWindow(self.window_name,0)
        cv2.setMouseCallback(self.window_name, self._mouse_ops)
        key = 0

        # 定義每次循環的持續時間
        delay = int(1000 / FPS)

        img = self.inage

        # 只要沒有按下Esc鍵,就持續循環
        while key != KEY_ESC:
            # 把標注和相關信息畫在圖片上並顯示指定的時間
            canvas = self._draw_bbox(img)
            cv2.imshow(self.window_name, canvas)
            key = cv2.waitKey(delay)

        print('Finished!')
        cv2.destroyAllWindows()



if __name__ == '__main__':
    img = cv2.imread('E:\DC BREAKER\python\Project\Auto-Detection\p1\P11P55o1_I90.jpg')
    labeling_task = SimpleBBoxLabeling(img)
    labeling_task.start()

 


免責聲明!

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



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