Skip to main content

屏幕截图

截图方式总结

image-20220207161237602

按速度排序

  • Ctypes,速度快直接调用系统函数,需要熟悉常用的windowsapi函数
  • win32API,速度快但是使用较复杂,需要熟悉常用的windowsapi函数
  • PyQt,速度块,仅次于api,使用简单,能通过句柄取图
  • PIL中的ImageGrab模块,速度较慢,使用简单
  • pyautogui,速度较慢,使用也简单
  • d3dshot ,b站看到,能直接导出numpy或者pil,对截取的图片进行处理,原理是调用winApi,未测试

实现原理

微软的文档中心上一篇文档Capturing an Image,其中描述了如何在Windows上实现窗口截图:

To store an image temporarily, your application must call CreateCompatibleDC to create a DC that is compatible with the current window DC. After you create a compatible DC, you create a bitmap with the appropriate dimensions by calling the CreateCompatibleBitmap function and then select it into this device context by calling the SelectObject function. After the compatible device context is created and the appropriate bitmap has been selected into it, you can capture the image. The BitBlt function captures images. This function performs a bit block transfer that is, it copies data from a source bitmap into a destination bitmap. However, the two arguments to this function are not bitmap handles. Instead, BitBlt receives handles that identify two device contexts and copies the bitmap data from a bitmap selected into the source DC into a bitmap selected into the target DC. In this case, the target DC is the compatible DC, so when BitBlt completes the transfer, the image has been stored in memory.

开启|关闭 系统DPI适配

如果在操作系统的显示设置中启用了缩放,必须使用SetProcessDPIAware防止截图不完整。

如果不是100%,必须要使用SetProcessDPIAware防止截图不完整

image-20220315125013963

方法1 (取自 onmyoji_bot )

# 查询当前 DPI 缩放
# Query DPI Awareness (Windows 10 and 8)
awareness = ctypes.c_int()
errorCode = ctypes.windll.shcore.GetProcessDpiAwareness(
0, ctypes.byref(awareness))

# 0 未开启缩放
# 1 开启了缩放
print(awareness.value)
# 设置缩放
# 0
# 1

# Set DPI Awareness (Windows 10 and 8)
errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(int)

# Set DPI Awareness (Windows 7 and Vista)
success = ctypes.windll.user32.SetProcessDPIAware()

Ctypes

from ctypes import windll, byref, c_ubyte
from ctypes.wintypes import RECT, HWND
  • 截取图片
import numpy as np

GetDC = windll.user32.GetDC
CreateCompatibleDC = windll.gdi32.CreateCompatibleDC
GetClientRect = windll.user32.GetClientRect
CreateCompatibleBitmap = windll.gdi32.CreateCompatibleBitmap
SelectObject = windll.gdi32.SelectObject
BitBlt = windll.gdi32.BitBlt
SRCCOPY = 0x00CC0020
GetBitmapBits = windll.gdi32.GetBitmapBits
DeleteObject = windll.gdi32.DeleteObject
ReleaseDC = windll.user32.ReleaseDC

# 排除缩放干扰
ctypes.windll.user32.SetProcessDPIAware()
ctypes.windll.shcore.SetProcessDpiAwareness(1)

def capture(handle: HWND):
"""窗口客户区截图

Args:
handle (HWND): 要截图的窗口句柄

Returns:
numpy.ndarray: 截图数据
"""
# 获取窗口客户区的大小
r = RECT()
GetClientRect(handle, byref(r))
width, height = r.right, r.bottom

# 开始截图
dc = GetDC(handle)
cdc = CreateCompatibleDC(dc)
bitmap = CreateCompatibleBitmap(dc, width, height)
SelectObject(cdc, bitmap)
BitBlt(cdc, 0, 0, width, height, dc, 0, 0, SRCCOPY)

# 截图是BGRA排列,因此总元素个数需要乘以4
total_bytes = width*height*4
buffer = bytearray(total_bytes)
byte_array = c_ubyte*total_bytes
GetBitmapBits(bitmap, total_bytes, byte_array.from_buffer(buffer))
DeleteObject(bitmap)
DeleteObject(cdc)
ReleaseDC(handle, dc)

# 返回截图数据为numpy.ndarray
return np.frombuffer(buffer, dtype=np.uint8).reshape(height, width, 4)

if __name__ == "__main__":
import cv2
handle = windll.user32.FindWindowW(None, "一梦江湖")
image = capture(handle)
cv2.imshow("Capture Test", image)
cv2.waitKey()

win32api

import time
import win32gui, win32ui, win32con, win32api
  • 截图图片
