0

首先。我已经在 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_()) 

复制粘贴程序,点击加载图片,多次左键绘制图形,结束绘制,右击查看效果。

4

1 回答 1

0

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

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

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

于 2018-02-16T15:17:28.617 回答