如何在Python中从视频中提取帧(详细实现教程)

Python如何从视频中提取帧?本文教你使用 Python 中的 OpenCV 或 MoviePy 库制作两种不同的方法来从带有时间戳的视频中提取帧。
你可能已经知道,视频由一系列图像组成。这些图像被称为帧,并以一定的速率一张一张地连续播放,这将被人眼识别为运动。
【如何在Python中从视频中提取帧(详细实现教程)】如何在Python中从视频中提取帧?在本教程中,你将学习两种在 Python 中从视频文件中提取帧的方法。首先,我们将讨论如何使用著名的OpenCV库来做到这一点。之后,我们将探索使用MoviePy 库提取帧的另一种方法。
首先,让我们安装库:

$ pip install python-opencv moviepy

方法 1:使用 OpenCV 提取帧Python从视频中提取帧示例:我将创建extract_frames_opencv.py文件并导入必要的模块:
from datetime import timedelta import cv2 import numpy as np import os

由于并非所有视频的长度和FPS都相同,我们将定义一个参数来调整我们想要每秒提取和保存的帧数:
# i.e if video of duration 30 seconds, saves 10 frame per second = 300 frames saved in total SAVING_FRAMES_PER_SECOND = 10

Python如何从视频中提取帧?我们将在这两种方法上使用此参数。例如,如果像现在一样设置为 10,即使视频 FPS 为 24,它也只会保存每秒 10 帧的视频。如果视频有 30 秒的持续时间,那么总共将保存 300 帧. 你也可以将此参数设置为 0.5,即每 2 秒保存一帧,依此类推。
接下来,让我们定义两个辅助函数:
def format_timedelta(td): """Utility function to format timedelta objects in a cool way (e.g 00:00:20.05) omitting microseconds and retaining milliseconds""" result = str(td) try: result, ms = result.split(".") except ValueError: return result + ".00".replace(":", "-") ms = int(ms) ms = round(ms / 1e4) return f"{result}.{ms:02}".replace(":", "-")def get_saving_frames_durations(cap, saving_fps): """A function that returns the list of durations where to save the frames""" s = [ ] # get the clip duration by dividing number of frames by the number of frames per second clip_duration = cap.get(cv2.CAP_PROP_FRAME_COUNT) / cap.get(cv2.CAP_PROP_FPS) # use np.arange() to make floating-point steps for i in np.arange(0, clip_duration, 1 / saving_fps): s.append(i) return s

如何在Python中从视频中提取帧?该format_timedelta()函数接受一个timedelta对象并返回一个漂亮的字符串表示,以毫秒为单位并省略微秒。
get_saving_frames_durations()函数接受VideoCapture来自 OpenCV的对象和我们之前讨论的保存参数,并返回我们应该保存帧的持续时间点列表。
现在我们有了这些辅助函数,让我们定义主函数并解释它:
def main(video_file): filename, _ = os.path.splitext(video_file) filename += "-opencv" # make a folder by the name of the video file if not os.path.isdir(filename): os.mkdir(filename) # read the video file cap = cv2.VideoCapture(video_file) # get the FPS of the video fps = cap.get(cv2.CAP_PROP_FPS) # if the SAVING_FRAMES_PER_SECOND is above video FPS, then set it to FPS (as maximum) saving_frames_per_second = min(fps, SAVING_FRAMES_PER_SECOND) # get the list of duration spots to save saving_frames_durations = get_saving_frames_durations(cap, saving_frames_per_second) # start the loop count = 0 while True: is_read, frame = cap.read() if not is_read: # break out of the loop if there are no frames to read break # get the duration by dividing the frame count by the FPS frame_duration = count / fps try: # get the earliest duration to save closest_duration = saving_frames_durations[ 0] except IndexError: # the list is empty, all duration frames were saved break if frame_duration >= closest_duration: # if closest duration is less than or equals the frame duration, # then save the frame frame_duration_formatted = format_timedelta(timedelta(seconds=frame_duration)) cv2.imwrite(os.path.join(filename, f"frame{frame_duration_formatted}.jpg"), frame) # drop the duration spot from the list, since this duration spot is already saved try: saving_frames_durations.pop(0) except IndexError: pass # increment the frame count count += 1

上面的函数看起来很复杂,其实不然,这就是我们要做的:
  • 首先,我们创建文件名变量,它是我们要创建和保存框架的文件夹名称,我们附加"-opencv"只是为了区分方法,但你可以删除它。
  • 然后,我们使用该os.mkdir()函数创建文件夹(如果尚未创建)。
  • 之后,我们使用 读取视频文件cv2.VideoCapture,并使用cap.get()方法获取 FPS并传递 FPS 的代码,即cv2.CAP_PROP_FPS.
  • 我们将每秒保存帧数设置为实际视频 FPS 和我们参数中的最小值。因此,我们确保不能绕过比实际视频 fps 更高的保存 fps。
  • 得到保存时长后,我们进入读取帧的循环,只有当我们确定时长在我们的saving_frames_durations列表中时才进行保存。我们使用 保存帧cv2.imwrite(),并将帧名称设置为实际持续时间。
