**更新(2020 年 7 月):**我现在使用并建议使用Decod在 Python 中更快地加载视频。您可以进一步查看原始的 OpenCV 版本。😃 速度比较。来自Decod的 Github 页面。
下面是一个如何使用 Decod 提取帧的示例脚本。它与 OpenCV 版本有相似之处,但更快、更简洁、更简单。请注意,它根据参数使用批量收集或顺序读取来提取帧every 。我已将阈值设置为25 和total 阈值 ,1000 因为这适合我的系统的内存限制和 CPU 能力。
import cv2
import os
import numpy as np
from decord import VideoReader
from decord import cpu, gpu
def extract_frames(video_path, frames_dir, overwrite=False, start=-1, end=-1, every=1):
"""
Extract frames from a video using decord's VideoReader
:param video_path: path of the video
:param frames_dir: the directory to save the frames
:param overwrite: to overwrite frames that already exist?
:param start: start frame
:param end: end frame
:param every: frame spacing
:return: count of images saved
"""
video_path = os.path.normpath(video_path)
frames_dir = os.path.normpath(frames_dir)
video_dir, video_filename = os.path.split(video_path)
assert os.path.exists(video_path)
vr = VideoReader(video_path, ctx=cpu(0))
if start < 0:
start = 0
if end < 0:
end = len(vr)
frames_list = list(range(start, end, every))
saved_count = 0
if every > 25 and len(frames_list) < 1000:
frames = vr.get_batch(frames_list).asnumpy()
for index, frame in zip(frames_list, frames):
save_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(index))
if not os.path.exists(save_path) or overwrite:
cv2.imwrite(save_path, cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
saved_count += 1
else:
for index in range(start, end):
frame = vr[index]
if index % every == 0:
save_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(index))
if not os.path.exists(save_path) or overwrite:
cv2.imwrite(save_path, cv2.cvtColor(frame.asnumpy(), cv2.COLOR_RGB2BGR))
saved_count += 1
return saved_count
def video_to_frames(video_path, frames_dir, overwrite=False, every=1):
"""
Extracts the frames from a video
:param video_path: path to the video
:param frames_dir: directory to save the frames
:param overwrite: overwrite frames if they exist?
:param every: extract every this many frames
:return: path to the directory where the frames were saved, or None if fails
"""
video_path = os.path.normpath(video_path)
frames_dir = os.path.normpath(frames_dir)
video_dir, video_filename = os.path.split(video_path)
os.makedirs(os.path.join(frames_dir, video_filename), exist_ok=True)
print("Extracting frames from {}".format(video_filename))
extract_frames(video_path, frames_dir, every=every)
return os.path.join(frames_dir, video_filename)
if __name__ == '__main__':
video_to_frames(video_path='test.mp4', frames_dir='test_frames', overwrite=False, every=5)
我’已经与影片做了很多多年来一两件事,我经常需要做的是提取视频帧,并将其保存为单独的图像。随着时间的推移,我不得不处理更大、更容易出错的视频文件,并且最近确定了一个不错的脚本,我想我会分享它。
让我们从基本功能开始,它可以毫不费力地从单个视频中提取帧。这个被调用的函数extract_frames() 需要一个视频路径、一个帧目录的路径,以及一些额外的东西,比如我们是否想要覆盖已经存在的帧,或者只每 x 帧执行一次。
import cv2
import os
def extract_frames(video_path, frames_dir, overwrite=False, start=-1, end=-1, every=1):
"""
Extract frames from a video using OpenCVs VideoCapture
:param video_path: path of the video
:param frames_dir: the directory to save the frames
:param overwrite: to overwrite frames that already exist?
:param start: start frame
:param end: end frame
:param every: frame spacing
:return: count of images saved
"""
video_path = os.path.normpath(video_path)
frames_dir = os.path.normpath(frames_dir)
video_dir, video_filename = os.path.split(video_path)
assert os.path.exists(video_path)
capture = cv2.VideoCapture(video_path)
if start < 0:
start = 0
if end < 0:
end = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
capture.set(1, start)
frame = start
while_safety = 0
saved_count = 0
while frame < end:
_, image = capture.read()
if while_safety > 500:
break
if image is None:
while_safety += 1
continue
if frame % every == 0:
while_safety = 0
save_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(frame))
if not os.path.exists(save_path) or overwrite:
cv2.imwrite(save_path, image)
saved_count += 1
frame += 1
capture.release()
return saved_count
您可能会注意到我们使用了一个带有有点奇怪的while_safety 计数器的 while 循环。这是因为有时 OpenCV 会返回空帧,在这种情况下,我们只想继续读取而不增加frame 计数器。这增加了无限循环已经存在的任何潜力,这就是为什么while_safety 每次frame 不使用计数器时都使用计数器并递增的原因。
虽然上面的代码很简单,但实际上很慢,结果提取和保存帧需要一段时间。幸运的是,如今的计算机往往具有多个 CPU 内核,可以并行执行操作。因此,让我们扩展上述代码以在所有 CPU 内核上添加并行处理。
为此,我们编写了一个包装函数video_to_frames() ,该函数首先将视频分成 length 的块chunk_size ,然后extract_frames() 在每个块上调用上面看到的函数。
from concurrent.futures import ProcessPoolExecutor, as_completed
import cv2
import multiprocessing
import os
import sys
def print_progress(iteration, total, prefix='', suffix='', decimals=3, bar_length=100):
"""
Call in a loop to create standard out progress bar
:param iteration: current iteration
:param total: total iterations
:param prefix: prefix string
:param suffix: suffix string
:param decimals: positive number of decimals in percent complete
:param bar_length: character length of bar
:return: None
"""
format_str = "{0:." + str(decimals) + "f}"
percents = format_str.format(100 * (iteration / float(total)))
filled_length = int(round(bar_length * iteration / float(total)))
bar = '#' * filled_length + '-' * (bar_length - filled_length)
sys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix)),
sys.stdout.flush()
def extract_frames(video_path, frames_dir, overwrite=False, start=-1, end=-1, every=1):
"""
Extract frames from a video using OpenCVs VideoCapture
:param video_path: path of the video
:param frames_dir: the directory to save the frames
:param overwrite: to overwrite frames that already exist?
:param start: start frame
:param end: end frame
:param every: frame spacing
:return: count of images saved
"""
video_path = os.path.normpath(video_path)
frames_dir = os.path.normpath(frames_dir)
video_dir, video_filename = os.path.split(video_path)
assert os.path.exists(video_path)
capture = cv2.VideoCapture(video_path)
if start < 0:
start = 0
if end < 0:
end = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
capture.set(1, start)
frame = start
while_safety = 0
saved_count = 0
while frame < end:
_, image = capture.read()
if while_safety > 500:
break
if image is None:
while_safety += 1
continue
if frame % every == 0:
while_safety = 0
save_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(frame))
if not os.path.exists(save_path) or overwrite:
cv2.imwrite(save_path, image)
saved_count += 1
frame += 1
capture.release()
return saved_count
def video_to_frames(video_path, frames_dir, overwrite=False, every=1, chunk_size=1000):
"""
Extracts the frames from a video using multiprocessing
:param video_path: path to the video
:param frames_dir: directory to save the frames
:param overwrite: overwrite frames if they exist?
:param every: extract every this many frames
:param chunk_size: how many frames to split into chunks (one chunk per cpu core process)
:return: path to the directory where the frames were saved, or None if fails
"""
video_path = os.path.normpath(video_path)
frames_dir = os.path.normpath(frames_dir)
video_dir, video_filename = os.path.split(video_path)
os.makedirs(os.path.join(frames_dir, video_filename), exist_ok=True)
capture = cv2.VideoCapture(video_path)
total = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
capture.release()
if total < 1:
print("Video has no frames. Check your OpenCV + ffmpeg installation")
return None
frame_chunks = [[i, i+chunk_size] for i in range(0, total, chunk_size)]
frame_chunks[-1][-1] = min(frame_chunks[-1][-1], total-1)
prefix_str = "Extracting frames from {}".format(video_filename)
with ProcessPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
futures = [executor.submit(extract_frames, video_path, frames_dir, overwrite, f[0], f[1], every)
for f in frame_chunks]
for i, f in enumerate(as_completed(futures)):
print_progress(i, len(frame_chunks)-1, prefix=prefix_str, suffix='Complete')
return os.path.join(frames_dir, video_filename)
if __name__ == '__main__':
video_to_frames(video_path='test.mp4', frames_dir='test_frames', overwrite=False, every=5, chunk_size=1000)
Ref: https://medium.com/@haydenfaulkner/extracting-frames-fast-from-a-video-using-opencv-and-python-73b9b7dc9661
|