使用 PyQt 根据角度裁剪图像

Posted

技术标签:

【中文标题】使用 PyQt 根据角度裁剪图像【英文标题】:Cropping an image based on its angles with PyQt 【发布时间】:2019-12-04 12:23:57 【问题描述】:

我想制作一个 python 脚本,其中程序显示包含矩形或正方形的图像,然后最终用户应通过在其角度上拖动一些指针来指定此形状的四个角度。这些指针将由程序呈现。

程序应根据这些角度从图像的其余部分裁剪形状。

程序将显示的图像示例:

指针图像:

用户应该做什么:

程序的输出:

我该怎么做? 我正在使用 Python3PyQt5

这是我到目前为止所做的,这是允许用户浏览以从他的计算机中选择图像并将其上传到程序的过程

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QCursor

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.setEnabled(True)
        Dialog.resize(1050, 800)
        Dialog.setMinimumSize(QtCore.QSize(1050, 800))
        Dialog.setMaximumSize(QtCore.QSize(1050, 800))
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("project pic/images LPR icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        Dialog.setWindowIcon(icon)
        Dialog.setStyleSheet("background-color: rgb(217, 217, 217);\n"
"background-color: rgb(243, 243, 243);")
        self.UserImageLbl = QtWidgets.QLabel(Dialog)
        self.UserImageLbl.setGeometry(QtCore.QRect(130, 60, 800, 600))
        self.UserImageLbl.setMinimumSize(QtCore.QSize(800, 600))
        self.UserImageLbl.setMaximumSize(QtCore.QSize(800, 600))
        self.UserImageLbl.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.UserImageLbl.setFrameShadow(QtWidgets.QFrame.Plain)
        self.UserImageLbl.setLineWidth(1)
        self.UserImageLbl.setMidLineWidth(0)
        self.UserImageLbl.setText("")
        self.UserImageLbl.setPixmap(QtGui.QPixmap("project pic/upn.png"))
        self.UserImageLbl.setScaledContents(False)
        self.UserImageLbl.setAlignment(QtCore.Qt.AlignCenter)
        self.UserImageLbl.setObjectName("UserImageLbl")
        self.BrowesImageButton = QtWidgets.QPushButton(Dialog)
        self.BrowesImageButton.setGeometry(QtCore.QRect(430, 690, 230, 60))
        self.BrowesImageButton.setMinimumSize(QtCore.QSize(230, 60))
        self.BrowesImageButton.setMaximumSize(QtCore.QSize(230, 60))
        font = QtGui.QFont()
        font.setFamily("Microsoft YaHei UI")
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)
        self.BrowesImageButton.setFont(font)
        self.BrowesImageButton.setStyleSheet("background-color: rgb(0, 214, 157);\n"
"background-color: rgb(0, 170, 127);")
        self.BrowesImageButton.setCursor(QCursor(QtCore.Qt.PointingHandCursor))
       # self.BrowesImageButton.setStyleSheet("BrowesImageButton:hover  background-color:  rgb(0, 214, 157) " )

        self.BrowesImageButton.setCheckable(True)
        self.BrowesImageButton.setObjectName("BrowesImageButton")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

        self.BrowesImageButton.clicked.connect(self.setImage)


    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", " Cropping Shapes"))
        self.BrowesImageButton.setText(_translate("Dialog", "Browse Image"))


    def setImage(self):
        #fileName, _ = QtWidgets.QFileDialog.getOpenFileName(None, "Select Image", "", "Image Files (*.png *.jpg *jpeg *.bmp);;All Files (*)") # Ask for file
        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(None, "Select Image", "", "Image Files (*.png)") # Ask for file

        if fileName: # If the user gives a file
            pixmap = QtGui.QPixmap(fileName) # Setup pixmap with the provided image
            pixmap = pixmap.scaled(self.UserImageLbl.width(), self.UserImageLbl.height(), QtCore.Qt.KeepAspectRatio) # Scale pixmap
            self.UserImageLbl.setPixmap(pixmap) # Set the pixmap onto the label
            self.UserImageLbl.setAlignment(QtCore.Qt.AlignCenter) # Align the label to center


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

提前致谢。

【问题讨论】:

即使不起作用,您也尝试过什么?你应该知道所以不是软件服务 我已经完成了允许用户浏览以从他的计算机中选择图像并将其上传到程序的过程。我现在对我在问题中解释的其余部分感到困惑。 再次:显示您尝试过的内容 我已经用我所做的更新了我的问题,谢谢。 裁剪区域是否应该始终是与屏幕边缘平行的矩形区域?如果是这样,2个点就足够了,否则点的数量可以不定,只有多边形区域会被切割? 【参考方案1】:

在这种情况下,最好使用 QGraphicsView,因为它允许我们添加其他图像而无需缩放图像。在 QGraphicsView 中,您将获得单击具有图像的项目的点,然后使用 my previous answer 使用该信息,您将获得以下信息:

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, "41uu2.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)
        self.fitInView(self.pixmap_item, QtCore.Qt.KeepAspectRatio)


class CropView(GraphicsView):
    resultChanged = 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())
            lp = self.pixmap_item.mapFromScene(sp)
            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()
                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())
                    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://***.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())

        pixmap = QtGui.QPixmap(source.size())
        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.resultChanged.emit(result)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(1050, 600)

        self.left_view = CropView()
        self.rigth_view = GraphicsView()

        self.left_view.resultChanged.connect(self.rigth_view.set_image)

        button = QtWidgets.QPushButton(self.tr("Browse Image"))
        button.setStyleSheet("background-color: rgb(0, 214, 157);")
        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)"
        )
        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_())

【讨论】:

我已经尝试过你的代码,这是我得到的输出:cdn1.imggmi.com/uploads/2019/12/5/…,有没有办法先连接这些指针,然后裁剪连接的区域。我是 pyqt 的新手 :( . @Sara 你必须按顺序点击,如果你乱按会生成另一种图形,即topleft -> topright -> bottomright -> bottomleft。请避免指出您是不相关的 PyQt 新手。 我知道了,非常感谢您的帮助。现在可以使用了。

以上是关于使用 PyQt 根据角度裁剪图像的主要内容,如果未能解决你的问题,请参考以下文章

裁剪图像时的角度性能问题

裁剪给定角度的图像

使用 FabricJs 的裁剪功能

图像拼接基于matlab最低能量线裁剪图像拼接含Matlab源码 2127期

图像拼接基于matlab最低能量线裁剪图像拼接含Matlab源码 2127期

从 WPF 中的图像裁剪对角线区域