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

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

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

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

【问题讨论】:

    标签: c++ opencv video-capture


    【解决方案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()
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2016-11-06
                    • 1970-01-01
                    • 2016-02-12
                    • 2019-05-18
                    • 2016-02-05
                    • 1970-01-01
                    • 2021-04-25
                    相关资源
                    最近更新 更多