处理视频文件

有时需要从标准视频文件(如.avi和.mov文件)中读取一系列图像。

在科学背景下,通常最好避免使用这些格式,而使用简单的图像目录或多维TIF。视频格式更难逐段读取,通常不支持随机帧访问或具有研究意识的元数据,如果没有仔细配置,还会使用有损压缩。但视频文件使用广泛,很容易共享,因此在需要时可以方便地配备读写。

用于读取视频文件的工具在安装和使用的简单性、磁盘和内存的使用以及跨平台兼容性方面各不相同。这是一个实用指南。

解决方法:将视频转换为图像序列

对于一次性解决方案,最简单、最可靠的方法是将视频转换为一组按顺序编号的图像文件,通常称为图像序列。然后,可以将图像文件读入到 ImageCollection by skimage.io.imread_collection. Converting the video to frames can be done easily in ImageJ ,一个来自生物成像社区的跨平台、基于图形用户界面的程序,或者 FFmpeg ,这是一个用于操作视频文件的强大命令行实用程序。

在FFmpeg中,以下命令从视频中的每一帧生成一个图像文件。这些文件用五位数字编号,左边用零填充。

ffmpeg -i "video.mov" -f image2 "video-frame%05d.png"

有关更多信息,请访问 FFmpeg tutorial on image sequences

生成图像序列有缺点:它们可能很大且不实用,而且生成它们可能需要一些时间。通常情况下,直接处理原始视频文件更可取。对于更直接的解决方案,我们需要从Python执行FFmpeg或LibAV来读取视频中的帧。FFmpeg和LibAV是两个大型开源项目,它们从野外使用的各种格式中解码视频。有几种方法可以在Python中使用它们。不幸的是,每一种都有一些缺点。

PYAV

PyAV 使用FFmpeg(或LibAV)库直接从视频文件读取图像数据。它使用Cython绑定来调用它们,因此速度非常快。

import av
v = av.open('path/to/video.mov')

PyAV的API反映了帧存储在视频文件中的方式。

for packet in container.demux():
    for frame in packet.decode():
        if frame.type == 'video':
            img = frame.to_image()  # PIL/Pillow image
            arr = np.asarray(img)  # numpy array
            # Do something!

将随机访问添加到PYAV

这个 Video class in PIMS 调用PyAV并添加附加功能以解决科学应用程序中的常见问题,即按帧编号访问视频。视频文件格式被设计为以近似的方式、按时间进行搜索,并且它们不支持搜索特定帧编号的有效方法。PIMS通过解码(但不是读取)整个视频并生成支持按帧索引的内部目录来增加这一缺失的功能。

import pims
v = pims.Video('path/to/video.mov')
v[-1]  # a 2D numpy array representing the last frame

MoviePy

Moviepy 通过子进程调用FFmpeg,通过管道将解码后的视频从FFmpeg传输到RAM,然后将其读出。这种方法很简单,但它可能很脆弱,而且不适用于超过可用RAM的大型视频。如果安装了FFmpeg,它可以在所有平台上运行。

因为它没有链接到FFmpeg的底层库,所以更容易安装,但关于 half as fast

from moviepy.editor import VideoFileClip
myclip = VideoFileClip("some_video.avi")

ImageIO

Imageio 采用与MoviePy相同的方法。它还支持多种其他图像文件格式。

import imageio
filename = '/tmp/file.mp4'
vid = imageio.get_reader(filename,  'ffmpeg')

for num, image in vid.iter_data():
    print(image.mean())

metadata = vid.get_meta_data()

OpenCV

最后,另一个解决方案是 VideoReader 类,它绑定到FFmpeg。如果出于其他原因需要OpenCV,则这可能是最好的方法。