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().
Related
I want to analyse image from IP cameras (from 2 to 6 cameras) with a Raspberry 3b+.
I'm using python opencv but there is a increasing delay (as the video was in x0.5 so the delay accumulates)
from threading import Thread
import cv2, time
class ThreadedCamera(object):
def __init__(self, src=0):
self.capture = cv2.VideoCapture(src)
self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2)
# FPS = 1/X
# X = desired FPS
self.FPS = 1/30
self.FPS_MS = int(self.FPS * 1000)
# Start frame retrieval thread
self.thread = Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
while True:
if self.capture.isOpened():
(self.status, self.frame) = self.capture.read()
time.sleep(self.FPS)
def show_frame(self):
cv2.imshow('frame', self.frame)
cv2.waitKey(self.FPS_MS)
if __name__ == '__main__':
src = 'rtsp://user:psword#192.168.8.108:554/Streaming/Channels/1401'
threaded_camera = ThreadedCamera(src)
while True:
try:
threaded_camera.show_frame()
except AttributeError:
pass
I try without the FPS logic and the result is the same (I try to reduce FPS and it doesnt work). I don't need 30 FPS but I want minimum 3 FPS.
What can I do ? Is there an good alternative to opencv ? Do I have to use another language
You have to remove the time.sleep after cv2.capture.
I don't know why but it works for me.
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.
I'm trying to capture a video of a duration of X seconds every Y hours with python3.8 and opencv(4.2.0) in Manjaro XFCE. I'd like to get at least 20-30fps with a resolution of 720p or 1080p. I'm using a Logitech c920, but with a good resolution (eg 1920x1080) I'm stack at 5fps. I can set the fps at 30 but then the video is about 6 times shorter (python takes video at ~5fps but records it as it was 30fps). Thus, to improve the fps I should use multithreading, although I can't get to implement in my code any of the examples I've seen. Any idea how to do it?
Here is my code:
import numpy as np
import cv2
import time
#choose resolution and fps:
x=1920
y=1080
fps=30
# The duration in seconds of the video captured (s)
capture_duration = 5
# Lapse of time between videos (s), from beginning to beginning
vids_lapse=10
# Name of videos
data_string= time.strftime("%m-%d-%H-%M-%S")
for i in range (0,2): #Let's say I only want to do this process twice, to simplify
cap = cv2.VideoCapture(2) #my camera is 2, but could be 0
fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
#set resolution and fps
cap.set(3,int(x))
cap.set(4,int(y))
cap.set(cv2.CAP_PROP_FPS, int(fps))
#set name of video
out = cv2.VideoWriter(data_string+"_vid"+str(i).zfill(2)+".avi",fourcc, fps, (x,y))
start_time = time.time()
#start recording for a determined duration
while( int(time.time() - start_time) < capture_duration+1 ):
ret, frame = cap.read()
if ret==True:
#frame = cv2.flip(frame,0)
out.write(frame)
cv2.imshow('frame',frame)
else:
break
cap.release()
out.release()
cv2.destroyAllWindows()
#Here I added time.sleep to wait for the next capture
time.sleep(vids_lapse-capture_duration)
With this code above, my video of 30fps is shorter than expected, as it cannot take more than ~5fps.
I've seen I can use multithread, and I'm able to record a video with 30fps (at least that's what ffprobe says, although I'm not sure it is) with the following code:
from threading import Thread
import cv2, time
#again define resolution and fps
x=1920
y=1080
fps=30
class VideoStreamWidget(object):
def __init__(self, src=2):
self.capture = cv2.VideoCapture(src)
self.capture.set(3,int(x))
self.capture.set(4,int(y))
self.capture.set(cv2.CAP_PROP_FPS, int(fps))
# Start the thread to read frames from the video stream
self.thread = Thread(target=self.update, args=())
self.thread.daemon = True
self.thread.start()
def update(self):
# Read the next frame from the stream in a different thread
while True:
if self.capture.isOpened():
(self.status, self.frame) = self.capture.read()
time.sleep(.03)
def show_frame(self):
out.write(self.frame)
# Display frames in main program
cv2.imshow('frame', self.frame)
key = cv2.waitKey(1)
if key == ord('q'):
self.capture.release()
out.release()
cv2.destroyAllWindows()
exit(1)
if __name__ == '__main__':
fourcc = cv2.VideoWriter_fourcc('M','J','P','G')
out = cv2.VideoWriter("output.avi",fourcc, fps, (x,y))
video_stream_widget = VideoStreamWidget()
while True:
try:
video_stream_widget.show_frame()
except AttributeError:
pass
The problem of this second code is that I don't really know how to introduce my arguments of (recording during 5 seconds, wait 10 seconds, and so on) or how many threads I'm using (I can specify number of buffers from my camera using V4L2 tools, don't know how relevant is it).
So my question is: Any idea how could I do it? Is it possible to merge both codes? Or is there a better way?
Here are some guides I've checked but couldn't get it working with my code:
https://www.pyimagesearch.com/2015/12/21/increasing-webcam-fps-with-python-and-opencv/
https://github.com/gilbertfrancois/video-capture-async
Thanks a lot in advance!
cheers!
EDIT
I finally got something to work. Nevertheless, even if my video is 30fps, it still looks like a 5fps video... Seems like all the pictures captured are almost the same... Any idea how to solve this?
Here the code:
rom threading import Thread, Lock
import cv2, time
#choose resolution
x=1920
y=1080
fps=60
# The duration in seconds of the video captured (s)
capture_duration = 5
# Days
Days=1
# Lapse of time between videos (s), from beginning to beginning
vids_lapse=10
data_string= time.strftime("%m-%d-%H-%M-%S")
class CameraStream(object):
def __init__(self, src=0):
self.stream = cv2.VideoCapture(src)
self.stream.set(3,int(x))
self.stream.set(4,int(y))
self.stream.set(cv2.CAP_PROP_FPS,int(fps))
self.video_file_name = data_string+"_vid"+str(i).zfill(2)+".avi"
(self.grabbed, self.frame) = self.stream.read()
self.started = False
self.read_lock = Lock()
# Set up codec and output video settings
self.codec = cv2.VideoWriter_fourcc('M','J','P','G')
self.out = cv2.VideoWriter(self.video_file_name, self.codec, fps, (x, y))
def start(self):
if self.started:
print("already started!!")
return None
self.started = True
self.thread = Thread(target=self.update, args=())
self.thread.start()
return self
def update(self):
while self.started:
(grabbed, frame) = self.stream.read()
self.read_lock.acquire()
self.grabbed, self.frame = grabbed, frame
self.read_lock.release()
# ~ time.sleep(1/fps)
def read(self):
self.read_lock.acquire()
frame = self.frame.copy()
self.read_lock.release()
self.save_frame()
return frame
def save_frame(self):
# Save obtained frame into video output file
self.out.write(self.frame)
def stop(self):
self.started = False
self.thread.join()
self.stream.release()
self.out.release()
def __exit__(self, exc_type, exc_value, traceback):
self.stream.release()
if __name__ == "__main__" :
for i in range (0,2):
start_time = time.time()
cap = CameraStream(0).start()
while (int(time.time() - start_time) < capture_duration):
frame = cap.read()
cv2.imshow('webcam', frame)
# ~ if cv2.waitKey(1) == 27 :
cap.stop()
cv2.destroyAllWindows()
time.sleep(vids_lapse-capture_duration)
The issue is with FOURCC, you should set cv2.CAP_PROP_FOURCC, try this example:
import cv2
HIGH_VALUE = 10000
WIDTH = HIGH_VALUE
HEIGHT = HIGH_VALUE
capture = cv2.VideoCapture(-1)
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
capture.set(cv2.CAP_PROP_FRAME_WIDTH, WIDTH)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, HEIGHT)
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps = capture.get(cv2.CAP_PROP_FPS)
print(width, height, fps)
while True:
ret, frame = capture.read()
if ret:
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
capture.release()
cv2.destroyAllWindows()
Recently i working on raspberry pi 3(OS raspbian) with OPENCV + PYTHON to detect face on live camera. And I see detected face on raspberry pi3 use opencv very slow, about 4 - 5 FPS/s. So I wonder can I use MultiThread or MultiProcessing to speed up the FPS and if yes, how can i do that?
Please help me, any idea will appreciate. Thanks
Yes, you can multithread the process.
Use the threading library in python 2, or thread in python 3.
Here is a simple example.
Besides your main thread,
Have one thread only operating the camera and constantly updating the latest frame, which is global in this case.
def camera_thread():
cam = cv2.VideoCapture(1)
_ret, self.image = cam.read()
cv2.imshow('camera', self.image)
A second thread can run the inference from the model using the latest frame.
An optional third could perhaps draw a bounding box over the face, or perform other operations etc.
You might need mutex locks in between 2nd and 3rd threads in this case as you can only begin drawing boxes after you obtain an output from the model. This allows your 2nd thread to begin inference of the next frame without waiting for other threads.
The above example will result in a smooth video output with the inference lagging behind slightly. If you're not sure how multithreading works, I suggest reading up on the basics first.
update. I use separate thread to capture image from camera and I don't see any speed up of FPS compare to serial... Please see my code.
import threading
import time
import cv2
import numpy as np
class myThread (threading.Thread):
def __init__(self, src):
print("thread -------------init-------------")
threading.Thread.__init__(self)
self.cap = cv2.VideoCapture(src)
self.stop = False
def run(self):
while(self.stop == False):
self.ret, self.frame = self.cap.read()
def Stop(self):
self.cap.release()
self.stop = True
def read(self):
return self.ret, self.frame
recognizer = cv2.face.LBPHFaceRecognizer_create()
recognizer.read('trainer/trainer.yml')
cascadePath = "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascadePath);
thread = myThread(0)
thread.start()
time.sleep(1)
start = time.time()
frames = 0
font = cv2.FONT_HERSHEY_SIMPLEX
cap = cv2.VideoCapture(0)
while(True):
ret, frame = thread.read()
frame = cv2.resize(frame, (640, 480))
frames += 1
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(gray, 1.2, 5)
for(x,y,w,h) in faces:
# Create rectangle around the face
cv2.rectangle(frame, (x-20,y-20), (x+w+20,y+h+20), (0,255,0), 4)
Id, con = recognizer.predict(gray[y:y+h,x:x+w])
print(Id, con)
# Check the ID if exist
if(con < 60):
if(Id == 1):
Id = "HUY"
if(Id == 2):
Id = "HOA"
#If not exist, then it is Unknown
else:
#print(Id)
Id = "Unknow"
# Put text describe who is in the picture
cv2.rectangle(frame, (x-22,y-90), (x+w+22, y-22), (0,255,0), -1)
cv2.putText(frame, str(Id), (x,y-40), font, 2, (255,255,255), 3)
if cv2.waitKey(10) & 0xFF == ord('q'):
thread.Stop()
break
cv2.imshow("frame", frame)
end = time.time()
second = end - start
print("second:", + second)
print(frames/second)
cv2.destroyAllWindows()
I am new to both OpenCV and Python, and I am trying to create a simple program that will read in a video file named 'SixtyFPS.mov' and replay the video when it has reached its end. Ideally I would like to have the video loop continuously. I have not been able to find a solution for this online, however most of the answers I see involve using cap.set(cv2.CAP_PROP_POS_FRAMES, 1) or something of that nature in order to reset the frames. If anyone can explain to me how to use the cap.set feature in order to restart the video that would be greatly appreciated.
# The video 'SixtyFPS.mov' was recorded on an iPhone 7 at 60 FPS
# The video has a length of roughly 4 seconds long and so the total number
# of frames should be ~240, however my number_of_frames variable is equal to 115
# I am looking for a way to restart the video once it has reached its end but
# I have not yet discovered a good method for doing so. Any advice would be
# greatly appreciated. I am using Python 3.6.4 and OpenCV 3.3.1
# I needed to rotate the video so that it would be viewed in portrait
# orientation rather than landscape
import cv2
import numpy as np
# Load in the video
video_cap = cv2.VideoCapture('SixtyFPS.mov')
# display the total number of frames. Should be ~240 and not 115
number_of_frames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT))
print("The total number of frames is: " + str(number_of_frames))
# Check if video opened successfully
if (video_cap.isOpened() == False):
print("Error opening video file")
frame_counter = 0
# Read until video is completed
while(video_cap.isOpened()):
# Capture frame-by-frame
ret, frame = video_cap.read()
if ret == True:
frame_counter += 1
# resize the window
resized_vid = cv2.resize(frame, (720, 1280))
# Convert the video to grayscale
grayscale_vid = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Change the orientation from landscape to portrait by rotating
(h, w) = grayscale_vid.shape[:2]
center = (w / 2, h / 2)
M = cv2.getRotationMatrix2D(center, 270, 1.0)
rotated_video = cv2.warpAffine(grayscale_vid, M, (w, h))
# Display the rotated video
cv2.imshow('Pitch', rotated_video)
# trying to restart the video when the frame_counter
# reaches its maximum value of 110
if frame_counter >= 110:
# here is where I think I need to restart the video
# by setting the current frame to zero
# Press Q on keyboard to exit
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Break the loop
else:
break
# When everything done, release the video capture object
video_cap.release()
# Closes all the frames
cv2.destroyAllWindows()
It's been a while since this question has been posted, but just in case someone stumbled upon this answer, this code snippet with cap.set() worked for me. I relied on the documentation that can be found here (cpp) and here (python).
import cv2
# Get video handle.
cap = cv2.VideoCapture("path/to/video")
if not cap.isOpened():
print("Cannot initialize cap.")
exit()
# Get length of the video.
video_length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# Initialize count.
count = 0
# Frames loop.
while True:
# Check length of the video.
if count == video_length:
# Reset to the first frame. Returns bool.
_ = cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
count = 0
# Get the frame.
success, image = cap.read()
if not success:
print("Cannot read frame.")
break
# do something with the image.
cv2.imshow("Frame", image)
# Quit by pressing 'q'.
if cv2.waitKey(1) & 0xFF == ord('q'):
break
count += 1
# Post loop.
cap.release()
cv2.destroyAllWindows()
I used the above to go some n number of frames back by combining cap.get() and cap.set().
This is a general way how the video can be resized to 480 x 800 (height x width) and made to loop.
import cv2
# Change this path to your video location
path = "path_to_your_video"
cap = cv2.VideoCapture(path)
while True:
ret, img = cap.read()
if not ret:
cap = cv2.VideoCapture(path)
ret, img = cap.read()
img = cv2.resize(img, (800, 480))
cv2.imshow("Video", img)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()