Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
OpenCV does not capture your desktop by itself. Use a screen-capture library such as MSS to obtain the pixels, convert the result to a NumPy array in OpenCV’s BGR order, and then display or process that array. The following MSS example captures the rectangle whose upper-left corner is (100, 80) and whose size is 640 × 400:
import cv2
import mss
from mss.models import Region
region = Region(left=100, top=80, width=640, height=400)
with mss.MSS() as sct:
shot = sct.grab(region)
frame = shot.to_numpy(channels="BGR")
cv2.imshow("Captured region", frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
left and top are screen coordinates; width and height are dimensions. MSS supplies the screenshot, NumPy holds the pixels, and OpenCV performs the computer-vision work.
What OpenCV contributes to a screen capture
A desktop screenshot is a stream of pixels obtained from the operating system. OpenCV is the processing layer: it can resize, crop, convert to grayscale, detect edges, threshold, compare frames, or write an image file. A separate capture API must first obtain those pixels.
MSS and PyAutoGUI are two practical choices. MSS returns a screenshot object that can be converted directly to a NumPy array. PyAutoGUI returns an image object from its screenshot() function; convert that image to an array before passing it to OpenCV.
#1 Best Overall
Install the Python packages
Install OpenCV, NumPy, and MSS in the environment that will run the script:
python -m pip install opencv-python numpy mss
Install PyAutoGUI only if you want the alternative workflow:
python -m pip install pyautogui
Run the examples in a normal desktop session. Operating-system permissions, headless behavior, protected surfaces, and high-DPI coordinate mapping vary by platform and are not uniform guarantees of these libraries.
Free tools Windows power users keep installed
One-click scans. No signup required.
Capture one rectangular area with MSS
Use a dimension-based region
The documented Region form is explicit and easy to read:
import cv2
import mss
from mss.models import Region
region = Region(left=100, top=80, width=640, height=400)
with mss.MSS() as sct:
shot = sct.grab(region)
frame = shot.to_numpy(channels="BGR")
cv2.imshow("Captured region", frame)
cv2.waitKey(0)
cv2.destroyAllWindows()
The capture object is opened with a context manager and closed automatically. shot is MSS’s screenshot object; frame is the NumPy array consumed by OpenCV.
Save the result instead of displaying it
For a file, replace the window calls with cv2.imwrite():
Rank #2
import cv2
import mss
from mss.models import Region
region = Region(left=100, top=80, width=640, height=400)
with mss.MSS() as sct:
frame = sct.grab(region).to_numpy(channels="BGR")
ok = cv2.imwrite("area.png", frame)
if not ok:
raise RuntimeError("OpenCV could not write area.png")
OpenCV’s image-writing functions infer the format from the filename extension. Check the Boolean return value when a failed write must be detected programmatically.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Use a dictionary or a PIL-style box
MSS also accepts a dictionary region. Its keys use the same dimension convention:
region = {"left": 100, "top": 80, "width": 640, "height": 400}
It also accepts a PIL-style four-value box, but this convention is different: (left, top, right, bottom), not width and height. For example, (100, 80, 740, 480) describes the same 640-by-400 rectangle. Mixing these conventions is a common cause of unexpectedly large, small, or misplaced captures.
Keep the capture open for repeated frames
For video-like processing, create one MSS object outside the loop and grab the same region on each iteration:
import cv2
import mss
from mss.models import Region
region = Region(left=100, top=80, width=640, height=400)
with mss.MSS() as sct:
while True:
shot = sct.grab(region)
frame = shot.to_numpy(channels="BGR")
cv2.imshow("Live region", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
break
cv2.destroyAllWindows()
Press q to stop. Keeping the capture object open avoids repeatedly constructing it. This example demonstrates the loop structure; it is not a speed measurement. The useful capture rate depends on the operating system, display setup, region size, and the processing performed inside the loop.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Why the BGR conversion matters
MSS documents ScreenShot.to_numpy() with selectable channel ordering. Request channels="BGR" when the array will go to OpenCV. OpenCV expects colors in BGR order, while RGB data interpreted as BGR swaps red and blue. Grayscale operations may hide the mistake, but color display, masking, and channel-specific analysis will not.
If you already have an RGB array, convert it explicitly:
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
Do not perform this conversion on an array that MSS has already returned as BGR.
Capture a selected monitor or a region spanning monitors
Understand MSS monitor entries
MSS exposes monitor geometry. Entry 0 represents the complete virtual desktop; entries after zero represent individual displays. Each record supplies its left, top, width, and height.
import mss
with mss.MSS() as sct:
for index, monitor in enumerate(sct.monitors):
print(index, monitor)
To capture a rectangle relative to a chosen monitor, add that monitor’s origin to the local coordinates:
import cv2
import mss
from mss.models import Region
monitor_index = 1
local_left, local_top = 40, 60
width, height = 800, 500
with mss.MSS() as sct:
monitor = sct.monitors[monitor_index]
region = Region(
left=monitor["left"] + local_left,
top=monitor["top"] + local_top,
width=width,
height=height,
)
frame = sct.grab(region).to_numpy(channels="BGR")
cv2.imwrite("monitor-area.png", frame)
A display positioned left of or above the primary display can have negative virtual-desktop coordinates. Use the values reported by MSS rather than assuming every monitor starts at a positive coordinate.
Process the captured pixels with OpenCV
Once frame is a BGR NumPy array, ordinary OpenCV operations apply. This example creates a grayscale image and an edge map while showing both:
import cv2
import mss
from mss.models import Region
region = Region(left=100, top=80, width=640, height=400)
with mss.MSS() as sct:
frame = sct.grab(region).to_numpy(channels="BGR")
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
cv2.imshow("Original", frame)
cv2.imshow("Edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
The capture boundary and the computer-vision pipeline are separate: change the rectangle without changing the processing code, or feed a saved image into the same processing functions.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use PyAutoGUI when its image API fits better
PyAutoGUI documents a region screenshot call whose tuple is (left, top, width, height):
import cv2
import numpy as np
import pyautogui
image = pyautogui.screenshot(region=(100, 80, 640, 400))
rgb = np.array(image)
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
cv2.imshow("PyAutoGUI region", bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()
The returned image object must be converted to an array before OpenCV processing. Confirm its channel order and convert RGB to BGR as shown. Do not assume that PyAutoGUI’s image object has MSS’s to_numpy(channels=...) method.
Which API should you choose?
| Consideration | MSS | PyAutoGUI |
|---|---|---|
| Capture result | MSS screenshot object, then NumPy via to_numpy() |
Image object from screenshot(), then NumPy conversion |
| Documented region form | Region or dictionary: left, top, width, height; also PIL-style left, top, right, bottom |
Tuple: left, top, width, height |
| OpenCV color hand-off | Request BGR directly with to_numpy(channels="BGR") |
Check the image’s channels and convert RGB to BGR when needed |
| Performance verdict | No universal winner is established here; measure your own workload if throughput matters | |
Choose based on the API and coordinate convention that make your application least error-prone. A workload-specific benchmark is more meaningful than a general claim about speed.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Troubleshoot common failures
The window is black, empty, or never appears
- Confirm that the script is running in an interactive desktop session rather than an unsupported headless context.
- Check operating-system screen-recording or capture permissions. The exact setting and behavior differ by platform.
- Verify that the selected rectangle lies inside the virtual desktop and that its width and height are positive.
- For protected or DRM-controlled content, capture availability can be restricted by the operating system or application.
The image has red and blue reversed
The array is probably RGB but is being interpreted as BGR. Request BGR from MSS or apply cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR) exactly once.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteThe rectangle is offset or the dimensions are wrong
- For
Region, a dictionary, and PyAutoGUI, useleft, top, width, height. - For the PIL-style MSS box, use
left, top, right, bottom. - On multiple monitors, inspect
sct.monitorsand include the selected monitor’s reported origin. Negative origins are valid. - High-DPI scaling can make application-reported coordinates differ from physical pixel coordinates. Validate the mapping on the target operating system instead of assuming a single rule.
cv2.imshow() closes immediately
A HighGUI window needs an event wait. Use cv2.waitKey(0) for a still image or a short delay such as cv2.waitKey(1) inside a loop, then call cv2.destroyAllWindows().
Best Value
The saved file is missing or unreadable
Check the Boolean result of cv2.imwrite(), use a writable absolute or verified working directory, and choose a filename extension supported by the OpenCV build.
The loop consumes too many resources
Capture only the rectangle you need, keep one MSS object open, and avoid expensive processing when a frame has not changed. If you need a specific frame rate, add your own timing and measure on the deployment machine; the documented examples do not establish a universal capture speed.
Or skip the browser setup
If your actual target is a public web page rather than pixels from your local desktop, ScreenshotNeo provides a website screenshot API. It is not a replacement for capturing an arbitrary local monitor area, but it removes the need to configure a browser for URL captures. Before the shot it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for all options. A one-call cURL request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The equivalent Python request is:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
In Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every feature is on every plan: 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try a URL capture.
Frequently Asked Questions
Can these libraries capture DRM-protected video or secure application surfaces?
There is no universal guarantee. Protected-content policies are enforced by the operating system or application and can produce a blank or restricted capture; test the exact environment and obtain permission where required.
How should I choose coordinates when a window moves?
Recalculate the rectangle from the window’s current position before each grab, or capture a stable monitor-relative area. MSS and PyAutoGUI accept coordinates; they do not, by themselves, identify a moving application window.
The Bottom Line
Use MSS to capture the rectangle, request BGR data, and let OpenCV process the resulting NumPy array. Keep the capture object open for loops, treat monitor origins and coordinate conventions explicitly, and use PyAutoGUI when its image API is a better fit.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

