0
votes

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)
1
Do you get good performance with a tool like guvcview? Also, what's your machine like?Warpstar22
I didn't try guvcview, but I'm sure I can get a good performance with this cam. I have a dell XPS13.sac

1 Answers

1
votes

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