图像查看器 GUI 无法正确映射鼠标按下事件的坐标 [英] Image Viewer GUI fails to properly map coordinates for mouse press event

查看:54
本文介绍了图像查看器 GUI 无法正确映射鼠标按下事件的坐标的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将来自各种来源的基于 PyQt5 的图像查看器 Python 代码拼凑在一起,并扩展功能以在加载的图像中裁剪感兴趣区域 (ROI).问题是映射坐标和鼠标点击在确定像素位置时会考虑滚动条和菜单栏.以下是加载图像并提供边界框功能的代码,但由于偏移,我似乎无法准确绘制/裁剪框.

I am trying to piece together PyQt5 based image viewer Python code from various sources and extend capability to crop regions of interest (ROI) within loaded images. The issue is that the mapped coordinates and mouse clicks consider scroll bar and menu bar when determining pixel locations. Following is the code that loads image and provide bounding box capability, but I cannot seem to draw/crop boxes accurately due to the offset.

from PyQt5.QtCore import QDir, Qt
from PyQt5.QtGui import QImage, QPainter, QPalette, QPixmap
from PyQt5.QtWidgets import (QAction, QApplication, QFileDialog, QLabel,
        QMainWindow, QMenu, QMessageBox, QScrollArea, QSizePolicy)
from PyQt5.QtPrintSupport import QPrintDialog, QPrinter


class ImageViewer(QMainWindow):
    def __init__(self):
        super(ImageViewer, self).__init__()

        self.printer = QPrinter()
        self.scaleFactor = 0.0

        self.imageLabel = QLabel()
        self.imageLabel.setBackgroundRole(QPalette.Base)
        self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.imageLabel.setScaledContents(True)

        self.scrollArea = QScrollArea()
        self.scrollArea.setBackgroundRole(QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)
        self.setCentralWidget(self.scrollArea)

        self.createActions()
        self.createMenus()

        self.setWindowTitle("Image Viewer")
        self.resize(500, 400)

    def open(self):
        fileName, _ = QFileDialog.getOpenFileName(self, "Open File",
                QDir.currentPath())
        if fileName:
            image = QImage(fileName)
            if image.isNull():
                QMessageBox.information(self, "Image Viewer",
                        "Cannot load %s." % fileName)
                return

            self.imageLabel.setPixmap(QPixmap.fromImage(image))
            self.scaleFactor = 1.0

            self.printAct.setEnabled(True)
            self.fitToWindowAct.setEnabled(True)
            self.updateActions()

            if not self.fitToWindowAct.isChecked():
                self.imageLabel.adjustSize()

    def print_(self):
        dialog = QPrintDialog(self.printer, self)
        if dialog.exec_():
            painter = QPainter(self.printer)
            rect = painter.viewport()
            size = self.imageLabel.pixmap().size()
            size.scale(rect.size(), Qt.KeepAspectRatio)
            painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
            painter.setWindow(self.imageLabel.pixmap().rect())
            painter.drawPixmap(0, 0, self.imageLabel.pixmap())

    def zoomIn(self):
        self.scaleImage(1.25)

    def zoomOut(self):
        self.scaleImage(0.8)

    def normalSize(self):
        self.imageLabel.adjustSize()
        self.scaleFactor = 1.0

    def fitToWindow(self):
        fitToWindow = self.fitToWindowAct.isChecked()
        self.scrollArea.setWidgetResizable(fitToWindow)
        if not fitToWindow:
            self.normalSize()

        self.updateActions()

    def about(self):
        QMessageBox.about(self, "About Image Viewer",
                "<p>The <b>Image Viewer</b> example shows how to combine "
                "QLabel and QScrollArea to display an image. QLabel is "
                "typically used for displaying text, but it can also display "
                "an image. QScrollArea provides a scrolling view around "
                "another widget. If the child widget exceeds the size of the "
                "frame, QScrollArea automatically provides scroll bars.</p>"
                "<p>The example demonstrates how QLabel's ability to scale "
                "its contents (QLabel.scaledContents), and QScrollArea's "
                "ability to automatically resize its contents "
                "(QScrollArea.widgetResizable), can be used to implement "
                "zooming and scaling features.</p>"
                "<p>In addition the example shows how to use QPainter to "
                "print an image.</p>")

    def createActions(self):
        self.openAct = QAction("&Open...", self, shortcut="Ctrl+O",
                triggered=self.open)

        self.printAct = QAction("&Print...", self, shortcut="Ctrl+P",
                enabled=False, triggered=self.print_)

        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
                triggered=self.close)

        self.zoomInAct = QAction("Zoom &In (25%)", self, shortcut="Ctrl++",
                enabled=False, triggered=self.zoomIn)

        self.zoomOutAct = QAction("Zoom &Out (25%)", self, shortcut="Ctrl+-",
                enabled=False, triggered=self.zoomOut)

        self.normalSizeAct = QAction("&Normal Size", self, shortcut="Ctrl+S",
                enabled=False, triggered=self.normalSize)

        self.fitToWindowAct = QAction("&Fit to Window", self, enabled=False,
                checkable=True, shortcut="Ctrl+F", triggered=self.fitToWindow)

        self.aboutAct = QAction("&About", self, triggered=self.about)

        self.aboutQtAct = QAction("About &Qt", self,
                triggered=QApplication.instance().aboutQt)

    def createMenus(self):
        self.fileMenu = QMenu("&File", self)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.printAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.viewMenu = QMenu("&View", self)
        self.viewMenu.addAction(self.zoomInAct)
        self.viewMenu.addAction(self.zoomOutAct)
        self.viewMenu.addAction(self.normalSizeAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.fitToWindowAct)

        self.helpMenu = QMenu("&Help", self)
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

        self.menuBar().addMenu(self.fileMenu)
        self.menuBar().addMenu(self.viewMenu)
        self.menuBar().addMenu(self.helpMenu)

    def updateActions(self):
        self.zoomInAct.setEnabled(not self.fitToWindowAct.isChecked())
        self.zoomOutAct.setEnabled(not self.fitToWindowAct.isChecked())
        self.normalSizeAct.setEnabled(not self.fitToWindowAct.isChecked())

    def scaleImage(self, factor):
        self.scaleFactor *= factor
        self.imageLabel.resize(self.scaleFactor * self.imageLabel.pixmap().size())

        self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor)
        self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor)

        self.zoomInAct.setEnabled(self.scaleFactor < 3.0)
        self.zoomOutAct.setEnabled(self.scaleFactor > 0.333)

    def adjustScrollBar(self, scrollBar, factor):
        scrollBar.setValue(int(factor * scrollBar.value()
                                + ((factor - 1) * scrollBar.pageStep()/2)))


    def mousePressEvent (self, eventQMouseEvent):
        self.originQPoint = self.scrollArea.mapFrom(self, eventQMouseEvent.pos())
        #self.originQPoint = eventQMouseEvent.pos()
        self.currentQRubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self)
        self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, QtCore.QSize()))
        self.currentQRubberBand.show()

    def mouseMoveEvent (self, eventQMouseEvent):
        self.x = int(eventQMouseEvent.x())
        self.y = int(eventQMouseEvent.y())
        text1 = str(self.x)
        text2 = str(self.y)
        #print(self.x,self.y)
        QtWidgets.QToolTip.showText(eventQMouseEvent.pos() , "X: "+text1+" "+"Y: "+text2,self)
        if self.currentQRubberBand.isVisible():
            self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, eventQMouseEvent.pos()).normalized() & self.imageLabel.pixmap().rect())

    def mouseReleaseEvent (self, eventQMouseEvent):
        self.currentQRubberBand.hide()
        currentQRect = self.currentQRubberBand.geometry()
        self.currentQRubberBand.deleteLater()
        cropQPixmap = self.imageLabel.pixmap().copy(currentQRect)
        cropQPixmap.save('output.png')

