要求
- 利用QLabel控件顯示圖片;
- 鼠標點擊,獲取單機座標,根據四個頂點座標,在圖中畫出截取區域;
- 確定,根據鼠標點擊的四個點,求取單適應矩陣,做圖像較正。
說明
畫圖
在QLabel畫圖,需要繼承QLabel類,重寫paintEvent和mouseReleaseEvent。
需要注意的是,在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_())