How do I screenshot a single monitor using OpenCV?

Question:

I am trying to devleope a device that changes the RGB led strips according to the colour of my display. To this I am planning on screnshotiing the screen an normalising/taking the mean of the colours of individual pixels in the display. I have figured out how to screenshot a single monitor but want to make it work with a multi monitor setup. Here’s my basic code. Any help would be greatly appreciated.

import numpy as np
import cv2
import pyautogui
   
  
# take screenshot using pyautogui
image = pyautogui.screenshot()
   
# since the pyautogui takes as a 
# PIL(pillow) and in RGB we need to 
# convert it to numpy array and BGR 
# so we can write it to the disk
image = cv2.cvtColor(np.array(image),
                     cv2.COLOR_RGB2BGR)

I tried this using the mss module but it isn’t working. It’s having an issue where the secondary display is just clipping in the final image.

import numpy as np
import cv2
import pyautogui
import mss 
  
with mss.mss() as sct:
    
    # Get information of monitor 2
    monitor_number = 1
    mon = sct.monitors[monitor_number]

    # The screen part to capture
    monitor = {
        "top": mon["top"],
        "left": mon["left"],
        "width": mon["width"],
        "height": mon["height"],
        "mon": monitor_number,
    }
    output = "sct-mon{mon}_{top}x{left}_{width}x{height}.png".format(**monitor)

    # Grab the data
    sct_img = sct.grab(monitor)
    img = np.array(sct.grab(monitor)) # BGR Image
Asked By: Tanay Upreti

||

Answers:

Using python-mss, we may iterate the list of monitors, and grab a frame from each monitor in a loop (we may place that loop in an endless loop).


Example for iterating the monitors:

for monitor_number, mon in enumerate(sct.monitors[1:]):
  • We are ignoring index 0 (it looks like sct.monitors[0] applies a large combined monitor).
  • enumerate is used as shortcut for iterating the list, and also getting an index.

The following code sample grabs a frame from each monitor (in an endless loop), down-scale the frame, and shows it for testing (using cv2.imshow).
Each monitor has a separate window, with monitor index shown at the title.

Code sample:

import numpy as np
import cv2
import mss 

with mss.mss() as sct:
    # Grab frames in an endless lopp until q key is pressed
    while True:
        # Iterate over the list of monitors, and grab one frame from each monitor (ignore index 0)
        for monitor_number, mon in enumerate(sct.monitors[1:]):
            monitor = {"top": mon["top"], "left": mon["left"], "width": mon["width"], "height": mon["height"], "mon": monitor_number}  # Not used in the example

            # Grab the data
            img = np.array(sct.grab(mon)) # BGRA Image (the format BGRA, at leat in Windows 10).

            # Show down-scaled image for testing
            # The window name is img0, img1... applying different monitors.
            cv2.imshow(f'img{monitor_number}', cv2.resize(img, (img.shape[1]//4, img.shape[0]//4)))
            key = cv2.waitKey(1)
            if key == ord('q'):
                break

cv2.destroyAllWindows()

Sample output (using two monitors):
enter image description here


The above sample demonstrates the grabbing process.
You still have to compute the normalized mean of colour…
Note the the pixel format is BGRA and not BGR (the last channel is alpha (transparency) channel, that may be ignored).

Answered By: Rotem