I am using low-level windows functions in conjunction with OpenCV to capture a window. I could use ImageGrab.grab()
, but I am trying to make my capture faster. I created a class called Window_Capture
that runs all of the functions necessary to capture the screen. My issue lies within the member function: get_screenshot()
. Whenever I capture the screen, OpenCV’s imshow
displays a seemingly blank output. I currently use Opera as the browser window I am trying to display, and I have tried using other browser windows, but all of them produce the same blank output. What am I doing wrong?
Window Capture Class:
import win32gui, win32ui, win32con, win32api
from ctypes import windll
import numpy as np
import cv2
class WindowNotFoundException(Exception):
def __init__(self, window_name, hwnd=None, debug=False):
self.window_name = window_name
self.hwnd = hwnd
self.debug = debug
super().__init__()
def __str__(self):
if self.debug:
string = f"Window '{self.window_name}' not found. Hwnd: {self.hwnd}"
else:
string = f"Window '{self.window_name}' not found."
return string
class Window_Capture:
w = 0
h = 0
cropped_x = 0
cropped_y = 0
offset_x = 0
offset_y = 0
hwnd = None
#constructor
def __init__(self, window_name=None):
#find the handle for the window we want to capture
#if no window name is given, capture the entire screen
if window_name is None:
self.hwnd = win32gui.GetDesktopWindow()
else:
self.hwnd = win32gui.FindWindow(None, window_name)
if not self.hwnd:
raise WindowNotFoundException(window_name, self.hwnd, debug=True)
#get the window size
window_rect = win32gui.GetWindowRect(self.hwnd)
self.w = window_rect[2] - window_rect[0]
self.h = window_rect[3] - window_rect[1]
#account for the window border and titlebar and cut them off
border_pixels = 1 # was 8
titlebar_pixels = 10 # was 30
self.w = self.w - border_pixels # was (border_pixels * 2)
# border_pixels = 8
# titlebar_pixels = 30
# self.w = self.w - (border_pixels * 2)
self.h = self.h - titlebar_pixels - border_pixels
self.cropped_x = border_pixels
self.cropped_y = titlebar_pixels
#set the cropped coordinates offset
self.offset_x = window_rect[0] + self.cropped_x
self.offset_y = window_rect[1] + self.cropped_y
#capture a screenshot of a window
def get_screenshot(self):
#get the window's device context
hwndDC = win32gui.GetWindowDC(self.hwnd)
#create a device context for the entire screen
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
#create a memory device context for the entire screen
saveDC = mfcDC.CreateCompatibleDC()
#create a bitmap object
saveBitMap = win32ui.CreateBitmap()
#set the bitmap object to the size of the window
saveBitMap.CreateCompatibleBitmap(mfcDC, self.w, self.h)
#copy the window's device context into the bitmap object
saveDC.SelectObject(saveBitMap)
#copy the window's device context into the memory device context
result = windll.user32.PrintWindow(self.hwnd, saveDC.GetSafeHdc(), 0)
#if the window was successfully captured
if result != 0:
print("[+] Window captured")
#copy the bitmap object into a numpy array
bmpinfo = saveBitMap.GetInfo()
bmpstr = saveBitMap.GetBitmapBits(True)
img = np.fromstring(bmpstr, dtype='uint8')
img.shape = (bmpinfo['bmHeight'], bmpinfo['bmWidth'], 4)
#crop the image to remove the window border and titlebar
img = img[self.cropped_y:self.cropped_y+self.h, self.cropped_x:self.cropped_x+self.w]
#convert the image to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
#if the window was not successfully captured
else:
print("[-] Failed to capture window")
img = None
#clean up
win32gui.DeleteObject(saveBitMap.GetHandle())
saveDC.DeleteDC()
mfcDC.DeleteDC()
win32gui.ReleaseDC(self.hwnd, hwndDC)
return img
Main function:
window = Window_Capture('Google - Opera')
def Main():
while(True):
screen = window.get_screenshot()
print(screen.shape)
cv2.imshow('window', cv2.cvtColor(screen, cv2.COLOR_BGR2RGB))
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
Extra Info:
screen.shape
returns (13, 197, 3)
A screenshot of what imshow
is displaying