使用Python在macOS(达尔文)上实时捕捉OpenCV窗口(截图)[复制]

zazmityj  于 2023-03-13  发布在  Python
关注(0)|答案(1)|浏览(190)

此问题在此处已有答案

Capture video data from screen in Python(11个答案)
6天前关闭。
这篇文章是6天前编辑并提交审查的。
我正在学习Open CV的教程,并尝试重写以下代码:https://github.com/learncodebygaming/opencv_tutorials/tree/master/005_real_time
(具体来说,就是windowcapture.py文件)
该文件使用win32gui、win32ui、win32con按窗口名捕获给定的打开窗口,并对它进行屏幕截图,以便稍后进行cv2处理。
我曾尝试使用Quartz for macOS重新创建此功能,示例如下:https://stackoverflow.com/a/48030215/14649706
我自己的www.example.com版本windowcapture.py是这样的:

import numpy as np
from Quartz import CGWindowListCopyWindowInfo, kCGNullWindowID, kCGWindowListOptionAll, CGRectNull, CGWindowListCreateImage, kCGWindowImageBoundsIgnoreFraming, kCGWindowListExcludeDesktopElements, CGImageGetDataProvider, CGDataProviderCopyData, CFDataGetBytePtr, CFDataGetLength
import os
from PIL import Image
import cv2 as cv

class WindowCapture:

    # properties
    window_name = None
    window = None
    window_id = None
    window_width = 0
    window_height = 0

    # constructor
    def __init__(self, given_window_name=None):
        if given_window_name is not None:

            self.window_name = given_window_name
            self.window = self.get_window()

            if self.window is None:
                raise Exception('Unable to find window: {}'.format(given_window_name))

            self.window_id = self.get_window_id()

            self.window_width = self.get_window_width()
            self.window_height = self.get_window_height()

            self.window_x = self.get_window_pos_x()
            self.window_y = self.get_window_pos_y()

    # determine the window we want to capture
    def get_window(self):
        windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID)
        for window in windows:
            name = window.get('kCGWindowName', 'Unknown')
            if name and self.window_name in name:
                
                return window
        return None
    
    def get_window_id(self):
        return self.window['kCGWindowNumber']

    def get_window_width(self):
        return int(self.window['kCGWindowBounds']['Width'])
    
    def get_window_height(self):
        return int(self.window['kCGWindowBounds']['Height'])

    def get_window_pos_x(self):
        return int(self.window['kCGWindowBounds']['X'])

    def get_window_pos_y(self):
        return int(self.window['kCGWindowBounds']['Y'])
    
    def get_image_from_window(self):
        image_filename = 'test-img.png'
        # -x mutes sound and -l specifies windowId
        os.system('screencapture -x -l %s %s' % (self.window_id, image_filename))
        pil_image = Image.open(image_filename)
        image_as_numpy_array = np.array(pil_image)
        os.remove(image_filename)

        image = cv.cvtColor(image_as_numpy_array, cv.COLOR_BGR2RGB)
        return image

这里我的get_image_from_window方法工作正常,我可以使用cv.imshow('cv', screenshot)查看它:

import cv2 as cv
from time import time
from windowcapture import WindowCapture

# initialize the WindowCapture class
wincap = WindowCapture('Blue Box Clicker')

loop_time = time()

while(True):
    # get an updated image of the game
    screenshot = wincap.get_image_from_window()

    cv.imshow('cv', screenshot)

    # debug the loop rate
    print('FPS {}'.format(1 / (time() - loop_time)))
    loop_time = time()

    # press 'q' with the output window focused to exit.
    # waits 1 ms every loop to process key presses
    if cv.waitKey(1) == ord('q'):
        cv.destroyAllWindows()
        break

print('Done.')

但是我不想在本地保存图像然后再加载它,我认为这是非常低效的,我想实现同样的功能,而不是实际保存图像文件,然后打开它。
类似于这里的做法(在上面的GitHub链接中):