定义主要Python从视频中提取帧示例代码:
if __name__ == "__main__": import sys video_file = sys.argv[ 1] main(video_file)

由于我们使用命令行参数传递视频文件,让我们运行它:
$ python extract_frames_opencv.py zoo.mp4

执行上述命令后,"zoo-opencv"会创建一个新文件夹,其中包含以下内容:
如何在Python中从视频中提取帧(详细实现教程)

文章图片
如你所见,帧与文件名中的时间戳一起保存。
相关:  如何在 Python 中从视频中提取音频
方法 2:使用 MoviePy 提取帧Python如何从视频中提取帧?在这个方法中,我们不会使用 OpenCV,而是使用另一个名为 MoviePy 的库,我将创建一个名为的文件extract_frames_moviepy.py并导入必要的模块:
from moviepy.editor import VideoFileClip import numpy as np import os from datetime import timedelta

与第一种方法一样,我们也将在SAVING_FRAMES_PER_SECOND这里使用参数:
# i.e if video of duration 30 seconds, saves 10 frame per second = 300 frames saved in total SAVING_FRAMES_PER_SECOND = 10

请参阅本教程的第一部分以了解它的确切含义。和以前一样,我们也需要这个format_timedelta()函数:
def format_timedelta(td): """Utility function to format timedelta objects in a cool way (e.g 00:00:20.05) omitting microseconds and retaining milliseconds""" result = str(td) try: result, ms = result.split(".") except ValueError: return result + ".00".replace(":", "-") ms = int(ms) ms = round(ms / 1e4) return f"{result}.{ms:02}".replace(":", "-")

现在转到主函数:
def main(video_file): # load the video clip video_clip = VideoFileClip(video_file) # make a folder by the name of the video file filename, _ = os.path.splitext(video_file) filename += "-moviepy" if not os.path.isdir(filename): os.mkdir(filename)# if the SAVING_FRAMES_PER_SECOND is above video FPS, then set it to FPS (as maximum) saving_frames_per_second = min(video_clip.fps, SAVING_FRAMES_PER_SECOND) # if SAVING_FRAMES_PER_SECOND is set to 0, step is 1/fps, else 1/SAVING_FRAMES_PER_SECOND step = 1 / video_clip.fps if saving_frames_per_second == 0 else 1 / saving_frames_per_second # iterate over each possible frame for current_duration in np.arange(0, video_clip.duration, step): # format the file name and save it frame_duration_formatted = format_timedelta(timedelta(seconds=current_duration)).replace(":", "-") frame_filename = os.path.join(filename, f"frame{frame_duration_formatted}.jpg") # save the frame with the current duration video_clip.save_frame(frame_filename, current_duration)

如何在Python中从视频中提取帧?你可能已经注意到,此方法需要的代码较少。首先,我们使用VideoFileClip()类加载我们的视频剪辑,我们创建我们的文件夹并确保保存的 fps 小于或等于视频 fps。
然后我们定义我们的循环步长,即 1 除以保存的 fps,如果我们将 设置SAVING_FRAMES_PER_SECOND为 10,那么步长将为 0.1(即每 0.1 秒保存帧)。
这里的区别在于VideoFileClip对象有save_frame()方法,该方法接受两个参数:帧文件名和要保存的帧的持续时间。所以我们所做的是我们循环使用np.arange()(常规range()函数的浮点版本)在我们想要的每一帧上采取步骤,并相应地调用该save_frame()方法。
下面是主要对Python从视频中提取帧示例代码:
if __name__ == "__main__": import sys video_file = sys.argv[ 1] main(video_file)

让我们测试一下: 
$ python extract_frames_moviepy.py zoo.mp4

几秒钟后,zoo-moviepy文件夹被创建,帧被保存在该文件夹中。
结论Python如何从视频中提取帧?在我的案例中使用这两种方法后,我注意到方法一(使用 OpenCV)在时间执行方面更快,但比 MoviePy 保存更大的图像。
对于该演示视频,使用第二种方法(使用 MoviePy)时190帧的大小为2.8MB,使用 OpenCV 时为5.46MB。然而,MoviePy 方法的持续时间为2.3秒,而 OpenCV 需要大约0.6秒。
话虽如此,我已经为你提供了两种在 Python 中从视频中提取帧的方法,你可以选择最适合你的方法。

    推荐阅读