Skip to content

Latest commit

 

History

History
329 lines (258 loc) · 11 KB

File metadata and controls

329 lines (258 loc) · 11 KB

wgc_python

English | 简体中文

🚀 为 Python 自动化而生的窗口捕获库
180+ FPS 极致性能 · 零资源待机 · 无视遮挡 · API 极简


为什么选择 wgc_python?

🎯 专为自动化场景设计

你是否在为以下问题困扰?

  • mss/BitBlt:无法捕获被遮挡或后台窗口
  • PrintWindow:性能瓶颈,固定 26ms+ 延迟
  • 其他 WGC 封装:持续运行占用资源,频繁启停开销巨大(50ms+)

wgc_python 通过独创的 Pause/Resume 机制,完美解决了这个矛盾:

# 传统方式:要么持续空转浪费资源,要么频繁启停承受延迟
start_capture()  # 50ms 开销
get_frame()      # 获取截图
stop_capture()   # 销毁会话(50ms)
# 下次截图又要重新开始...

# wgc_python 方式:一次启动,按需截图,零开销待机
with WindowCapture("窗口", "类名") as cap:
    while running:
        frame = cap.capture_one()  # auto Resume → 等待帧 → 拷贝 → Pause
        # 处理图像...

📊 性能对比

方案 FPS 后台捕获 CPU 占用 频繁切换开销 暂停后 GPU 占用
python-mss / BitBlt ~60 N/A (无暂停概念)
PrintWindow ~38 N/A (每次调用即捕获)
其他 WGC 封装 180+ 高(持续空转) 极高 (50ms+) 高 (无法真正暂停)
wgc_python 180+ 极低(Pause时归零) <1μs(原子标志位) 归零(无 D3D 操作)

✨ 核心优势

1. 极致性能

  • 180+ FPS 高帧率捕获,比 PrintWindow 快 5 倍
  • 双缓冲 Staging 纹理:GPU 异步拷贝,读写互不阻塞
  • 零拷贝友好np.ndarray(strides=...) 直接从 GPU 映射内存构造视图

2. 智能资源管理

  • Pause/Resume <1μs 软暂停:不销毁不重建 WGC session,仅原子标志位跳过帧处理
  • capture_one() 自动管理:Resume → 等待帧 → 拷贝 → Pause,间隙 GPU 驱动零开销
  • 会话复用:避免频繁创建/销毁 D3D 设备的开销

3. 极简 API

  • capture_one():一行代码完成按需捕获,返回 numpy 数组
  • get_frame():零拷贝裸指针路径(高级使用)
  • 线程安全:C++ 层处理所有多线程复杂性

4. 多开并发

  • 同一进程内可同时创建多个捕获会话,互不干扰
  • 每个会话独立 D3D11 设备 + 独立纹理 + 独立 WinRT session,完全隔离
  • 支持同窗口多路并发捕获

5. 客户区精准裁剪(默认不截取标题栏/边框)

  • 默认 client_area_only=True:只捕获窗口客户区内容,自动裁剪标题栏和边框,直接输出有效像素
  • 设置 client_area_only=False:捕获整个窗口(含标题栏和边框),满足 UI 记录场景
  • DPI 感知:自动修正高 DPI 缩放偏移,裁剪精度像素级
  • GPU 级裁剪:CopySubresourceRegion 在 GPU 上完成裁剪,不浪费带宽和 CPU

6. 无视遮挡

  • 支持捕获被遮挡、最小化、后台窗口
  • 完美适配游戏、桌面应用等各种场景

快速开始

安装

pip install wgc-python

基础用法

from wgc_python import WindowCapture, enumerate_windows

# 枚举所有窗口
for title, class_name in enumerate_windows():
    print(f"{title} ({class_name})")

# 按需捕获(推荐 —— 零开销待机)
with WindowCapture("窗口标题", "窗口类名") as cap:
    frame = cap.capture_one()     # BGRA numpy 数组,shape (h, w, 4)
    if frame is not None:
        print(f"捕获成功: {frame.shape}")

# 客户区裁剪演示
# 默认 client_area_only=True:只截取客户区,不含标题栏/边框
cap_client = WindowCapture("记事本", "Notepad")                      # 只截内容
cap_full  = WindowCapture("记事本", "Notepad", client_area_only=False)  # 含标题栏
frame_client = cap_client.capture_one()  # 只有编辑区
frame_full  = cap_full.capture_one()    # 含标题栏 + 菜单 + 编辑区
cap_client.close()
cap_full.close()

自动化最佳实践

from wgc_python import WindowCapture

cap = WindowCapture("游戏窗口", "UnityWndClass")

while True:
    frame = cap.capture_one(timeout=1.0)
    if frame is not None:
        # frame 是 BGRA numpy 数组,直接用于 OpenCV/模板匹配
        pass
    time.sleep(1)

cap.close()

零拷贝高级用法

from wgc_python import WindowCapture
import numpy as np
import ctypes

with WindowCapture("窗口", "类名") as cap:
    cap.resume()
    r = cap.get_frame()  # (ptr, w, h, row_pitch) — GPU 映射裸指针
    if r:
        ptr, w, h, rp = r
        arr = np.ndarray((h, w, 4), dtype=np.uint8,
                         buffer=(ctypes.c_ubyte * (h * rp)).from_address(ptr),
                         strides=(rp, 4, 1))
        # arr 是 GPU 内存的零拷贝视图
        cap.release_frame()
    cap.pause()

