pyqt5笔记——截取QLabel图片四个顶点,做文档较正

要求

  1. 利用QLabel控件显示图片;
  2. 鼠标点击,获取单机坐标,根据四个顶点坐标,在图中画出截取区域;
  3. 确定,根据鼠标点击的四个点,求取单适应矩阵,做图像较正。

说明

画图

在QLabel画图,需要继承QLabel类,重写paintEventmouseReleaseEvent

需要注意的是,在mouseReleaseEvent中获取鼠标点击的点的时候,global_point = event.globalPos()是在你1920*1080的屏幕上的点击坐标,event.pos()是你在UI窗口中的坐标,UI界面左上角为(0,0),而用event.globalPos()获取UI界面左上角的坐标可能是(765,532),一般不为0。而真实对应的图片中的坐标则是pos()-(centralWidget.pos()+leftImgLabel.pos())具体见参考3。

有一个简单的方法,获得真实图片中的坐标,利用self.mapFromGlobal(global_point)即可。

在ImageLabel类中,pantEvent和mouseReleaseEvent代码如下:

    def paintEvent(self, event):
        QLabel.paintEvent(self, event)
        painter = QPainter()
        painter.begin(self)

        pen = QPen(Qt.red, 4 , Qt.DashDotLine)#虚线画笔
        painter.setPen(pen)

        for k in range(len(self.points)):
            if k + 1 != len(self.points):
                painter.drawLine(self.points[k][0], self.points[k][1], self.points[k + 1][0], self.points[k + 1][1])
            else:
                painter.drawLine(self.points[k][0], self.points[k][1], self.points[0][0], self.points[0][1])
        painter.end()

    # 开启标记功能时,获取点击坐标
    def mouseReleaseEvent(self, event):
        if len(self.points) < 4:
            global_point = event.globalPos()
            local_point = self.mapFromGlobal(global_point)

            point_x = local_point.x()
            point_y = local_point.y()
            self.points.append([point_x, point_y])

            self.update()#获取鼠标点击的点之后,通知画线

功能如下:

pyqt5笔记——截取QLabel图片四个顶点,做文档较正_第1张图片

求解单适应矩阵

可以用 h, s = cv2.findHomography(src_point, dst_point, cv2.RANSAC, 10) 来求解。

我们获取上面的点范围是(400, 600)之后,需要利用mapfromLoal()函数,将点击的坐标,还原到真实图像(3024, 4536)上,需要注意的是,要让上面两个坐标值,是按比例缩放,否则,会有问题。mapfromLoal()代码如下:

    def mapfromLoal(self, points):
        points_origanal = []
        y_ratio = np.float32(self.image.shape[0] / self.image_label.height())
        x_ratio = np.float32(self.image.shape[1] / self.image_label.width())
        for point in points:
            points_origanal.append([point[0] * x_ratio, point[1] * y_ratio])

        return points_origanal

单适应矩阵求解如下:

            points = self.image_label.get_points()#获取image_label上点击的点
            #单适应矩阵
            points_origanal = self.mapfromLoal(points)
            src_point = np.float32(points_origanal)
            print("src_point", src_point)
            #想要变换成图像的大小
            dsize = (self.image.shape[1], self.image.shape[0])
            #变换前后四个点要一一对应
            dst_point = np.float32([[0, 0], [0, dsize[1] - 1],  [dsize[0] - 1, dsize[1] - 1], [dsize[0] - 1, 0]])
            print("dst_point", dst_point)

            h, s = cv2.findHomography(src_point, dst_point, cv2.RANSAC, 10)
            self.image = cv2.warpPerspective(self.image, h, dsize,borderMode=cv2.BORDER_REPLICATE)
            cv2.imwrite("warp.jpg", self.image)

以上只是简单的求解,点击4个顶点的顺序只能按照左上、左下,右下,右上的顺序来。下面修改一下mapfromLocal函数,对四个点进行排序,之后,不管如何点击,都可以准确的进行变换。参考4.的思路,写成代码,修改后的map函数如下:

 def mapfromLoal(self, points):
        #从局部点投影到原图,并且将4个点的顺序,按照左上、左下,右下,右上的顺序排序
        points_origanal = []
        print("in map shape:", self.image.shape)
        print("before largen", points)
        y_ratio = np.float32(self.image.shape[0] / self.image_label.height())
        x_ratio = np.float32(self.image.shape[1] / self.image_label.width())
        for point in points:
            points_origanal.append([point[0] * x_ratio, point[1] * y_ratio])

        order_points = self.order_points(points_origanal)
        print("order",order_points)
        return order_points

