3

I'm trying to grab specific frames (e.g. frame 0, 10, 20, ...) within a video and save them as images using Python and CV2. For some reasons, my code only saves the first frame. All other frames are created, but with size 0 (they are corrupt).

How can I fix the problem?

import cv2
from numpy import integer

number = 10;
filename = "18s.mp4";

def uniform():
    cap = cv2.VideoCapture(filename);
    frame_count= int(cap.get(cv2.CAP_PROP_FRAME_COUNT));
    print(frame_count)

    for x in range(0, number):
            frame_no = 1*(x/number)
            frame_no_int=int(frame_no*frame_count)

            cap.set(2,frame_no);
            ret, frame = cap.read()
            cv2.imwrite(filename+'_frame_'+str(frame_no_int)+'.jpg', frame);

    # When everything done, release the capture
    cap.release()

if __name__ == '__main__':
    uniform()
Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
Tina J
  • 4,983
  • 13
  • 59
  • 125

1 Answers1

2

Apparently, CAP_PROP_POS_AVI_RATIO (constant 2, which you were using in cap.set()) doesn't work well. Take a look at the output of your modified script:

import cv2
from numpy import integer

number = 10
filename = 'chaplin.mp4'

def uniform():
    cap = cv2.VideoCapture(filename)
    frame_count= int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    print(frame_count)

    for x in range(0, number):
        frame_pos_ratio = 1.0*x/number
        frame_no_int=int(frame_pos_ratio*frame_count)
        cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no_int)
        print (frame_no_int, cap.get(cv2.CAP_PROP_POS_AVI_RATIO))
        ret, frame = cap.read()
        cv2.imwrite('_frame_'+str(frame_no_int)+'.jpg', frame)

    # Attempt to go the end of film
    cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 1)
    print (cap.get(cv2.CAP_PROP_POS_FRAMES))

    # When everything done, release the capture
    cap.release()

if __name__ == '__main__':
    uniform()

Output:

172
(0, 6.510416666666667e-05)
(17, 6.510416666666667e-05)
(34, 6.510416666666667e-05)
(51, 6.510416666666667e-05)
(68, 6.510416666666667e-05)
(86, 6.510416666666667e-05)
(103, 6.510416666666667e-05)
(120, 6.510416666666667e-05)
(137, 6.510416666666667e-05)
(154, 6.510416666666667e-05)
150.0

As you can see, cap.get(cv2.CAP_PROP_POS_AVI_RATIO) inside the cycle just returns a constant 6.51e-05.

And even though there are 174 frames, cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 1) takes you only to frame 150, which is definitely a bug.

This behavior is in line with this question.

P.S. Interestingly, even cv2.CAP_PROP_FRAME_COUNT doesn't work properly. Apparently, my video file contained only 150 frames, but they were numbered from 22 to 171, as evidenced by ffprobe -show_frames chaplin.mp4 | grep coded_picture_number. So the output of CAP_PROP_FRAME_COUNT is just max(frame_no)+1.

Andriy Makukha
  • 7,580
  • 1
  • 38
  • 49
  • I see! I was guessing so. I used `cap.set(1,frame_no_int)` instead and this one works. Weird behavior. – Tina J Aug 02 '18 at 17:57
  • Do you happen to know if/how we can get the list of key-frames (I-frame) number in python? – Tina J Aug 02 '18 at 17:58
  • 1
    @TinaJ, see function `get_frame_types` in my answer here: https://stackoverflow.com/questions/42798634/extracting-keyframes-python-opencv/51666059#51666059 - this solution uses a call to ffprobe to identify frame types. – Andriy Makukha Aug 03 '18 at 05:53
  • Thanks. It looks to be helpful! – Tina J Aug 03 '18 at 14:35