信號與槽函數篇轉載於:https://www.xdbcb8.com/archives/190.html
**********************************************************************************
信號與槽函數
GUI應用程序是事件驅動的。 事件主要由應用程序的用戶生成。 但它們也可以通過其他手段產生,例如:網絡連接,窗口管理器或定時器。 當我們調用應用程序的exec_()方法時,應用程序進入主循環。 主循環獲取事件並將其發送到對象。
在事件模型中,有三個參與者:
- 事件來源
- 事件對象
- 事件目標
事件源是其狀態更改的對象。 它會生成事件。 事件對象(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()