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