order_points()思路如下:

  • 先计算出这四个点的中心位置,然后根据中心位置来做判断。 
  • 那么中心位置就是把他们四个点的坐标全部加起来再除以4。 
  • 与中心位置的x坐标比较,区分左右
  • 对于左边的点,根据与中心位置的y坐标区分上下
  • 对于右边的点,同理区分上下
  • 按照左上,左下,右下,右上的顺序返回

代码如下:

#根据左上、左下,右下,右上的顺序排序
    def order_points(self, points):
        #求中心点的坐标
        center = [0,0]
        for point in points:
            center[0] += point[0]
            center[1] += point[1]
        center[0] = center[0] / 4
        center[1] = center[1] / 4
        print(center)
        #根据中心点x坐标,大于为左,小于为右
        left = []
        right = []
        for point in points:
            if point[0] > center[0]:
                right.append(point)
            else:
                left.append(point)
        print("left",left, "right",right)

        #区分左边坐标的上下,y坐标大于中心坐标为下,否则为上
        bl = []
        tl = []
        for l in left:
            if l[1] > center[1]:
                bl = l
            else:
                tl = l
        #同理区分右上,右下
        br = []
        tr = []
        for r in right:
            if r[1] > center[1]:
                br = r
            else:
                tr = r

        return [tl, bl, br, tr]

 

全部代码

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import os
import numpy as np
import threading
import cv2
import datetime

buffer = ''

class ImageLabel(QLabel):
    '''获取用户裁剪坐标点,画线
        Attributes:
            points:用户点击的裁剪点

        '''

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

    #normal function
    def show_image(self, image):
        #参数image为np.array类型
        rgb_image = cv2.cvtColor(image.copy(), cv2.COLOR_BGR2RGB)
        rgb_image = cv2.resize(rgb_image, (self.width(), self.height()))
        label_image = QImage(rgb_image.data, rgb_image.shape[1], rgb_image.shape[0], QImage.Format_RGB888)
        self.setPixmap(QPixmap(label_image))

    def reselect(self):
        self.points.clear()
        self.update()

    def get_points(self):
        return self.points

    #slot function
    # 根据点过的点来画图
    def paintEvent(self, event):
        QLabel.paintEvent(self, event)
        painter = QPainter()
        painter.begin(self)

        pen = QPen(Qt.red, 4 , Qt.DashDotLine)#虚线画笔
        painter.setPen(pen)

        for k in range(len(self.points)):
            if k + 1 != len(self.points):
                painter.drawLine(self.points[k][0], self.points[k][1], self.points[k + 1][0], self.points[k + 1][1])
            else:
                painter.drawLine(self.points[k][0], self.points[k][1], self.points[0][0], self.points[0][1])
        painter.end()

    # 开启标记功能时,获取点击坐标
    def mouseReleaseEvent(self, event):
        if len(self.points) < 4:
            global_point = event.globalPos()
            local_point = self.mapFromGlobal(global_point)

            point_x = local_point.x()
            point_y = local_point.y()
            self.points.append([point_x, point_y])

            self.update()#获取鼠标点击的点之后,通知画线

