pyqt QGraphicsView 可根据鼠标改变大小的标注框,状态栏显示坐标

项目描述和效果展示

因为需要做一个图像标注软件,利用QGraphicsView实现可根据鼠标来调整大小的标注框。实现效果如下:
鼠标移动左上和右下两个点的位置,框的大小随之改变(但是实时拖动框还需要后续优化,等之后有时间再说。欢迎大佬提建议。)开始移动的红色圆和黄色蓝色圆和那个框,与功能无关,可以不写,当背景看就好。
pyqt QGraphicsView 可根据鼠标改变大小的标注框,状态栏显示坐标
关于三个坐标的知识,可以看这个:GraphicsView框架介绍
或者自己搜一下,也有更简单的教程。(但实际上不掌握这个知识点也可以写这个代码)

总体思路

写一个自己的类继承QGraphicsView,在类中自定义信号,重写鼠标方法,在鼠标点击/移动/松开的时候将位置坐标发送出去。

在主类中接收信号得到坐标,并利用坐标,在鼠标点击的时候生成左上角点,松开的时候生成右下角的点和框。鼠标移动点时,重绘框。

(关于框的实现方法,试了很多种都失败了OTZ,最后只能用这种傻办法:根据两个点的坐标画四条线连成一个框)

具体代码

继承QGraphicsView的类

class QMyGraphicsView(QGraphicsView):
    sigMouseMovePoint = pyqtSignal(QPoint)  # 发送鼠标移动的时候的坐标
    sigMousePressPoint = pyqtSignal(QPoint)  # 发送鼠标点击时的坐标
    sigMouseReleasePoint = pyqtSignal(QPoint)  # 发送鼠标松开时的坐标
    #自定义信号sigMouseMovePoint,当鼠标移动时,在mouseMoveEvent事件中,将当前的鼠标位置发送出去
    #QPoint--传递的是view坐标
    
    def __init__(self,parent=None):
        super(QMyGraphicsView,self).__init__(parent)
        self.flag = False  # 这个标志是用来表示鼠标有没有选中点拖动,还是在画板上拖动

    def mousePressEvent(self, evt):
        self.item = self.get_item_at_click(evt)
        if self.item is not None:
            self.flag = True  # 鼠标拖着某物移动
            print('self.item: ', self.item)
        else:
            self.flag = False  # 鼠标在空白处移动
            print('self.item is None')

        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMousePressPoint.emit(pt) #发送鼠标位置
        QGraphicsView.mousePressEvent(self, evt)

    def mouseMoveEvent(self, evt):
        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMouseMovePoint.emit(pt) #发送鼠标位置
        QGraphicsView.mouseMoveEvent(self, evt)

    def mouseReleaseEvent(self, evt):
        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMouseReleasePoint.emit(pt) #发送鼠标位置
        QGraphicsView.mouseReleaseEvent(self, evt)

    def get_item_at_click(self, e):
    	# 获得当前点击对象,如果没有则返回None
        pos = e.pos()
        item = self.itemAt(pos)
        return item

在主类中接收信号

先设置好状态栏,画好背景(三个圈一个框)

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(600,400)
        self.view=QMyGraphicsView()  #创建视图窗口
        self.setCentralWidget(self.view) # 设置*控件
        self.statusbar=self.statusBar()  #添加状态栏
        self.labviewcorrd=QLabel('view坐标:')
        self.labviewcorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labviewcorrd)
        self.labscenecorrd=QLabel('scene坐标:')
        self.labscenecorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labscenecorrd)
        self.labitemcorrd = QLabel('item坐标:')
        self.labitemcorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labitemcorrd)


        rect = QRectF(-200, -100, 400, 200)
        self.scene=QGraphicsScene(rect)  #创建场景
        #参数:场景区域
        #场景坐标原点默认在场景中心---场景中心位于界面中心
        self.view.setScene(self.scene)  #给视图窗口设置场景
        item1=QGraphicsRectItem(rect)  #创建矩形---以场景为坐标
        item1.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable|QGraphicsItem.ItemIsMovable)  #给图元设置标志
        #QGraphicsItem.ItemIsSelectable---可选择
        #QGraphicsItem.ItemIsFocusable---可设置焦点
        #QGraphicsItem.ItemIsMovable---可移动
        #QGraphicsItem.ItemIsPanel---
        self.scene.addItem(item1)  #给场景添加图元
        for pos,color in zip([rect.left(),0,rect.right()],[Qt.red,Qt.yellow,Qt.blue]):
            item=QGraphicsEllipseItem(-50,-50,100,100)  #创建椭圆--场景坐标
            #参数1 参数2  矩形左上角坐标
            #参数3 参数4 矩形的宽和高
            item.setPos(pos,0)  #给图元设置在场景中的坐标(移动图元)--图元中心坐标
            item.setBrush(color)  #设置画刷
            item.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable|QGraphicsItem.ItemIsMovable)
            self.scene.addItem(item)
        self.scene.clearSelection()  #【清除选择】