# 0为当前激活的窗口
def capture(hwnd: int = 0, fillename=None):
client_left, client_top, client_right, client_bottom = win32gui.GetClientRect(hwnd)
w = client_right - client_left
h = client_bottom - client_top

# 先获取设备上下文DC(Divice Context)
hwndDC = win32gui.GetWindowDC(hwnd)

# 根据窗口的DC获取mfcDC
mfcDC = win32ui.CreateDCFromHandle(hwndDC)

# 创建可兼容的DC
saveDC = mfcDC.CreateCompatibleDC()

# 创建bigmap用来存放图片信息
saveBitMap = win32ui.CreateBitmap()

# 创建bigmap用来存放图片信息对内存对象
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
print("h: ", h)
print("w: ", w)

# 获取窗口内容的信息
saveDC.SelectObject(saveBitMap)

X = 0
Y = 0

x = 50
y = 50

# 截图左上角0,0 长宽为w,h 的尺寸作为图片
saveDC.BitBlt((X, Y), (w, h), mfcDC, (x, y), win32con.SRCCOPY)

# 将saveBitMap导出成文件、CV2、或者PIL
result = bitMap_to_cv2_img(saveBitMap, w, h)

# 释放内存空间(优化空间:将用过一次的尺寸存入一个内存池)
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(hwnd, hwndDC)

return result
  • 保存成【png】文件
# 保存成文件
saveBitMap.SaveBitmapFile(saveDC, filename)
  • 保存成【CV2】图片对象
# 返回一个cv2的内容图片实例
def bitMap_to_cv2_img(BitMap, w, h, gray=False):
signedIntsArray = BitMap.GetBitmapBits(True)
img = np.frombuffer(signedIntsArray, dtype="uint8")
img.shape = (h, w, 4)

# 灰度处理
if gray:
return cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
else:
return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)

def show_cv2_img(img):
print("img: ", img.shape)
import cv2

cv2.imshow("tset", img)
cv2.waitKey(0)
  • 保存成【PIL】图片对象
# 返回一个PIL的内容图片实例
def bitMap_to_PIL_img(BitMap) -> Image:
try:
# 提取当前截取的图片信息
bitMapInfo = BitMap.GetInfo()

# 转换为buffer供pil使用
bitMapStr = BitMap.GetBitmapBits(True)

# 通过buffer生成pil的图片对象实例
img_PIL = Image.frombuffer(
"RGB",
(bitMapInfo["bmWidth"], bitMapInfo["bmHeight"]),
bitMapStr,
"raw",
"BGRX",
0,
1,
)

return img_PIL
except:
return None

Selenium (web程序截图)

from selenium import webdriver
import time

# 浏览器的chromedriver路径
browser_path = r"C:\Users\weidiao\Desktop\chromedriver_win32\chromedriver.exe"

# 滚动屏幕的js代码
Scroll_Page="""
(function () {
var y = 0;
var step = 100;
window.scroll(0, 0);
function f() {
if (y < document.body.scrollHeight) {
y += step;
window.scroll(0, y);
setTimeout(f, 50);
} else {
window.scroll(0, 0);
document.title += "scroll-done";
}
}
setTimeout(f, 1000);
})();"""


def capture(url, filename:str="capture.png", browser_path:str, size:tuple):
browser = webdriver.Chrome(browser_path)
browser.set_window_size(1200, 900)
browser.get(url)

# 执行一段jscript用来滚动屏幕
browser.execute_script(Scroll_Page)
for i in range(30):
if "scroll-done" in browser.title:
break
time.sleep(1)
beg = time.time()
for i in range(10):
browser.save_screenshot(filename)
end = time.time()

print(end - beg)
browser.close()

capture("//www.jb51.net")

PyQt5

后台截图

from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import *
import win32gui
import sys

hwnd = win32gui.FindWindow(None, 'C:\Windows\system32\cmd.exe')
app = QApplication(sys.argv)
screen = QApplication.primaryScreen()
img = screen.grabWindow(hwnd).toImage()
img.save("screenshot.jpg")

PIL的ImageGrab

前台截图

import time
import numpy as np
from PIL import ImageGrab

img = ImageGrab.grab(bbox=(100, 161, 1141, 610))
img = np.array(img.getdata(), np.uint8).reshape(img.size[1], img.size[0], 3)

pyautogui

前台截图

import pyautogui
import cv2
# x,y,w,h
img = pyautogui.screenshot(region=[0,0,100,100])
# img.save('screenshot.png')
img = cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR)

d3dshot

查看源码得知,d3dshot基本是依赖ctypes调用系统 user32相关api完成工作的,然后采用numpy对结果进行处理,基本可以确定期执行效率跟win32api差不多,甚至更好,待性能实测

安装

python -m pip install d3dshot