如何避免PyQt5+YOLOv5模糊覆盖被屏幕捕获以消除闪烁
解决PyQt5+YOLOv5人体模糊覆盖层闪烁问题
问题根源
闪烁的核心原因是:屏幕捕获工具(mss)会抓取到PyQt绘制的黑色覆盖层,YOLOv5检测到这些黑色矩形后,误判原本的人体目标消失,进而移除覆盖层;下一次捕获时又能检测到人体,重新绘制覆盖层,如此反复就造成了闪烁。
解决方案
要消除闪烁,关键是让屏幕捕获过程完全忽略PyQt的覆盖层窗口,确保YOLOv5只分析真实的屏幕内容,而非我们绘制的模糊层。下面提供两种可靠的实现方式:
方法1:给覆盖层窗口添加「捕获透明」扩展样式
通过Windows API给窗口设置WS_EX_TRANSPARENT和WS_EX_LAYERED扩展样式,让屏幕捕获工具(如mss)忽略该窗口,同时保持窗口在视觉上可见且置顶。
修改后的完整代码:
import sys, win32api, win32gui, win32con import torch import numpy as np import mss from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QPainter, QColor # Load YOLOv5 model model = torch.hub.load('ultralytics/yolov5', 'yolov5x', pretrained=True) # Initialize screen capture sct = mss.mss() if torch.cuda.is_available(): print("CUDA ACCELERATION [ENABLED]") else: print("[!] CUDA ACCELERATION IS UNAVAILABLE") # Screen dimensions SCREEN_WIDTH, SCREEN_HEIGHT = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN), win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN) def get_screen(): monitor = {"top": 0, "left": 0, "width": SCREEN_WIDTH, "height": SCREEN_HEIGHT} screenshot = sct.grab(monitor) return np.array(screenshot) class BlurOverlay(QWidget): def __init__(self): super().__init__() self.setGeometry(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) # 设置基础窗口属性 self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool | Qt.WindowTransparentForInput) self.setAttribute(Qt.WA_TranslucentBackground) self.detections = [] # 添加Windows扩展样式,让窗口对屏幕捕获透明 hwnd = int(self.winId()) ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) # WS_EX_TRANSPARENT:让窗口不被屏幕捕获工具识别;WS_EX_LAYERED:配合半透明属性 ex_style |= win32con.WS_EX_TRANSPARENT | win32con.WS_EX_LAYERED win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, ex_style) def paintEvent(self, event): painter = QPainter(self) for x1, y1, x2, y2, _, cls in self.detections: if cls == 0: # class 0 is person in COCO dataset width, height = int(x2 - x1), int(y2 - y1) # Draw dark black rectangle painter.setBrush(QColor(0, 0, 0)) painter.drawRect(int(x1), int(y1), width, height) def update_detections(self, detections): self.detections = detections self.update() def main(): app = QApplication(sys.argv) overlay = BlurOverlay() overlay.show() def update_overlay(): img = get_screen() results = model(img) detections = results.xyxy[0].cpu().numpy() overlay.update_detections(detections) timer = QTimer() timer.timeout.connect(update_overlay) # 适当调高间隔,减少资源占用(根据GPU性能调整) timer.start(40) sys.exit(app.exec_()) if __name__ == "__main__": main()
方法2:改用GDI直接捕获桌面(绕过顶层窗口)
如果mss的捕获方式仍有问题,可以直接使用Windows GDI API捕获桌面DC,这种方式会直接抓取桌面底层内容,完全忽略所有顶层窗口。
修改get_screen函数如下:
def get_screen(): # 获取桌面DC hdesktop = win32gui.GetDesktopWindow() desktop_dc = win32gui.GetWindowDC(hdesktop) img_dc = win32ui.CreateDCFromHandle(desktop_dc) # 创建兼容DC mem_dc = img_dc.CreateCompatibleDC() # 创建位图 screenshot = win32ui.CreateBitmap() screenshot.CreateCompatibleBitmap(img_dc, SCREEN_WIDTH, SCREEN_HEIGHT) mem_dc.SelectObject(screenshot) # 捕获桌面 mem_dc.BitBlt((0, 0), (SCREEN_WIDTH, SCREEN_HEIGHT), img_dc, (0, 0), win32con.SRCCOPY) # 转换为numpy数组 bmpinfo = screenshot.GetInfo() bmpstr = screenshot.GetBitmapBits(True) img = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo['bmHeight'], bmpinfo['bmWidth'], 4)) img = img[..., :3] # 去掉Alpha通道,转为RGB # 释放资源 mem_dc.DeleteDC() img_dc.DeleteDC() win32gui.ReleaseDC(hdesktop, desktop_dc) win32gui.DeleteObject(screenshot.GetHandle()) return img
关键说明
- 方法1更轻量,保留mss的高效捕获,仅通过窗口属性实现隔离,适合大多数场景。
- 方法2完全绕过顶层窗口,兼容性最好,但代码稍复杂,资源占用略高。
- 调整定时器间隔(如从30ms改为40ms)可以平衡检测精度和资源占用,进一步减少闪烁概率。
内容的提问来源于stack exchange,提问作者iddwsdgh
相关产品推荐
相关产品推荐