def get_screenshot(self):

        # get the window image data
        wDC = win32gui.GetWindowDC(self.hwnd)
        dcObj = win32ui.CreateDCFromHandle(wDC)
        cDC = dcObj.CreateCompatibleDC()
        dataBitMap = win32ui.CreateBitmap()
        dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h)
        cDC.SelectObject(dataBitMap)
        cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY)

        # convert the raw data into a format opencv can read
        #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp')
        signedIntsArray = dataBitMap.GetBitmapBits(True)
        img = np.fromstring(signedIntsArray, dtype='uint8')
        img.shape = (self.h, self.w, 4)

        # free resources
        dcObj.DeleteDC()
        cDC.DeleteDC()
        win32gui.ReleaseDC(self.hwnd, wDC)
        win32gui.DeleteObject(dataBitMap.GetHandle())

        # drop the alpha channel, or cv.matchTemplate() will throw an error like:
        #   error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 
        #   && _img.dims() <= 2 in function 'cv::matchTemplate'
        img = img[...,:3]

        # make image C_CONTIGUOUS to avoid errors that look like:
        #   File ... in draw_rectangles
        #   TypeError: an integer is required (got type tuple)
        # see the discussion here:
        # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109
        img = np.ascontiguousarray(img)

        return img

如何使用Quartz实现这一点?
我在MacOS(M1 Pro)上,真的很想让这个工作。
目前,这个程序运行在12fps左右。
它试图捕获的程序是另一个python程序(一个简单的pygame):

import pygame
import random

# Set up the game window
pygame.init()
window_width, window_height = 640, 480
window = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption("Blue Box Clicker")

# Set up the clock
clock = pygame.time.Clock()

# Set up the game variables
background_color = (0, 0, 0)
box_color = (0, 0, 255)
box_width, box_height = 50, 50
box_x, box_y = 0, 0

# Set up the game loop
running = True
while running:

    # Handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            if box_x <= mouse_x <= box_x + box_width and box_y <= mouse_y <= box_y + box_height:
                # Correct click
                box_x, box_y = random.randint(
                    0, window_width - box_width), random.randint(0, window_height - box_height)
                # Incorrect click

    # Draw the background
    window.fill(background_color)

    # Draw the box
    pygame.draw.rect(window, box_color, (box_x, box_y, box_width, box_height))

    # Update the window
    pygame.display.update()

    # Limit the frame rate
    clock.tick(60)

# Clean up
pygame.quit()
8cdiaqws

8cdiaqws1#

我使用下面的代码修复了这个问题:

def get_image_from_window(self):
    core_graphics_image = QZ.CGWindowListCreateImage(
        QZ.CGRectNull,
        QZ.kCGWindowListOptionIncludingWindow,
        self.window_id,
        QZ.kCGWindowImageBoundsIgnoreFraming | QZ.kCGWindowImageNominalResolution
    )

    bytes_per_row = QZ.CGImageGetBytesPerRow(core_graphics_image)
    width = QZ.CGImageGetWidth(core_graphics_image)
    height = QZ.CGImageGetHeight(core_graphics_image)

    core_graphics_data_provider = QZ.CGImageGetDataProvider(core_graphics_image)
    core_graphics_data = QZ.CGDataProviderCopyData(core_graphics_data_provider)

    np_raw_data = np.frombuffer(core_graphics_data, dtype=np.uint8)

    numpy_data = np.lib.stride_tricks.as_strided(np_raw_data,
                                            shape=(height, width, 3),
                                            strides=(bytes_per_row, 4, 1),
                                            writeable=False)
    
    final_output = np.ascontiguousarray(numpy_data, dtype=np.uint8)

    return final_output

此方法以cv2可以识别并用于matchTemplate的格式返回捕获的CGImage。
我的完整代码如下所示:
x一个一个一个一个x一个一个二个x
而这在MacBook Pro(配有M1 Pro处理器)上的平均60fps下也能正常工作。

相关问题