cv2.VideoCapture cap.set 和循环读取之间的不一致行为

cv2.VideoCapture inconsistent behavior between cap.set and loop read

我正在尝试使用 cv2.VideoCapture 读取视频的第 600 帧。但是,我发现下面两种方法都成功读取了一张图片,但是图片不一样。我想知道读取第 600 帧的正确方法是什么,为什么生成的图像不同?与mp4编码有关吗?谢谢!

方法一

cap = cv2.VideoCapture("test.mp4")
print(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 1187
cap.set(1, 600)
ret, frame1 = cap.read()  # Read the frame

方法二

cap = cv2.VideoCapture("test.mp4")
print(cap.get(cv2.CAP_PROP_FRAME_COUNT)) # 1187
for i in range(601):
    ret, frame2 = cap.read()  # Read the frame

到read/obtain一个视频的第X帧或者类似的判断一个视频文件的帧数,有两种方法:

  • 方法 #1:利用 built-in OpenCV 属性访问视频文件元信息 快速高效但不准确
  • 方法 #2:使用计数器手动循环视频文件中的每一帧,该计数器缓慢且低效但准确

方法#1 速度很快,并且依赖于 OpenCV 的 video property functionality,它几乎可以瞬间确定视频文件中的帧信息。但是,有一个准确度 trade-off,因为它取决于您的 OpenCV 和视频编解码器版本。来自文档:

Reading / writing properties involves many layers. Some unexpected result might happen along this chain. Effective behavior depends from device hardware, driver and API Backend.

另一方面,手动计算每一帧直到我们达到所需的帧数将是 100% 准确的,尽管它会慢得多。下面是演示两种方法之间不一致行为的示例。它默认尝试执行方法#1,如果失败,它将自动使用方法#2

def frame_count(video_path, manual=False):
    def manual_count(handler):
        frames = 0
        while True:
            status, frame = handler.read()
            if not status:
                break
            frames += 1
        return frames 

    cap = cv2.VideoCapture(video_path)
    # Slow, inefficient but 100% accurate method 
    if manual:
        frames = manual_count(cap)
    # Fast, efficient but inaccurate method
    else:
        try:
            frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        except:
            frames = manual_count(cap)
    cap.release()
    return frames

基准

if __name__ == '__main__':
    import timeit
    import cv2

    start = timeit.default_timer()
    print('frames:', frame_count('testtest.mp4', manual=False))
    print(timeit.default_timer() - start, '(s)')

    start = timeit.default_timer()
    print('frames:', frame_count('testtest.mp4', manual=True))
    print(timeit.default_timer() - start, '(s)')

方法#1 结果

frames: 3671
0.018054921 (s)

方法 #2 结果

frames: 3521
9.447095287 (s)

请注意这两种方法有 150 帧的差异,并且 方法 #2 比方法 #1 慢得多。一般来说,如果您需要速度但又愿意牺牲准确性,请使用方法#1。在您可以延迟但需要精确帧的情况下,请使用方法 #2。


所以结论是:当您使用 cap.get 或任何内置 VideoCaptureProperties(例如 cv2.CAP_PROP_FRAME_COUNT 时,您实质上是在使用方法 #1,该方法快速高效但不准确.在您的第一个示例中,当您尝试使用 cap.set 读取精确帧时,您实际上得到的是接近所需 X 帧的“估计”帧,而不是实际的 X 框架。 相比之下,从您的第二个代码片段中,您手动逐帧检查每一帧,因此当它落在第 X 帧时,保证是准确的。这就是为什么当您尝试使用每种方法读取相同的帧编号时,您可能会得到不同的图像。