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