How to capture video in python with opencv and multithread (logitech c920) - python

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()

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()

Multiple rtsp ip camera stream on raspberry using python opencv lagging and increasing delay

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.

Parallel Processing in merging two camera streams

I need some help in speeding up the merging process from two camera sources. The merge_fn is like kind of a panorama generation function and it takes a very minimal time. The frame rate is reduced because I am reading the camera sources one by one and I need to speed up that process (like separate threads?).
how to parallelize that?
my code:
import numpy as np
import cv2
leftStream = cv2.VideoCapture(0)
rightStream = cv2.VideoCapture(1)
def merge_fn(left, right):
pass
while True:
_, left = leftStream.read()
_, right = rightStream.read()
merge = merge_fn(left, right)
cv2.imshow("Merge", merge)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Release the windows and the cameras
leftStream.release()
rightStream.release()
cv2.destroyAllWindows()
Using multiprocessing
from multiprocessing import Process, Pipe
import cv2
import numpy as np
def start_camera_1(chi_c, camera):
cam = cv2.VideoCapture(camera)
while True:
ret, frame = cam.read()
if ret:
chi_c.send(frame)
def start_camera_2(chi_c, camera):
cam = cv2.VideoCapture(camera)
while True:
ret, frame = cam.read()
if ret:
chi_c.send(frame)
def start_stream():
par_c_1, chi_c_1 = Pipe()
par_c_2, chi_c_2 = Pipe()
process = Process(target=start_camera_1,
args=(chi_c_1, 0))
process.start()
process = Process(target=start_camera_2,
args=(chi_c_2, 1))
process.start()
while True:
result_1 = par_c_1.recv()
result_2 = par_c_2.recv()
frame = np.hstack((result_1, result_2))
frame = cv2.resize(frame, (1600, 900)) # resize according to your need
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
if __name__ == '__main__':
start_stream()
Here's a threaded version that acquires from two separate cameras in two separate threads and then stacks the images side-by-side:
#!/usr/bin/env python3
import cv2
import time
import threading
import numpy as np
class VideoStream():
"""
Class that acquires video frames continuously, as fast as possible on a
separate thread and makes them available to caller, via 'latestFrame()'
method.
"""
def __init__(self, width=1920, height=1080, stream=0):
self.width = width
self.height = height
self.stream = stream
self.frame = None
self.thread = threading.Thread(target=self.acquireFrames, args=())
self.thread.daemon = True
self.thread.start()
def acquireFrames(self):
cap = cv2.VideoCapture(self.stream)
if cap.isOpened():
cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
while True:
ret, self.frame = cap.read()
if not ret:
print(f'ERROR: Stream: {stream}, error reading frame')
def latestFrame(self):
if not self.frame is None:
return self.frame
# Return blank frame if none available
return np.zeros((self.height, self.width, 3), dtype=np.uint8)
def main():
# Start streaming from two cameras
Stream0 = VideoStream(stream=0)
Stream1 = VideoStream(stream=1)
frames = 0
t0 = time.time()
while True:
# Grab latest frame from each camera
frame0 = Stream0.latestFrame()
frame1 = Stream1.latestFrame()
# Stack frames side-by-side
combined = np.hstack((frame0, frame1))
cv2.imshow('Streams', combined)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Show frame rate in case anyone is interested
frames += 1
if frames % 100 == 0:
elapsed = time.time() - t0
print(f'FPS: {int(frames/elapsed)}')
cv2.destroyAllWindows()
if __name__ == '__main__':
main()

How to get specific frame every n seconds during streaming?

