ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-03-06 10:31:03  阅读:314  来源: 互联网

标签:QGraphicsView 状态栏 self scene pyqt pos item 坐标 view


项目描述和效果展示

因为需要做一个图像标注软件,利用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_())

以上

标签:QGraphicsView,状态栏,self,scene,pyqt,pos,item,坐标,view
来源: https://blog.csdn.net/Cleo_Gao/article/details/114433851

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有