基于 4 个 Qpoints 的图像正确方向
Correct orientation of an image based on 4 Qpoints
我正在尝试根据从用户那里获取的四个 Qpoints 来校正图像的方向(旋转)。我找到了一个我正在处理的类似代码,它作为解决方案发布在这个 .
中
代码:
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):
#
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_())
当前输出:
预期输出:
裁剪后用户输入图像的更正方向
谁能指导我如何操作?
谢谢。
选择四个任意点不会给你一个矩形,而是一个四边形,它可能不会所有角都是 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 始终适应实际场景矩形。
此外,您应该记得在加载新图像后立即删除所有点项目。
我正在尝试根据从用户那里获取的四个 Qpoints 来校正图像的方向(旋转)。我找到了一个我正在处理的类似代码,它作为解决方案发布在这个
代码:
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):
#
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_())
当前输出:
预期输出: 裁剪后用户输入图像的更正方向
谁能指导我如何操作?
谢谢。
选择四个任意点不会给你一个矩形,而是一个四边形,它可能不会所有角都是 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 始终适应实际场景矩形。
此外,您应该记得在加载新图像后立即删除所有点项目。