在这篇文章中,我们将讨论两种基于深度学习的使用光流进行运动估计的方法。FlowNet是第一种用于计算光流的CNN方法,RAFT是目前最先进的估算光流的方法。我们还将看到如何使用作者提供的经过训练的模型来使用PyTorch对新数据进行推理。
在前一篇文章中,我们讨论了估算光流的基本组件和一些算法方法。让我们提醒您,光流任务包括估计两个连续帧之间的逐像素运动。我们的目标是找到稀疏特征集或所有图像像素的位移来计算它们的运动向量。在前一篇文章中,我们回顾了基于OpenCV的理论和实际解决方案,现在我们将继续讨论用于光流估计的深度学习方法。
FlowNet架构作为第一种预测光流的CNN方法,于2015年被引入。CNN体系结构在分类、深度估计和语义分割任务中的成功启发了作者。随着深度学习方法和CNN在解决许多计算机视觉任务上的成功,作者介绍了两种用于光流估计的神经网络。
FlowNetS和FlowNetCorr架构都包含类似于U-Net架构的编码器和解码器部分。编码器从连续的两幅图像中提取特征,解码器对编码器的特征映射进行放大,得到最终的光流预测。让我们更深入地了解FlowNetS和FlowNetCorr网络。
FlowNetS(也称为FlowNetSimple)中的输入数据是两个连续帧的连接。这两张图像连接后是6通道张量,其中前三个通道属于第一张图像,其余三个通道属于第二张图像。编码器部分由几个卷积层和激活函数组成。这种架构允许网络自行决定如何处理两个堆叠的图像,并给出特征图,以进行后续的结果细化。
FlowNetCorr架构每次只接受一帧作为输入,所以在这里图像不会堆叠。在该网络中,作者使用共享权重的CNN第一阶段分别从两幅连续图像中提取特征。下一步是分别从第一帧和第二帧计算出两个特征映射 f 1 和 f 2 \mathbf{f}_1和\mathbf{f}_2 f1和f2。为此,作者介绍了一种叫做相关层的新技术,这是FlowNetS和FlowNetCorr的主要区别。相关性的计算等于卷积运算,但这里没有可训练的核——一个特征映射与另一个特征映射进行卷积。因此,该层对两个特征映射 ( f 1 , f 2 ) (\mathbf{f}_1, \mathbf{f}_2) (f1,f2)进行乘法块比较,并且没有可训练的权值。
对于第一个和第二个特征图,以 x 1 , x 2 x_1, x_2 x1,x2为中心的两个方形块的相关公式分别定义为:
正方形块的大小定义为 K = 2 k + 1 K = 2k+1 K=2k+1。值得一提的是,作者并没有在两个特性图之间做完整的关联,而是在局部上做。
两个特征图匹配完成后,相关结果前向传递到其余卷积层,提取高级特征。下图是FlowNetCorr的架构:
编码器输出的特征图比输入图像分辨率小64倍,所以我们需要上采样结果。这里的策略对于两种架构都是相同的。在解码器阶段使用可训练的上采样卷积层来提升编码器的光流输出。每个解码器阶段将上一阶段的放大结果和编码器相应层的特征图连接起来。使用来自编码器的数据有助于预测细节,就像在U-Net中做的那样。
作者只使用了四个上采样阶段,因为使用更多的阶段提供了质量的边际改善。为了上采样输出到初始图像分辨率,最好使用计算成本较低的双线性上采样。
网络输出是一个具有两个通道的张量,第一个通道由x轴上每个像素的运动位移组成,第二个通道由y轴上每个像素的运动位移组成。因此,我们对图像中的每个像素都有一个运动向量。
多尺度训练损失是FlowNet较好的惩罚策略。最后的预测是放大的小尺度光流,所以小尺度的预测对下一个大尺度的预测有很大的影响。使用影响减少参数为每个解码器阶段计算损失,以获得对每个解码器阶段的精细预测。结果表明,小尺度预测的计算损失对总损失的贡献大于大尺度预测的损失。
每个译码阶段的损失公式是基于预测 V p r e d = ( Δ x p r e d , Δ y p r e d ) V_{pred} =(\Delta x_{pred}, \Delta y_{pred}) Vpred=(Δxpred,Δypred)与真实数据 V g t = ( Δ x g t , Δ y g t ) V_{gt}=(\Delta x_{gt}, \Delta y_{gt}) Vgt=(Δxgt,Δygt)之间的端点误差:
L = ∣ ∣ V g t − V c a l c ∣ ∣ 1 = ( Δ x g t − Δ x c a l c ) 2 + ( Δ y g t − Δ y c a l c ) 2 L = ||V_ {gt} -V_ {calc}||_{1} =\sqrt{(\Delta x_{gt}-\Delta x_{calc})^2+(\Delta y_{gt}-\Delta y_{calc})^2} L=∣∣Vgt−Vcalc∣∣1=(Δxgt−Δxcalc)2+(Δygt−Δycalc)2
根据SINTEL基准,最先进的方法是2020年引入的CNN和RNN架构的组合。这种新方法被称为RAFT(Recurrent All-Pairs Field Transforms)。与前面的架构一样,它也有两种不同的类型——RAFT和RAFT- s。这两种体系结构有一个共同点,但是RAFT- s是RAFT的轻量级版本。在这里,我们将了解RAFT架构的基本组件和思想,然后将其与RAFT- s进行比较。
RAFT可分为三个阶段:
GRU单元输出的光流分辨率为初始图像的1/8,因此作者提出了两种不同的上采样方法来匹配真值分辨率。第一种是光流的双线性插值。它是一种简单快速的方法,但这种方法的质量不如一个可学习名叫Convex Upsampling的上采样模块:
凸上采样(Convex Upsampling)方法表明,全分辨率光流是GRU单元预测的3x3加权网格的凸组合。8 倍图像上采样意味着必须将 1 个像素扩展为 64(8x8) 个像素。凸上采样模块由两个卷积层和末端的softmax激活来预测上采样光流预测中每个新像素的 H / 8 × W / 8 × ( 8 × 8 × 9 ) H/8 \times W/8 \times (8 \times 8 \times 9) H/8×W/8×(8×8×9)掩码。现在,上采样图像上的每个像素都是之前粗糙分辨率像素的凸组合,由预测掩码加权,系数为 w 1 , w 2 , … , w 9 {w_1, w_2,…,w_9} w1,w2,…,w9:
作者认为,该方法预测了更准确的光流输出,特别是在运动边界附近。
损失函数定义为真实值与预测值之间的L1距离,等于FlowNet中的损失。所有上采样的循环单元输出创建一个光流预测序列 f 1 , … , f N {\mathbf{f}_1,…,\mathbf{f}_N} f1,…,fN。
总损失是真实值与上采样预测之间每个循环块输出的损失之和:
L = ∑ i = 1 N γ i − N ∣ ∣ g t − f i ∣ ∣ 1 , γ = 0.8 L = \sum_ {i = 1} ^ N \gamma^ {i-N} | | gt - \mathbf {f} _i | | _1 , \: \: \: \ γ= 0.8 L=∑i=1Nγi−N∣∣gt−fi∣∣1, γ=0.8
如前所述,在原始论文中介绍了两种类型的网络架构——RAFT和RAFT- s。让我们来看看它们的图像比较:
首先,Feature and Context Extractor具有不同数量的通道,RAFT-S中残差单元被瓶颈残差单元代替;其次,RAFT架构在一个block中有两个卷积GRU cell,卷积核分别为1x5和5x1,而RAFT- s只有一个3x3卷积GRU cell。因此,RAFT 的轻量级版本 RAFT-S 具有较少的参数数量,同时在质量方面的准确性稍差。
代码地址:
链接:https://pan.baidu.com/s/1nxrcPzshw3Tp5yFC5dZrZw
提取码:123a
作者还将RAFT架构的实现开源并附加了预训练权重,所以我们可以检查这个体系结构,并使用作者的开发创建推理脚本。要启动演示,您需要下载我们的代码并安装所需的库。所有关于安装的信息都可以在README中找到。
项目的结构组成:
import os
import sys
sys.path.append('RAFT/core')
from argparse import ArgumentParser
from collections import OrderedDict
import cv2
import numpy as np
import torch
from raft import RAFT
from utils import flow_viz
def frame_preprocess(frame, device):
frame = torch.from_numpy(frame).permute(2, 0, 1).float()
frame = frame.unsqueeze(0)
frame = frame.to(device)
return frame
def vizualize_flow(img, flo, save, counter):
# 更换通道和更换设备是必要的
img = img[0].permute(1, 2, 0).cpu().numpy()
flo = flo[0].permute(1, 2, 0).cpu().numpy()
# 将光流映射到RGB图像
flo = flow_viz.flow_to_image(flo)
flo = cv2.cvtColor(flo, cv2.COLOR_RGB2BGR)
# 连接,保存和显示图像
img_flo = np.concatenate([img, flo], axis=0)
if save:
cv2.imwrite(f"demo_frames/frame_{str(counter)}.jpg", img_flo)
cv2.imshow("Optical Flow", img_flo / 255.0)
k = cv2.waitKey(25) & 0xFF
if k == 27:
return False
return True
def get_cpu_model(model):
new_model = OrderedDict()
# 从模型中获取所有层的名称
for name in model:
# 创建新名称并更新新模型
new_name = name[7:]
new_model[new_name] = model[name]
return new_model
def inference(args):
# 得到RAFT模型
model = RAFT(args)
# 加载预训练权重
pretrained_weights = torch.load(args.model)
save = args.save
if save:
if not os.path.exists("demo_frames"):
os.mkdir("demo_frames")
if torch.cuda.is_available():
device = "cuda"
# 并行计算
model = torch.nn.DataParallel(model)
# 将预先训练权重加载到模型中
model.load_state_dict(pretrained_weights)
model.to(device)
else:
device = "cpu"
# 更改CPU运行时的键名
pretrained_weights = get_cpu_model(pretrained_weights)
# 将预先训练权重加载到模型中
model.load_state_dict(pretrained_weights)
# 将模型模式改为评价模式
model.eval()
video_path = args.video
# 捕获视频并获得第一帧
cap = cv2.VideoCapture(video_path)
ret, frame_1 = cap.read()
# 帧预处理
frame_1 = frame_preprocess(frame_1, device)
counter = 0
with torch.no_grad():
while True:
# 读取下一帧
ret, frame_2 = cap.read()
if not ret:
break
# 预处理
frame_2 = frame_preprocess(frame_2, device)
# 预测
flow_low, flow_up = model(frame_1, frame_2, iters=args.iters, test_mode=True)
# 将光流输出转换为numpy数组
ret = vizualize_flow(frame_1, flow_up, save, counter)
if not ret:
break
frame_1 = frame_2
counter += 1
def main():
parser = ArgumentParser()
parser.add_argument("--model", help="restore checkpoint")
parser.add_argument("--iters", type=int, default=12)
parser.add_argument("--video", type=str, default="./videos/car.mp4")
parser.add_argument("--save", action="store_true", help="save demo frames")
parser.add_argument("--small", action="store_true", help="use small model")
parser.add_argument(
"--mixed_precision", action="store_true", help="use mixed precision"
)
args = parser.parse_args()
inference(args)
if __name__ == "__main__":
main()
# python inference.py --model=./models/raft-sintel.pth --video ./videos/crowd.mp4
module
。为了在CPU上运行推理,我们需要删除这个前缀并获得新的适合CPU的模型;现在,我们可以继续我们的演示任务。读取两个连续的帧,然后输出光流预测。然后对光流预测进行编码,转换为BGR图像。为了可视化流程,我们使用标准的RAFT编码器。可视化策略几乎与我们在前一篇文章中使用的HSV相似。在得到BGR格式的光流结果后,我们将图像与光流连接起来进行可视化。光流在视频编辑的稳定化、压缩、慢动作等方面都有广泛的应用。此外,一些跟踪和动作识别系统也使用了光流数据。我们回顾了一些深度学习方法,这些方法在FlowNet和RAFT架构的例子中提高了质量。目前,RAFT 架构在 SINTEL 数据集上显示了最佳结果,在 KITTY 数据集上前三名。
https://learnopencv.com/optical-flow-using-deep-learning-raft/