Чтение каждого n-го кадра из VideoCapture в OpenCV

можно ли читать кадры из видео шагами (например, я хочу читать каждый пятый кадр видеопотока). В настоящее время я делаю это как обходной путь, но это не очень эффективно.

bool bSuccess
int FramesSkipped = 5;
 for (int a = 0;  < FramesSkipped; a++)
      bSuccess = cap.read(NextFrameBGR);

любые предложения, поэтому мне не нужно перебирать пять кадров, чтобы получить желаемый кадр?

4 ответов


боюсь, вы мало что можете сделать, и это не просто недостаток OpenCV. Видите ли, современные видеокодеки-это, как правило, сложные животные. Чтобы получить более высокую скорость сжатия, кодирование кадра часто зависит от предыдущих, а иногда даже последовательных кадров.

таким образом, большую часть времени вам нужно декодировать кадры до желаемого, даже если они вам не нужны.

существуют довольно нетривиальные трюки для конкретного кодирования видеофайла, чтобы он было бы дешево получить каждый N-й кадр, но это невозможно в общем случае.

тем не менее, вы можете попробовать функцию поиска OpenCV (см. Функция Поиска OpenCV / Перемотка). Он может (а также не может) работать быстрее в зависимости от обстоятельств. Однако лично я бы на это не поставил.


Я получил его для работы в Python... См. ниже два примера использования и некоторые предостережения.

сначала импортируйте некоторые пакеты

import cv2
import math
import numpy as np

захват каждые n секунд (здесь N = 5)

#################### Setting up the file ################
videoFile = "Jumanji.mp4"
vidcap = cv2.VideoCapture(videoFile)
success,image = vidcap.read()

#################### Setting up parameters ################

seconds = 5
fps = vidcap.get(cv2.CAP_PROP_FPS) # Gets the frames per second
multiplier = fps * seconds

#################### Initiate Process ################

while success:
    frameId = int(round(vidcap.get(1))) #current frame number, rounded b/c sometimes you get frame intervals which aren't integers...this adds a little imprecision but is likely good enough
    success, image = vidcap.read()

    if frameId % multiplier == 0:
        cv2.imwrite("FolderSeconds/frame%d.jpg" % frameId, image)

vidcap.release()
print "Complete"

альтернативно, захватите каждые N кадров (здесь, n = 10)

#################### Setting up the file ################
videoFile = "Jumanji.mp4"
vidcap = cv2.VideoCapture(videoFile)
success,image = vidcap.read()

#################### Setting up parameters ################

#OpenCV is notorious for not being able to good to 
# predict how many frames are in a video. The point here is just to 
# populate the "desired_frames" list for all the individual frames
# you'd like to capture. 

fps = vidcap.get(cv2.CAP_PROP_FPS)
est_video_length_minutes = 3         # Round up if not sure.
est_tot_frames = est_video_length_minutes * 60 * fps  # Sets an upper bound # of frames in video clip

n = 5                             # Desired interval of frames to include
desired_frames = n * np.arange(est_tot_frames) 


#################### Initiate Process ################

for i in desired_frames:
    vidcap.set(1,i-1)                      
    success,image = vidcap.read(1)         # image is an array of array of [R,G,B] values
    frameId = vidcap.get(1)                # The 0th frame is often a throw-away
    cv2.imwrite("FolderFrames/frame%d.jpg" % frameId, image)

vidcap.release()
print "Complete"

это в значительной степени.


Некоторые unfortunante оговорками...в зависимости от вашей версии opencv (это построено для opencv V3), вам может потребоваться установить fps переменная по-разному. См.здесь для сведения. Чтобы узнать свою версию, вы можете сделать следующее:
(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')
major_ver

вот что я предлагаю:

     CvCapture* capture = cvCaptureFromFile("input_video_path");
     int loop = 0;
     IplImage* frame = NULL;
     Mat matframe;                                                                   
     char fname[20];                                                                 
     do {
        frame = cvQueryFrame(capture);  
        matframe = cv::cvarrToMat(frame);
        cvNamedWindow("video_frame", CV_WINDOW_AUTOSIZE);                                 
        cvShowImage("video_frame", frame);
        sprintf(fname, "frame%d.jpg", loop);
        cv::imwrite(fname, matframe);//save each frame locally
        loop++;
        cvWaitKey(100);
     } while( frame != NULL );

теперь, когда вы сохранили все кадры локально, вы можете быстро прочитать N-й кадр, который вы хотите.
CATUION:образец видео 12 секунд, который у меня был, состоял из > 200 изображений. Это съест много места.

простая, но эффективная оптимизация будет заключаться в чтении n-го кадра с использованием подхода, который вы используете, или подхода, предложенного @sergie. После этого вы можете сохранить изображение с индексом, так что более поздний запрос с тем же индексом вернет сохраненное изображение, а не пропускать кадры, как вы. Таким образом, вы сэкономите место, которое вы потратили бы впустую на сохранение кадров, которые вы бы не запросили, и время, необходимое для чтения и сохранения этих нежелательных кадров.


у меня был успех в Python 3, используя простой счетчик и установив захват в кадр этого счетчика, следующим образом:

import cv2

cap = cv2.VideoCapture('XYZ.avi')
count = 0

while cap.isOpened():
    ret, frame = cap.read()

    if ret:
        cv2.imwrite('frame{:d}.jpg'.format(count), frame)
        count += 30 # i.e. at 30 fps, this advances one second
        cap.set(1, count)
    else:
        cap.release()
        break

Я пытался найти способ, чтобы сделать это немного более подходящие для Python с помощью with оператор, но я не верю, что библиотека CV2 была обновлена для него.