I am a newbie in Image processing area and in fact, I got into trouble with a small code related to Classification. During streaming, I just want to get a specific frame in every 3 seconds and then, I use that frame with a classification algorithm. Here is my current coding with a thread: (it got total frames and I do not want to take them all)
class ThreadCapture():
def __init__(self, knn):
self.frame = []
self.status = False
self.isStop = False
self.knn = knn
self.cap = cv2.VideoCapture(0)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 480)
def start(self):
threading.Thread(target=self.current_frame, daemon=True, args=()).start()
def stop(self):
self.isStop = True
def get_frame(self):
return self.status, self.frame
def current_frame(self):
while(not self.isStop):
self.status, self.frame = self.cap.read()
self.crop_frame()
self.cap.release()
def run_knn(self):
img_resize = cv2.resize(self.frame, (224, 224))
img = cv2.cvtColor(img_resize, cv2.COLOR_BGR2RGB)
img_pil = Image.fromarray(img)
return self.knn.classify(img_pil)
def main(arg):
.....
stream = ThreadCapture(EagleEyes)
stream.start()
time.sleep(1)
while(True):
status, frame = stream.get_frame()
if status:
info, res = stream.run_knn()
print(info)
cv2.putText(frame, info, (10,40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 1, cv2.LINE_AA)
cv2.imshow('abc', frame)
if cv2.waitKey(1)==ord('q'):
break
stream.stop()
cv2.destroyAllWindows()
I am not good at coding so I would like to know if anyone could support me to improve above codings.
Thank you all.
The simplest way may be to continue along the lines you have above but only take any action on frames at an interval.
For example, if you want to do something with a frame every 10 seconds and your video frame rate is 25fps, then you can count until you reach 250 frames and then do your work on that frame.
.....
stream = ThreadCapture(EagleEyes)
stream.start()
time.sleep(1)
frameCounter = 0
while(True):
status, frame = stream.get_frame()
//Increment the frame counter
frameCounter += 1
if frameCounter == 250
//Do whatever work you want on the frame here
//Reset the counter
frameCounter = 0
// Add some condition to break out of the loop,
// e.g. end of file or status check.
cap.release()
cv2.destroyAllWindows()

Can't convert video to grayscale

I'm trying to convert a video from my camera feed, which has low fps to gray. I have successfully fetched the video now I want to convert it to grayscale.
I've tried basic opencv operations and it isn't working. I get a video file when I open it there is no video.
import cv2
import time
cap = cv2.VideoCapture('output.avi')
fourcc = cv2.VideoWriter_fourcc(*'XVID')
print(fourcc)
out = cv2.VideoWriter('grey.avi',fourcc, 30.0, (800,600))
while True:
ret, frame = cap.read()
time.sleep(0.1)
cv2.imshow('frame1',frame)
frame = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
out.write(frame)
cv2.imwrite('img.jpg',frame)
cv2.imshow('frame',frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
out.release()
cv2.destroyAllWindows()
You need to change the isColor flag in cv2.VideoWriter. Currently the video writer setting is set to color instead of gray scale. You're incorrectly attempting to save a 3-channel color image (OpenCV default is BGR) as a gray scale image.
Change
out = cv2.VideoWriter('grey.avi',fourcc, 30.0, (800,600))
to
out = cv2.VideoWriter('grey.avi',fourcc, 30.0, (800,600), isColor=False)
Also your overall goal seems to capture video from a stream/camera feed and save the captured video in gray scale format. Here's an 'all in one' widget that reads frames from a camera stream link (RTSP), converts each frame to gray scale, and saves it as a video. Change video_src to your camera stream link.
from threading import Thread
import cv2
class VideoToGrayscaleWidget(object):
def __init__(self, src=0):
# Create a VideoCapture object
self.capture = cv2.VideoCapture(src)
# Default resolutions of the frame are obtained (system dependent)
self.frame_width = int(self.capture.get(3))
self.frame_height = int(self.capture.get(4))
# Set up codec and output video settings
self.codec = cv2.VideoWriter_fourcc('X','V','I','D')
self.output_video = cv2.VideoWriter('output.avi', self.codec, 30, (self.frame_width, self.frame_height), isColor=False)
# 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()
def show_frame(self):
# Convert to grayscale and display frames
if self.status:
self.gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('grayscale frame', self.gray)
# Press 'q' on keyboard to stop recording
key = cv2.waitKey(1)
if key == ord('q'):
self.capture.release()
self.output_video.release()
cv2.destroyAllWindows()
exit(1)
def save_frame(self):
# Save grayscale frame into video output file
self.output_video.write(self.gray)
if __name__ == '__main__':
video_src = 'Your video stream link!'
video_stream_widget = VideoToGrayscaleWidget(video_src)
while True:
try:
video_stream_widget.show_frame()
video_stream_widget.save_frame()
except AttributeError:
pass

Categories

Resources