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()

 

posted @ 2022-02-22 19:40  菜芽caiya  阅读(944)  评论(0编辑  收藏  举报