Python如何从视频中提取帧?本文教你使用 Python 中的 OpenCV 或 MoviePy 库制作两种不同的方法来从带有时间戳的视频中提取帧。
你可能已经知道,视频由一系列图像组成。这些图像被称为帧,并以一定的速率一张一张地连续播放,这将被人眼识别为运动。
如何在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 中从视频中提取音频
方法 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 中从视频中提取帧的方法,你可以选择最适合你的方法。