pyqt5 信号与槽函数简介,利用cv2鼠标事件在图片中绘制矩形
信号与槽函数篇转载于: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()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?