实时显示

from wgc_python import WindowCapture
import cv2

with WindowCapture("窗口标题", "窗口类名") as cap:
    while True:
        frame = cap.capture_one()
        if frame is not None:
            cv2.imshow("Capture", cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR))
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    cv2.destroyAllWindows()

API 参考

from wgc_python import (
    WindowCapture,             # 窗口捕获类(上下文管理器支持)
    enumerate_windows,         # 枚举所有可见窗口
    get_last_error,            # 获取最后错误信息(线程安全)
    get_active_capture_count,  # 获取活跃捕获数
)

# WindowCapture 类方法:
#   cap = WindowCapture(title, class_name, client_area_only=True)
#
#   cap.capture_one(timeout=0.5)  -> np.ndarray | None  ★ 推荐
#      自动 Resume → 等待帧 → 拷贝为 numpy → Pause
#      捕获间隙 WGC 完全休眠,GPU 驱动零开销
#
#   cap.get_frame()              -> (ptr, w, h, row_pitch) | None
#   cap.release_frame()           # 释放 GPU 映射
#   cap.pause()                   # 暂停捕获(零资源待机)
#   cap.resume()                  # 恢复捕获
#   cap.stop()                    # 停止帧到达
#   cap.close()                   # 销毁会话
#   cap.is_capturing()  -> bool
#   cap.is_paused()     -> bool
#   cap.get_frame_count()  -> int
#   cap.handle             -> int (DLL handle)

技术架构

WGC捕获 → GPU Surface纹理
              │
     ┌────────▼────────┐
     │  FrameArrived    │
     │  if pausing → ↑  │  ← Pause时直接返回,零 D3D 操作
     └────────┬─────────┘
              │
         CopyResource (GPU异步复制)
              ↓
    ┌─────────────────────────┐
    │  双缓冲Staging纹理       │
    │  [0] 写入 ←→ [1] 读取   │
    │  m_textureInUse 防冲撞   │
    └─────────────────────────┘
              ↓
         Map (永久映射 GPU 内存)
              ↓
    ┌────── 零拷贝输出 ───────┐
    │ get_frame()              │
    │ 返回裸指针 → numpy零拷贝  │
    │ 需手动 release_frame()    │
    └──────────────────────────┘

    ┌────── 一键捕获 ──────────┐
    │ capture_one()            │
    │ auto Pause/Resume        │
    │ 返回 numpy 数组          │
    │ 间隙 GPU 驱动零开销      │
    └──────────────────────────┘

Pause/Resume 工作原理

  用户调用 cap.pause()
         │
    m_isPaused = true   ◄──── 原子标志位,<1μs
    m_readableStagingIndex = -1
         │
    ┌────▼────────────────────────────────────────────┐
    │                FrameArrived 回调(WGC 仍会触发)  │
    │                                                  │
    │  lock(mutex);                                    │
    │  if (m_isPaused) return;     // ← 纯CPU判断,跳过│
    │  // ↓ 以下只在 resume 后执行 ↓                   │
    │  CopyResource(staging, frame);                   │
    │  m_readableStagingIndex = idx;                   │
    │  unlock(mutex);                                  │
    └────▲────────────────┬───────────────────────────┘
         │                │
  用户调用 cap.resume()  MapFrame 检查 readableStagingIndex
  m_isPaused = false      <0 → 最近帧尚未就绪,返回 false
  
  不销毁 WGC session / 不重建 D3D 设备 / 不重新注册回调
  → 恢复零延迟,无突刺

文件结构

wgc_python/
├── wgc_python/                   # Python 包
│   ├── __init__.py               # Python API(ctypes FFI)
│   └── wgc_python.dll            # 编译后的 DLL
├── wgc_python_dll/               # C++ DLL 项目
│   ├── WGCWindowCapture.h/cpp    # WGC 捕获核心(双缓冲 + 零拷贝)
│   ├── WGCExport.h/cpp           # DLL 导出(含线程安全错误处理)
│   ├── D3DInterop.cpp            # D3D11 设备互操作
│   ├── WindowEnumerator.h/cpp    # 窗口枚举
│   ├── pch.h                     # 预编译头
│   └── packages/                 # NuGet 包
├── test.py                       # 功能测试
├── test_mt.py                    # 多线程按需捕获示例
├── pyproject.toml                # pip 构建配置
├── BUILD.md / BUILD_EN.md        # 构建说明(中/英)
├── README.md / README_EN.md      # 使用文档(中/英)
├── CONTRIBUTING.md               # 贡献指南
├── CODE_OF_CONDUCT.md            # 行为准则
├── LICENSE                       # MIT 许可证
└── requirements.txt              # Python 依赖

系统要求

  • Windows 10 1903+ (Build 18362)
  • Python 3.6+

构建 DLL

详见 BUILD.md


故障排除

问题 解决方案
DLL 未找到 确保 wgc_python.dll 在正确位置
捕获失败 检查窗口是否可见,Windows 版本 >= 1903
中文路径保存失败 使用 cv2.imencode + open().write() 代替 cv2.imwrite
依赖缺失 pip install numpy opencv-python

适用场景

  • ✅ 游戏 AI / 自动化脚本
  • ✅ RPA 流程自动化
  • ✅ 屏幕录制 / 直播
  • ✅ UI 自动化测试
  • ✅ 计算机视觉应用

鸣谢

本项目基于 robmikh/Win32CaptureSample 开发。


License

MIT License