优化OpenCV视频的读取速度

我们使用Opencv读取视频时,常规的做法是使用read()函数逐帧读取,如

import cv2

cap = cv2.VideoCapture("./test.mp4")
while True:
    # read a frame
    ret, frame = cap.read()
    if not ret:
        break
    # do something else
    ...
cap.release()

但是在图像处理时,并不需要逐帧处理,而是抽样的方式,这种情况下每一帧都读取会造成计算资源的浪费。查看Opencv的文档,会发现有这么几个函数介绍

virtual bool read (OutputArray image)
 Grabs, decodes and returns the next video frame. More...

virtual bool grab ()
 Grabs the next frame from video file or capturing device. More...
 
virtual bool retrieve (OutputArray image, int flag=0)
 Decodes and returns the grabbed video frame. More...

可以看出,read函数其实是grab和retireve函数的组合,grap是跳转到下一帧的位置,而retrieve是做具体的解码工作。显而易见,单纯的跳转要比解码快的多,所以我们不妨修改以下代码,假如抽样频率是5

import cv2

cap = cv2.VideoCapture("./test.mp4")
idx = 0
freq = 5
While True:
    idx += 1
    ret = cap.grab()
    if not ret:
         break
        
    if idx % freq == 1:
         continue
    
         ret, frame = cap.retrieve()
         if frame is None:    # exist broken frame
             break
         # do something else
         ...
cap.release()

读取速度会变成之前的1/5。

virtual bool read (OutputArray image)
 Grabs, decodes and returns the next video frame. More...

virtual bool grab ()
 Grabs the next frame from video file or capturing device. More...
 
virtual bool retrieve (OutputArray image, int flag=0)
 Decodes and returns the grabbed video frame. More...

但是对于超大的视频,这样单线程的读取方式还是不够快,因此可以考虑多线程同时读取,以加速处理速度

def process_video(self, start_frame, length):
    cap = cv2.VideoCapture("./test.mp4")
    cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
    freq = 5
    for idx in range(length):
        ret = cap.grab()
        if not ret:
            break
        
        if idx % freq == 0:
            continue
    
        ret, frame = cap.retrieve()
        if frame is None:    # exist broken frame
            break
        # do something else
        ...
    cap.release()

在通过

frames_num = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

获取视频的总帧数,就可以平均分N个线程来处理视频了。