项目描述和效果展示
因为需要做一个图像标注软件,利用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_())
以上