然后继续在__init__()里面绑定信号函数、设置坐标属性

class MainWindow(QMainWindow):
    def __init__(self):
    	... ... (续前)
    	self.view.sigMouseMovePoint.connect(self.slotMouseMovePoint)
        self.view.sigMousePressPoint.connect(self.slotMousePressPoint)
        self.view.sigMouseReleasePoint.connect(self.slotMouseReleasePoint)

        self.x1 = 0  # 记录点击鼠标坐标
        self.y1 = 0
        self.x2 = 0  # 记录鼠标松开坐标
        self.y2 = 0
        self.x_real = 0  # 记录鼠标移动坐标(暂时没用得上,后续优化有实时拉动框可能用得到)
        self.y_real = 0

然后定义自己的左上和右下角点的类,其实就是一个椭圆,所以继承QGraphicsEllipseItem

class MyQGraphicsEllipseItem(QGraphicsEllipseItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setRect(0, 0, 10, 10)
        self.pos = 0  # 等于1是左上角,等于2是右下角

        pen = QPen()
        pen.setColor(Qt.blue)
        self.setPen(pen)

然后定义自己的边框类:

class Edge:

    def __init__(self, scene, star_point, end_point, parent=None):
        self.start_point = star_point
        self.end_point = end_point
        self.scene = scene

        self.x1 = self.start_point[0]
        self.y1 = self.start_point[1]
        self.x2 = self.end_point[0]
        self.y2 = self.end_point[1]

    def drawEdges(self):
    	# 有时候鼠标点的是左上/右下点,但实际点的是边,所以边也要相应地加上标签。(这个bug找了好久OTZ)
        self.l1 = QGraphicsLineItem(self.x1, self.y1, self.x2, self.y1)
        self.scene.addItem(self.l1)
        self.l1.pos = 1

        self.l2 = QGraphicsLineItem(self.x2, self.y1, self.x2, self.y2)
        self.scene.addItem(self.l2)
        self.l2.pos = 2

        self.l3 = QGraphicsLineItem(self.x1, self.y1, self.x1, self.y2)
        self.scene.addItem(self.l3)
        self.l3.pos = 1

        self.l4 = QGraphicsLineItem(self.x1, self.y2, self.x2, self.y2)
        self.scene.addItem(self.l4)
        self.l4.pos = 2

    def repaint2(self, x2, y2):
    	# 点击的是右下角,调用repaint2
        self.x2 = x2
        self.y2 = y2

        self.repaint()

    def repaint1(self, x1, y1):
    	# 点击的是左上角,调用repaint1
        self.x1 = x1
        self.y1 = y1
        self.repaint()

    def repaint(self):
        self.scene.removeItem(self.l1)
        self.l1.setLine(self.x1, self.y1, self.x2, self.y1)
        self.scene.addItem(self.l1)

        self.scene.removeItem(self.l2)
        self.l2.setLine(self.x2, self.y1, self.x2, self.y2)
        self.scene.addItem(self.l2)

        self.scene.removeItem(self.l3)
        self.l3.setLine(self.x1, self.y1, self.x1, self.y2)
        self.scene.addItem(self.l3)

        self.scene.removeItem(self.l4)
        self.l4.setLine(self.x1, self.y2, self.x2, self.y2)
        self.scene.addItem(self.l4)

然后在 MainWindow类 里写好三个接收接收信号的绑定函数:

    def slotMousePressPoint(self,pt):
        ptscene = self.view.mapToScene(pt)  # 把view坐标转换为场景坐标

        if not self.view.flag:
            e = MyQGraphicsEllipseItem()
            e.setPos(ptscene.x(), ptscene.y())
            e.pos = 1  # 标志是左上角
            self.scene.addItem(e)
            print('press: ', ptscene)

        self.x1 = ptscene.x()
        self.y1 = ptscene.y()
        print('x1:', self.x1, 'y1', self.y1)
    def slotMouseMovePoint(self,pt):

        self.labviewcorrd.setText('view坐标:{},{}'.format(pt.x(),pt.y()))
        ptscene=self.view.mapToScene(pt)  #把view坐标转换为场景坐标

        self.x_real = ptscene.x()
        self.y_real = ptscene.y()

        self.labscenecorrd.setText('scene坐标:{:.0f},{:.0f}'.format(ptscene.x(),ptscene.y()))

        item=self.scene.itemAt(ptscene,self.view.transform())  #在场景某点寻找图元--最上面的图元
        #返回值:图元地址
        #参数1 场景点坐标
        #参数2 ????
        if item != None:
            ptitem=item.mapFromScene(ptscene)  #把场景坐标转换为图元坐标
            self.labitemcorrd.setText('item坐标:{:.0f},{:.0f}'.format(ptitem.x(),ptitem.y()))
    def slotMouseReleasePoint(self,pt):
        ptscene = self.view.mapToScene(pt)  # 把view坐标转换为场景坐标

        self.x2 = ptscene.x()
        self.y2 = ptscene.y()
        print('x2:', self.x2, 'y2', self.y2)

        if not self.view.flag:
            e = MyQGraphicsEllipseItem()
            e.setPos(ptscene.x(), ptscene.y())
            e.pos = 2
            self.scene.addItem(e)
            self.edge = Edge(self.scene, [self.x1, self.y1], [self.x2, self.y2])
            self.edge.drawEdges()
        else:
            if self.view.item.pos == 1:
                print('pos: ', self.view.item.pos)
                self.edge.repaint1(self.x2, self.y2)
                # 有时候鼠标选中的是左上或右下点,但实际选中的是线,所以也要给线赋值
            elif self.view.item.pos == 2:
                self.edge.repaint2(self.x2, self.y2)
                print('release:', self.view.item)

最后加上↓运行

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

完整的代码

import sys

from PyQt5.QtGui import QPen
from PyQt5.QtWidgets import QApplication,QGraphicsScene,QGraphicsView,QGraphicsRectItem,QMainWindow,QLabel,QGraphicsItem,QGraphicsEllipseItem, QGraphicsLineItem
from PyQt5.QtCore import Qt,pyqtSignal,QPoint,QRectF

class QMyGraphicsView(QGraphicsView):
    sigMouseMovePoint=pyqtSignal(QPoint)
    sigMousePressPoint = pyqtSignal(QPoint)
    sigMouseReleasePoint = pyqtSignal(QPoint)
    #自定义信号sigMouseMovePoint,当鼠标移动时,在mouseMoveEvent事件中,将当前的鼠标位置发送出去
    #QPoint--传递的是view坐标
    def __init__(self,parent=None):
        super(QMyGraphicsView,self).__init__(parent)
        self.flag = False

    def mousePressEvent(self, evt):
        self.item = self.get_item_at_click(evt)
        if self.item is not None:
            self.flag = True
            print('self.item: ', self.item)
        else:
            self.flag = False
            print('self.item is None')

        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMousePressPoint.emit(pt) #发送鼠标位置
        QGraphicsView.mousePressEvent(self, evt)

    def mouseMoveEvent(self, evt):
        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMouseMovePoint.emit(pt) #发送鼠标位置
        QGraphicsView.mouseMoveEvent(self, evt)

    def mouseReleaseEvent(self, evt):
        pt=evt.pos()  #获取鼠标坐标--view坐标
        self.sigMouseReleasePoint.emit(pt) #发送鼠标位置
        QGraphicsView.mouseReleaseEvent(self, evt)

    def get_item_at_click(self, e):
        pos = e.pos()
        item = self.itemAt(pos)
        return item


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(600,400)
        self.view=QMyGraphicsView()  #创建视图窗口
        self.setCentralWidget(self.view) # 设置*控件
        self.statusbar=self.statusBar()  #添加状态栏
        self.labviewcorrd=QLabel('view坐标:')
        self.labviewcorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labviewcorrd)
        self.labscenecorrd=QLabel('scene坐标:')
        self.labscenecorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labscenecorrd)
        self.labitemcorrd = QLabel('item坐标:')
        self.labitemcorrd.setMinimumWidth(150)
        self.statusbar.addWidget(self.labitemcorrd)


        rect = QRectF(-200, -100, 400, 200)
        self.scene=QGraphicsScene(rect)  #创建场景
        #参数:场景区域
        #场景坐标原点默认在场景中心---场景中心位于界面中心
        self.view.setScene(self.scene)  #给视图窗口设置场景
        item1=QGraphicsRectItem(rect)  #创建矩形---以场景为坐标
        item1.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable|QGraphicsItem.ItemIsMovable)  #给图元设置标志
        #QGraphicsItem.ItemIsSelectable---可选择
        #QGraphicsItem.ItemIsFocusable---可设置焦点
        #QGraphicsItem.ItemIsMovable---可移动
        #QGraphicsItem.ItemIsPanel---
        self.scene.addItem(item1)  #给场景添加图元
        for pos,color in zip([rect.left(),0,rect.right()],[Qt.red,Qt.yellow,Qt.blue]):
            item=QGraphicsEllipseItem(-50,-50,100,100)  #创建椭圆--场景坐标
            #参数1 参数2  矩形左上角坐标
            #参数3 参数4 矩形的宽和高
            item.setPos(pos,0)  #给图元设置在场景中的坐标(移动图元)--图元中心坐标
            item.setBrush(color)  #设置画刷
            item.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable|QGraphicsItem.ItemIsMovable)
            self.scene.addItem(item)
        self.scene.clearSelection()  #【清除选择】
        self.view.sigMouseMovePoint.connect(self.slotMouseMovePoint)
        self.view.sigMousePressPoint.connect(self.slotMousePressPoint)
        self.view.sigMouseReleasePoint.connect(self.slotMouseReleasePoint)

        self.x1 = 0
        self.y1 = 0
        self.x2 = 0
        self.y2 = 0
        self.x_real = 0
        self.y_real = 0
        # self.edges = []
        # self.flag=False

    def slotMouseMovePoint(self,pt):

        self.labviewcorrd.setText('view坐标:{},{}'.format(pt.x(),pt.y()))
        ptscene=self.view.mapToScene(pt)  #把view坐标转换为场景坐标

        self.x_real = ptscene.x()
        self.y_real = ptscene.y()

        self.labscenecorrd.setText('scene坐标:{:.0f},{:.0f}'.format(ptscene.x(),ptscene.y()))

        item=self.scene.itemAt(ptscene,self.view.transform())  #在场景某点寻找图元--最上面的图元
        #返回值:图元地址
        #参数1 场景点坐标
        #参数2 ????
        if item != None:
            ptitem=item.mapFromScene(ptscene)  #把场景坐标转换为图元坐标
            self.labitemcorrd.setText('item坐标:{:.0f},{:.0f}'.format(ptitem.x(),ptitem.y()))
            # 这里可以设旗子
            # edge = Edge(self.scene, [self.x1, self.y1], [self.x_real, self.y_real])
            # edge.repaint(self.x1, self.y1, self.x_real, self.y_real)
        # if not self.view.flag:
        #     edge = Edge(self.scene, [self.x1, self.y1], [self.x_real, self.y_real])
            # edge.repaint(self.x1, self.y1, self.x_real, self.y_real)

    def slotMousePressPoint(self,pt):
        ptscene = self.view.mapToScene(pt)  # 把view坐标转换为场景坐标

        if not self.view.flag:
            e = MyQGraphicsEllipseItem()
            e.setPos(ptscene.x(), ptscene.y())
            e.pos = 1  # 标志是左上角
            self.scene.addItem(e)
            print('press: ', ptscene)

        self.x1 = ptscene.x()
        self.y1 = ptscene.y()
        print('x1:', self.x1, 'y1', self.y1)
        # self.x1 = pt.x()
        # self.y1 = pt.y()

    def slotMouseReleasePoint(self,pt):
        ptscene = self.view.mapToScene(pt)  # 把view坐标转换为场景坐标

        self.x2 = ptscene.x()
        self.y2 = ptscene.y()
        print('x2:', self.x2, 'y2', self.y2)

        if not self.view.flag:
            e = MyQGraphicsEllipseItem()
            e.setPos(ptscene.x(), ptscene.y())
            e.pos = 2
            self.scene.addItem(e)
            self.edge = Edge(self.scene, [self.x1, self.y1], [self.x2, self.y2])
            self.edge.drawEdges()
        else:
            if self.view.item.pos == 1:
                print('pos: ', self.view.item.pos)
                self.edge.repaint1(self.x2, self.y2)
                # 有时候鼠标选中的是左上或右下点,但实际选中的是线,所以也要给线赋值
            elif self.view.item.pos == 2:
                self.edge.repaint2(self.x2, self.y2)
                print('release:', self.view.item)

    # def get_item_at_click(self, e):
    #     pos = e.pos()
    #     item = self.itemAt(pos)
    #     return item


