从OpenCV中的VideoCapture读取每第n帧

时间:2014-03-28 05:33:03

标签: c++ opencv video-capture

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

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

任何建议,所以我不必遍历五个帧来获得所需的帧?

10 个答案:

答案 0 :(得分:14)

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

所以,大多数时候你必须在所需的帧之前解码帧,即使你不需要它们。

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

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

答案 1 :(得分:4)

我让它在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变量。有关详细信息,请参阅here。要查找您的版本,您可以执行以下操作:

(major_ver, minor_ver, subminor_ver) = (cv2.__version__).split('.')
major_ver

答案 2 :(得分:2)

我在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语句使它变得更加Python化,但是我不相信CV2库已经为此进行了更新。

答案 3 :(得分: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帧 CATUION :我拥有的12秒样本视频由> 200张图片组成。这将占用大量空间。

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

答案 4 :(得分:1)

如果有人需要根据Ishan Shah的代码捕获每5帧并将其另存为jpg:

def closest_point_on_path(point:QPointF, path:QPainterPath) -> QPointF:
    if path.isEmpty():
        return point

    vec = QVector2D(point)
    poly = path.toFillPolygon()
    minDist = sys.float_info.max

    for k in range(poly.count()):
        p = QVector2D(poly.at(k))
        if k == poly.count() - 1:
            k = -1 
        q = QVector2D(poly.at(k+1))
        v = q - p
        u = v.normalized()
        d = QVector2D.dotProduct(u, vec - p)

        if d < 0.0:
            d = (vec - p).lengthSquared()
            if d < minDist:
                minDist = d
                minVec = p
        elif d*d > v.lengthSquared():
            d = (vec - q).lengthSquared()
            if d < minDist:
                minDist = d
                minVec = q
        else:
            u *= d
            u += p
            d = (vec - u).lengthSquared()
            if d < minDist:
                minDist = d
                minVec = u

    if minDist >= sys.float_info.max:
        return point

    return minVec.toPointF()

答案 5 :(得分:0)

当我为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个帧。

答案 6 :(得分: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()

希望这会有所帮助。

答案 7 :(得分:0)

我使用this回购!

主要思想是:

main.py

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.py

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)

答案 8 :(得分:0)

您应该使用grab功能移至下一帧。并且仅使用retrieve来解码所需的帧。

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

答案 9 :(得分:-1)

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