c/c++语言开发共享详解如何在pyqt中通过OpenCV实现对窗口的透视变换

窗口的透视变换效果   当我们点击win10的uwp应用中的小部件时,会发现小部件会朝着鼠标点击位置凹陷下去,而且不同的点击位置对应着不同的凹陷情况,看起来就好像小部件在屏幕上

窗口的透视变换效果

   当我们点击win10的uwp应用中的小部件时,会发现小部件会朝着鼠标点击位置凹陷下去,而且不同的点击位置对应着不同的凹陷情况,看起来就好像小部件在屏幕上不只有x轴和y轴,甚至还有一个z轴。要做到这一点,其实只要对窗口进行透视变换即可。下面是对qt的窗口和按钮进行透视变换的效果:

详解如何在pyqt中通过OpenCV实现对窗口的透视变换

具体代码

   1.下面先定义一个类,它的作用是将传入的 qpixmap 转换为numpy 数组,然后用 opencvwarpperspective 对数组进行透视变换,最后再将 numpy 数组转为 qpixmap 并返回;

  # 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:    """ 透视变换图像,返回qpixmapn    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

  2.接下来就是这篇博客的主角——perspectivewidget,当我们的鼠标单击这个类实例化出来的窗口时,窗口会先通过 self.grab() 被渲染为qpixmap,然后调用 pixmapperspectivetransform 中的方法对qpixmap进行透视变换,拿到透视变换的结果后只需隐藏窗口内的小部件并通过 paintevent 将结果绘制到窗口上即可。虽然思路很通顺,但是实际操作起来会发现对于透明背景的窗口进行透视变换时,与透明部分交界的部分会被插值上半透明的像素。对于本来就属于深色的像素来说这没什么,但是如果像素是浅色的就会带来很大的视觉干扰,你会发现这些浅色部分旁边被描上了一圈黑边,我们先将这个图像记为img_1。img_1差不多长这个样子,可以很明显看出白色的文字围绕着一圈黑色的描边。

详解如何在pyqt中通过OpenCV实现对窗口的透视变换

为了解决这个烦人的问题,我又对桌面上的窗口进行截屏,再次透视变换。注意是桌面上看到的窗口,这时的窗口肯定是会有背景的,这时的透视变换就不会存在上述问题,记这个透视变换完的图像为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中那样对按钮也进行透视变换,只要按代码中所做的那样重写mousepresseventmousereleaseeventpaintevent 即可,如果有对按钮使用qss,记得在paintevent中加上super().paintevent(e),这样样式表才会起作用。总之框架已经给出,具体操作取决于你。如果你喜欢这篇博客的话,记得点个赞哦(o゚▽゚)o 。顺便做个下期预告:在gif中可以看到界面切换时带了弹入弹出的动画,在下一篇博客中我会对如何实现qstackedwidget的界面切换动画进行介绍,敬请期待~~

到此这篇关于详解如何在pyqt中通过opencv实现对窗口的透视变换的文章就介绍到这了,更多相关pyqt opencv窗口透视变换内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/597500.html

(0)
上一篇 2021年5月8日
下一篇 2021年5月8日

精彩推荐