class MyQGraphicsEllipseItem(QGraphicsEllipseItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setRect(0, 0, 10, 10)
        self.pos = 0  # 等于1是左上角,等于2是右下角

        pen = QPen()
        pen.setColor(Qt.blue)
        self.setPen(pen)


class Edge:

    def __init__(self, scene, star_point, end_point, parent=None):
        self.start_point = star_point
        self.end_point = end_point
        self.scene = scene

        self.x1 = self.start_point[0]
        self.y1 = self.start_point[1]
        self.x2 = self.end_point[0]
        self.y2 = self.end_point[1]

    def drawEdges(self):
        self.l1 = QGraphicsLineItem(self.x1, self.y1, self.x2, self.y1)
        self.scene.addItem(self.l1)
        self.l1.pos = 1

        self.l2 = QGraphicsLineItem(self.x2, self.y1, self.x2, self.y2)
        self.scene.addItem(self.l2)
        self.l2.pos = 2

        self.l3 = QGraphicsLineItem(self.x1, self.y1, self.x1, self.y2)
        self.scene.addItem(self.l3)
        self.l3.pos = 1

        self.l4 = QGraphicsLineItem(self.x1, self.y2, self.x2, self.y2)
        self.scene.addItem(self.l4)
        self.l4.pos = 2

    def repaint2(self, x2, y2):
        self.x2 = x2
        self.y2 = y2

        self.repaint()

    def repaint1(self, x1, y1):
        self.x1 = x1
        self.y1 = y1
        self.repaint()

    def repaint(self):
        self.scene.removeItem(self.l1)
        self.l1.setLine(self.x1, self.y1, self.x2, self.y1)
        self.scene.addItem(self.l1)

        self.scene.removeItem(self.l2)
        self.l2.setLine(self.x2, self.y1, self.x2, self.y2)
        self.scene.addItem(self.l2)

        self.scene.removeItem(self.l3)
        self.l3.setLine(self.x1, self.y1, self.x1, self.y2)
        self.scene.addItem(self.l3)

        self.scene.removeItem(self.l4)
        self.l4.setLine(self.x1, self.y2, self.x2, self.y2)
        self.scene.addItem(self.l4)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

以上

上一篇:Python适合开发桌面软件吗?很多人都不懂!


下一篇:安全帽识别