class cropWindow(QWidget):
    ok_signal = pyqtSignal()
    def __init__(self, image):
        super(cropWindow, self).__init__()
        self.pannel_height = 400
        self.pannel_width = 600
        self.button_height = 64
        self.button_width = 64

        self.image = image
        # 设置宽高比
        w_h_ratio = self.pannel_width * 1.0 / self.pannel_height
        print("w_h_ration",w_h_ratio)
        self.image = cv2.resize(self.image, ((int)(self.image.shape[0] * w_h_ratio), self.image.shape[0]))
        # self.image = image

        self.image_label = ImageLabel(self)
        self.image_label.setFixedHeight(self.pannel_height)
        self.image_label.setFixedWidth(self.pannel_width)
        self.image_label.show_image(self.image)

        #button
        self.ok_button = QPushButton(self)
        self.ok_button.setFixedWidth(self.button_width)
        self.ok_button.setFixedHeight(self.button_height)
        self.ok_button.setToolTip("ok")
        # self.ok_button.setText("ok")
        self.ok_button.setIcon(QIcon("./icons/ok.png"))
        self.ok_button.clicked.connect(self.on_ok_button)

        self.reselect_button = QPushButton(self)
        self.reselect_button.setFixedHeight(self.button_height)
        self.reselect_button.setFixedWidth(self.button_width)
        # self.reselect_button.setText("reset")
        self.reselect_button.setIcon(QIcon("./icons/reset.png"))
        self.reselect_button.setToolTip("cancel")
        self.reselect_button.clicked.connect(self.on_reselect_button)

        #layout
        self.h_layout = QHBoxLayout()
        self.h_layout.addWidget(self.ok_button)
        self.h_layout.addWidget(self.reselect_button)

        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.image_label)
        self.v_layout.addLayout(self.h_layout)

        self.setLayout(self.v_layout)
        self.setFixedWidth(630)
        self.setFixedHeight(590)

    #normal function
    def get_image(self):
        return self.image

    #根据左上、左下,右下,右上的顺序排序
    def order_points(self, points):
        #求中心点的坐标
        center = [0,0]
        for point in points:
            center[0] += point[0]
            center[1] += point[1]
        center[0] = center[0] / 4
        center[1] = center[1] / 4
        print(center)
        #根据中心点x坐标,大于为左,小于为右
        left = []
        right = []
        for point in points:
            if point[0] > center[0]:
                right.append(point)
            else:
                left.append(point)
        print("left",left, "right",right)

        #区分左边坐标的上下,y坐标大于中心坐标为下,否则为上
        bl = []
        tl = []
        for l in left:
            if l[1] > center[1]:
                bl = l
            else:
                tl = l
        #同理区分右上,右下
        br = []
        tr = []
        for r in right:
            if r[1] > center[1]:
                br = r
            else:
                tr = r

        return [tl, bl, br, tr]

    def mapfromLoal(self, points):
        #从局部点投影到原图,并且将4个点的顺序,按照左上、左下,右下,右上的顺序排序
        points_origanal = []
        print("in map shape:", self.image.shape)
        print("before largen", points)
        y_ratio = np.float32(self.image.shape[0] / self.image_label.height())
        x_ratio = np.float32(self.image.shape[1] / self.image_label.width())
        for point in points:
            points_origanal.append([point[0] * x_ratio, point[1] * y_ratio])

        order_points = self.order_points(points_origanal)
        print("order",order_points)
        return order_points
        # return points_origanal
    #slot function
    def on_ok_button(self):
        if len(self.image_label.get_points()) != 4:#判断是否选取完
            reply = QMessageBox.warning(self,
                                        "提示",
                                        "请选取四个点",
                                        QMessageBox.Ok)
            return

        reply = QMessageBox.warning(self,
                                    "提示",
                                    "你确定截取好了吗",
                                    QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            points = self.image_label.get_points()
            #单适应变换
            points_origanal = self.mapfromLoal(points)
            src_point = np.float32(points_origanal)
            print("src_point", src_point)
            #想要变换成图像的大小
            dsize = (self.image.shape[1], self.image.shape[0])

            dst_point = np.float32([[0, 0], [0, dsize[1] - 1],  [dsize[0] - 1, dsize[1] - 1], [dsize[0] - 1, 0]])
            print("dst_point", dst_point)

            h, s = cv2.findHomography(src_point, dst_point, cv2.RANSAC, 10)
            self.image = cv2.warpPerspective(self.image, h, dsize,borderMode=cv2.BORDER_REPLICATE)
            cv2.imwrite("warp.jpg", self.image)
            self.ok_signal.emit()
            self.close()

    def on_reselect_button(self):
        self.image_label.reselect()
if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setApplicationName("较正")
    window = cropWindow(cv2.imread("tests/2.jpg"))
    window.show()
    sys.exit(app.exec_())

pyqt5笔记——截取QLabel图片四个顶点,做文档较正_第2张图片

pyqt5笔记——截取QLabel图片四个顶点,做文档较正_第3张图片

pyqt5笔记——截取QLabel图片四个顶点,做文档较正_第4张图片

pyqt5笔记——截取QLabel图片四个顶点,做文档较正_第5张图片

参考

1.单应性(homography)变换的推导

2.计算单应矩阵,图像矫正,opencv的findHomography

3.Qt获取鼠标位置(绝对位置、相对位置)

4.根据四个点坐标排列出左上右上右下左下位置关系

你可能感兴趣的:(python,pyqt5,单适应矩阵,图片矫正,pyqt5)