【问题标题】:Correct orientation of an image based on 4 Qpoints基于 4 个 Q 点的图像正确方向
【发布时间】:2019-12-08 16:54:33
【问题描述】:

我正在尝试根据从用户那里获取的四个 Q 点来校正图像的方向(旋转)。我发现了一个我正在处理的类似代码,并将其作为解决方案发布在 link 中。

代码:

import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets

current_dir = os.path.dirname(os.path.realpath(__file__))
point_filename = os.path.join(current_dir, "Lastout.png")

class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(QtWidgets.QGraphicsScene(), parent)
        self.pixmap_item = self.scene().addPixmap(QtGui.QPixmap())
        self.pixmap_item.setShapeMode(QtWidgets.QGraphicsPixmapItem.BoundingRectShape)
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    def set_image(self, pixmap):
        self.pixmap_item.setPixmap(pixmap)
        #The pixmap is scaled to a rectangle as small as possible outside size, preserving the aspect ratio.
        self.fitInView(self.pixmap_item, QtCore.Qt.KeepAspectRatio)

class CropView(GraphicsView):
    Changed_view = QtCore.pyqtSignal(QtGui.QPixmap)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.point_items = []

    def mousePressEvent(self, event):
        if not self.pixmap_item.pixmap().isNull():
            sp = self.mapToScene(event.pos())
            #print("Event position = " +str(sp))
            lp = self.pixmap_item.mapFromScene(sp)
            #print("Event position FromScene = " +str(lp))
            if self.pixmap_item.contains(lp):
                size = QtCore.QSize(30, 30)
                height = (
                    self.mapToScene(QtCore.QRect(QtCore.QPoint(), size))
                    .boundingRect()
                    .size()
                    .height()
                )
                pixmap = QtGui.QPixmap(point_filename)
                point_item = QtWidgets.QGraphicsPixmapItem(pixmap, self.pixmap_item)
                point_item.setOffset(
                    -QtCore.QRect(QtCore.QPoint(), pixmap.size()).center()
                )
                point_item.setPos(lp)
                scale = height / point_item.boundingRect().size().height()
               # print ("Scale: "+str(scale))
                point_item.setScale(scale)
                self.point_items.append(point_item)
                if len(self.point_items) == 4:
                    points = []
                    for it in self.point_items:
                        points.append(it.pos().toPoint())
                        print ("points: " + str (it.pos().toPoint()))
                        print (" x " + str(it.x()) +" y "+ str( it.y()) )
                    self.crop(points)
                elif len(self.point_items) == 5:
                    for it in self.point_items[:-1]:
                        self.scene().removeItem(it)
                    self.point_items = [self.point_items[-1]]
            else:
                print("outside")
        super().mousePressEvent(event)

    def crop(self, points):
        # https://stackoverflow.com/a/55714969/6622587
        polygon = QtGui.QPolygonF(points)
        path = QtGui.QPainterPath()
        path.addPolygon(polygon)
        source = self.pixmap_item.pixmap()
        r = path.boundingRect().toRect().intersected(source.rect())
        print (str(r))
        #t = QtGui.QTransform()  #added
        pixmap = QtGui.QPixmap(source.size())
        #t.translate (pixmap._center.x() -pixmap.width() / 2, pixmap._center.y() -pixmap.height() / 2)
        #t.translate(pixmap.width() / 2, pixmap.height() / 2)
        # t.rotate(45.0)
        #t.translate(-pixmap.width() / 2, -pixmap.height() / 2)
        pixmap.fill(QtCore.Qt.transparent)
        painter = QtGui.QPainter(pixmap)
        painter.setClipPath(path)
        painter.drawPixmap(QtCore.QPoint(), source, source.rect())
        painter.end()
        result = pixmap.copy(r)
        self.Changed_view.emit(result)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(1200, 700)
        self.left_view = CropView()
        self.rigth_view = GraphicsView()
        self.left_view.Changed_view.connect(self.rigth_view.set_image)
        button = QtWidgets.QPushButton(self.tr("Select Image"))
        button.setStyleSheet("background-color: rgb(0, 100, 100);")
        button.setFixedSize(230, 60)
        font = QtGui.QFont()
        font.setFamily("Microsoft YaHei UI")
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)
        button.setFont(font)
        button.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
        button.clicked.connect(self.load_image)
        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(self.left_view, 0, 0)
        lay.addWidget(self.rigth_view, 0, 1)
        lay.addWidget(button, 1, 0, 1, 2, alignment=QtCore.Qt.AlignHCenter)

    @QtCore.pyqtSlot()
    def load_image(self):
        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
            None, "Select Image", "", "Image Files (*.png *.jpg *jpeg *.bmp)"
        )
        if fileName:
            pixmap = QtGui.QPixmap(fileName)
            self.left_view.set_image(pixmap)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

电流输出:

预期输出: 裁剪后用户输入图像的校正方向

谁能指导我如何做到这一点?

谢谢。

【问题讨论】:

  • 链接的问题旨在选择一个四边形区域,不一定是矩形区域。考虑到上述情况,与矩形区域不同,四边形区域的方向没有很好地定义。因此,在没有建立更多限制的情况下使用链接代码意味着您的情况没有解决方案。您能否在不以链接代码为基础的情况下更好地解释您的目标?
  • 我正在实施与此链接上提出的问题类似的解决方案,其中用户选择图像然后识别图像中某物的角度,例如车牌,并且大部分将在矩形形状。但是,我需要在裁剪后利用方向校正中的指定角度(左上>右上>右下>左下)来纠正此图像的方向。非常感谢您的帮助,我通常会在大多数有关 pyqt 的帖子中看到您的名字,它真的很有帮助。
  • 我正在使用与我的问题中提供的代码相同的代码(鼠标事件+裁剪)。我真的需要帮助来纠正旋转点????。谢谢

