Search code examples
pythonopencvraspberry-pipicamera

Why is the resulting video half the time long, when doubling the fps from 30 to 60 in openCV and python?


my overall goal is to capture a video with the Raspberry Pi HQ Camera. When using the terminal and the standard raspivid command like raspivid -w 640 -h 480 -fps 90 -t 10000 -o video.h264 the resulting video didn't store the right infomation regarding fps (and frame_count). I checked it using openCV and python with the following code:

import cv2

cap = cv2.VideoCapture('video.h264')

if cap.isOpened():

    fps = cap.get(cv2.CAP_PROP_FPS)

    print('fps:', fps)  # float `fps`

    frame_count = cap.get(cv2.CAP_PROP_FRAME_COUNT)

    print('frames count:', frame_count)  # float `frame_count`

I expected it to be fps: 90.0 and frame_count: 900.0, but it was fps: 25.0 and frame_count: -192153584101141.0.

After some research I came to the conclusion, that .h264 doesn't store the right fps value but a default value (in my case 25.0 fps).

Now my next attempt was to use openCV and python to capture video with the HQ camera. I tried to do so with the following code (which is pretty much standart when trying to capture a video with openCV):

import numpy as np
import cv2

#had to use (-1,cv2.CAP_V4L) instead of (0) for it to find my HQ Camera
cap = cv2.VideoCapture(-1,cv2.CAP_V4L)

fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('video_out.avi', fourcc, 30.0, (640, 480))

while(cap.isOpened()):
    ret, frame = cap.read()
    if ret==True:
        out.write(frame)
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    else:
        break
cap.release()
out.release()
cv2.destroyAllWindows()

Now when recording roughly a 10 sec. long video and and analyzing it afterwards using the same method as before, the result was as expected fps: 30.0 and frame_count: (roughly) 300.0.

Now when changing the fps from 30 to 60 in line #8 from out = cv2.VideoWriter('video_out.avi', fourcc, 30.0, (640, 480)) to out = cv2.VideoWriter('video_out.avi', fourcc, 60.0, (640, 480)) and analyzing a roughly 10 sec. long video afterwards the result was different as expected. Instead of fps: 60.0 and frame_count: (roughly) 600.0 the result was fps: 60.0 and frame_count: (roughly) 300.0. Also the video was now only 5 sec. long instead of 10 sec.

I came to the conclusion, that the video was still recorded at 30 fps and with 10 sec. of recording the total number of 300 frames is correct. But in the video_out.avi file the fps was set to 60 fps, what resulted in a (60 fps) video which is only half the time long, because there are still only 300 frames in total available.

Now what still remains is the question:

In which way do I change the above code, to be able to capture a 10 sec. long video with 60 fps and get 600 frames in total?

EDIT #1

@Tiphel stated in his comment that adding cap.set(cv2.CAP_PROP_FPS, 10) could solve the problem; and it seemed to work. I now tried to edit the code in a way, that it records a previous defined time (e.g. 5 sec.) and then stops. By doing this I could verify, if a 60 fps video with 5 sec. of recording would indeed have 300 frames. This attempt resulted in following code:

import numpy as np
import cv2
import time

f=open("fps_and_time_passed.txt", "w")

cap = cv2.VideoCapture(-1,cv2.CAP_V4L)
cap.set(cv2.CAP_PROP_FPS, 30)

fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('vid_out.avi', fourcc, 30.0, (640, 480))

capture_time=5.0
start_time=time.time()

while(time.time() - start_time < capture_time):
    
    #print time passed in console
    #print('time passed: ' + str(time.time()-start_time))
    
    #write time passed in .txt file
    f.write('time passed: ' + str(time.time()-start_time) + ' ')
    
    fps=cap.get(cv2.CAP_PROP_FPS)
    
    #print fps in console
    #print('fps: ',fps)
    
    #write fps in .txt file
    f.write('fps: ' + str(fps) + '\n')
    ret, frame = cap.read()
    if ret==True:
        out.write(frame)
        cv2.imshow('frame', frame)       
        #if cv2.waitKey(1) & 0xFF == ord('q'):
        #    break
    else:
        break
f.close()
cap.release()
out.release()
cv2.destroyAllWindows()

After analyzing the now resulting "vid_out.avi" file I expected it to be fps: 30.0 and frame_count 150.0 but it was fps: 30.0 and frame_count: 115.0. I then looked into the "fps_and_time_passed.txt" file and to my surprise realized, that there is a roughly 1,4 sec. delay in the beginning:

time passed: 1.1682510375976562e-05 fps: 30.0
time passed: 1.4038400650024414 fps: 30.0
time passed: 1.4290287494659424 fps: 30.0
time passed: 1.4499273300170898 fps: 30.0
...
time passed: 4.89248514175415 fps: 30.0
time passed: 4.925801515579224 fps: 30.0
time passed: 4.9621946811676025 fps: 30.0
time passed: 4.995436191558838 fps: 30.0

As you can see the delay is between the first and the second output.

So my new question is:

How can I get rid of this delay?


Solution

  • I think it is because you start your timer before the first frame is captured so you end up short of your desired duration.

    I believe if you count the frames, based on the video duration you want, you will have the desired output.

    Try something like :

    fps = 30
    capture_time = 5.0
    n_frames = capture_time * fps
    curr_frame = 0
    
    while (curr_frame < n_frames):
        ret, frame = cap.read()
        if ret==True:
            out.write(frame)
            cv2.imshow('frame', frame)       
        else:
            break
    
        fps=cap.get(cv2.CAP_PROP_FPS)  
        #write fps in .txt file
        f.write('fps: ' + str(fps) + '\n')
    
        curr_frame+=1
    

    I haven't tested this, it might not work but that's the idea.