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,那么这可能是最好的方法。