从OpenCV中的VideoCapture读取每第n帧

问题描述 投票:13回答:8

是否可以逐步读取视频中的帧(例如,我想读取视频流的每五帧)。目前我这样做是一种解决方法,但它不是很有效。

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

任何建议,所以我不必循环通过五个帧来获得所需的帧?

c++ opencv video-capture
8个回答
17
投票

我担心你无能为力,这不仅仅是OpenCV的缺点。你看,现代视频编解码器通常是复杂的野兽。为了获得更高的压缩率,帧的编码通常取决于先前的帧,有时甚至是连续的帧。

因此,大多数情况下,即使您不需要帧,也必须在所需帧之前解码帧。

有一些非常重要的技巧来专门编码视频文件,因此获得每个第N帧都很便宜,但在一般情况下这是不可行的。

也就是说,您可以尝试OpenCV提供的搜索功能(请参阅OpenCV Seek Function/Rewind)。它可能(也可能不会)根据具体情况更快地工作。但是,就个人而言,我不会赌它。


6
投票

我让它在Python中工作...请参阅下面的两个示例用例和一些注意事项。

First, import some packages

import cv2
import math
import numpy as np

Capture every n seconds (here, 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"

Alternatively, capture every n frames (here, 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"

这就是它。


Some unfortunante caveats...depending on your version of opencv (this is built for opencv V3), you may need to set the fps variable differently. See here for details. To find out your version, you can do the following:
(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')
major_ver

6
投票

我在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语句使这更加pythonic但我不相信CV2库已更新它。


1
投票

这是我的建议:

     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帧。 教学:我有12秒的样本视频由> 200张图像组成。这将占用大量空间。

一个简单而有效的优化是使用您正在使用的方法或@sergie建议的方法读取第n帧。在此之后,您可以使用其索引保存图像,以便稍后在相同索引处查询将返回保存的图像,而不必像您一样跳过帧。这样,您将节省浪费的空间,以节省您不会查询的帧,以及读取和保存这些不需要的帧的时间。


1
投票

当我与OpenCV有相同的目标时,我只调整了每秒视频所需的“关键帧”数量,无论帧速率或帧总数如何。所以,这给了我目标KPS的第N个密钥。

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

请注意,我正在浏览所有帧,但只使用hop作为N来编写第N帧。


0
投票

由于编码方案通常非常复杂,因此无法提取随机帧。例如,在MPEG-4中,仅存储包含两帧之间差异的信息,因此显然需要先前的帧。


0
投票

我遇到了同样的问题。我所做的就是:

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

希望这可以帮助。


0
投票

我用this回购!

主要想法是:

卖弄.朋友

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)

camera.朋友

import cv2
import logging


class VideoCam():
    def __init__(self, url=0):
        self.url = url
        self.cap = cv2.VideoCapture(self.url)
        self.get_frame()
        self.get_frame_read()
        logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)

    def check_camera(self, cap):
        logging.info('Camera {} status: {}'.format(self.url, cap.isOpened()))

    def show_frame(self, frame, name_fr='NAME'):
        cv2.imshow(name_fr, frame)
        # cv2.imshow(name_fr, cv2.resize(frame, (0, 0), fx=0.4, fy=0.4))
        cv2.waitKey(1)


    def get_frame(self):
        return self.cap.retrieve()

    def get_frame_read(self):
        return self.cap.read()

    def close_cam(self):
        self.cap.release()
        cv2.destroyAllWindows()

    def restart_capture(self, cap):
        cap.release()
        self.cap = cv2.VideoCapture(self.url)
© www.soinside.com 2019 - 2024. All rights reserved.