7. 处理视频文件#

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

在科学领域,通常最好避免使用这些格式,而选择简单的图像目录或多维 TIF。视频格式更难零散读取,通常不支持随机帧访问或研究导向的元数据,如果配置不当,还会使用有损压缩。但是,视频文件应用广泛,易于共享,因此在必要时能够读取和写入它们非常方便。

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

7.1. 一种解决方法:将视频转换为图像序列#

对于一次性解决方案,最简单、最可靠的方法是将视频转换为一系列按顺序编号的图像文件,通常称为图像序列。然后,图像文件可以通过 skimage.io.imread_collection 读取到 ImageCollection 中。将视频转换为帧可以很容易地在 ImageJ(一个来自生物成像社区的跨平台、基于 GUI 的程序)或 FFmpeg(一个用于处理视频文件的强大命令行实用程序)中完成。

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

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

更多信息请参考 关于图像序列的 FFmpeg 教程

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

7.2. PyAV#

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

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

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

import numpy as np
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!

7.3. 向 PyAV 添加随机访问#

PIMS 中的 Video 类调用 PyAV 并添加额外的功能来解决科学应用中的一个常见问题,即按帧号访问视频。视频文件格式旨在按时间以近似方式搜索,它们不支持高效查找特定帧号的方法。PIMS 通过解码(但不读取)整个视频并生成一个支持按帧索引的内部目录来添加此缺失的功能。

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

7.4. MoviePy#

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

由于它没有链接到 FFmpeg 的底层库,因此更容易安装,但速度大约 慢一半

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

7.5. Imageio#

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

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

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

metadata = vid.get_meta_data()

7.6. OpenCV#

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