使用 OpenCV 和 Python 从视频中快速提取帧
**更新(2020 年 7 月):**我現在使用并建議使用Decod在 Python 中更快地加載視頻。您可以進一步查看原始的 OpenCV 版本。😃
速度比較。來自Decod的 Github 頁面。
下面是一個如何使用 Decod 提取幀的示例腳本。它與 OpenCV 版本有相似之處,但更快、更簡潔、更簡單。請注意,它根據參數使用批量收集或順序讀取來提取幀every。我已將閾值設置為25和total閾值 ,1000因為這適合我的系統的內存限制和 CPU 能力。
import cv2 # still used to save images out import os import numpy as np from decord import VideoReader from decord import cpu, gpudef 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) # make the paths OS (Windows) compatibleframes_dir = os.path.normpath(frames_dir) # make the paths OS (Windows) compatiblevideo_dir, video_filename = os.path.split(video_path) # get the video path and filename from the pathassert os.path.exists(video_path) # assert the video file exists# load the VideoReadervr = VideoReader(video_path, ctx=cpu(0)) # can set to cpu or gpu .. ctx=gpu(0)if start < 0: # if start isn't specified lets assume 0start = 0if end < 0: # if end isn't specified assume the end of the videoend = len(vr)frames_list = list(range(start, end, every))saved_count = 0if every > 25 and len(frames_list) < 1000: # this is faster for every > 25 frames and can fit in memoryframes = vr.get_batch(frames_list).asnumpy()for index, frame in zip(frames_list, frames): # lets loop through the frames until the endsave_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(index)) # create the save pathif not os.path.exists(save_path) or overwrite: # if it doesn't exist or we want to overwrite anywayscv2.imwrite(save_path, cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) # save the extracted imagesaved_count += 1 # increment our counter by oneelse: # this is faster for every <25 and consumes small memoryfor index in range(start, end): # lets loop through the frames until the endframe = vr[index] # read an image from the captureif index % every == 0: # if this is a frame we want to write out based on the 'every' argumentsave_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(index)) # create the save pathif not os.path.exists(save_path) or overwrite: # if it doesn't exist or we want to overwrite anywayscv2.imwrite(save_path, cv2.cvtColor(frame.asnumpy(), cv2.COLOR_RGB2BGR)) # save the extracted imagesaved_count += 1 # increment our counter by onereturn saved_count # and return the count of the images we saveddef 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) # make the paths OS (Windows) compatibleframes_dir = os.path.normpath(frames_dir) # make the paths OS (Windows) compatiblevideo_dir, video_filename = os.path.split(video_path) # get the video path and filename from the path# make directory to save frames, its a sub dir in the frames_dir with the video nameos.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) # let's now extract the framesreturn os.path.join(frames_dir, video_filename) # when done return the directory containing the framesif __name__ == '__main__':# test itvideo_to_frames(video_path='test.mp4', frames_dir='test_frames', overwrite=False, every=5)我’已經與影片做了很多多年來一兩件事,我經常需要做的是提取視頻幀,并將其保存為單獨的圖像。隨著時間的推移,我不得不處理更大、更容易出錯的視頻文件,并且最近確定了一個不錯的腳本,我想我會分享它。
讓我們從基本功能開始,它可以毫不費力地從單個視頻中提取幀。這個被調用的函數extract_frames()需要一個視頻路徑、一個幀目錄的路徑,以及一些額外的東西,比如我們是否想要覆蓋已經存在的幀,或者只每 x 幀執行一次。
import cv2 import osdef 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) # make the paths OS (Windows) compatibleframes_dir = os.path.normpath(frames_dir) # make the paths OS (Windows) compatiblevideo_dir, video_filename = os.path.split(video_path) # get the video path and filename from the pathassert os.path.exists(video_path) # assert the video file existscapture = cv2.VideoCapture(video_path) # open the video using OpenCVif start < 0: # if start isn't specified lets assume 0start = 0if end < 0: # if end isn't specified assume the end of the videoend = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))capture.set(1, start) # set the starting frame of the captureframe = start # keep track of which frame we are up to, starting from startwhile_safety = 0 # a safety counter to ensure we don't enter an infinite while loop (hopefully we won't need it)saved_count = 0 # a count of how many frames we have savedwhile frame < end: # lets loop through the frames until the end_, image = capture.read() # read an image from the captureif while_safety > 500: # break the while if our safety maxs out at 500break# sometimes OpenCV reads None's during a video, in which case we want to just skipif image is None: # if we get a bad return flag or the image we read is None, lets not savewhile_safety += 1 # add 1 to our while safety, since we skip before incrementing our frame variablecontinue # skipif frame % every == 0: # if this is a frame we want to write out based on the 'every' argumentwhile_safety = 0 # reset the safety countsave_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(frame)) # create the save pathif not os.path.exists(save_path) or overwrite: # if it doesn't exist or we want to overwrite anywayscv2.imwrite(save_path, image) # save the extracted imagesaved_count += 1 # increment our counter by oneframe += 1 # increment our frame countcapture.release() # after the while has finished close the capturereturn saved_count # and return the count of the images we saved您可能會注意到我們使用了一個帶有有點奇怪的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 sysdef 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}" # format the % done number stringpercents = format_str.format(100 * (iteration / float(total))) # calculate the % donefilled_length = int(round(bar_length * iteration / float(total))) # calculate the filled bar lengthbar = '#' * filled_length + '-' * (bar_length - filled_length) # generate the bar stringsys.stdout.write('\r%s |%s| %s%s %s' % (prefix, bar, percents, '%', suffix)), # write out the barsys.stdout.flush() # flush to stdoutdef 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) # make the paths OS (Windows) compatibleframes_dir = os.path.normpath(frames_dir) # make the paths OS (Windows) compatiblevideo_dir, video_filename = os.path.split(video_path) # get the video path and filename from the pathassert os.path.exists(video_path) # assert the video file existscapture = cv2.VideoCapture(video_path) # open the video using OpenCVif start < 0: # if start isn't specified lets assume 0start = 0if end < 0: # if end isn't specified assume the end of the videoend = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))capture.set(1, start) # set the starting frame of the captureframe = start # keep track of which frame we are up to, starting from startwhile_safety = 0 # a safety counter to ensure we don't enter an infinite while loop (hopefully we won't need it)saved_count = 0 # a count of how many frames we have savedwhile frame < end: # lets loop through the frames until the end_, image = capture.read() # read an image from the captureif while_safety > 500: # break the while if our safety maxs out at 500break# sometimes OpenCV reads None's during a video, in which case we want to just skipif image is None: # if we get a bad return flag or the image we read is None, lets not savewhile_safety += 1 # add 1 to our while safety, since we skip before incrementing our frame variablecontinue # skipif frame % every == 0: # if this is a frame we want to write out based on the 'every' argumentwhile_safety = 0 # reset the safety countsave_path = os.path.join(frames_dir, video_filename, "{:010d}.jpg".format(frame)) # create the save pathif not os.path.exists(save_path) or overwrite: # if it doesn't exist or we want to overwrite anywayscv2.imwrite(save_path, image) # save the extracted imagesaved_count += 1 # increment our counter by oneframe += 1 # increment our frame countcapture.release() # after the while has finished close the capturereturn saved_count # and return the count of the images we saveddef 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) # make the paths OS (Windows) compatibleframes_dir = os.path.normpath(frames_dir) # make the paths OS (Windows) compatiblevideo_dir, video_filename = os.path.split(video_path) # get the video path and filename from the path# make directory to save frames, its a sub dir in the frames_dir with the video nameos.makedirs(os.path.join(frames_dir, video_filename), exist_ok=True)capture = cv2.VideoCapture(video_path) # load the videototal = int(capture.get(cv2.CAP_PROP_FRAME_COUNT)) # get its total frame countcapture.release() # release the capture straight awayif total < 1: # if video has no frames, might be and opencv errorprint("Video has no frames. Check your OpenCV + ffmpeg installation")return None # return Noneframe_chunks = [[i, i+chunk_size] for i in range(0, total, chunk_size)] # split the frames into chunk listsframe_chunks[-1][-1] = min(frame_chunks[-1][-1], total-1) # make sure last chunk has correct end frame, also handles case chunk_size < totalprefix_str = "Extracting frames from {}".format(video_filename) # a prefix string to be printed in progress bar# execute across multiple cpu cores to speed up processing, get the count automaticallywith 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] # submit the processes: extract_frames(...)for i, f in enumerate(as_completed(futures)): # as each process completesprint_progress(i, len(frame_chunks)-1, prefix=prefix_str, suffix='Complete') # print it's progressreturn os.path.join(frames_dir, video_filename) # when done return the directory containing the framesif __name__ == '__main__':# test itvideo_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
總結
以上是生活随笔為你收集整理的使用 OpenCV 和 Python 从视频中快速提取帧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双精度浮点数的输入输出
- 下一篇: 单页应用 vs 多页应用