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

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

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

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

Ответ 1

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

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

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

Тем не менее, вы можете попробовать искать функции OpenCV (см. OpenCV Seek Function/Rewind). Он может (а также не может) работать быстрее в зависимости от обстоятельств. Тем не менее, лично я бы не стал делать ставку на него.

Ответ 2

Я получил его для работы на 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"

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


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

Ответ 3

Я добился успеха в 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

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

Ответ 4

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

     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. После этого вы можете сохранить изображение с его индексом, чтобы последующий запрос в том же индексе возвращал сохраненное изображение, а не пропускал такие кадры, как вы. Таким образом вы сохраните пространство, которое вы потратили бы впустую на сохранение кадров, которые вы бы не задали, и время, затраченное на чтение и сохранение этих нежелательных кадров.

Ответ 5

Когда у меня была та же цель с OpenCV, я просто настраивал количество "ключевых кадров", которое я хотел в секунду видео, независимо от частоты кадров или общего количества кадров. Итак, это дает мне N-й ключ, учитывая мою целевую KPS.

# python3.6 code using OpenCV 3.4.2

import cv2
KPS = 5 # Target Keyframes Per Second
VIDEO_PATH = "path/to/video/folder" # Change this
IMAGE_PATH = "path/to/image/folder" # ...and this 
EXTENSION = ".png"

cap = cv2.VideoCapture(VIDEO_PATH)
    # Set frames-per-second for capture
    fps = round(cap.get(cv2.CAP_PROP_FPS))
    hop = round(fps / KPS)
    curr_frame = 0
    while(True):
        ret, frame = cap.read()
        if not ret: break
        if curr_frame % hop == 0:
            print('Creating... {0}'.format(name,))
            name = IMAGE_PATH + "_" + str(curr_frame) + EXTENSION
            cv2.imwrite(name, frame)
        curr_frame += 1
    cap.release()

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

Ответ 6

Я столкнулся с той же проблемой. Все, что я сделал, было это:

import cv2

vs = cv2.VideoCapture("<path of your video>.mp4")

print("Showing frames...")
c=1
while True:

    grabbed, frame = vs.read()
    if c%5==0:
        cv2.imshow('Frame',frame)
        cv2.waitKey(1)
    c+=1

vs.release()

Надеюсь это поможет.

Ответ 7

Я использую этот репо!

Основная идея:

from camera import VideoCam
SKIPFRAME = 8
url = 0
v1 = VideoCam(url)
v1.check_camera(v1.cap)
ct = 0
while True:
    ct += 1
    try:
        ret = v1.cap.grab()
        if ct % SKIPFRAME == 0:  # skip some frames
            ret, frame = v1.get_frame()
            if not ret:
                v1.restart_capture(v1.cap)
                v1.check_camera(v1.cap)
                continue
            # frame HERE
            v1.show_frame(frame, 'frame')
    except KeyboardInterrupt:
        v1.close_cam()
        exit(0)

Ответ 8

Невозможно извлечь случайные кадры, поскольку схема кодирования, как правило, чрезвычайно сложна. Например, в MPEG-4, сохраняется только информация, содержащая разницу между двумя кадрами. Следовательно, очевидно, что требуются предыдущие кадры.