从 OpenCV 中的 VideoCapture 中读取每第 n 帧

Posted

技术标签:

【中文标题】从 OpenCV 中的 VideoCapture 中读取每第 n 帧【英文标题】:Reading every nth frame from VideoCapture in OpenCV 【发布时间】:2014-05-07 10:46:21 【问题描述】:

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

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

有什么建议让我不必遍历五个帧来获得所需的帧吗?

【问题讨论】:

【参考方案1】:

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

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

有相当重要的技巧可以专门对视频文件进行编码,因此每 N 帧获取一次会很便宜,但在一般情况下这是不可行的。

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

【讨论】:

我不是专家,所以可能是非常错误的,但是是否可以在需要的帧之前跳过中间 P 和 B 帧直到最后一个 I 帧? 可能,它对上述搜索功能做了类似的事情。但我不认为 OpenCV 中有一个 API 可以在编解码器级别进行这种细粒度的操作。 谢谢。我最终选择使用 ffmpeg 提取关键帧。发现从opencv中提取出来的帧没有一个像ffmpeg那么好。【参考方案2】:

我在 Python 3 中成功使用了一个简单的计数器并将捕获设置为该计数器的帧,如下所示:

import cv2

cap = cv2.VideoCapture('XYZ.avi')
# For streams:
#   cap = cv2.VideoCapture('rtsp://url.to.stream/media.amqp')
# Or e.g. most common ID for webcams:
#   cap = cv2.VideoCapture(0)
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(cv2.CAP_PROP_POS_FRAMES, count)
    else:
        cap.release()
        break

我试图找到一种方法,使用 with 语句使它更 Pythonic,但我不相信 CV2 库已为此更新。

【讨论】:

使用cap.set(cv2.CAP_PROP_POS_FRAMES, count)而不是cap.set(1, count)会更清楚 请随时贡献编辑,@chanban。诚然,我是作为一个初学者来到这里的,为了我自己的目的试图弄清楚这一点。 非常感谢@benJephunneh,您的回答对我帮助很大!我刚刚添加了推荐的属性值和一些示例值,因为我认为 OP 可能涉及帧同步问题,因此知道这不仅适用于媒体文件,而且适用于真实的相机设置,这可能是有价值的,其中如果图像处理需要太多时间来处理所有帧,则需要同步。【参考方案3】:

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

# First, import some packages

import cv2
import math
import numpy as np

# Make sure that the print function works on Python 2 and 3
from future import print_function

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

差不多了。


一些不幸的警告...根据您的 `opencv` 版本(这是为 `opencv` V3 构建的),您可能需要以不同的方式设置 fps 变量。有关详细信息,请参阅 [此处][1]。要找出您的版本,您可以执行以下操作:
major_ver, minor_ver, subminor_ver = cv2.__version__.split('.')
print(major_ver)

【讨论】:

在您的第二个示例中,您可以使用 cv2.CAP_PROP_FRAME_COUNT 获得准确的帧数并避免计算它。另外,你能解释一下n * np.arange(est_tot_frames)的目的吗?它似乎没有做它应该做的事情。 虽然有助于找到解决方案,但这个答案并不能解决关于第 n 帧的问题。 这会很慢!坏主意【参考方案4】:

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

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

【讨论】:

【参考方案5】:

这是我的建议:

     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 帧。在此之后,您可以使用其索引保存图像,以便稍后在同一索引处查询将返回保存的图像,而不必像您一样跳过帧。这样,您将节省在保存您不会查询的帧时浪费的空间,以及读取和保存那些不需要的帧所花费的时间。

【讨论】:

我正在播放 130 个 1 分钟的 4K 视频样本,我向你保证这是一个非常糟糕的主意!【参考方案6】:

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

【讨论】:

【参考方案7】:

您应该使用grab 函数移动到下一帧。并且仅使用retrieve 来解码您需要的帧。

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

【讨论】:

【参考方案8】:

如果有人需要每 5 帧捕获一次并将其保存为 jpg,基于 Ishan Shah 的代码:

import cv2

vid = cv2.VideoCapture('C:/python_works/creatives_gardenscapes/0c52b83ed1dec617092aaf83278f12ad.mp4')

if not os.path.exists('images'):
    os.makedirs('images')

index = 0
while(True):
    ret, frame = vid.read()
    if not ret: 
        break
    name = 'C:/python_works/creatives_gardenscapes/frames/0c52b83ed1dec617092aaf83278f12ad' + str(index) + '.jpg'
    if index%50==0:
        cv2.imwrite(name, frame)
    index += 1

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

我使用this repo!

主要思想是:

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)

【讨论】:

【参考方案11】:

代码:

import cv2
vidcap = cv2.VideoCapture('bali.mp4')
success,image = vidcap.read()

fps = vidcap.get(cv2.CAP_PROP_FPS)
print('video fps :', fps)
length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
print( 'total frames :', length )

count = 0
skip = 10 

success = True
while success:
  success,image = vidcap.read()
  if count%skip==0:
    print(count)
    cv2.imwrite("frames/frame%d.jpg" % count, image)     # save frame as JPEG file
  if cv2.waitKey(10) == 27:                     # exit if Escape is hit
      break
  count += 1

输出:

【讨论】:

【参考方案12】:

您可以通过阅读然后丢弃它来有效地“跳过”一帧。诚然,这完全不是您所要求的,但仍可能足以作为一种解决方法。显然,这会对性能产生影响,但可能足以满足您的目的。

vs = cv2.videoCapture('/my/clip.mp4')

for i in (thing):
    # Read a frame
    _, frame = vs.read()
    # Skip the frame you just read by reading another
    _, frame = vs.read()
    # Run a process on said frame
    cv2.imshow("Window", frame)  
    

vs.release()

【讨论】:

以上是关于从 OpenCV 中的 VideoCapture 中读取每第 n 帧的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 OpenCV 从辅助网络摄像头读取 VideoCapture 中的帧

从网络摄像头捕获帧,它在 opencv cv2.VideoCapture() 中仅返回 1 帧

使用 VideoCapture (OpenCV) 从图像创建视频

4.OpenCV视频处理

从接口名称而不是相机编号创建 openCV VideoCapture

无法使用videoCapture opencv打开视频