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