原文:http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/
本文通过管道调用外部软件FFMPEG,轻松使用几行Python读取或编写视频帧。 如果你想要经过实战考验和更复杂的版本,请查看我的github代码MoviePy。 另请参阅与音频文件相关的此文章。
在开始之前,您必须在计算机上安装FFMPEG,并且必须知道FFMPEG二进制文件的名称(或路径)。
它应该是以下之一:
FFMPEG_BIN = “ffmpeg” # on Linux ans Mac OS
FFMPEG_BIN = “ffmpeg.exe” #on Windows
要读取视频“myHolidays.mp4”的帧,我们首先要求FFMPEG打开此文件并将其输出定向到Python:
import subprocess as sp
command = [ FFMPEG_BIN,
'-i', 'myHolidays.mp4',
'-f', 'image2pipe',
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-']
pipe = sp.Popen(command, stdout = sp.PIPE, bufsize=10**8)
在上面的代码中,-i myHolidays.mp4表示输入文件,而rawvideo / rgb24请求原始RGB输出。
格式image2pipe和 - 最后告诉FFMPEG它正被另一个程序用于管道。在sp.Popen中,bufsize参数必须大于一帧的大小(见下文)。
在Python 2中大部分时间都可以省略它,但在Python 3中它(buffsize)的默认值非常小。
现在我们只需要考虑FFMPEG的输出。
如果视频的大小为420x360像素,则FFMPEG输出的第一个420x360x3字节将逐行给出第一帧像素的RGB值,从上到下。
下一个420x360x3字节将代表第二帧,等等。
在下一段中,我们提取一帧并将其转为420x360x3 Numpy数组:
import numpy
#读取420 * 360 * 3字节(= 1帧)
raw_image = pipe.stdout.read(420*360*3)
#将读取的字节转换为numpy数组
image = numpy.fromstring(raw_image, dtype='uint8')
image = image.reshape((360,420,3))
#丢弃管道缓冲区中的数据。
pipe.stdout.flush()
您现在可以使用Pylab的imshow(图像)查看图像。 通过重复上面的两行,您可以一个接一个地读取视频的所有帧。 在我的计算机上使用此方法读取一帧需要2毫秒的时间。
如果您想要在视频中读取时间=01h00的帧,该怎么办?
您可以这样做:打开管道,逐个读取视频的所有帧,直到达到对应于t = 01h00的帧。 但这可能很长。 更好的解决方案是使用参数调用FFMPEG,告诉它在01h00时开始读取“myHolidays.mp4”:
command = [FFMPEG_BIN,
'-ss', '00:59;59',
'-i', 'myHolidays.mp4',
'-ss', '1',
'-f', 'image2pipe',
'-pix_fmt', 'rgb24',
'-vcodec','rawvideo', '-']
pipe = sp.Popen(command, stdout=sp.PIPE, bufsize=10**8)
在上面的代码中,我们要求FFMPEG快速(并且不精确地)达到00:59:59,然后以精确度(-ss 1)跳过1秒的电影,这样它将有效地从01:00:00开始(见此页面了解更多信息)。
然后您可以开始阅读前面显示的框架。 使用此方法寻找帧在我的计算机上最多需要0.1秒。
您还可以通过调用获取文件的信息(帧大小,每秒帧数等)
command = [FFMPEG_BINARY,'-i', 'my_video.mp4', '-']
pipe = sp.Popen(command, stdout=sp.PIPE stderr=sp.PIPE)
pipe.stdout.readline()
pipe.terminate()
infos = proc.stderr.read()
现在infos包含一个描述文件的文本,您需要解析该文件以获取相关信息。 有关实现的链接,请参见最后一节。
要将一系列大小为420x360的帧写入文件’my_output_videofile.mp4’,我们打开FFMPEG并指示原始RGB数据将通过管道输入:
command = [ FFMPEG_BIN,
'-y', #(可选)覆盖输出文件(如果存在)
'-f', 'rawvideo',
'-vcodec','rawvideo',
'-s', '420x360', #一帧的大小
'-pix_fmt', 'rgb24',
'-r', '24', #每秒帧数
'-i', '-', #输入来自管道
'-an', #告诉FFMPEG不要期待任何音频(静音)
'-vcodec', 'mpeg'",
'my_output_videofile.mp4' ]
pipe = sp.Popen( command, stdin=sp.PIPE, stderr=sp.PIPE)
输出视频的编解码器可以是任何有效的FFMPEG编解码器,但对于许多编解码器,您需要提供比特率作为附加参数(例如-bitrate 3000k)。
现在我们可以在文件中一个接一个地写原始帧。 这些将是原始帧,如上一节中由FFMPEG输出的那些:它们应该是“RGBRGBRGB …”形式的字符串,其中R,G,B是表示0到255之间的数字的字符。
如果我们的帧表示作为Numpy数组,我们只需写:
pipe.proc.stdin.write( image_array.tostring() )
我试着在这里尽可能简化代码。 使用更多行可以创建有用的类来操作视频文件,例如我为视频编辑软件编写的FFMPEG_VideoReader和FFMPEG_VideoWriter。 在这些文件中特别是如何解析视频信息,如何使用FFMPEG保存/加载图片等。