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()#獲取鼠標點擊的點之後,通知畫線

功能如下:

求解單適應矩陣

可以用 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_())

參考

1.單應性(homography)變換的推導

2.計算單應矩陣,圖像矯正,opencv的findHomography

3.Qt獲取鼠標位置(絕對位置、相對位置)

4.根據四個點座標排列出左上右上右下左下位置關係

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章