if __name__ == '__main__':
    import sys
    from PyQt5 import QtGui, QtCore, QtWidgets

    app = QApplication(sys.argv)
    imageViewer = ImageViewer()
    imageViewer.show()
    sys.exit(app.exec_())

推荐答案

在这些情况下,QRubberBand 是 QLabel 的儿子会更好,因此不需要进行很多转换.

It is better in these cases that the QRubberBand is the son of the QLabel so there will be no need to make many transformations.

另一方面,事件的坐标与窗口有关,所以我们必须将其转换为QLabel的坐标.为此,一个简单的方法是将相对于窗口的局部坐标转换为全局坐标,然后将全局坐标转换为相对于 QLabel 的局部坐标.

On the other hand, the coordinates of the event are related to the window, so we have to convert it to the coordinates of the QLabel. For this a simple methodology is to convert the local coordinate with respect to the window to global coordinates and then the global coordinates to local coordinates with respect to the QLabel.

最后,当您缩放图像时,您会影响坐标,因为 currentQRect 与缩放后的 QLabel 相关,但内部 QPixmap 未缩放.

And finally when you scale the image you affect the coordinates since the currentQRect is relative to the scaled QLabel but the internal QPixmap is not scaled.

def mousePressEvent (self, event):
    self.originQPoint = self.imageLabel.mapFromGlobal(self.mapToGlobal(event.pos())) 
    self.currentQRubberBand = QtWidgets.QRubberBand(QtWidgets.QRubberBand.Rectangle, self.imageLabel)
    self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, QtCore.QSize()))
    self.currentQRubberBand.show()

def mouseMoveEvent (self, event):
    p = self.imageLabel.mapFromGlobal(self.mapToGlobal(event.pos()))
    QtWidgets.QToolTip.showText(event.pos() , "X: {} Y: {}".format(p.x(), p.y()), self)
    if self.currentQRubberBand.isVisible() and self.imageLabel.pixmap() is not None:
        self.currentQRubberBand.setGeometry(QtCore.QRect(self.originQPoint, p).normalized() & self.imageLabel.rect())

def mouseReleaseEvent (self, event):
    self.currentQRubberBand.hide()
    currentQRect = self.currentQRubberBand.geometry()
    self.currentQRubberBand.deleteLater()
    if self.imageLabel.pixmap() is not None:
        tr = QtGui.QTransform()
        if self.fitToWindowAct.isChecked():
            tr.scale(self.imageLabel.pixmap().width()/self.scrollArea.width(), 
                self.imageLabel.pixmap().height()/self.scrollArea.height())
        else:
            tr.scale(1/self.scaleFactor, 1/self.scaleFactor)
        r = tr.mapRect(currentQRect)
        cropQPixmap = self.imageLabel.pixmap().copy(r)
        cropQPixmap.save('output.png')

这篇关于图像查看器 GUI 无法正确映射鼠标按下事件的坐标的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