Python中的交互式绘图

时间:2018-02-16 15:05:10

标签: python pyqt5 drawing2d

首先。我已经在python和PyQt上运行了一些代码,用户在其中绘制图像并且程序返回绘制的图像。我想做的是让用户在完成后修改绘图。例如,他可以点击他绘制的点并拖动它来修改绘画。

有人可以给我创意或图书馆吗?

这是我现有的代码:

import sys 
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import cv2

##
# MAIN WINDOW LAYOUT
##
class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.view = View(self)

        # Button to clear both image and drawing
        self.button = QPushButton('Clear Drawing', self)            
        self.button.clicked.connect(self.handleClearView)

        # 'Load image' button
        self.btnLoad = QToolButton(self)
        self.btnLoad.setText('Load image')
        self.btnLoad.clicked.connect(self.loadImage)

        # Save
        self.btnSave = QToolButton(self)
        self.btnSave.setText('Save image')
        self.btnSave.clicked.connect(self.file_save)

        # Save as
        self.btnSaveAs = QToolButton(self)
        self.btnSaveAs.setText('Save as...')
        self.btnSaveAs.clicked.connect(self.file_save_as)

        # Arrange Layout
        self.layout = QVBoxLayout(self)                         
        self.layout.addWidget(self.view)        # Drawing
        self.layout.addWidget(self.button)      # Clear view
        self.layout.addWidget(self.btnLoad)     # Load photo
        self.layout.addWidget(self.btnSave)     # Save
        self.layout.addWidget(self.btnSaveAs)   # Save as...


        self.setGeometry(0, 25, 1365, 700)
        self.setWindowTitle('Processed Slices')
        self.show()

    def file_save_as(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        filename, _ = QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()","","All Files (*);;Text Files (*.txt)", options=options)
        cv2.imwrite(filename + '.png', self.view.cvImage)

    def file_save(self):
        cv2.imwrite(self.view._filename, self.view.cvImage)

    def openFileNameDialog(self):    
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "","All Files (*);;Python Files (*.py)", options=options)
        if fileName:
            self.view._filename = fileName

    def loadImage(self):
        self.view._empty = False
        # Load Image to pixmap
        self.openFileNameDialog()
        self.view.cvImage = cv2.imread(self.view._filename)
        self.view.height, self.view.width , self.view.bytesPerComponent= self.view.cvImage.shape
        self.view.bytesPerLine = self.view.bytesPerComponent * self.view.width
        cv2.cvtColor(self.view.cvImage, cv2.COLOR_BGR2RGB, self.view.cvImage)
        self.view.mQImage = QImage(self.view.cvImage.data, self.view.width, self.view.height, self.view.bytesPerLine, QImage.Format_RGB888)        
        self.pixmap = QPixmap(self.view.mQImage) 

        # Include pixmap on the drawing scene
        self.view.setScene(QGraphicsScene(self))
        self.view.setSceneRect(QRectF(0,0,self.view.width, self.view.height))   # Scene has same dimension as image so that we can map the segmented area to the cv2 image
        self.view.scene().addPixmap(self.pixmap)
        self.view.fitInView()

    def handleClearView(self):
        self.view.scene().clear()
        self.view.scene().addPixmap(self.pixmap)
        self.view.contour = []
        self.view.fitInView()



##
# DRAWING AND ZOOMING
##
class View(QGraphicsView):
    def __init__(self, parent):

        super().__init__()

        # Attributes
        self._zoom = 0
        self._empty = True
        self._scene = QGraphicsScene(self)
        self._photo = QGraphicsPixmapItem()
        self._scene.addItem(self._photo)
        self._filename = ""
        # Resettings for Zooming
        self.setScene(self._scene)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QBrush(QColor(30, 30, 30)))
        self.setFrameShape(QFrame.NoFrame)

        self.contour = []                   # Contains points of the contour for cv2 drawing
        self.first = QPointF(0,0)
        self.ii = 0

    """ PAINTING """
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._start = event.pos()                                                                               # Get point where we pressed
            self.contour.append(QPointF(self.mapToScene(self._start)))              
            if self.first == QPointF(0,0):                                                           # If it is the first we press, save the point which will be used to close 
                                                                                                     # the shape whenever the right button is pressed
                self.first = QPointF(self.mapToScene(self._start))
                self.ii = 0
            else:                                                                                   # If it is not the first point draw a line joining the point we previously 
                                                                                                    # pressed and the one we have just clicked
                self.scene().addItem(QGraphicsLineItem(QLineF(self.contour[self.ii+1], self.contour[self.ii]))) 
                self.ii += 1


    def mouseReleaseEvent(self, event):
        # RIGHT BUTTON
        if event.button() == Qt.RightButton:
            self.scene().addItem(QGraphicsLineItem(QLineF(self.contour[-1], self.first)))  # close contour
            self.first = QPointF(0,0)
            self.contour.append((self.contour[0]))

            #Drawing CV_IMAGE
            for i in range(len(self.contour) - 1):
                A = ( int( self.contour[i].x() ), int( self.contour[i].y() ) )
                B = ( int( self.contour[i+1].x() ), int( self.contour[i+1].y() ))
                cv2.line(self.cvImage, A, B, (0,0,255), 2)
            cv2.imshow('drawed slice', self.cvImage)
            cv2.waitKey()


    """ ZOOMING """
    def hasPhoto(self):
        return not self._empty

    def fitInView(self, scale=True):
        rect = QRectF(0, 0, self.width, self.height)
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
            self._zoom = 0

    def wheelEvent(self, event):
        if self.hasPhoto():
            if event.angleDelta().y() > 0:              # event.angleDelta() returns the distance that the wheel is rotated, in eighths of a degree.
                factor = 1.25                           # Zooming in
                self._zoom += 1
            else:
                factor = 0.8                            # Zooming out
                self._zoom -= 1

            if self._zoom > 0:
                self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:                                       # Cannot zoom out from the original size
                self._zoom = 0


##
# RUN PROGRAM
##
if __name__ == '__main__':

    import sys
    app = QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_()) 

只需复制粘贴程序,单击加载图像并通过多次左键单击绘制图形,结束图形并查看必须右键单击的结果。

1 个答案:

答案 0 :(得分:0)

所以没有简单的答案。我已经做到了,但很多年前。你需要实现某种命中测试机制和“拖动句柄”。这是一个简单的架构,但Qt没有内置的。 基本上,您必须将场景视为创建项目的一系列步骤的产物。您的行self.scene().addItem(...)是关键。您需要返回场景并查看存在的项目(我忘记了场景能够告诉您有关项目的内容)或启用您自己的外部跟踪。

但是一旦您知道场景中的项目,您需要修改mousePress事件以查看它们是否单击已存在的内容,如果是,请以所需方式修改该对象。这将总是需要一些阈值处理,因此您必须在鼠标点击周围查找大约1到5个像素的项目,并将其包括在内。一旦您确定用户点击了有效的内容,您就可以应用这些事件。所以你需要为mousePress添加一个测试,除非你有一个切换(模式,工具等)进行编辑而不是创建。

这些都不是特定于PyQt的,一般只是Qt。