标签: python python-3.x pyqt rotation pyqt5


【解决方案1】:

选择四个任意点不会给你一个矩形,而是一个四边形,它可能不是所有的角都是 90°角。您将如何决定 将哪条线作为旋转的参考?
此外,简单的旋转不会补偿透视失真。

您可能应该使用变换,而不是简单地旋转矩形。

我冒昧地更改了创建点背后的逻辑(使其更简单一些):这样它们就不是像素图项的子项,而是场景的子项;它们也可以移动,立即显示结果图像。

在下图中,透视变形得到了更好的解释:我正在使用具有可见透视的源,并且通过转换,我能够将四边形变成矩形。

在此示例中,我假设点的顺序始终为左上、右上、右下、左下。
如果用户遵循另一个顺序,结果显然是错误的,因此您可能需要找到一种方法来更好地检查点的位置。

class CropView(GraphicsView):
    Changed_view = QtCore.pyqtSignal(QtGui.QPixmap)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.point_items = []
        self.crosshair = QtGui.QPixmap(point_filename)

    def mousePressEvent(self, event):
        if not self.pixmap_item.pixmap().isNull():
            if not self.itemAt(event.pos()) in self.point_items:
                scenePos = self.mapToScene(event.pos())
                if len(self.point_items) == 4:
                    while self.point_items:
                        self.scene().removeItem(self.point_items.pop())
                if self.pixmap_item.sceneBoundingRect().contains(scenePos):
                    point_item = self.scene().addPixmap(self.crosshair)
                    point_item.setPos(scenePos)
                    point_item.setFlag(point_item.ItemIgnoresTransformations)
                    point_item.setFlag(point_item.ItemIsMovable)
                    point_item.setOffset(-self.crosshair.rect().center())
                    self.point_items.append(point_item)
                    if len(self.point_items) == 4:
                        self.crop()
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        if len(self.point_items) == 4 and self.itemAt(event.pos()) in self.point_items:
            # update the rectangle if the points have been moved
            self.crop()

    def crop(self):
        points = []
        for point_item in self.point_items:
            points.append(self.pixmap_item.mapFromScene(point_item.pos()))

        # get the width and height based on the 4 points:
        # I'm assuming that the points are always in this order:
        # top-left, top-right, bottom-right, bottom-left
        # so we get the width from the longest two top and bottom lines
        # and the height from the longest left and right lines
        width = max(QtCore.QLineF(points[0], points[1]).length(), QtCore.QLineF(points[2], points[3]).length())
        height = max(QtCore.QLineF(points[1], points[2]).length(), QtCore.QLineF(points[3], points[0]).length())

        sourcePolygon = QtGui.QPolygonF(points)
        source = self.pixmap_item.pixmap()
        pixmap = QtGui.QPixmap(width, height)

        transform = QtGui.QTransform()
        rect = pixmap.rect()
        # this is the target used for the transformation
        targetPolygon = QtGui.QPolygonF([rect.topLeft(), rect.topRight(), rect.bottomRight(), rect.bottomLeft()])
        # quadToQuad is a static that sets the matrix of a transform based on two
        # four-sided polygons
        QtGui.QTransform.quadToQuad(sourcePolygon, targetPolygon, transform)

        painter = QtGui.QPainter(pixmap)
        # smooth pixmap transform is required for better results
        painter.setRenderHints(painter.SmoothPixmapTransform)
        painter.setTransform(transform)
        painter.drawPixmap(QtCore.QPoint(), source)
        painter.end()

        self.Changed_view.emit(pixmap)

请注意,我还在set_image 函数中添加了一行:

        self.setSceneRect(self.scene().sceneRect())

这可确保视图的 sceneRect 始终适应实际的场景矩形。
此外,您应该记住在加载新图像后立即删除所有点项。

【讨论】:

  • 非常感谢您的帮助,这正是我正在寻找的。我感谢你的回应和善意。非常感谢。
  • 对不起,我使用了相同的代码,但我没有使用 CropView(GraphicsView),而是使用了 CropView(QtWidgets.QLabel)。但是,如果 self.point_items: 'CropView' object has no attribute 'itemAt' 中的 self.itemAt(event.pos()) 不是 self.itemAt(event.pos()),我在 mousePressEvent 中遇到错误。我该如何解决?
  • 如果你从另一个类类型子类化你不能指望它工作!如果您不使用 QGraphicsView 您正在更改原始示例的很多行为,如果我不知道您做了什么,我无法帮助您。如果需要,请创建另一个问题。
  • 我想将 QGraphicsView 都替换为 Qlabels,以便更轻松地在界面上操作不同的其他小部件。我发现自己被 QGridLayout 限制了,这不容易处理
  • 正如我所说,更改类类型从根本上改变了方法,因为您不能使用 QGraphicsView 功能,因此您需要为此创建一个新问题。
猜你喜欢
  • 1970-01-01
  • 2012-05-12
  • 2016-04-16
  • 2011-06-26
  • 1970-01-01
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多