7. 處理影片檔案#

有時候需要從標準影片檔案(例如 .avi 和 .mov 檔案)讀取一系列影像。

在科學研究的背景下,通常最好避免使用這些格式,而傾向於使用簡單的影像目錄或多維 TIF。影片格式更難以逐段讀取,通常不支援隨機影格存取或以研究為導向的中繼資料,如果未仔細設定,則會使用有損壓縮。但是影片檔案被廣泛使用,而且它們很容易分享,因此在必要時能夠讀取和寫入它們是很方便的。

讀取影片檔案的工具在安裝和使用上的便利性、磁碟和記憶體的使用量以及跨平台相容性方面各不相同。這是一個實用指南。

7.1. 一種權宜之計:將影片轉換為影像序列#

對於一次性的解決方案,最簡單、最可靠的方法是將影片轉換為一系列依序編號的影像檔案,通常稱為影像序列。然後可以將影像檔案讀入 ImageCollection,透過 skimage.io.imread_collection。將影片轉換為影格可以在 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,那麼這可能是最好的方法。