You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5中如何在透视窗口的透明孔洞上方绘制内容?

解决PyQt5透视窗口孔洞上方绘制Bounding Box的问题

问题说明

实现带透视孔洞的PyQt5窗口后,尝试在孔洞上方绘制EasyOCR识别出的文字Bounding Box时,仅能在窗口白色区域看到部分边框,孔洞区域无法显示绘制内容。

原因分析

原代码使用QRegion创建窗口遮罩,将孔洞区域从窗口中"移除",这部分区域不再属于窗口的绘制范围,因此paintEvent中的绘制指令无法作用于孔洞区域,同时鼠标事件也会直接穿透该区域。

解决方案:双层窗口实现

通过两个窗口拆分功能:

  • 背景窗口:提供半透明白色遮罩和拖拽功能,通过绘制透明矩形实现视觉上的孔洞(不使用遮罩)
  • 覆盖窗口:完全透明的无边框窗口,仅负责在对应孔洞位置绘制Bounding Box,覆盖在背景窗口的孔洞区域上方

修改后的完整代码

import sys
import cv2
import numpy as np
import mss
import easyocr
from PyQt5.QtCore import Qt, QRect, QPoint, QTimer
from PyQt5.QtGui import QPainter, QBrush, QColor, QPen
from PyQt5.QtWidgets import QApplication, QWidget

class BackgroundWindow(QWidget):
    def __init__(self, overlay_window):
        super().__init__()
        self.overlay_window = overlay_window
        self.initUI()
        self.dragging = False
        self.drag_start_position = QPoint()
        self.hole_rect = QRect(300, 200, 200, 200)

    def initUI(self):
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setGeometry(100, 100, 800, 600)
        self.setWindowTitle('Background Window')

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制半透明白色背景
        painter.setBrush(QBrush(QColor(255, 255, 255, 255)))
        painter.setPen(Qt.NoPen)
        painter.drawRect(self.rect())
        
        # 绘制透明孔洞(视觉上实现透视效果)
        painter.setCompositionMode(QPainter.CompositionMode_Clear)
        painter.drawRect(self.hole_rect)
        painter.setCompositionMode(QPainter.CompositionMode_SourceOver)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.dragging = True
            self.drag_start_position = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()

    def mouseMoveEvent(self, event):
        if self.dragging:
            new_pos = event.globalPos() - self.drag_start_position
            self.move(new_pos)
            # 同步更新覆盖窗口位置
            overlay_pos = QPoint(new_pos.x() + self.hole_rect.left(), new_pos.y() + self.hole_rect.top())
            self.overlay_window.move(overlay_pos)
            event.accept()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.dragging = False
            event.accept()

class OverlayWindow(QWidget):
    def __init__(self, hole_size):
        super().__init__()
        self.initUI(hole_size)
        self.reader = easyocr.Reader(['en'])
        self.bounding_boxes = []
        
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.capture_and_process)
        self.timer.start(1000)

    def initUI(self, hole_size):
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setFixedSize(hole_size.width(), hole_size.height())
        self.setWindowTitle('Overlay Window')

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        
        # 绘制红色Bounding Box
        painter.setPen(QPen(Qt.red, 2))
        for box in self.bounding_boxes:
            x1, y1, x2, y2 = box
            painter.drawRect(QRect(x1, y1, x2 - x1, y2 - y1))

    def capture_and_process(self):
        # 获取当前窗口的屏幕位置
        screen_pos = self.mapToGlobal(QPoint(0,0))
        with mss.mss() as sct:
            monitor = {
                "top": screen_pos.y(),
                "left": screen_pos.x(),
                "width": self.width(),
                "height": self.height()
            }
            img = sct.grab(monitor)
            img_rgb = np.array(img)
            img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_BGRA2RGB)
            
            self.bounding_boxes = []
            results = self.reader.readtext(img_rgb, detail=1)
            for (bbox, text, prob) in results:
                (top_left, top_right, bottom_right, bottom_left) = bbox
                x1, y1 = int(top_left[0]), int(top_left[1])
                x2, y2 = int(bottom_right[0]), int(bottom_right[1])
                self.bounding_boxes.append((x1, y1, x2, y2))
            
            self.update()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    hole_rect = QRect(300, 200, 200, 200)
    overlay = OverlayWindow(hole_rect)
    background = BackgroundWindow(overlay)
    
    # 初始化覆盖窗口位置
    bg_pos = background.frameGeometry().topLeft()
    overlay.move(bg_pos.x() + hole_rect.left(), bg_pos.y() + hole_rect.top())
    
    background.show()
    overlay.show()
    sys.exit(app.exec_())

关键修改点

  1. 拆分窗口职责:背景窗口负责拖拽和遮罩视觉效果,覆盖窗口仅负责绘制Bounding Box
  2. 取消QRegion遮罩:背景窗口通过CompositionMode_Clear绘制透明区域实现透视效果,保留窗口的完整绘制范围
  3. 同步窗口位置:拖拽背景窗口时,同步更新覆盖窗口的位置,确保始终对齐孔洞区域
  4. 覆盖窗口透明化:覆盖窗口完全透明,仅绘制Bounding Box,不遮挡下方内容

内容的提问来源于stack exchange,提问作者secemp9

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 23:06:01