详解如何在pyqt中通过OpenCV实现对窗口的透视变换
(编辑:jimmy 日期: 2024/11/17 浏览:3 次 )
窗口的透视变换效果
具体代码
"htmlcode">
# coding:utf-8 import cv2 as cv import numpy from PyQt5.QtGui import QImage, QPixmap class PixmapPerspectiveTransform: """ 透视变换基类 """ def __init__(self, pixmap=None): """ 实例化透视变换对象\n Parameter --------- src : numpy数组 """ self.pixmap = pixmap def setPixmap(self, pixmap: QPixmap): """ 设置被变换的QPixmap """ self.pixmap = QPixmap self.src=self.transQPixmapToNdarray(pixmap) self.height, self.width = self.src.shape[:2] # 变换前后的边角坐标 self.srcPoints = numpy.float32( [[0, 0], [self.width - 1, 0], [0, self.height - 1], [self.width - 1, self.height - 1]]) def setDstPoints(self, leftTop: list, rightTop, leftBottom, rightBottom): """ 设置变换后的边角坐标 """ self.dstPoints = numpy.float32( [leftTop, rightTop, leftBottom, rightBottom]) def getPerspectiveTransform(self, imWidth, imHeight, borderMode=cv.BORDER_CONSTANT, borderValue=[255, 255, 255, 0]) -> QPixmap: """ 透视变换图像,返回QPixmap\n Parameters ---------- imWidth : 变换后的图像宽度\n imHeight : 变换后的图像高度\n borderMode : 边框插值方式\n borderValue : 边框颜色 """ # 如果是jpg需要加上一个透明通道 if self.src.shape[-1] == 3: self.src = cv.cvtColor(self.src, cv.COLOR_BGR2BGRA) # 透视变换矩阵 perspectiveMatrix = cv.getPerspectiveTransform( self.srcPoints, self.dstPoints) # 执行变换 self.dst = cv.warpPerspective(self.src, perspectiveMatrix, ( imWidth, imHeight), borderMode=borderMode, borderValue=borderValue) # 将ndarray转换为QPixmap return self.transNdarrayToQPixmap(self.dst) def transQPixmapToNdarray(self, pixmap: QPixmap): """ 将QPixmap转换为numpy数组 """ width, height = pixmap.width(), pixmap.height() channels_count = 4 image = pixmap.toImage() # type:QImage s = image.bits().asstring(height * width * channels_count) # 得到BGRA格式数组 array = numpy.fromstring(s, numpy.uint8).reshape( (height, width, channels_count)) return array def transNdarrayToQPixmap(self, array): """ 将numpy数组转换为QPixmap """ height, width, bytesPerComponent = array.shape bytesPerLine = 4 * width # 默认数组维度为 m*n*4 dst = cv.cvtColor(array, cv.COLOR_BGRA2RGBA) pix = QPixmap.fromImage( QImage(dst.data, width, height, bytesPerLine, QImage.Format_RGBA8888)) return pix
为了解决这个烦人的问题,我又对桌面上的窗口进行截屏,再次透视变换。注意是桌面上看到的窗口,这时的窗口肯定是会有背景的,这时的透视变换就不会存在上述问题,记这个透视变换完的图像为img_2
。但实际上我们本来是不想要img_2中的背景的,所以只要将img_2中的背景替换完img_1中的透明背景,下面是具体代码:
# coding:utf-8 import numpy as np from PyQt5.QtCore import QPoint, Qt from PyQt5.QtGui import QPainter, QPixmap, QScreen, QImage from PyQt5.QtWidgets import QApplication, QWidget from my_functions.get_pressed_pos import getPressedPos from my_functions.perspective_transform_cv import PixmapPerspectiveTransform class PerspectiveWidget(QWidget): """ 可进行透视变换的窗口 """ def __init__(self, parent=None, isTransScreenshot=False): super().__init__(parent) self.__visibleChildren = [] self.__isTransScreenshot = isTransScreenshot self.__perspectiveTrans = PixmapPerspectiveTransform() self.__screenshotPix = None self.__pressedPix = None self.__pressedPos = None @property def pressedPos(self) -> str: """ 返回鼠标点击位置 """ return self.__pressedPos def mousePressEvent(self, e): """ 鼠标点击窗口时进行透视变换 """ super().mousePressEvent(e) self.grabMouse() pixmap = self.grab() self.__perspectiveTrans.setPixmap(pixmap) # 根据鼠标点击位置的不同设置背景封面的透视变换 self.__setDstPointsByPressedPos(getPressedPos(self,e)) # 获取透视变换后的QPixmap self.__pressedPix = self.__getTransformPixmap() # 对桌面上的窗口进行截图 if self.__isTransScreenshot: self.__adjustTransformPix() # 隐藏本来看得见的小部件 self.__visibleChildren = [ child for child in self.children() if hasattr(child, 'isVisible') and child.isVisible()] for child in self.__visibleChildren: if hasattr(child, 'hide'): child.hide() self.update() def mouseReleaseEvent(self, e): """ 鼠标松开时显示小部件 """ super().mouseReleaseEvent(e) self.releaseMouse() self.__pressedPos = None self.update() # 显示小部件 for child in self.__visibleChildren: if hasattr(child, 'show'): child.show() def paintEvent(self, e): """ 绘制背景 """ super().paintEvent(e) painter = QPainter(self) painter.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform) painter.setPen(Qt.NoPen) # 绘制背景图片 if self.__pressedPos: painter.drawPixmap(self.rect(), self.__pressedPix) def __setDstPointsByPressedPos(self,pressedPos:str): """ 通过鼠标点击位置设置透视变换的四个边角坐标 """ self.__pressedPos = pressedPos if self.__pressedPos == 'left': self.__perspectiveTrans.setDstPoints( [5, 4], [self.__perspectiveTrans.width - 2, 1], [3, self.__perspectiveTrans.height - 3], [self.__perspectiveTrans.width - 2, self.__perspectiveTrans.height - 1]) elif self.__pressedPos == 'left-top': self.__perspectiveTrans.setDstPoints( [6, 5], [self.__perspectiveTrans.width - 1, 1], [1, self.__perspectiveTrans.height - 2], [self.__perspectiveTrans.width - 2, self.__perspectiveTrans.height - 1]) elif self.__pressedPos == 'left-bottom': self.__perspectiveTrans.setDstPoints( [2, 3], [self.__perspectiveTrans.width - 3, 0], [4, self.__perspectiveTrans.height - 4], [self.__perspectiveTrans.width - 2, self.__perspectiveTrans.height - 2]) elif self.__pressedPos == 'top': self.__perspectiveTrans.setDstPoints( [3, 5], [self.__perspectiveTrans.width - 4, 5], [1, self.__perspectiveTrans.height - 2], [self.__perspectiveTrans.width - 2, self.__perspectiveTrans.height - 2]) elif self.__pressedPos == 'center': self.__perspectiveTrans.setDstPoints( [3, 4], [self.__perspectiveTrans.width - 4, 4], [3, self.__perspectiveTrans.height - 3], [self.__perspectiveTrans.width - 4, self.__perspectiveTrans.height - 3]) elif self.__pressedPos == 'bottom': self.__perspectiveTrans.setDstPoints( [2, 2], [self.__perspectiveTrans.width - 3, 3], [3, self.__perspectiveTrans.height - 3], [self.__perspectiveTrans.width - 4, self.__perspectiveTrans.height - 3]) elif self.__pressedPos == 'right-bottom': self.__perspectiveTrans.setDstPoints( [1, 0], [self.__perspectiveTrans.width - 3, 2], [1, self.__perspectiveTrans.height - 2], [self.__perspectiveTrans.width - 5, self.__perspectiveTrans.height - 4]) elif self.__pressedPos == 'right-top': self.__perspectiveTrans.setDstPoints( [0, 1], [self.__perspectiveTrans.width - 7, 5], [2, self.__perspectiveTrans.height - 1], [self.__perspectiveTrans.width - 2, self.__perspectiveTrans.height - 2]) elif self.__pressedPos == 'right': self.__perspectiveTrans.setDstPoints( [1, 1], [self.__perspectiveTrans.width - 6, 4], [2, self.__perspectiveTrans.height - 1], [self.__perspectiveTrans.width - 4, self.__perspectiveTrans.height - 3]) def __getTransformPixmap(self) -> QPixmap: """ 获取透视变换后的QPixmap """ pix = self.__perspectiveTrans.getPerspectiveTransform( self.__perspectiveTrans.width, self.__perspectiveTrans.height).scaled( self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) return pix def __getScreenShot(self) -> QPixmap: """ 对窗口口所在的桌面区域进行截图 """ screen = QApplication.primaryScreen() # type:QScreen pos = self.mapToGlobal(QPoint(0, 0)) # type:QPoint pix = screen.grabWindow( 0, pos.x(), pos.y(), self.width(), self.height()) return pix def __adjustTransformPix(self): """ 对窗口截图再次进行透视变换并将两张图融合,消除可能存在的黑边 """ self.__screenshotPix = self.__getScreenShot() self.__perspectiveTrans.setPixmap(self.__screenshotPix) self.__screenshotPressedPix = self.__getTransformPixmap() # 融合两张透视图 img_1 = self.__perspectiveTrans.transQPixmapToNdarray(self.__pressedPix) img_2 = self.__perspectiveTrans.transQPixmapToNdarray(self.__screenshotPressedPix) # 去除非透明背景部分 mask = img_1[:, :, -1] == 0 img_2[mask] = img_1[mask] self.__pressedPix = self.__perspectiveTrans.transNdarrayToQPixmap(img_2)
在mousePressEvent
中调用了一个全局函数 getPressedPos(widget,e)
,如果将窗口分为九宫格,它就是用来获取判断鼠标的点击位置落在九宫格的哪个格子的,因为我在其他地方有用到它,所以没将其设置为PerspectiveWidget
的方法成员。下面是这个函数的代码:
# coding:utf-8 from PyQt5.QtGui import QMouseEvent def getPressedPos(widget, e: QMouseEvent) -> str: """ 检测鼠标并返回按下的方位 """ pressedPos = None width = widget.width() height = widget.height() leftX = 0 <= e.x() <= int(width / 3) midX = int(width / 3) < e.x() <= int(width * 2 / 3) rightX = int(width * 2 / 3) < e.x() <= width topY = 0 <= e.y() <= int(height / 3) midY = int(height / 3) < e.y() <= int(height * 2 / 3) bottomY = int(height * 2 / 3) < e.y() <= height # 获取点击位置 if leftX and topY: pressedPos = 'left-top' elif midX and topY: pressedPos = 'top' elif rightX and topY: pressedPos = 'right-top' elif leftX and midY: pressedPos = 'left' elif midX and midY: pressedPos = 'center' elif rightX and midY: pressedPos = 'right' elif leftX and bottomY: pressedPos = 'left-bottom' elif midX and bottomY: pressedPos = 'bottom' elif rightX and bottomY: pressedPos = 'right-bottom' return pressedPos
使用方法
很简单,只要将代码中的QWidget替换为PerspectiveWidget就可以享受透视变换带来的无尽乐趣。要想向gif中那样对按钮也进行透视变换,只要按代码中所做的那样重写mousePressEvent
、mouseReleaseEvent
和 paintEven
t 即可,如果有对按钮使用qss,记得在paintEvent中加上super().paintEvent(e)
,这样样式表才会起作用。总之框架已经给出,具体操作取决于你。如果你喜欢这篇博客的话,记得点个赞哦(o゚▽゚)o 。顺便做个下期预告:在gif中可以看到界面切换时带了弹入弹出的动画,在下一篇博客中我会对如何实现QStackedWidget的界面切换动画进行介绍,敬请期待~~
下一篇:python实现人工蜂群算法