光流:使用 RAFT 模型预测运动¶
光流是预测两幅图像之间运动的任务,通常是视频的两个连续帧。光流模型以两幅图像作为输入,并预测一个流:该流指示第一幅图像中每个像素的位移,并将其映射到第二幅图像中对应的像素。流是 (2, H, W) 维张量,其中第一个轴对应于预测的水平和垂直位移。
以下示例说明了如何使用 torchvision 来使用我们实现的 RAFT 模型预测流。我们还将了解如何将预测的流转换为 RGB 图像以进行可视化。
import numpy as np
import torch
import matplotlib.pyplot as plt
import torchvision.transforms.functional as F
plt.rcParams["savefig.bbox"] = "tight"
def plot(imgs, **imshow_kwargs):
if not isinstance(imgs[0], list):
# Make a 2d grid even if there's just 1 row
imgs = [imgs]
num_rows = len(imgs)
num_cols = len(imgs[0])
_, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)
for row_idx, row in enumerate(imgs):
for col_idx, img in enumerate(row):
ax = axs[row_idx, col_idx]
img = F.to_pil_image(img.to("cpu"))
ax.imshow(np.asarray(img), **imshow_kwargs)
ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])
plt.tight_layout()
使用 Torchvision 读取视频¶
我们将首先使用 read_video()
读取视频。或者,可以使用新的 VideoReader
API(如果 torchvision 是从源代码构建的)。我们将在此处使用的视频来自 pexels.com,可免费使用,版权归 Pavel Danilyuk 所有。
import tempfile
from pathlib import Path
from urllib.request import urlretrieve
video_url = "https://download.pytorch.org/tutorial/pexelscom_pavel_danilyuk_basketball_hd.mp4"
video_path = Path(tempfile.mkdtemp()) / "basketball.mp4"
_ = urlretrieve(video_url, video_path)
read_video()
返回视频帧、音频帧和与视频关联的元数据。在我们的例子中,我们只需要视频帧。
在这里,我们只会在 2 对预先选择的帧之间进行 2 次预测,即帧 (100, 101) 和 (150, 151)。每一对都对应于一个模型输入。
from torchvision.io import read_video
frames, _, _ = read_video(str(video_path), output_format="TCHW")
img1_batch = torch.stack([frames[100], frames[150]])
img2_batch = torch.stack([frames[101], frames[151]])
plot(img1_batch)
/pytorch/vision/torchvision/io/video.py:169: UserWarning: The pts_unit 'pts' gives wrong results. Please use pts_unit 'sec'.
warnings.warn("The pts_unit 'pts' gives wrong results. Please use pts_unit 'sec'.")
RAFT 模型接受 RGB 图像。我们首先从 read_video()
获取帧,并将它们调整大小以确保其尺寸可被 8 整除。请注意,我们显式地使用 antialias=False
,因为这些模型就是如此训练的。然后,我们使用捆绑在权重中的转换来预处理输入,并将其值重新缩放至所需的 [-1, 1]
区间。
from torchvision.models.optical_flow import Raft_Large_Weights
weights = Raft_Large_Weights.DEFAULT
transforms = weights.transforms()
def preprocess(img1_batch, img2_batch):
img1_batch = F.resize(img1_batch, size=[520, 960], antialias=False)
img2_batch = F.resize(img2_batch, size=[520, 960], antialias=False)
return transforms(img1_batch, img2_batch)
img1_batch, img2_batch = preprocess(img1_batch, img2_batch)
print(f"shape = {img1_batch.shape}, dtype = {img1_batch.dtype}")
shape = torch.Size([2, 3, 520, 960]), dtype = torch.float32
使用 RAFT 估计光流¶
我们将使用来自 raft_large()
的 RAFT 实现,其架构与 原始论文 中描述的架构相同。我们还提供了 raft_small()
模型构建器,它更小,运行速度更快,但牺牲了一点准确性。
from torchvision.models.optical_flow import raft_large
# If you can, run this example on a GPU, it will be a lot faster.
device = "cuda" if torch.cuda.is_available() else "cpu"
model = raft_large(weights=Raft_Large_Weights.DEFAULT, progress=False).to(device)
model = model.eval()
list_of_flows = model(img1_batch.to(device), img2_batch.to(device))
print(f"type = {type(list_of_flows)}")
print(f"length = {len(list_of_flows)} = number of iterations of the model")
Downloading: "https://download.pytorch.org/models/raft_large_C_T_SKHT_V2-ff5fadd5.pth" to /root/.cache/torch/hub/checkpoints/raft_large_C_T_SKHT_V2-ff5fadd5.pth
type = <class 'list'>
length = 12 = number of iterations of the model
RAFT 模型输出预测流的列表,其中每个条目都是 (N, 2, H, W) 预测流批次,对应于模型中的给定“迭代”。有关模型迭代性质的更多详细信息,请参阅 原始论文。在这里,我们只对最终预测的流感兴趣(它们是最准确的),因此我们将只检索列表中的最后一项。
如上所述,流是一个维度为 (2, H, W)(或 (N, 2, H, W) 用于流批次)的张量,其中每个条目对应于每个像素从第一幅图像到第二幅图像的水平和垂直位移。请注意,预测的流以“像素”为单位,它们没有根据图像的尺寸进行归一化。
predicted_flows = list_of_flows[-1]
print(f"dtype = {predicted_flows.dtype}")
print(f"shape = {predicted_flows.shape} = (N, 2, H, W)")
print(f"min = {predicted_flows.min()}, max = {predicted_flows.max()}")
dtype = torch.float32
shape = torch.Size([2, 2, 520, 960]) = (N, 2, H, W)
min = -3.8997151851654053, max = 6.400382995605469
可视化预测流¶
Torchvision 提供了 flow_to_image()
实用程序来将流转换为 RGB 图像。它还支持流批次。流中的每个“方向”都将映射到给定的 RGB 颜色。在下图中,颜色相似的像素被模型认为是在相似的方向上移动。该模型能够正确预测球和球员的运动。尤其要注意第一幅图像中球的不同预测方向(向左移动)和第二幅图像中球的不同预测方向(向上移动)。
from torchvision.utils import flow_to_image
flow_imgs = flow_to_image(predicted_flows)
# The images have been mapped into [-1, 1] but for plotting we want them in [0, 1]
img1_batch = [(img1 + 1) / 2 for img1 in img1_batch]
grid = [[img1, flow_img] for (img1, flow_img) in zip(img1_batch, flow_imgs)]
plot(grid)
额外:创建预测流的 GIF¶
在上面的示例中,我们仅显示了 2 对帧的预测流。应用光流模型的一种有趣方法是在整个视频上运行模型,并从所有预测的流中创建一个新视频。下面是一个可以帮助您开始的代码片段。我们注释掉了代码,因为此示例在没有 GPU 的机器上呈现,运行时间过长。
# from torchvision.io import write_jpeg
# for i, (img1, img2) in enumerate(zip(frames, frames[1:])):
# # Note: it would be faster to predict batches of flows instead of individual flows
# img1, img2 = preprocess(img1, img2)
# list_of_flows = model(img1.to(device), img2.to(device))
# predicted_flow = list_of_flows[-1][0]
# flow_img = flow_to_image(predicted_flow).to("cpu")
# output_folder = "/tmp/" # Update this to the folder of your choice
# write_jpeg(flow_img, output_folder + f"predicted_flow_{i}.jpg")
保存 .jpg 流图像后,可以使用 ffmpeg 将其转换为视频或 GIF,例如:
ffmpeg -f image2 -framerate 30 -i predicted_flow_%d.jpg -loop -1 flow.gif
脚本的总运行时间:(0 分 8.951 秒)