How to display mouse pointer in my recording - python

I am making a screen recorder that records the screen of the desktop. However, when I record the screen, the mouse pointer (cursor) is not visible in the recording.
Is there any way I can show the mouse pointer in my recording?
This is my current code:
import cv2
import numpy as np
import pyautogui
import datetime
date = datetime.datetime.now()
SCREEN_SIZE = (1366, 768)
framerate = 12
fourcc = cv2.VideoWriter_fourcc(*'XVID')
filename = 'E:/project/videos/rec_%s%s%s%s%s%s.avi' % (date.year, date.month, date.day, date.hour, date.minute, date.second)
out = cv2.VideoWriter(filename, fourcc,framerate, SCREEN_SIZE)
while True:
img = pyautogui.screenshot()
frame = np.array(img)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
out.write(frame)
cv2.imshow('screenshot', frame)
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
out.release()

I don't think it's possible with that approach - but am always happy to be corrected and learn something new. I know of a couple of workarounds.
The first is to continue to use pyautogui and to call its mouseposition() function and paste/draw your own synthetic mouse pointer onto the grab. I did that with OpenCV's fillPoly() function:
#!/usr/bin/env python3
import cv2
import numpy as np
import pyautogui
import datetime
# X and Y coordinates of mouse pointer
Xs = [0,8,6,14,12,4,2,0]
Ys = [0,2,4,12,14,6,8,0]
while True:
img = pyautogui.screenshot()
mouseX,mouseY = pyautogui.position()
mouseX *= 2
mouseY *= 2
frame = np.array(img)
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
# Synthesize mouse pointer
Xthis = [4*x+mouseX for x in Xs]
Ythis = [4*y+mouseY for y in Ys]
points = list(zip(Xthis,Ythis))
points = np.array(points, 'int32')
cv2.fillPoly(frame,[points],color=[255,255,255])
# Make it a bit smaller for display
frame = cv2.resize(frame,(960,540))
cv2.imshow('Title', frame)
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
out.release()
The second is to use ffmpeg which can capture the mouse - you can either run ffmpeg in place of your current app, or pipe the output from ffmpeg into your app through a pipe and continue to process it as you are now. That might look like this:
#!/usr/bin/env python3
# ffmpeg -y -pix_fmt bgr0 -f avfoundation -r 20 -t 10 -i 1 -vf scale=w=3840:h=2160 -f rawvideo /dev/null
import sys
import cv2
import time
import subprocess
import numpy as np
w,h = 3840, 2160
def ffmpegGrab():
"""Generator to read frames from ffmpeg subprocess"""
cmd = [
'ffmpeg',
'-pix_fmt', 'bgr0',
'-f', 'avfoundation',
'-capture_cursor', '1',
'-capture_mouse_clicks', '1',
'-r', '20',
'-i', '1',
'-vf','scale=w=3840:h=2160',
'-f', 'rawvideo',
'pipe:1'
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while True:
frame = proc.stdout.read(w*h*4)
yield np.frombuffer(frame, dtype=np.uint8).reshape((h,w,4))
# Get frame generator
gen = ffmpegGrab()
# Get start time
start = time.time()
# Read video frames from ffmpeg in loop
nFrames = 0
while True:
# Read next frame from ffmpeg
frame = next(gen)
nFrames += 1
frame = cv2.resize(frame,(960,540))
cv2.imshow('screenshot', frame)
if cv2.waitKey(1) == ord("q"):
break
fps = nFrames/(time.time()-start)
print(f'FPS: {fps}')
cv2.destroyAllWindows()
out.release()
Note that pyautogui takes around 600ms to capture one frame on my Mac, whereas the ffmpeg above achieves around 20fps, or 50ms per frame.
Keywords: Python. image processing, ffmpeg, pyautogui, screen-grab, screen-capture, screengrab, screencapture, fps. speed, prime.

Related

Changing FPS in OpenCV

I have an application that needs to capture only a few frames per second from a webcam. Setting videowriter in the below code to 3 frames per second results in the webcam's normal framerate of approximately 30 fps being saved.
What are the options to save only the recorded 3 frames per second, and let the other 27 or so go? Thanks in advance.
import cv2
import numpy as np
import time
import datetime
import pathlib
import imutils
cap = cv2.VideoCapture(0)
if (cap.isOpened() == False):
print("Unable to read camera feed")
capture_duration = 15
frame_per_sec = 3
frame_width = 80
frame_height = 60
out = cv2.VideoWriter('C:\\Users\\student\\Desktop\\videoFile.avi',cv2.VideoWriter_fourcc('m','j','p','g'),frame_per_sec, (frame_width,frame_height))
start_time = time.time()
while( int(time.time() - start_time) < capture_duration ):
ret, frame = cap.read()
if ret==True:
frame = imutils.resize(frame, width=frame_width)
out.write(frame)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
out.release()
cv2.destroyAllWindows()
You set the FPS for the output via the VideoWriter,
but you didn't attempt to set the FPS for the input via the VideoCapture.
In order to do that you can try to call cv2.VideoCapture, with the cv2.CAP_PROP_FPS property after you create cap.
For example:
cap.set(cv2.CAP_PROP_FPS, 3)
However - note that the actual behavior is dependant on the specific capture device you are using. Some support only certain FPSs. See also this post regarding it: change frame rate in opencv 3.4.2.
If it does work you will be able to simplify your code a lot - just capture frames, process them and save (without any manual fps management).
This method programmatically sets frames per second. A 6.1mb file was created when frame rate was set for 30fps, and a 0.9mb file when set for 3fps.
#!/usr/bin/env python3
import cv2
import numpy as np
import time
import datetime
import pathlib
import imutils
cap = cv2.VideoCapture(0)
if (cap.isOpened() == False):
print("Unable to read camera feed")
capture_duration = 15
frame_per_sec = 30
prev = 0
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
out = cv2.VideoWriter('C:\\videoPy\\LZ\\'outpout.avi',cv2.VideoWriter_fourcc('m','j','p','g'),frame_per_sec, (frame_width,frame_height))
start_time = time.time()
while( int(time.time() - start_time) < capture_duration ):
#start fps
time_elapsed = time.time() - prev
while(time_elapsed > 1./frame_per_sec):
ret, frame = cap.read()
if not ret:
break
if time_elapsed > 1./frame_per_sec:
prev = time.time()
#end fps
if ret==True:
frame = imutils.resize(frame, width=frame_width)
out.write(frame)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
cap.release()
out.release()
cv2.destroyAllWindows()

Record screen from memory of Linux

So , basically I want to design an app that record the screen not by capturing the screen as below
import datetime
from PIL import ImageGrab
import numpy as np
import cv2
time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S')
file_name = f'{time_stamp}.mp4'
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
captured_video = cv2.VideoWriter(file_name, fourcc, 20.0, (630, 330))
while True:
img = ImageGrab.grab(bbox=(0, 0, 630, 330))
img_np = np.array(img)
img_final = cv2.cvtColor(img_np, cv2.COLOR_BGR2RGB)
cv2.imshow("Screen Recorder", img_final)
captured_video.write(img_final)
if cv2.waitKey(10) == ord('q'):
break
It give the screen capturing but I need to record a specific process
Like
I want to record the screen output of
process 31328
Nothing else. Means while recording the screen the pop ups or any mini windows or any floating window opened during the recording would not capture in our screen recorder.
I am thinking that the Linux will throw the output on screen like
We print ('hello'). We should grab that output before it reaches on screen and record it. Is it possible?

How to increase the fps of screen recorder in cv2

I have written a code to record the screen recorder using python but when i see the output then i found that its fps is very low .Is there any better code than mine to increase the fps of the screen recorder.
If yes then please reply.
Here is mine code:-
import cv2
import numpy as np
import pyautogui
import datetime
# display screen resolution, get it from your OS settings
SCREEN_SIZE = (1366, 768)
# define the codec
fourcc = cv2.VideoWriter_fourcc(*"XVID")
# create the video write object
now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
out = cv2.VideoWriter("screen recorder"+now+".avi", fourcc, 5.0, (SCREEN_SIZE))
while True:
img = pyautogui.screenshot()
frame = np.array(img)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
out.write(frame)
cv2.imshow("screenshot", frame)
# if the user clicks q, it exits
if cv2.waitKey(1) == ord("q"):
break
cv2.destroyAllWindows()
out.release()
img = pyautogui.screenshot(region=(0, 0, 300, 400))
The third parameter to the cv2.VideoWriter constructor is the frame rate (https://docs.opencv.org/3.4/dd/d9e/classcv_1_1VideoWriter.html#ac3478f6257454209fa99249cc03a5c59). Currently, you have it set to 5.0. For example, for 30 fps, instantiate the VideoWriter with:
out = cv2.VideoWriter("screen recorder"+now+".avi", fourcc, 30.0, (SCREEN_SIZE))
EDIT: In order to also read in images at the correct framerate, we can pause the while loop using the waitKey function. We can re-write the OP's code like so:
import cv2
import numpy as np
import pyautogui
import datetime
import time
# display screen resolution, get it from your OS settings
SCREEN_SIZE = (1366, 768)
FRAME_RATE = 30.0 # desired frame-rate
# define the codec
fourcc = cv2.VideoWriter_fourcc(*"XVID")
# create the video write object
now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
out = cv2.VideoWriter("screen recorder"+now+".avi", fourcc, FRAME_RATE, (SCREEN_SIZE))
while True:
st = time.time() # collect start time
img = pyautogui.screenshot()
frame = np.array(img)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
out.write(frame)
cv2.imshow("screenshot", frame)
en = time.time() # collect end time
# calculate time to wait before next frame:
delay = max(0, (1 / FRAME_RATE - (en - st)) * 1000)
# if the user clicks q, it exits
if cv2.waitKey(delay) == ord("q"):
break
cv2.destroyAllWindows()
out.release()
img = pyautogui.screenshot(region=(0, 0, 300, 400))
Note: If collecting the frames is too slow (requires more than 1 / FRAMERATE seconds), then you may want to reduce the frame rate or the resolution.

Python3 process and display webcam stream at the webcams fps

How can I read a camera and display the images at the cameras frame rate?
I want to continuously read images from my webcam, (do some fast preprocessing) and then display the image in a window. This should run at the frame rate, that my webcam provides (29 fps).
It seems like the OpenCV GUI and Tkinter GUI is too slow, to display images at such a frame rate. These are clearly the bottlenecks in my experiments. Even without the preprocessing, the images are not displayed fast enough. I am on a MacBook Pro 2018.
Here is what I tried. The webcam is always read with OpenCV:
Everything happens in the main thread, the images are displayed with OpenCV: 12 fps
Read camera and do preprocessing in separate threads, show image with OpenCV in the main thread: 20 fps
multithreaded like above, but do not show the image: 29 fps
multithreaded like above, but show the images with Tkinter: don't know the exact fps but it feels like <10 fps.
Here is the code:
Single loop, OpenCV GUI:
import cv2
import time
def main():
cap = cv2.VideoCapture(0)
window_name = "FPS Single Loop"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
start_time = time.time()
frames = 0
seconds_to_measure = 10
while start_time + seconds_to_measure > time.time():
success, img = cap.read()
img = img[:, ::-1] # mirror
time.sleep(0.01) # simulate some processing time
cv2.imshow(window_name, img)
cv2.waitKey(1)
frames = frames + 1
cv2.destroyAllWindows()
print(
f"Captured {frames} in {seconds_to_measure} seconds. FPS: {frames/seconds_to_measure}"
)
if __name__ == "__main__":
main()
Captured 121 in 10 seconds. FPS: 12.1
Multithreaded, opencv gui:
import logging
import time
from queue import Full, Queue
from threading import Thread, Event
import cv2
logger = logging.getLogger("VideoStream")
def setup_webcam_stream(src=0):
cap = cv2.VideoCapture(src)
width, height = (
cap.get(cv2.CAP_PROP_FRAME_WIDTH),
cap.get(cv2.CAP_PROP_FRAME_HEIGHT),
)
logger.info(f"Camera dimensions: {width, height}")
logger.info(f"Camera FPS: {cap.get(cv2.CAP_PROP_FPS)}")
grabbed, frame = cap.read() # Read once to init
if not grabbed:
raise IOError("Cannot read video stream.")
return cap
def video_stream_loop(video_stream: cv2.VideoCapture, queue: Queue, stop_event: Event):
while not stop_event.is_set():
try:
success, img = video_stream.read()
# We need a timeout here to not get stuck when no images are retrieved from the queue
queue.put(img, timeout=1)
except Full:
pass # try again with a newer frame
def processing_loop(input_queue: Queue, output_queue: Queue, stop_event: Event):
while not stop_event.is_set():
try:
img = input_queue.get()
img = img[:, ::-1] # mirror
time.sleep(0.01) # simulate some processing time
# We need a timeout here to not get stuck when no images are retrieved from the queue
output_queue.put(img, timeout=1)
except Full:
pass # try again with a newer frame
def main():
stream = setup_webcam_stream(0)
webcam_queue = Queue()
processed_queue = Queue()
stop_event = Event()
window_name = "FPS Multi Threading"
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
start_time = time.time()
frames = 0
seconds_to_measure = 10
try:
Thread(
target=video_stream_loop, args=[stream, webcam_queue, stop_event]
).start()
Thread(
target=processing_loop, args=[webcam_queue, processed_queue, stop_event]
).start()
while start_time + seconds_to_measure > time.time():
img = processed_queue.get()
cv2.imshow(window_name, img)
cv2.waitKey(1)
frames = frames + 1
finally:
stop_event.set()
cv2.destroyAllWindows()
print(
f"Captured {frames} frames in {seconds_to_measure} seconds. FPS: {frames/seconds_to_measure}"
)
print(f"Webcam queue: {webcam_queue.qsize()}")
print(f"Processed queue: {processed_queue.qsize()}")
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()
INFO:VideoStream:Camera dimensions: (1280.0, 720.0)
INFO:VideoStream:Camera FPS: 29.000049
Captured 209 frames in 10 seconds. FPS: 20.9
Webcam queue: 0
Processed queue: 82
Here you can see that there are images remaining in the second queue where the images get fetched for displaying them.
When I uncomment these two lines:
cv2.imshow(window_name, img)
cv2.waitKey(1)
then the output is:
INFO:VideoStream:Camera dimensions: (1280.0, 720.0)
INFO:VideoStream:Camera FPS: 29.000049
Captured 291 frames in 10 seconds. FPS: 29.1
Webcam queue: 0
Processed queue: 0
So it is able to process all frames at the webcams speed without a GUI displaying them.
Multithreaded, Tkinter gui:
import logging
import time
import tkinter
from queue import Full, Queue, Empty
from threading import Thread, Event
import PIL
from PIL import ImageTk
import cv2
logger = logging.getLogger("VideoStream")
def setup_webcam_stream(src=0):
cap = cv2.VideoCapture(src)
width, height = cap.get(cv2.CAP_PROP_FRAME_WIDTH), cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
logger.info(f"Camera dimensions: {width, height}")
logger.info(f"Camera FPS: {cap.get(cv2.CAP_PROP_FPS)}")
grabbed, frame = cap.read() # Read once to init
if not grabbed:
raise IOError("Cannot read video stream.")
return cap, width, height
def video_stream_loop(video_stream: cv2.VideoCapture, queue: Queue, stop_event: Event):
while not stop_event.is_set():
try:
success, img = video_stream.read()
# We need a timeout here to not get stuck when no images are retrieved from the queue
queue.put(img, timeout=1)
except Full:
pass # try again with a newer frame
def processing_loop(input_queue: Queue, output_queue: Queue, stop_event: Event):
while not stop_event.is_set():
try:
img = input_queue.get()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img[:, ::-1] # mirror
time.sleep(0.01) # simulate some processing time
# We need a timeout here to not get stuck when no images are retrieved from the queue
output_queue.put(img, timeout=1)
except Full:
pass # try again with a newer frame
class App:
def __init__(self, window, window_title, image_queue: Queue, image_dimensions: tuple):
self.window = window
self.window.title(window_title)
self.image_queue = image_queue
# Create a canvas that can fit the above video source size
self.canvas = tkinter.Canvas(window, width=image_dimensions[0], height=image_dimensions[1])
self.canvas.pack()
# After it is called once, the update method will be automatically called every delay milliseconds
self.delay = 1
self.update()
self.window.mainloop()
def update(self):
try:
frame = self.image_queue.get(timeout=0.1) # Timeout to not block this method forever
self.photo = ImageTk.PhotoImage(image=PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
self.window.after(self.delay, self.update)
except Empty:
pass # try again next time
def main():
stream, width, height = setup_webcam_stream(0)
webcam_queue = Queue()
processed_queue = Queue()
stop_event = Event()
window_name = "FPS Multi Threading"
try:
Thread(target=video_stream_loop, args=[stream, webcam_queue, stop_event]).start()
Thread(target=processing_loop, args=[webcam_queue, processed_queue, stop_event]).start()
App(tkinter.Tk(), window_name, processed_queue, (width, height))
finally:
stop_event.set()
print(f"Webcam queue: {webcam_queue.qsize()}")
print(f"Processed queue: {processed_queue.qsize()}")
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
main()
INFO:VideoStream:Camera dimensions: (1280.0, 720.0)
INFO:VideoStream:Camera FPS: 29.000049
Webcam queue: 0
Processed queue: 968
On this answer I share some considerations on camera FPS VS display FPS and some code examples that demonstrates:
The basics on FPS calculation;
How to increase the display FPS from 29 fps to 300+ fps;
How to use threading and queue efficiently to capture at the closest maximum fps supported by the camera;
For anyone going through your issue, here is a couple of important questions that need to be answered first:
What's the size of the images being captured?
How many FPS does your webcam support? (camera FPS)
How fast can you grab a frame from the webcam and display it in a window? (display FPS)
Camera FPS VS Display FPS
The camera fps refers to what the hardware of the camera is capable of. For instance, ffmpeg tells that at 640x480 my camera can return 15 fps minimum and 30 at maximum, among other formats:
ffmpeg -list_devices true -f dshow -i dummy
ffmpeg -f dshow -list_options true -i video="HP HD Camera"
[dshow # 00000220181cc600] vcodec=mjpeg min s=640x480 fps=15 max s=640x480 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=320x180 fps=15 max s=320x180 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=320x240 fps=15 max s=320x240 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=424x240 fps=15 max s=424x240 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=640x360 fps=15 max s=640x360 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=848x480 fps=15 max s=848x480 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=960x540 fps=15 max s=960x540 fps=30
[dshow # 00000220181cc600] vcodec=mjpeg min s=1280x720 fps=15 max s=1280x720 fps=30
The important realization here is that despite being able to capture 30 fps internally, there is NO guarantee that an application will be able to pull those 30 frames from the camera in a second. The reasons behind this are clarified on the following sections.
The display fps refers to how many images can be draw in a window per second. This number is not limited by the camera at all and its usually much much higher than the camera fps. As you'll see later, its possible to create and application that pulls 29 images per second from the camera and draws them more than 300 times a second. That means that the same image from the camera is drawn multiple times in a window before the next frame is pulled from the camera.
How many FPS can my webcam capture?
The following application simply demonstrates how to print the default settings used by the camera (size, fps) and how to retrieve frames from it, display it in a window and compute the amount of FPS being rendered:
import numpy as np
import cv2
import datetime
def main():
# create display window
cv2.namedWindow("webcam", cv2.WINDOW_NORMAL)
# initialize webcam capture object
cap = cv2.VideoCapture(0)
# retrieve properties of the capture object
cap_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
cap_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
cap_fps = cap.get(cv2.CAP_PROP_FPS)
fps_sleep = int(1000 / cap_fps)
print('* Capture width:', cap_width)
print('* Capture height:', cap_height)
print('* Capture FPS:', cap_fps, 'ideal wait time between frames:', fps_sleep, 'ms')
# initialize time and frame count variables
last_time = datetime.datetime.now()
frames = 0
# main loop: retrieves and displays a frame from the camera
while (True):
# blocks until the entire frame is read
success, img = cap.read()
frames += 1
# compute fps: current_time - last_time
delta_time = datetime.datetime.now() - last_time
elapsed_time = delta_time.total_seconds()
cur_fps = np.around(frames / elapsed_time, 1)
# draw FPS text and display image
cv2.putText(img, 'FPS: ' + str(cur_fps), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow("webcam", img)
# wait 1ms for ESC to be pressed
key = cv2.waitKey(1)
if (key == 27):
break
# release resources
cv2.destroyAllWindows()
cap.release()
if __name__ == "__main__":
main()
Output:
* Capture width: 640.0
* Capture height: 480.0
* Capture FPS: 30.0 wait time between frames: 33 ms
As mentioned earlier, my camera is able to capture 640x480 images at 30 fps by default and even though the loop above is pretty simple, my display FPS is lower: I'm only able to retrieve frames and display them at 28 or 29 fps and that's without performing any custom image processing in between. What's going on?
The reality is that even though the loop looks pretty simple, there are things happening under the hood that costs just enough processing time to make it difficult for one iteration of the loop to happen in less than 33ms:
cap.read() executes I/O calls to the camera driver in order to pull the new data. This function blocks execution of your application until the data has been transferred completely;
a numpy array needs to be setup with the new pixels;
other calls are required to display a window and draw the pixels in it, namely cv2.imshow(), which is usually slow operation;
there's also a 1ms delay thanks to cv2.waitKey(1) which is required to keep the window opened;
All of these operations, as small as they are, make it incredibly difficult for an application to call cap.read(), get a new frame and display it at precisely 30 fps.
There's a number of things you can try to speed up the application to be able to display more frames than the camera driver allows and this post covers them well. Just remember this: you won't be able to capture more frames from the camera than what the driver says it supports. You will, however, be able to display more frames.
How to increase the display FPS to 300+? A threading example.
One of the approaches used to increase the amount of images being displayed per second relies on the threading package to create a separate thread to continuously pull frames from the camera. This happens because the main loop of the application is not blocked on cap.read() anymore waiting for it to return a new frame, thus increasing the number of frames that can be displayed (or draw) per second.
Note: this approach renders the same image multiple times on a window until the next image from the camera is retrieved. Keep in mind that it might even draw an image while it's contents are still being updated with new data from the camera.
The following application is just an academic example, not something I recommend as production code, to increase the amount of frames per second that are display in a window:
import numpy as np
import cv2
import datetime
from threading import Thread
# global variables
stop_thread = False # controls thread execution
img = None # stores the image retrieved by the camera
def start_capture_thread(cap):
global img, stop_thread
# continuously read fames from the camera
while True:
_, img = cap.read()
if (stop_thread):
break
def main():
global img, stop_thread
# create display window
cv2.namedWindow("webcam", cv2.WINDOW_NORMAL)
# initialize webcam capture object
cap = cv2.VideoCapture(0)
# retrieve properties of the capture object
cap_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
cap_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
cap_fps = cap.get(cv2.CAP_PROP_FPS)
fps_sleep = int(1000 / cap_fps)
print('* Capture width:', cap_width)
print('* Capture height:', cap_height)
print('* Capture FPS:', cap_fps, 'wait time between frames:', fps_sleep)
# start the capture thread: reads frames from the camera (non-stop) and stores the result in img
t = Thread(target=start_capture_thread, args=(cap,), daemon=True) # a deamon thread is killed when the application exits
t.start()
# initialize time and frame count variables
last_time = datetime.datetime.now()
frames = 0
cur_fps = 0
while (True):
# blocks until the entire frame is read
frames += 1
# measure runtime: current_time - last_time
delta_time = datetime.datetime.now() - last_time
elapsed_time = delta_time.total_seconds()
# compute fps but avoid division by zero
if (elapsed_time != 0):
cur_fps = np.around(frames / elapsed_time, 1)
# TODO: make a copy of the image and process it here if needed
# draw FPS text and display image
if (img is not None):
cv2.putText(img, 'FPS: ' + str(cur_fps), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow("webcam", img)
# wait 1ms for ESC to be pressed
key = cv2.waitKey(1)
if (key == 27):
stop_thread = True
break
# release resources
cv2.destroyAllWindows()
cap.release()
if __name__ == "__main__":
main()
How to capture at the closest maximum fps supported by the camera? A threading and queue example.
The problem of using a queue is that, performance-wise, what you get depends on how many frames per second the application can pull from the camera. If the camera supports 30 fps then that's what your application might get as long as the image processing operations being done are fast. Otherwise, there will be a drop in the number of frames being displayed (per second) and the size of the queue will slowly increase until all your RAM memory runs out. To avoid that problem, make sure to set queueSize with a number that prevents the queue from growing beyond what your OS can handle.
The following code is a naive implementation that creates a dedicated thread to grab frames from the camera and puts them in a queue that is later used by the main loop of the application:
import numpy as np
import cv2
import datetime
import queue
from threading import Thread
# global variables
stop_thread = False # controls thread execution
def start_capture_thread(cap, queue):
global stop_thread
# continuously read fames from the camera
while True:
_, img = cap.read()
queue.put(img)
if (stop_thread):
break
def main():
global stop_thread
# create display window
cv2.namedWindow("webcam", cv2.WINDOW_NORMAL)
# initialize webcam capture object
cap = cv2.VideoCapture(0)
#cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
# retrieve properties of the capture object
cap_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
cap_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
cap_fps = cap.get(cv2.CAP_PROP_FPS)
print('* Capture width:', cap_width)
print('* Capture height:', cap_height)
print('* Capture FPS:', cap_fps)
# create a queue
frames_queue = queue.Queue(maxsize=0)
# start the capture thread: reads frames from the camera (non-stop) and stores the result in img
t = Thread(target=start_capture_thread, args=(cap, frames_queue,), daemon=True) # a deamon thread is killed when the application exits
t.start()
# initialize time and frame count variables
last_time = datetime.datetime.now()
frames = 0
cur_fps = 0
while (True):
if (frames_queue.empty()):
continue
# blocks until the entire frame is read
frames += 1
# measure runtime: current_time - last_time
delta_time = datetime.datetime.now() - last_time
elapsed_time = delta_time.total_seconds()
# compute fps but avoid division by zero
if (elapsed_time != 0):
cur_fps = np.around(frames / elapsed_time, 1)
# retrieve an image from the queue
img = frames_queue.get()
# TODO: process the image here if needed
# draw FPS text and display image
if (img is not None):
cv2.putText(img, 'FPS: ' + str(cur_fps), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)
cv2.imshow("webcam", img)
# wait 1ms for ESC to be pressed
key = cv2.waitKey(1)
if (key == 27):
stop_thread = True
break
# release resources
cv2.destroyAllWindows()
cap.release()
if __name__ == "__main__":
main()
Earlier I said might and here is what I meant: even when I use a dedicated thread to pull frames from the camera and a queue to store them, the displayed fps is still capped to 29.3 when it should have been 30 fps. In this case, I assume that the camera driver or the backend implementation used by VideoCapture can be blamed for the issue. On Windows, the backend used by default is MSMF.
It is possible to force VideoCapture to use a different backend by passing the right arguments on the constructor:
cap = cv2.VideoCapture(0 + cv2.CAP_DSHOW)
My experience with DShow was terrible: the returned CAP_PROP_FPS from the camera was 0 and the displayed FPS got stuck around 14. This is just an example to illustrate how the backend capture driver can interfere negatively with the camera capture.
But that's something you can explore. Maybe using a different backend on your OS can provide better results. Here's a nice high-level overview of the Video I/O module from OpenCV that lists the supported backends:
Update
In one of the comments of this answer, the OP upgraded OpenCV 4.1 to 4.3 on Mac OS and observed a noticeable improvement on FPS rendering. It looks like it was a performance issue related to cv2.imshow().

Use Python to save screen shots in array

How do I use python, mss, and opencv to capture my computer screen and save it as an array of images to form a movie? I am converting to gray-scale so it can be a 3 dimensional array. I would like to store each 2d screen shot in a 3d array for viewing and processing. I am having a hard time constructing an array that saves the sequence of screen shots as well as plays back the sequence of screen shots in cv2.
Thanks a lot
import time
import numpy as np
import cv2
import mss
from PIL import Image
with mss.mss() as sct:
fps_list=[]
matrix_list = []
monitor = {'top':40, 'left':0, 'width':800, 'height':640}
timer = 0
while timer <100:
last_time = time.time()
#get raw pizels from screen and save to numpy array
img = np.array(sct.grab(monitor))
img=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#Save img data as matrix
matrix_list[timer,:,:] = img
#Display Image
cv2.imshow('Normal', img)
fps = 1/ (time.time()-last_time)
fps_list.append(fps)
#press q to quit
timer += 1
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
#calculate fps
fps_list = np.asarray(fps_list)
print(np.average(fps_list))
#playback image movie from screencapture
t=0
while t < 100:
cv.imshow('Playback',img_matrix[t])
t += 1
A clue perhaps, save screenshots into a list and replay them later (you will have to adapt the sleep time):
import time
import cv2
import mss
import numpy
with mss.mss() as sct:
monitor = {'top': 40, 'left': 0, 'width': 800, 'height': 640}
img_matrix = []
for _ in range(100):
# Get raw pizels from screen and save to numpy array
img = numpy.array(sct.grab(monitor))
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Save img data as matrix
img_matrix.append(img)
# Display Image
cv2.imshow('Normal', img)
# Press q to quit
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
# Playback image movie from screencapture
for img in img_matrix:
cv2.imshow('Playback', img)
# Press q to quit
if cv2.waitKey(25) & 0xFF == ord('q'):
cv2.destroyAllWindows()
break
use collections.OrderedDict() to saves the sequence
import collections
....
fps_list= collections.OrderedDict()
...
fps_list[timer] = fps

Categories

Resources