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_())
关键修改点
- 拆分窗口职责:背景窗口负责拖拽和遮罩视觉效果,覆盖窗口仅负责绘制Bounding Box
- 取消QRegion遮罩:背景窗口通过
CompositionMode_Clear绘制透明区域实现透视效果,保留窗口的完整绘制范围 - 同步窗口位置:拖拽背景窗口时,同步更新覆盖窗口的位置,确保始终对齐孔洞区域
- 覆盖窗口透明化:覆盖窗口完全透明,仅绘制Bounding Box,不遮挡下方内容
内容的提问来源于stack exchange,提问作者secemp9
相关产品推荐
相关产品推荐

