NVIDIA TensorRT—推理引擎加速深度学习推理

模型转换工具: https://convertmodel.com/

深度学习的工作流程,如下图所示,可分为训练和推理两个部分。

训练过程通过设定数据处理方式,并设计合适的网络模型结构以及损失函数和优化算法,在此基础上将数据集以小批量的方式(mini-batch)反复进行前向计算并计算损失,然后 反向计算梯度利用特定的优化函数来更新模型,来使得损失函数达到最优的结果。训练过程最重要的就是梯度的计算和反向传播。

而推理就是在训练好的模型结构和参数基础上,做一次前向传播得到模型输出的过程。相对于训练而言,推理不涉及梯度和损失优化。推理的最终目标是将训练好的模型部署生产环境中。

高性能推理引擎的工作项

虽然推理就是数据经过模型的一次前向计算,但是推理是面向不同的终端部署,一般推理需要满足:

  • 精度要求: 推理的精度需要和训练的精度保持一致,
  • 效率要求:性能尽可能的快
  • 异构的推理设备:生产环境因为场景不同,支持不同的设备如TPU,CPU,GPU, NPU等

所以推理框架一般包括模型优化和推理加速,以便于支持高性能的推理要求。

那么一个推理框架要做哪些事情呢?

首先,因为推理框架要支持现有流行的深度学习框架如TensorFlow和Pytorch等,而不同的深度学习框内在的不一致性,就要求推理框架需要有一种同一个表达形式,来统一外部的不一致性,这就需要推理框架外部模型解析和转换为内在形式的功能。

其次,为了追求性能的提升,需要能够对训练好的模型针对特定推理设备进行特定的优化,主要优化可以包括

  • 低精度优化:FP16低精度转换,INT8后训练量化
  • 算子编译优化
  • 内存优化
  • 计算图调度

低精度优化

一般模型训练过程中都是采用FP32或者FP64高精度的方式进行存储模型参数,主要是因为梯度计算更新的可能是很小的一个小数。高精度使得模型更大,并且计算很耗时。而在推理不需要梯度更新,所以通常如果精度从FP32降低到FP16,模型就会变小很多,并且计算量也下降,而相对于模型的推理效果几乎不会有任何的变化,一般都会做FP16的精度裁剪

而FP32如果转换到INT8,推理性能会提高很多,但是裁剪不是直接裁剪,参数变动很多,会影响模型的推理效果,需要做重新的训练,来尽可能保持模型的效果

算子编译优化

我们先来了解下计算图的概念,计算图是由算子和张量构建成一个数据计算流向图,通常深度学习网络都可以看成一个计算图。而推理可以理解成数据从计算图起点到终点的过程。

算子编译优化其中一项优化就是计算图的优化。计算图优化的目标是对计算图进行等价的组合变换,使得减少算子的读写操作提供效率。

最简单的情况,就是算子融合。比如常见Conv+ReLu的两个算子,因为Conv需要做大量卷积计算,需要密集的计算单元支持,而Relu几乎不需要计算,如果Relu算子单独运算,则不仅需要一个计算单元支持其实不需要怎么计算的算子,同时又要对前端的数据进行一次读操作,很浪费资源和增加I/O操作; 此时,可以将Conv和Relu合并融合成一个算子,可以节省I/O访问和带宽开销,也可以节省计算单元。

这种算子融合对于所有推理设备都是支持,是通用的硬件优化。有些是针对特定硬件优化,比如某些硬件的计算单元不支持过大算子输入,此时就需要对算子进行拆解。

计算图的优化可以总结为算子拆解、算子聚合、算子重建,以便达到在硬件设备上更好的性能。

算子编译优化的另一个优化就是数据排布优化。我们知道,在TensorFlow框架的输入格式NHWC,而pytorch是NCHW。这些格式是框架抽象出来的矩阵格式,实际在内存中的存储都是按照1维的形式存储。这就涉及物理存储和逻辑存储之间的映射关系,如何更好的布局数据能带来存储数据的访问是一个优化方向;另外在硬件层面,有些硬件在某种存储下有最佳的性能,通常可以根据硬件的读写特点进行优化。

内存优化

我们推理的时候都需要借助额外的硬件设备来达到高速推理,如GPU,NPU等,此时就需要再CPU和这些硬件设备进行交互;以GPU为例,推理时需要将CPU中的数据copy到GPU显存中,然后进行模型推理,推理完成后的数据是在GPU显存中,此时又需要将GPU显存中的数据copy回cpu中。

这个过程就涉及到存储设备的申请、释放以及内存对齐等操作,而这部分也是比较耗时的。

因此内存优化的方向,通常是减少频繁的设备内存空间的申请和尽量做到内存的复用。

一般的,可以根据张量生命周期来申请空间:

  • 静态内存分配:比如一些固定的算子在整个计算图中都会使用,此时需要再模型初始化时一次性申请完内存空间,在实际推理时不需要频繁申请操作,提高性能
  • 动态内存分配:对于中间临时的内存需求,可以进行临时申请和释放,节省内存使用,提高模型并发能力
  • 内存复用:对于同一类同一个大小的内存形式,又满足临时性,可以复用内存地址,减少内存申请。

计算图调度

在计算图中,存在某些算子是串行依赖,而某些算子是不依赖性;这些相互独立的子计算图,就可以进行并行计算,提高推理速度,这就是计算图的调度。

TensorRT

我们讲解了推理引擎的一般工作流程和优化思路,这一部分介绍一个具体的推理引擎框架:TensorRT。NVIDIA TensorRT 是一个用于深度学习推理的 SDK 。 TensorRT 提供了 API 和解析器,可以从所有主要的深度学习框架中导入经过训练的模型。然后,它生成可在数据中心以及汽车和嵌入式环境中部署的优化运行时引擎。TensorRT是NVIDIA出品的针对深度学习的高性能推理SDK。

目前,TensorRT只支持NVIDIA自家的设备的推理服务,如服务器GPUTesla v100、NVIDIA GeForce系列以及支持边缘的NVIDIA Jetson等。

TensorRT通过将现有深度学习框架如TensorFlow、mxnet、pytorch、caffe2以及theano等训练好的模型进行转换和优化,并生成TensorRT的运行时(Runtime Engine),利用TensorRT提供的推理接口(支持不同前端语言如c++/python等),部署不同的NVIDIA GPU设备上,提供高性能人工智能的服务。

在性能方面,TensorRT在自家的设备上提供了优越的性能:

对于TensorRT而言,主要优化如下:

  • 算子和张量的融合 Layer & Tensor Fusion

以上面Inception模块的计算图为例子,左边是未优化原始的结构图,右边是经过TensorRT优化过的计算图。优化的目标是减少GPU核数的使用,以便于减少GPU核计算需要的数据读写,提高GPU核数的计算效率

  • 首先是合并conv+bias+relu为一个CBR模块,减少2/3 核的使用
  • 然后是对于同一输入1x1conv,合并为一个大的CBR,输出保持不变,减少了2次的相同数据的读写
  • 有没有发现还少了一个concat层,这个是怎么做到的?concat操作可以理解为数据的合并,TensorRT采用预先先申请足够的缓存,直接把需要concat的数据放到相应的位置就可以达到concat的效果。

经过优化,使得整个模型层数更少,占用更少GPU核,运行效率更快。

  • 精度裁剪 Precision Calibration
    这个是所有推理引擎都有部分,TensorRT支持低精度FP16和INT8的模型精度裁剪,在尽量不降低模型性能的情况,通过裁剪精度,降低模型大小,提供推理速度。但需要注意的是:不一定FP16就一定比FP32的要快。这取决于设备的不同精度计算单元的数量,比如在GeForce 1080Ti设备上由于FP16的计算单元要远少于FP32的,裁剪后反而效率降低,而GeForce 2080Ti则相反。
  • Dynamic Tensor Memory: 这属于提高内存利用率
  • Multi-Stream Execution: 这属于内部执行进程控制,支持多路并行执行,提供效率
  • Auto-Tuning 可理解为TensorRT针对NVIDIA GPU核,设计有针对性的GPU核优化模型,如上面所说的算子编译优化。

 TensorRT安装

了解了TensorRT是什么和如何做优化,我们实际操作下TensorRT, 先来看看TensorRT的安装。

TensorRT是针对NVIDIA GPU的推理引擎,所以需要CUDA和cudnn的支持,需要注意版本的对应关系; 以TensorRT 7.1.3.4为例,需要至少CUDA10.2和cudnn 8.x。

本质上 TensorRT的安装包就是动态库文件(CUDA和cudnn也是如此),需要注意的是TensorRT提供的模型转换工具。

下载可参考

rpm -i cuda-repo-rhel7-10-2-local-10.2.89-440.33.01-1.0-1.x86_64.rpm
tar -zxvf cudnn-10.2-linux-x64-v8.0.1.13.tgz
# tar -xzvf TensorRT-${version}.Linux.${arch}-gnu.${cuda}.${cudnn}.tar.gz
tar -xzvf TensorRT-7.1.3.4.CentOS-7.6.x86_64-gnu.cuda-10.2.cudnn8.0.tar.gz

TensorRT也提供了python版本(底层还是c的动态库)

#1.创建虚拟环境 tensorrt
  conda create -n tensorrt python=3.6
  
  #安装其他需要的工具包, 按需包括深度学习框架
  pip install keras,opencv-python,numpy,tensorflow-gpu==1.14,pytorch,torchvision
 
#2. 安装pycuda
  #首先使用nvcc确认cuda版本是否满足要求: nvcc -V
  pip install 'pycuda>=2019.1.1'
       
#3. 安装TensorRT
  # 下载解压的tar包
  tar -xzvf TensorRT-7.1.3.4.CentOS-7.6.x86_64-gnu.cuda-10.2.cudnn8.0.tar.gz
  
  #解压得到 TensorRT-7.1.3.4的文件夹,将里面lib绝对路径添加到环境变量中
  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/TensorRT-7.1.3.4/lib
  
  #安装TensorRT
  cd TensorRT-7.1.3.4/python
  pip install pip install tensorrt-7.1.3.4-cp36-none-linux_x86_64.whl
 
#4.安装UFF
  cd TensorRT-7.1.3.4/uff
  pip install uff-0.6.9-py2.py3-none-any.whl
 
#5. 安装graphsurgeon
  cd TensorRT-7.1.3.4/graphsurgeon
  pip install uff-0.6.9-py2.py3-none-any.whl
 
#6. 环境测试
  #进入python shell,导入相关包没有报错,则安装成功
  import tensorrt
  import uff

安装完成后,在该路径的samples/python给了很多使用tensorrt的python接口进行推理的例子(图像分类、目标检测等),以及如何使用不同的模型解析接口(uff,onnx,caffe)。

另外给了一个common.py文件,封装了tensorrt如何为engine分配显存,如何进行推理等操作,我们可以直接调用该文件内的相关函数进行tensorrt的推理工作。

TensorRT工作流程

在安装TensorRT之后,如何使用TensorRT呢?我们先来了解下TensorRT的工作流程

总体流程可以拆分成两块:

  • 模型转换
    TensorRT需要将不同训练框架训练出来的模型,转换为TensorRT支持的中间表达(IR),并做计算图的优化等,并序列化生成plan文件。
  • 模型推理:在模型转换好后之后,在推理时,需要加plan文件进行反序列化加载模型,并通过TensorRT运行时进行模型推理,输出结果

模型转换

由于不同的深度学习框架的实现逻辑不同,TensorRT在转换模型时采用不同适配方法。以当前最流行深度学习框架TensorFlow和Pytorch为例为例。

由于pytorch采用动态的计算图,也就是没有图的概念,需要借助ONNX生成静态图。

Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移.最初的ONNX专注于推理(评估)所需的功能。 ONNX解释计算图的可移植,它使用graph的序列化格式

pth 转换为onnx

import onnx
import torch
def export_onnx(onnx_model_path, model, cuda, height, width, dummy_input=None):
    model.eval()
    if dummy_input is None:
        dummy_input = torch.randn(1, 3, height, width).float()
    dummy_input.requires_grad = True
    print("dummy_input shape: ", dummy_input.shape, dummy_input.requires_grad)

    if cuda:
        dummy_input = dummy_input.cuda()

    torch.onnx.export(
        model,  # model being run
        dummy_input,  # model input (or a tuple for multiple inputs)
        onnx_model_path,  # where to save the model (can be a file or file-like object)
        export_params=True,  # store the trained parameter weights inside the model file
        opset_version=10,  # the ONNX version to export the model to
        do_constant_folding=True,  # whether to execute constant folding for optimization
        verbose=True,
        input_names=['input'],  # the model's input names
        output_names=['output'],  # the model's output names
    )

从上可知,onnx通过pytorch模型完成一次模型输入和输出的过程来遍历整个网络的方式来构建完成的计算图的中间表示。

这里需要注意三个重要的参数:

  • opset_version: 这个是onnx支持的op算子的集合的版本,因为onnx目标是在不同深度学习框架之间做模型转换的中间格式,理论上onnx应该支持其他框架的所有算子,但是实际上onnx支持的算子总是滞后的,所以需要知道那个版本支持什么算子,如果转换存在问题,大部分当前的版本不支持需要转换的算子。
  • input_names:模型的输入,如果是多个输入,用列表的方式表示,如[“input”, “scale”]
  • output_names: 模型的输出, 多个输出,通input_names

onnx转换为plan engine模型

这里给出的通过TensorRT的python接口来完成onnx到plan engine模型的转换。

import tensorrt as trt
def build_engine(onnx_path):
          EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
        with trt.Builder(TRT_LOGGER) as builder, builder.create_network(EXPLICIT_BATCH) as network, trt.OnnxParser(network, TRT_LOGGER) as parser:
            builder.max_batch_size = 128
            builder.max_workspace_size = 1<<15
            builder.fp16_mode = True
            builder.strict_type_constraints = True
            with open(onnx_path, 'rb') as model:
                parser.parse(model.read())
            # Build and return an engine.
            return builder.build_cuda_engine(network)

从上面的转换过程可知,TensortRT的转换涉及到几个关键的概念:builder 、 network 、parser

  • builder:TensorRT构建器,在构建器中设置模型,解析器和推理的参数设置等 trt.Builder(TRT_LOGGER)
  • network: TensorRT能识别的模型结构(计算图)
  • parser:这里是指解析onnx模型结构(计算图)

从总体上看,TensorRT的转换模型是,将onnx的模型结构(以及参数)转换到TensorRT的network中,同时设置模型推理和优化的参数(如精度裁剪等)。 用一张图来总结下上述过程:

保存engine和读取engine

#解析模型,构建engine并保存
with build_engine(onnx_path) as engine:
    with open(engine_path, "wb") as f:
        f.write(engine.serialize())

#直接加载engine   
with open(engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
    engine = runtime.deserialize_cuda_engine(f.read())

TensorFlow / Keras

TensorFlow或者Keras(后台为TensorFlow)采用的是静态的计算图,本身就有图的完整结构,一般模型训练过程会保留ckpt格式,有很多冗余的信息,需要转换为pb格式。针对TensorFlow,TensorRT提供了两种转换方式,一种是pb直接转换,这种方式加速效果有限所以不推荐;另一种是转换uff格式,加速效果明显。

  • 转换为pb
from tensorflow.python.framework import graph_io
from tensorflow.python.framework import graph_util
from tensorflow.python.platform import gfile
# 设置输出节点为固定名称
OUTPUT_NODE_PREFIX = 'output_'
NUMBER_OF_OUTPUTS = 1
#输入和输出节点名称
output_names = ['output_']
input_names = ['input_']
input_tensor_name = input_names[0] + ":0"
output_tensor_name = output_names[0] + ":0"

def keras_to_pb(model_path, pb_path):
    K.clear_session()#可以保持输入输出节点的名称每次执行都一致
    K.set_learning_phase(0)
    sess = K.get_session()
    try:
        model = load_model(model_path)# h5 model file_path
    except ValueError as err:
        print('Please check the input saved model file')
        raise err

    output = [None]*NUMBER_OF_OUTPUTS
    output_node_names = [None]*NUMBER_OF_OUTPUTS
    for i in range(NUMBER_OF_OUTPUTS):
        output_node_names[i] = OUTPUT_NODE_PREFIX+str(i)
        output[i] = tf.identity(model.outputs[i], name=output_node_names[i])
    
    try:
        frozen_graph = graph_util.convert_variables_to_constants(sess, sess.graph.as_graph_def(), output_node_names)
        graph_io.write_graph(frozen_graph, os.path.dirname(pb_path), os.path.basename(pb_path), as_text=False)
        print('Frozen graph ready for inference/serving at {}'.format(pb_path))
    except:
        print("error !")
  • pb 到uff

采用TensorRT提供的uff模块的from_tensorflow_frozen_model()将pb格式模型转换成uff格式模型

import uff
def pb_to_uff(pb_path, uff_path, output_names):
        uff_model = uff.from_tensorflow_frozen_model(pb_path, output_names, output_filename=uff_path)

uff转换成plan engine模型

import tensorrt as trt

TRT_LOGGER = trt.Logger(trt.Logger.INFO)
img_size_tr = (3,224,224) #CHW
input_names = ['input_0']
output_names = ['output_0']

def build_engine(uff_path):
    with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.UffParser() as parser:
        builder.max_batch_size = 128 #must bigger than batch_size
        builder.max_workspace_size =1<<15  #cuda buffer size
        builder.fp16_mode = True  #set dtype: fp32, fp16, int8
        builder.strict_type_constraints = True
        # Parse the Uff Network
        parser.register_input(input_names[0], img_size_tr)#NCHW
        parser.register_output(output_names[0])
        parser.parse(uff_path, network)
        # Build and return an engine.
        return builder.build_cuda_engine(network)

在绑定完输入输出节点之后,parser.parse()可以解析uff格式文件,并保存相应网络到network。而后通过builder.build_cuda_engine()得到可以直接在cuda执行的engine文件。该engine文件的构建需要一定时间,可以保存下来,下次直接加载该文件,而不需要解析模型后再构建。

TensorFlow的模型转换基本和onnx是一样的,主要是解析器不一样是UffParser。

#解析模型,构建engine并保存
with build_engine(uff_path) as engine:
    with open(engine_path, "wb") as f:
        f.write(engine.serialize())

#直接加载engine   
with open(engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
    engine = runtime.deserialize_cuda_engine(f.read())

模型推理

通过TensorRT的模型转换后,外部训练好的模型都被TensorRT统一成TensorRT可识别的engine文件(并优化过)。在推理时,只要通过TensorRT的推理SDK就可以完成推理。

具体的推理过程如下:

  • 通过TensorRT运行时,加载转换好的engine
  • 推理前准备:(1)在CPU中处理好输入(如读取数据和标准化等)(2)利用TensorRT的推理SDK中common模块进行输入和输出GPU显存分配
  • 执行推理:(1)将CPU的输入拷贝到GPU中 (2)在GPU中进行推理,并将模型输出放入GPU显存中
  • 推理后处理:(1)将输出从GPU显存中拷贝到CPU中 (2)在CPU中进行其他后处理
import common
import numpy as np
import cv2
import tensorrt as trt

def inference_test(engine_path, img_file):

    # process input
    input_image = cv2.imread(img_file)
    input_image = input_image[..., ::-1] / 255.0
    input_image = np.expand_dims(input_image, axis=0)
    
    input_image = input_image.transpose((0, 3, 1, 2))  # NCHW for pytorch
    input_image = input_image.reshape(1, -1)  # .ravel()
        
    # infer
    batch_size = 1
    TRT_LOGGER = trt.Logger(trt.Logger.INFO)
    with open(engine_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
        engine = runtime.deserialize_cuda_engine(f.read())
        # Allocate buffers and create a CUDA stream
        inputs, outputs, bindings, stream = common.allocate_buffers(engine, batch_size)
        # Contexts are used to perform inference.
        with engine.create_execution_context() as context:
             np.copyto(inputs[0].host, input_image)
             [output] = common.do_inference(context, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream, batch_size=batch_size)

TensorRT进阶和缺点

前面较全面了介绍了TensorRT的特点(优点)和工作流程;希望能感受到TensorRT的魅力所在。

在实际代码中主要是通过python的接口来讲解,TensorRT也提供了C++的转换和推理方式,但是主要的关键概念是一样

那TensorRT有什么局限性吗?

首先,TensorRT只支持NVIDIA自家的设备,并根据自家设备的特点,做了很多的优化,如果是其他设备,TensorRT就不适用了。这时候可以考虑其他的推理框架,比如以推理编译为基础的TVM, 针对移动平台推理NCNN,MACE、MNN以及TFLite等,以及针对Intel CPU的OPENVINO。

其次,算子的支持程度;这几乎是所有第三方推理框架都遇到的问题,TensorRT在某些不支持的算子的情况下,TensorRT提供了plugin的方式,plugin提供了标准接口,允许自己开发新的算子,并以插件的方式加入TensorRT(后面会专门介绍,欢迎关注)。

总结

  • 训练需要前向计算和反向梯度更新,推理只需要前向计算
  • 推理框架优化:低精度优化、算子编译优化、内存优化、计算图调度
  • TensorRT是针对NVIDIA设备的高性能推理框架
  • TensorRT工作流程包括模型转换和模型推理
  • 针对Pytorch, TensorRT模型转换链路为:pth->onnx->trt plan
  • 针对TensorFlow,TensorRT模型转换链路为:ckpt->pb->uff->trt plan
  • TensorRT模型转换关键点为build,network和parse
  • TensorRT模型推理关键点为:tensorrt runtime,engine context,显存操作和推理

ONNX—-模型部署教程(1)

转自:mmdeploy

模型转换工具 https://convertmodel.com/

官网1:https://pytorch.org/docs/stable/onnx.html#functions

官网2:https://onnxruntime.ai/docs/get-started/

前言

OpenMMLab 的算法如何部署?是很多社区用户的困惑。而模型部署工具箱 MMDeploy 的开源,强势打通了从算法模型到应用程序这 “最后一公里”!

今天我们将开启模型部署入门系列教程,在模型部署开源库 MMDeploy 的辅助下,介绍以下内容:

  • 中间表示 ONNX 的定义标准
  • PyTorch 模型转换到 ONNX 模型的方法
  • 推理引擎 ONNX Runtime、TensorRT 的使用方法
  • 部署流水线 PyTorch – ONNX – ONNX Runtime/TensorRT 的示例及常见部署问题的解决方法
  • MMDeploy C/C++ 推理 SDK

希望通过本系列教程,带领大家学会如何把自己的 PyTorch 模型部署到 ONNX Runtime/TensorRT 上,并学会如何把 OpenMMLab 开源体系中各个计算机视觉任务的模型用 MMDeploy 部署到各个推理引擎上。

初识模型部署

在软件工程中,部署指把开发完毕的软件投入使用的过程,包括环境配置、软件安装等步骤。类似地,对于深度学习模型来说,模型部署指让训练好的模型在特定环境中运行的过程。相比于软件部署,模型部署会面临更多的难题:

1)运行模型所需的环境难以配置。深度学习模型通常是由一些框架编写,比如 PyTorch、TensorFlow。由于框架规模、依赖环境的限制,这些框架不适合在手机、开发板等生产环境中安装。

2)深度学习模型的结构通常比较庞大,需要大量的算力才能满足实时运行的需求。模型的运行效率需要优化。

因为这些难题的存在,模型部署不能靠简单的环境配置与安装完成。经过工业界和学术界数年的探索,模型部署有了一条流行的流水线:

为了让模型最终能够部署到某一环境上,开发者们可以使用任意一种深度学习框架来定义网络结构,并通过训练确定网络中的参数。之后,模型的结构和参数会被转换成一种只描述网络结构的中间表示,一些针对网络结构的优化会在中间表示上进行。最后,用面向硬件的高性能编程框架(如 CUDA,OpenCL)编写,能高效执行深度学习网络中算子的推理引擎会把中间表示转换成特定的文件格式,并在对应硬件平台上高效运行模型。

这一条流水线解决了模型部署中的两大问题:使用对接深度学习框架和推理引擎的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。

中间表示 – ONNX

在介绍 ONNX 之前,我们先从本质上来认识一下神经网络的结构。神经网络实际上只是描述了数据计算的过程,其结构可以用计算图表示。比如 a+b 可以用下面的计算图来表示:

为了加速计算,一些框架会使用对神经网络“先编译,后执行”的静态图来描述网络。静态图的缺点是难以描述控制流(比如 if-else 分支语句和 for 循环语句),直接对其引入控制语句会导致产生不同的计算图。比如循环执行 n 次 a=a+b,对于不同的 n,会生成不同的计算图:

ONNX (Open Neural Network Exchange)是 Facebook 和微软在2017年共同发布的,用于标准描述计算图的一种格式。目前,在数家机构的共同维护下,ONNX 已经对接了多种深度学习框架和多种推理引擎。因此,ONNX 被当成了深度学习框架到推理引擎的桥梁,就像编译器的中间语言一样。由于各框架兼容性不一,我们通常只用 ONNX 表示更容易部署的静态图。

创建 PyTorch 模型

让我们用 PyTorch 实现一个超分辨率模型,并把模型部署到 ONNX Runtime 这个推理引擎上。

# 安装 ONNX Runtime, ONNX, OpenCV 
pip install onnxruntime onnx opencv-python

在一切都配置完毕后,用下面的代码来创建一个经典的超分辨率模型 SRCNN。

import os 
 
import cv2 
import numpy as np 
import requests 
import torch 
import torch.onnx 
from torch import nn 
 
class SuperResolutionNet(nn.Module): 
    def __init__(self, upscale_factor): 
        super().__init__() 
        self.upscale_factor = upscale_factor 
        self.img_upsampler = nn.Upsample( 
            scale_factor=self.upscale_factor, 
            mode='bicubic', 
            align_corners=False) 
 
        self.conv1 = nn.Conv2d(3,64,kernel_size=9,padding=4) 
        self.conv2 = nn.Conv2d(64,32,kernel_size=1,padding=0) 
        self.conv3 = nn.Conv2d(32,3,kernel_size=5,padding=2) 
 
        self.relu = nn.ReLU() 
 
    def forward(self, x): 
        x = self.img_upsampler(x) 
        out = self.relu(self.conv1(x)) 
        out = self.relu(self.conv2(out)) 
        out = self.conv3(out) 
        return out 
 
# Download checkpoint and test image 
urls = ['https://download.openmmlab.com/mmediting/restorers/srcnn/srcnn_x4k915_1x16_1000k_div2k_20200608-4186f232.pth', 
    'https://raw.githubusercontent.com/open-mmlab/mmediting/master/tests/data/face/000001.png'] 
names = ['srcnn.pth', 'face.png'] 
for url, name in zip(urls, names): 
    if not os.path.exists(name): 
        open(name, 'wb').write(requests.get(url).content) 
 
def init_torch_model(): 
    torch_model = SuperResolutionNet(upscale_factor=3) 
 
    state_dict = torch.load('srcnn.pth')['state_dict'] 
 
    # Adapt the checkpoint 
    for old_key in list(state_dict.keys()): 
        new_key = '.'.join(old_key.split('.')[1:]) 
        state_dict[new_key] = state_dict.pop(old_key) 
 
    torch_model.load_state_dict(state_dict) 
    torch_model.eval() 
    return torch_model 
 
model = init_torch_model() 
input_img = cv2.imread('face.png').astype(np.float32) 
 
# HWC to NCHW 
input_img = np.transpose(input_img, [2, 0, 1]) 
input_img = np.expand_dims(input_img, 0) 
 
# Inference 
torch_output = model(torch.from_numpy(input_img)).detach().numpy() 
 
# NCHW to HWC 
torch_output = np.squeeze(torch_output, 0) 
torch_output = np.clip(torch_output, 0, 255) 
torch_output = np.transpose(torch_output, [1, 2, 0]).astype(np.uint8) 
 
# Show image 
cv2.imwrite("face_torch.png", torch_output)

SRCNN 先把图像上采样到对应分辨率,再用 3 个卷积层处理图像。为了方便起见,我们跳过训练网络的步骤,直接下载模型权重(由于 MMEditing 中 SRCNN 的权重结构和我们定义的模型不太一样,我们修改了权重字典的 key 来适配我们定义的模型),同时下载好输入图片。为了让模型输出成正确的图片格式,我们把模型的输出转换成 HWC 格式,并保证每一通道的颜色值都在 0~255 之间。如果脚本正常运行的话,一幅超分辨率的人脸照片会保存在 “face_torch.png” 中。

在 PyTorch 模型测试正确后,我们来正式开始部署这个模型。我们下一步的任务是把 PyTorch 模型转换成用中间表示 ONNX 描述的模型。

让我们用下面的代码来把 PyTorch 的模型转换成 ONNX 格式的模型:

x = torch.randn(1, 3, 256, 256) 
 
with torch.no_grad(): 
    torch.onnx.export( 
        model, 
        x, 
        "srcnn.onnx", 
        opset_version=11, 
        input_names=['input'], 
        output_names=['output'])

其中,torch.onnx.export 是 PyTorch 自带的把模型转换成 ONNX 格式的函数。让我们先看一下前三个必选参数:前三个参数分别是要转换的模型、模型的任意一组输入、导出的 ONNX 文件的文件名。转换模型时,需要原模型和输出文件名是很容易理解的,但为什么需要为模型提供一组输入呢?这就涉及到 ONNX 转换的原理了。从 PyTorch 的模型到 ONNX 的模型,本质上是一种语言上的翻译。直觉上的想法是像编译器一样彻底解析原模型的代码,记录所有控制流。但前面也讲到,我们通常只用 ONNX 记录不考虑控制流的静态图。因此,PyTorch 提供了一种叫做追踪(trace)的模型转换方法:给定一组输入,再实际执行一遍模型,即把这组输入对应的计算图记录下来,保存为 ONNX 格式。export 函数用的就是追踪导出方法,需要给任意一组输入,让模型跑起来。我们的测试图片是三通道,256×256大小的,这里也构造一个同样形状的随机张量。

剩下的参数中,opset_version 表示 ONNX 算子集的版本。深度学习的发展会不断诞生新算子,为了支持这些新增的算子,ONNX会经常发布新的算子集,目前已经更新15个版本。我们令 opset_version = 11,即使用第11个 ONNX 算子集,是因为 SRCNN 中的 bicubic (双三次插值)在 opset11 中才得到支持。剩下的两个参数 input_names, output_names 是输入、输出 tensor 的名称,我们稍后会用到这些名称。

如果上述代码运行成功,目录下会新增一个”srcnn.onnx”的 ONNX 模型文件。我们可以用下面的脚本来验证一下模型文件是否正确。

import onnx 
 
onnx_model = onnx.load("srcnn.onnx") 
try: 
    onnx.checker.check_model(onnx_model) 
except Exception: 
    print("Model incorrect") 
else: 
    print("Model correct")

其中,onnx.load 函数用于读取一个 ONNX 模型。onnx.checker.check_model 用于检查模型格式是否正确,如果有错误的话该函数会直接报错。我们的模型是正确的,控制台中应该会打印出”Model correct”。

接下来,让我们来看一看 ONNX 模型具体的结构是怎么样的。我们可以使用 Netron (开源的模型可视化工具)来可视化 ONNX 模型。把 srcnn.onnx 文件从本地的文件系统拖入网站,即可看到如下的可视化结果:

点击 input 或者 output,可以查看 ONNX 模型的基本信息,包括模型的版本信息,以及模型输入、输出的名称和数据类型。

点击某一个算子节点,可以看到算子的具体信息。比如点击第一个 Conv 可以看到:

每个算子记录了算子属性、图结构、权重三类信息。

  • 算子属性信息即图中 attributes 里的信息,对于卷积来说,算子属性包括了卷积核大小(kernel_shape)、卷积步长(strides)等内容。这些算子属性最终会用来生成一个具体的算子。
  • 图结构信息指算子节点在计算图中的名称、邻边的信息。对于图中的卷积来说,该算子节点叫做 Conv_2,输入数据叫做 11,输出数据叫做 12。根据每个算子节点的图结构信息,就能完整地复原出网络的计算图。
  • 权重信息指的是网络经过训练后,算子存储的权重信息。对于卷积来说,权重信息包括卷积核的权重值和卷积后的偏差值。点击图中 conv1.weight, conv1.bias 后面的加号即可看到权重信息的具体内容。

现在,我们有了 SRCNN 的 ONNX 模型。让我们看看最后该如何把这个模型运行起来。

推理引擎 -ONNX Runtime

ONNX Runtime 是由微软维护的一个跨平台机器学习推理加速器,也就是我们前面提到的”推理引擎“。ONNX Runtime 是直接对接 ONNX 的,即 ONNX Runtime 可以直接读取并运行 .onnx 文件, 而不需要再把 .onnx 格式的文件转换成其他格式的文件。也就是说,对于 PyTorch – ONNX – ONNX Runtime 这条部署流水线,只要在目标设备中得到 .onnx 文件,并在 ONNX Runtime 上运行模型,模型部署就算大功告成了。

通过刚刚的操作,我们把 PyTorch 编写的模型转换成了 ONNX 模型,并通过可视化检查了模型的正确性。最后,让我们用 ONNX Runtime 运行一下模型,完成模型部署的最后一步。

ONNX Runtime 提供了 Python 接口。接着刚才的脚本,我们可以添加如下代码运行模型:

import onnxruntime 
 
ort_session = onnxruntime.InferenceSession("srcnn.onnx") 
ort_inputs = {'input': input_img} 
ort_output = ort_session.run(['output'], ort_inputs)[0] 
 
ort_output = np.squeeze(ort_output, 0) 
ort_output = np.clip(ort_output, 0, 255) 
ort_output = np.transpose(ort_output, [1, 2, 0]).astype(np.uint8) 
cv2.imwrite("face_ort.png", ort_output)

这段代码中,除去后处理操作外,和 ONNX Runtime 相关的代码只有三行。让我们简单解析一下这三行代码。onnxruntime.InferenceSession用于获取一个 ONNX Runtime 推理器,其参数是用于推理的 ONNX 模型文件。推理器的 run 方法用于模型推理,其第一个参数为输出张量名的列表,第二个参数为输入值的字典。其中输入值字典的 key 为张量名,value 为 numpy 类型的张量值。输入输出张量的名称需要和torch.onnx.export 中设置的输入输出名对应。

如果代码正常运行的话,另一幅超分辨率照片会保存在”face_ort.png”中。这幅图片和刚刚得到的”face_torch.png”是一模一样的。这说明 ONNX Runtime 成功运行了 SRCNN 模型,模型部署完成了!以后有用户想实现超分辨率的操作,我们只需要提供一个 “srcnn.onnx” 文件,并帮助用户配置好 ONNX Runtime 的 Python 环境,用几行代码就可以运行模型了。或者还有更简便的方法,我们可以利用 ONNX Runtime 编译出一个可以直接执行模型的应用程序。我们只需要给用户提供 ONNX 模型文件,并让用户在应用程序选择要执行的 ONNX 模型文件名就可以运行模型了。

总结

  • 模型部署,指把训练好的模型在特定环境中运行的过程。模型部署要解决模型框架兼容性差和模型运行速度慢这两大问题。
  • 模型部署的常见流水线是“深度学习框架-中间表示-推理引擎”。其中比较常用的一个中间表示是 ONNX。
  • 深度学习模型实际上就是一个计算图。模型部署时通常把模型转换成静态的计算图,即没有控制流(分支语句、循环语句)的计算图。
  • PyTorch 框架自带对 ONNX 的支持,只需要构造一组随机的输入,并对模型调用 torch.onnx.export 即可完成 PyTorch 到 ONNX 的转换。
  • 推理引擎 ONNX Runtime 对 ONNX 模型有原生的支持。给定一个 .onnx 文件,只需要简单使用 ONNX Runtime 的 Python API 就可以完成模型推理。

关于模型部署

当我们千辛万苦完成了前面的数据获取、数据清洗、模型训练、模型评估等等步骤之后,终于等到“上线”啦。想到辛苦训练出来的模型要被调用还有点小激动呢,可是真当下手的时候就有点懵了:模型要怎么部署?部署在哪里?有什么限制或要求?

模型训练重点关注的是如何通过训练策略来得到一个性能更好的模型,其过程似乎包含着各种“玄学”,被戏称为“炼丹”。整个流程包含从训练样本的获取(包括数据采集与标注),模型结构的确定,损失函数和评价指标的确定,到模型参数的训练,这部分更多是业务方去承接相关工作。一旦“炼丹”完成(即训练得到了一个指标不错的模型),如何将这颗“丹药”赋能到实际业务中,充分发挥其能力,这就是部署方需要承接的工作。

目前来说,我还没有真正的接触工业界的模型应用,仅仅只是在学术界进行模型训练和探索。部署只是简单的将模型推理代码直接部署在服务器上,因此也没有考虑过模型在工业界的部署,对于工业应用来说,模型的表现和推理速度同等重要,因此,如何提高模型的推理速度成为模型能否落地的关键因素。

部署流程大致分为以下几个步骤:模型转换、模型量化压缩、模型打包封装 SDK。这里我们主要探讨模型转换。

模型转换主要用于模型在不同框架之间的流转,常用于训练和推理场景的连接。目前主流的框架都以 ONNX 或者 caffe 为模型的交换格式,另外,根据需要,还可以在中间插入计算图优化,对计算机进行推理加速(诸如常见的 CONV/BN 的算子融合)。

模型部署

在软件工程中,部署指把开发完毕的软件投入使用的过程,包括环境配置、软件安装等步骤。类似地,对于深度学习模型来说,模型部署指让训练好的模型在特定环境中运行的过程。相比于软件部署,模型部署会面临更多的难题:

1)运行模型所需的环境难以配置。深度学习模型通常是由一些框架编写,比如 PyTorch、TensorFlow。由于框架规模、依赖环境的限制,这些框架不适合在手机、开发板等生产环境中安装。

2)深度学习模型的结构通常比较庞大,需要大量的算力才能满足实时运行的需求。模型的运行效率需要优化。

因为这些难题的存在,模型部署不能靠简单的环境配置与安装完成。经过工业界和学术界数年的探索,模型部署有了一条流行的流水线:

为了让模型最终能够部署到某一环境上,开发者们可以使用任意一种深度学习框架来定义网络结构,并通过训练确定网络中的参数。之后,模型的结构和参数会被转换成一种只描述网络结构的中间表示,一些针对网络结构的优化会在中间表示上进行。最后,用面向硬件的高性能编程框架(如 CUDA,OpenCL)编写,能高效执行深度学习网络中算子的推理引擎会把中间表示转换成特定的文件格式,并在对应硬件平台上高效运行模型。

这一条流水线解决了模型部署中的两大问题:使用对接深度学习框架和推理引擎的中间表示,开发者不必担心如何在新环境中运行各个复杂的框架;通过中间表示的网络结构优化和推理引擎对运算的底层优化,模型的运算效率大幅提升。

MonoViT—基于ViT的自监督单目深度估计

Self-Supervised Monocular Depth Estimation witha Vision Transformer

paper: https://arxiv.org/pdf/2208.03543.pdf

基于深度学习单目深度估计任务简介

深度估计是计算机视觉领域的一个基础性问题,其可以应用在机器人导航、增强现实、三维重建、自动驾驶等领域。而目前大部分深度估计都是基于二维RGB图像到RBG-D图像的转化估计,主要包括从图像明暗、不同视角、光度、纹理信息等获取场景深度形状的Shape from X方法,还有结合SFM(Structure from motion)和SLAM(Simultaneous Localization And Mapping)等方式预测相机位姿的算法。其中虽然有很多设备可以直接获取深度,但是设备造价昂贵。也可以利用双目进行深度估计,但是由于双目图像需要利用立体匹配进行像素点对应和视差计算,所以计算复杂度也较高,尤其是对于低纹理场景的匹配效果不好。而单目深度估计则相对成本更低,更容易普及。

那么对于单目深度估计,顾名思义,就是利用一张或者唯一视角下的RGB图像,估计图像中每个像素相对拍摄源的距离。对于人眼来说,由于存在大量的先验知识,所以可以从一只眼睛所获取的图像信息中提取出大量深度信息。那么单目深度估计不仅需要从二维图像中学会客观的深度信息,而且需要提取一些经验信息,后者则对于数据集中相机和场景会比较敏感。

摘要:

自监督单眼深度估计是一种有吸引力的解决方案,它不需要难以获取的深度标签来进行训练。 卷积神经网络 (CNN) 最近在这项任务中取得了巨大成功。 然而,它们有限的接受域限制了现有的网络架构只能在局部进行推理,从而削弱了自我监督范式的有效性。 鉴于 Vision Transformers (ViTs) 最近取得的成功,我们提出了 MonoViT,这是一个全新的框架,结合了 ViT 模型支持的全局推理和自监督单目深度估计的灵活性。 通过将普通卷积与 Transformer 块相结合,我们的模型可以在局部和全局进行推理,以更高的细节和准确性产生深度预测,从而使 MonoViT 在已建立的 KITTI 数据集上实现sota的性能。 此外,MonoViT 在 Make3D 和 Driving Stereo 等其他数据集上证明了其卓越的泛化能力。

介绍:

Transformers (ViTs)最近表现出杰出的目标检测和 语义分割等任务的结果,这要归功于它们能够建立像素之间的长距离关系,因此是全局感受野。另外,有相关工作将VIT应用于深度估计,但不是采用自监督单目深度估计。 本文弥补了这个缺失的步骤,提出了MonoViT architecture。它在其骨干网中结合了卷积层和最先进的 (SoTA) MPViT块【1】进而对图片中的局部信息(objects)和全局信息(前景和背景之间的关系,以及物体之间)进行建模。 该策略使我们能够消除由 CNN 编码器的有限感知域引起的瓶颈,产生自然更细粒度的预测。

作者在KITTI dataset进行实验,表现优于其他sota模型,还分析了模型泛化能力跨不同的数据集,将 MonoViT 与它在 Make3D 和 Driving-Stereo datasets进行比较,也突出显示了 MonoViT 的卓越泛化能力

模型架构:

Deep Network:

Joint CNN & Transformer Layer used in depthencoder:

PoseNet:

PoseNet 倾向于简单而有效的实现。 具体来说, PoseNet 使用 ResNet18【2】的轻量级结构。 接收相邻图像 [I, I†] 作为输入,输出视频序列相邻帧之间的 6 DoF 相对位姿 T。这个网络用于最终辅助计算loss,提供监督信息。

Loss损失函数

View reconstruction loss:

Smoothness loss. As in previous works, the edge-aware smoothness loss is used to improve the inverse depth map d:

【1】MPViT: Multi-Path Vision Transformer for Dense Prediction

【2】Deep residual learning for image recognition

单张图片实现MIP:Single-view view synthesis with multiplane images

CVPR 2020:https://single-view-mpi.github.io/

最近在视图合成方面的一项工作是在已知的视点上通过给定两个或更多的输入图像,利用深度学习来生成多平面图像(以相机为中心的分层三维展示)。我们将这种表示方法应用于单视角的视图合成,这是一个更具挑战性的问题,但可能有更广泛的应用。我们的方法直接从单个图像输入中学习预测多平面图像,并引入了尺度不变量视图合成的监督,使我们能够在在线视频上进行训练。我们展示了这种方法适用于几个不同的数据集,它还能额外生成合理的深度图,并能学习实现对背景层中的前景对象边缘后面的内容进行填充。

备注:SFM(Structure From Motion),主要基于多视觉几何原理,用于从运动中实现3D重建,也就是从无时间序列的2D图像中推算三维信息,是计算机视觉学科的重要分支。

单目图像深度估计 – 自监督方法

近几年有关单目图像深度识别的算法以CNN为主流,更细的说是以无监督的同时对深度、计算机角度、光流等同时计算的端到端深度网络为主流。所谓无监督其实是指在训练过程中不需要输入真实的深度值,这样做有一个好处就是目前能够测量到深度信息的传感器还不够精确,因此由不够精确的label训练出的model得到的预测结果必然不会特别令人满意;
所谓同时计算呢,在我理解是指在训练过程中,用一个能够表征时间序列上有前后关系的帧之间的差别的loss同时训练多个网络,而在得到model后每个网络可以单独使用。
很聪明,不同作用的网络相当于人为的特征提取过程,最后的预测基于这个人为的特征提取结果,但这种方法也有其缺点,我能想到的就是参数的增加,网络结构的复杂化和人为特征对最终预测结果有没有起引导作用只能用实验去证明。

详细说呢,首先,所谓的“自监督”虽然不需要输入真实深度信息,但需要输入双目摄像头获取到的同一时刻不同角度的图像或者前后帧图像。

自监督的单目深度估计包括: 基于双目训练的无监督模型 和 基于视频序列的无监督模型

基于双目训练的无监督模型:

UnSupervised Monocular Depth Estimation with Left-Right Consistency, CVPR, 2017

学习方法在单目深度估计上面有比较好的结果了,但还把深度估计问题作为一个有监督的回归问题。所以需要大量相对应的ground truth用来训练。记录有质量的各种场景的深度信息是一个比较难的事情。作者做了一个新方法,替代了现在直接用深度图数据训练。这个方法是用容易获得的双目立体视觉的角度。

作者提出了全新的训练函数目标函数,可以让卷积神经网络学习到深度估计,虽然没有深度信息的ground truth。利用一些对极几何约束,作者产生了视差损失。还发现只用图像重建结果会产生低质量的深度图。为了克服这个困难,作者构建了一个新颖的训练损失,可以加强左右视差图的一致性,这样能够提升性能和鲁棒性。作者的方法在KITTI数据集的单目深度估计上达到了state-of-the-art,超过用ground-truth深度训练的有监督方法。

基于视频序列的无监督模型:

UnSupervised Learning of Depth and Ego-Motion from Video,CVPR,2017

这篇文章提出了一种非监督的多功能网络,主要思想就像之前提到过的用一个loss同时训练两个网络。网络的结果如图,其中第一个网络可接受一幅图片作为输入,输出其对应的深度图片;第二个网络为姿态网络,接受t,t+1和t-1三个时刻三幅图片作为输入,输出从t到t+1和从t到t-1的相机姿态变化矩阵。

输入为前中后三帧连续的图片,同时训练两个网络,一个得到深度预测结果,一个得到视差矩阵结果

[ICCV 2019] Digging into Self-Supervised Monocular Depth Prediction

目前自监督深度学习取得最好进展的地方,一般说来自监督不需要标注,使用内在几何(通常是多视图几何)关系监督学习,从另一个侧面说明3d视觉才是视觉的本质。

这篇文章中的作者开发了一种方法,该方法使用深度估计和姿态估计网络的组合来预测单帧图像中的深度。 它通过在一系列运动的图像(包括单目和双目)序列上训练一个建立在自监督损失函数上的架构来实现,这一架构包括两个网络,一个用来在单目图像上预测深度,另一个在运动图像之间预测姿态。 此方法不需要标注训练数据集。 相反,它使用图像序列中的连续时间帧和姿态的重投影关系来进行训练。 稍后将更详细地描述重建过程。 论文的主要贡献是:

1.一种自动mask技术,可消除loss对不重要像素的注意力,减少它们的影响

2.用深度图修正光度重建误差

3.多尺度深度估计

模型架构:本文的方法使用深度网络和姿态网络。 深度网络是经典的U-Net [2]编码器-解码器模型结构。 编码器是经过预训练的ResNet模型,当然也可以考虑其他模型。 深度解码器将输出转换为深度值。作者使用基于ResNet18的姿势网络,该姿态网络经过修改后,可以使用两个彩色图像作为输入来预测单个6自由度相对姿势或旋转和平移参数。 姿势网络使用前后两帧而不是典型的立体图像对作为图像对输入。 它可以从序列中的前一帧和后一帧通过角度预测目标图像的外观。

训练:下图说明了模型的训练过程。

可以同时使用运动自监督和双目自监督

光度重建误差:目标图像位于第0帧,并且用于姿态估计过程的图像可以是前一帧或后一帧,也就是帧+1或帧-1。该损失是基于目标图像和重建的目标图像之间的相似性。重建过程通过使用姿态网络从源帧(帧+1或帧-1)计算转换矩阵开始。然后使用旋转和平移的信息来计算从源帧到目标帧的映射。最后使用从深度网络预测的目标图像的深度图和从姿势网络转换的矩阵,将其投影到具有固有内参矩阵K的摄像机中,以获取重建的目标图像。此过程需要先将深度图转换为3D点云,通过姿态将点云转换到另一个坐标系后再使用相机内参将3D点转换为2D点。所得的点用作从目标图像进行双线性插值的采样网格。

光度重建误差,ssim是经常用在图像相似度里的度量

这种loss的目的是减少目标图像和重建的目标图像之间的差异,在目标图像和重建的目标图像中,姿态和深度估计的过程中都需要使用它。

自动mask:最终的光度重建误差要乘以一个mask,该mask解决与假设相机在静态场景(例如静态场景)中移动的变化有关的问题。尤其是 一个物体正在以与相机相似的速度移动,或者在其他物体正在移动时照相机已停止,也就是那些在相机坐标系里静止的物体。 这些相对静止的物体理论上应该有无穷大的深度。 作者使用一种自动mask方法解决了这一问题,该方法可以过滤不会将外观从一帧更改为下一帧的像素,也就是那些和相机同步运动的像素。 mask是二进制的,如果目标图像和重建的目标图像之间的最小光度误差小于目标图像和源图像的最小光度误差,则为1,否则为0。

当相机是静止的时,这种方法会图像中的所有像素都被掩盖(实际场景中概率很低)。 当物体以与照相机相同的速度移动时,会导致图像中静止物体的像素被掩盖。

多尺度估计:作者将各个尺度的损失合并在一起。 将较低分辨率的深度图上采样到较高的输入图像分辨率,然后在较高的输入分辨率下重新投影,重新采样并计算光度误差。 作者声称,这使得各个比例尺上的深度图以实现相同的目标,即对目标图像进行精确的高分辨率重建。

其他形式的loss:作者加入了平均归一化的逆深度图值和输入/目标图像之间的边缘敏感的平滑度损失。 这鼓励模型学习尖锐的边缘并消除噪声。

Depth Prediction Without the Sensors: Leveraging Structure for Unsupervised Learning from Monocular Videos

项目地址:https://sites.google.com/view/struct2depth

目标运动建模:来自Google大脑的作者发表了该文章,该文章进一步扩展了Monodepth2。 它们通过预测单个目标而不是整个图像的运动来改善姿态网络估计。 因此,现在重建的图像序列不再是单个投影,而是组合在一起的一系列目标的投影。 这通过两个模型一个目标运动模型和一个相机运动估计网络(类似于前面几节中描述的姿态网络)来做到。 步骤如下:

1.预训练的MASK-RCNN [2]模型用于捕获潜在移动目标的语义分割。

2.使用二进制掩码从静态图像(帧-1,帧0和帧+1)中删除这些可能移动的对象

3.被掩盖的图像被发送到ego-motion(相机自身运动)网络,并输出帧-1和0与帧0和+1之间的转换矩阵。

4.mask过程可提取静态背景,然后提取ego-motion转换矩阵,而无需移动对象。 使用来自[3]的方程。

遮罩过程可提取静态背景,然后提取自我运动转换矩阵,而无需移动对象。

具体操作时需要注意以下几点

1.使用之前步骤3中产生的相机运动转换矩阵,并将其应用于帧-1和帧+1,用这个矩阵来变换帧0。

2.使用从步骤3得到的相机运动变换矩阵,并将其应用于可能移动的对象的分割mask到帧-1和帧+1,以获取每个目标相当于帧0的形变后的分割mask。

3.二进制掩码用于保持与变形分割掩码关联的像素。

4.mask图像与变形图像组合在一起,并传递到目标运动模型,该模型输出预测的对象运动。

结果显示必须知道相机必如何运动才能“解释”对象外观的变化。 然后,要根据目标运动建模过程的步骤4中生成的运动模型来移动对象。 最后,将形变后的对目标运动与形变后的静态背景结合起来,以获得最终的形变图像:

整体架构

学习目标尺度:虽然Monodepth2通过其自动mask技术解决了静态物体或以与照相机相同速度移动的物体的问题,但struct2 depth作者还是建议对模型进行约束,以识别物体的比例,从而改善物体运动的建模。

基于对象的类别(例如楼房)定义每个对象的比例loss,旨在基于对象比例的知识来限制深度。 loss是图像中对象的输出深度图与通过使用相机的焦距,基于对象类别的先验高度和图像中分割后的对象的实际高度计算出的近似深度图之间的差 ,两者均按目标图片的平均深度进行缩放

基于深度学习的单目深度估计综述

Monocular Depth Estimation

Monocular Depth Estimation is the task of estimating the depth value (distance relative to the camera) of each pixel given a single (monocular) RGB image. This challenging task is a key prerequisite for determining scene understanding for applications such as 3D scene reconstruction, autonomous driving, and AR. State-of-the-art methods usually fall into one of two categories: designing a complex network that is powerful enough to directly regress the depth map, or splitting the input into bins or windows to reduce computational complexity. The most popular benchmarks are the KITTI and NYUv2 datasets. Models are typically evaluated using RMSE or absolute relative error. 这项具有挑战性的任务是确定 3D 场景重建、自动驾驶和 AR 等应用场景理解的关键先决条件。

任务介绍

深度估计是计算机视觉领域的一个基础性问题,其可以应用在机器人导航、增强现实、三维重建、自动驾驶等领域。而目前大部分深度估计都是基于二维RGB图像到RBG-D图像的转化估计,主要包括从图像明暗、不同视角、光度、纹理信息等获取场景深度形状的Shape from X方法,还有结合SFM(Structure from motion)和SLAM(Simultaneous Localization And Mapping)等方式预测相机位姿的算法。其中虽然有很多设备可以直接获取深度,但是设备造价昂贵。也可以利用双目进行深度估计,但是由于双目图像需要利用立体匹配进行像素点对应和视差计算,所以计算复杂度也较高,尤其是对于低纹理场景的匹配效果不好。而单目深度估计则相对成本更低,更容易普及。

那么对于单目深度估计,顾名思义,就是利用一张或者唯一视角下的RGB图像,估计图像中每个像素相对拍摄源的距离。对于人眼来说,由于存在大量的先验知识,所以可以从一只眼睛所获取的图像信息中提取出大量深度信息。那么单目深度估计不仅需要从二维图像中学会客观的深度信息,而且需要提取一些经验信息,后者则对于数据集中相机和场景会比较敏感。

通过阅读文献,可以将基于深度学习的单目深度估计算法大致分为以下几类:

  • 监督算法

顾名思义,直接以2维图像作为输入,以深度图为输出进行训练::监督方法的监督信号基于深度图的地面真值,因此单目深度估计可以看作是一个回归问题。从单个深度图像设计神经网络来预测深度。利用预测深度图和实际深度图之间的差异来监督网络的训练 L2损失

上面给的例子是KITTI数据集中的一组例子,不过深度图可能看的不是很明显,我重新将深度图涂色之后:

深度网络通过近似真值的方法来学习场景的深度。基于不同结构和损失函数的方法:据我们所知,Eigen等人首先用CNNs解决单目深度估计问题。该体系结构由两个组成部分组成(全局粗尺度网络和局部精细尺度网络),在文献中用于从单个图像进行端到端的深度图预测。

基于条件随机场的方法:Li等人提出了一种基于多层的条件随机场(CRFs)的细化方法,该方法也被广泛应用于语义分割。在深度的估计中,考虑到深度的连续特征,可以广泛地使用CRF的深度信息,因此可以广泛地应用于深度的估计中。

基于对抗性学习的方法:由于提出的对抗性学习在数据生成方面的突出表现,近年来成为一个研究热点。各种算法、理论和应用已得到广泛发展。对抗式学习深度估计的框架如图所示。

file
  • 无监督算法

首先,所谓的“无监督”虽然不需要输入真实深度信息,但需要输入双目摄像头获取到的同一时刻不同角度的图像或者前后帧图像,只是这样就叫做无监督在我看来略显牵强。

有监督学习方法要求每幅RGB图像都有其对应的深度标签,而深度标签采集通常需要深度相机或激光雷达,前者范围受限后者成本昂贵。再者,采集的原始深度标签通常是一些稀疏的点,不能与原图很好的匹配。因此不用深度标签的无监督估计方法是近年的研究趋势,其基本思路是利用左右视图,结合对极几何与自动编码机的思想求解深度。 

由于深度数据的获取难度较高,所以目前有大量算法都是基于无监督模型的。即仅仅使用两个摄像机采集的双目图像数据进行联合训练。其中双目数据可彼此预测对方,从而获得相应的视差数据,再根据视差与深度的关系进行演化。亦或是将双目图像中各个像素点的对应问题看作是立体匹配问题进行训练。左视图-右视图示例:

视差,以我们人眼为例,两只眼睛看到的图像分别位于不同的坐标系。将手指从较远地方慢慢移动到眼前,会发现,手指在左眼的坐标系中越来越靠右,而在右眼坐标系中越来越靠左,这种差异性就是视差。与此同时,可以说明,视差与深度成反比。除此之外,由于摄像机参数也比较容易获取,所以也可以以相机位姿作为标签进行训练。

同时同一水平线上的两个照相机拍摄到的照片是服从以下物理规律的:

在图中, Z 为场景所距离我们的深度, X为三维场景映射到的二维图像平面,也就是最终我们得到的二维图像所在的平面。 f为相机的焦距。 b为两个相机之间的距离,Xl和 Xr 分别为相同物体在左右两个不同相机中成像的坐标。根据以上信息,和简单的三角形相似规律我们可以得到:

这种思路最先应用于使用单张图片生成新视角问题:DeepStereo 和 Deep3d之中, 在传统的视角生成问题之中,首先会利用两张图(或多张)求取图片之间的视差d,其次通过得到的视差(相当于三维场景)来生成新视角。

基于可解释性掩模的方法:基于投影函数的视图重建算法依赖于静态场景假设,即动态目标在相邻帧上的位置不满足投影函数,从而影响测光度误差和训练过程。

基于传统视觉里程计的方法:用传统的直接视觉里程计回归的位姿来辅助深度估计,而不是使用位姿网络估计的位姿。直接视觉里程计利用深度网络生成的深度图和一个三帧图像,通过最小化光度误差来估计帧间的姿态,然后将计算出的姿态发送回训练框架。因此,由于深度网络由更精确的姿态来监督,因此深度估计的精度显着提高。

基于多任务框架的方法:最近的方法在基本框架中引入了额外的多任务网络,如光流、物体运动和相机内参矩阵,作为一个附加的训练框架,加强了整个训练任务之间的关系

基于对抗学习的方法:将对抗学习框架引入到无监督的单目深度估计中。由于在无监督训练中没有真正的深度图。因此,将视图重建算法合成的图像和真实图像作为鉴别器的输入,而不是使用鉴别器来区分真实深度图和预测深度图。

  • Structure from motion/基于视频的深度估计(无监督学习)

这一部分中既包含了单帧视频的单目深度估计,也包含了多帧间视频帧的像素的立体匹配,从而近似获取多视角图像,对相机位姿进行估计。

评估指标:

在单目深度估计问题中,常用的精度评估指标有相对误差(REL)、均方根误差(RMS)、对数误差(LG)及阈值误差(% correct)

深度估计相关数据集 

在深度估计的研究中,由于室内外场景类型与深度范围具有较大的差异,对应不同的场景分别会构造不同的数据集

  • 真实场景数据集
    • NYU depth v2(来自纽约大学)是常用的室内数据集之一,
      • 选取了464个不同的场景,
      • 利用RGB相机和微软的Kinect深度相机同时采集室内场景的RGB信息深度信息,收集了407 024帧RGBD图像对构建数据集。
      • 由于红外相机和摄像机之间的位置偏差,深度相机采集的原始深度图存在缺失部分或是噪点,
      • 作者从中选取了1 449幅图像,利用着色算法对深度图进行填充得到稠密深度图,同时人工标注语义信息
    • Make3D(斯坦福大学)是常用的室外场景数据集之一,
      • 使用激光扫描仪采集室外场景的深度信息,
      • 选取的场景类型为白天的城市和自然风光,深度范围是5~81 m,大于该范围统一映射为81 m。
      • 数据集共包含534幅RGBD图像对,其中400幅用于训练,134幅用于测试。
    • KITTI(德国卡尔斯鲁厄理工学院和美国丰田技术研究院)自动驾驶领域常用的数据集之一,链接:http://www.cvlibs.net/datasets/kit
      • 包含深度数据标签
      • 通过一辆装配有2台高分辨率彩色摄像机、2台灰度摄像机、激光扫描仪和GPS定位系统的汽车采集数据,其中激光扫描仪的最大测量距离为120 m。
      • 图像场景包括卡尔斯鲁厄市、野外地区以及高速公路。
      • 数据集共包含93 000个RGBD训练样本。
    • Depth in the Wild(DIW)(密歇根大学)以相对深度作为标签的数据集
      • 从词典中随机选取单词作为搜索关键字,然后从互联网中收集得到原始的RGB图像。
      • 标注工作外包给专门的工作人员,为了更加高效,每一幅图像选取了两个高亮的点,工作人员只需判定两个点的远近关系即可。
      • 对于采样点对之间的位置关系,采用50%随机采样,另外50%对称采样的方法以使构建的数据集尽可能平衡。最终获得的有效标注图像约5×E11张。
    • Cityscapes. Cityscapes的数据取自德国的50多个城市的户外场景,其中数据包含有左右视角图像、视差深度图、相机校准、车辆测距、行人标定、目标分割等,同时也包含有类似于vKITTI的虚拟渲染场景图像。其中简单的左视角图像、相机标定、目标分割等数据需要利用学生账号注册获取,其他数据需要联系管理员获取。链接:https://www.cityscapes-dataset.com/
  • 虚拟场景数据集
    • SceneNet RGB-D数据集
    • SYNTHIA数据集
    • 由于是通过虚拟场景生成,数据集中包括更多天气、环境及光照,场景类型多样。各数据集有各自的优缺点,在实际研究中,应根据具体研究问题来选择合适的数据集。

 综上,可以看到基于深度学习的单目深度估计是本领域的发展方向。目前,该领域的发展主要集中在数据集和深度学习模型两方面。首先,数据集的质量在很大程度上决定了模型的鲁棒性与泛化能力,深度学习要求训练数据必须有更多的数量、更多的场景类型,如何构建满足深度学习的数据集成为一个重要的研究方向。目前,基于虚拟场景生成深度数据具有不需要昂贵的深度采集设备、场景类型多样、节省人力成本等优势,结合真实场景和虚拟场景的数据共同训练也是未来深度学习方法的趋势。其次,为了提高深度学习估计单幅图像深度的精度,要求更新的更复杂的深度框架。除了神经网络模型本身结构的优化,更新颖的算法设计也能有效地提升预测精度。研究工作大多采用有监督回归模型对连续的绝对深度值进行回归拟合。考虑到场景由远及近的特性,也有用分类模型进行绝对深度估计的方法。由深度信息和其他信息之间的互补性,部分工作结合表面法线等信息提升深度预测的精度。深度学习发展迅速,新的模型层出不穷,如何将这些模型应用于单幅图像深度估计问题中需要更加深入地研究。另外,探索神经网络在单目深度估计问题中学到的是何种特征也是一个重要的研究方向。

对于单目深度估计模型,目前主要分为基于回归/分类的监督模型,基于双目训练/视频序列的无监督模型,以及基于生成学习的图像风格迁移模型。大概从2017年起,即CVPR2018开始,单目深度估计的效果就已经达到了双目深度估计的效果,主要是监督模型。但是由于现有的数据集主要为KITTI、Cityscapes、NYU DepthV2等,其场景和相机都是固定的,从而导致监督学习下的模型无法适用于其他场景,尤其是多目标跟踪这类细节丰富的场景,可以从论文中看到,基本上每个数据集都会有一个单独的预训练模型。

对于GAN,其对于图像风格的迁移本身是一个很好的泛化点,既可以用于将场景变为晴天、雾天等情况,也可以用于图像分割场景。但是深度估计问题中,像素点存在相对大小,因此必定涉及到回归,因此其必定是监督学习模型,所以泛化性能也不好。对于无监督的算法,可能场景适应性会更好,但依旧不适用于对行人深度的估计。

参考文献

[1] Long J, Shelhamer E, Darrell T. Fully convolutional networks for semantic segmentation[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2015: 3431-3440.

[2] Ronneberger O, Fischer P, Brox T. U-net: Convolutional networks for biomedical image segmentation[C]//International Conference on Medical image computing and computer-assisted intervention. Springer, Cham, 2015: 234-241.

[3] Laina I, Rupprecht C, Belagiannis V, et al. Deeper depth prediction with fully convolutional residual networks[C]//2016 Fourth international conference on 3D vision (3DV). IEEE, 2016: 239-248.

[4] Fu H, Gong M, Wang C, et al. Deep ordinal regression network for monocular depth estimation[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018: 2002-2011.

[5] Godard C, Mac Aodha O, Brostow G J. Unsupervised monocular depth estimation with left-right consistency[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2017: 270-279.

[6] Dosovitskiy A, Fischer P, Ilg E, et al. Flownet: Learning optical flow with convolutional networks[C]//Proceedings of the IEEE international conference on computer vision. 2015: 2758-2766.

[7] Ilg E, Mayer N, Saikia T, et al. Flownet 2.0: Evolution of optical flow estimation with deep networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2017: 2462-2470.

[8] Mayer N, Ilg E, Hausser P, et al. A large dataset to train convolutional networks for disparity, optical flow, and scene flow estimation[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2016: 4040-4048.

[9] Xie J, Girshick R, Farhadi A. Deep3d: Fully automatic 2d-to-3d video conversion with deep convolutional neural networks[C]//European Conference on Computer Vision. Springer, Cham, 2016: 842-857.

[10] Luo Y, Ren J, Lin M, et al. Single View Stereo Matching[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018.

[11] Zhou T, Brown M, Snavely N, et al. Unsupervised learning of depth and ego-motion from video[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2017: 1851-1858.

[12] Yin Z, Shi J. Geonet: Unsupervised learning of dense depth, optical flow and camera pose[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018: 1983-1992.

[13] Zhan H, Garg R, Saroj Weerasekera C, et al. Unsupervised learning of monocular depth estimation and visual odometry with deep feature reconstruction[C]// Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018: 340-349.

[14] Goodfellow I, Pouget-Abadie J, Mirza M, et al. Generative adversarial nets[C]//Advances in neural information processing systems. 2014: 2672-2680.

[15] Radford A , Metz L , Chintala S . Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks[J]. Computer Science, 2015.

[16] Arjovsky M, Chintala S, Bottou L. Wasserstein gan[J]. arXiv preprint arXiv:1701.07875, 2017.

[17] Gulrajani I, Ahmed F, Arjovsky M, et al. Improved training of wasserstein gans[C]//Advances in Neural Information Processing Systems. 2017: 5767-5777.

[18] Mao X, Li Q, Xie H, et al. Least squares generative adversarial networks[C]//Proceedings of the IEEE International Conference on Computer Vision. 2017: 2794-2802.

[19] Mirza M, Osindero S. Conditional generative adversarial nets[J]. arXiv preprint arXiv:1411.1784, 2014.

[20] Isola P, Zhu J Y, Zhou T, et al. Image-to-image translation with conditional adversarial networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2017: 1125-1134.

[21] Wang T C, Liu M Y, Zhu J Y, et al. High-resolution image synthesis and semantic manipulation with conditional gans[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018: 8798-8807.

[22] Zhu J Y, Park T, Isola P, et al. Unpaired image-to-image translation using cycle-consistent adversarial networks[C]//Proceedings of the IEEE international conference on computer vision. 2017: 2223-2232.

[23] Wang T C , Liu M Y , Zhu J Y , et al. Video-to-Video Synthesis[J]. arXiv preprint arXiv:1808.06601,2018.

[24] Zheng C, Cham T J, Cai J. T2net: Synthetic-to-realistic translation for solving single-image depth estimation tasks[C]//Proceedings of the European Conference on Computer Vision (ECCV). 2018: 767-783.

[25] Atapour-Abarghouei A, Breckon T P. Real-time monocular depth estimation using synthetic data with domain adaptation via image style transfer[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2018: 2800-2810.

[26] Nekrasov V , Dharmasiri T , Spek A , et al. Real-Time Joint Semantic Segmentation and Depth Estimation Using Asymmetric Annotations[J]. arXiv preprint arXiv:1809.04766,2018.

[27] Nekrasov V , Shen C , Reid I . Light-Weight RefineNet for Real-Time Semantic Segmentation[J]. arXiv preprint arXiv:1810.03272, 2018.

[28] Lin G , Milan A , Shen C , et al. RefineNet: Multi-Path Refinement Networks for High-Resolution Semantic Segmentation[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition.,2017:1925-1934

[29] Zou Y , Luo Z , Huang J B . DF-Net: Unsupervised Joint Learning of Depth and Flow using Cross-Task Consistency[C]//Proceedings of the European Conference on Computer Vision (ECCV). 2018:36-53.

[30] Ranjan A, Jampani V, Balles L, et al. Competitive collaboration: Joint unsupervised learning of depth, camera motion, optical flow and motion segmentation[C]//Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2019: 12240-12249.

MIP 首次用于新视角合成

Stereo magnification:Learning view synthesis using multiplane images

本文主要研究新视角合成任务中 Narrow-baseline Stereo Images Pairs(处于同一水平基线的左右视角图像)输入的情况。本文首次提出了 Multiplane Images (MPI) 的场景表达方式,其优点在于:

  1. 只需用网络预测一次 MPI 的场景表达,后续就能重复利用该 MPI 来生成多个不同视角下的图片;
  2. 能够有效获取未出现在 Stereo 输入图像中(被遮挡)的场景结构。

另外,为了训练网络,本文还提出了一种使用在线视频来生成训练数据的方法

方法:

1. MPI 场景表达

MPI 包含多个平面,每个平面 d 编码两种信息:RGB 颜色图像 C d,透明度 Alpha 图 α d ​,因此整个 MPI 可表示为 RGBA 图像的集合,即 { ( C 1 , α 1 ) , . . . , ( C D , α D ) },其中 D 表示平面的数量(作者最终采用了 32 个平面)。

MPI

2. MPI 网络学习

模型训练过程中,输入为(I1​,I2​,c1​,c2​),ground truth 为(It​,ct​),其中 I 表示图像,ci​=(pi​,ki​),pi​ 表示相机外参,ki​ 表示相机内参。目标是学习一个 MPI 表达网络 f θ ( ⋅ ) ,以(I1​,I2​,c1​,c2​) 作为输入,推断出 MPI 的场景表达,并重建出 ct​ 相机参数下的目标图像 It​ 。

网络输入:下面假设 I1​ 为 Reference Source,I2​ 为 Second Source,为了将 I2​ 的位姿信息嵌入到I1​ 中,作者先计算了一个 Plane Sweep Volume (PSV),即将 I2​ 投影到I1​ 的不同深度平面上(由于这里采用 Stereo 图像输入,I1​ 和 I2​ 位于同一水平基线上,故只需将I2​ 做不同程度的水平偏移即可得到 PSV。和 Stereo Depth Estimation 任务中的 Cost Volume 类似)。之后将I2​ 的 PSV 和I1​ concat 到一起作为网络的输入,输入的尺度为 H × W × 3 ( D + 1 )。

网络输出:作者认为如果让网络回归出每个平面对应的 RGBA 四个通道,网络输出的通道数太多,对于网络的学习太过困难,因此,作者采用了一种简单有效的做法,即将每个平面的 RGB 看作是参考图 I1​ 和一张统一背景图 I^b​ 的加权平均:

直观地来说,对于前景内容占主导的附近的平面,I1​ 将占有更高的权重,而 I ^ b​ 用于捕捉在参考视图中被遮挡的表面

那么网络仅需要回归出一张背景图I^b​,每个平面的融合概率 wd​,以及透明度αd​,就能够获得完整的 MPI 表达了。总体而言,原本输出的尺寸为 WH⋅4D,在经过调整之后,变为WH⋅(2D+3) 。

 3、使用MPIs进行可微视图合成

给定关于一个参考帧的IMPI 表示,我们能够通过对每个平面的RGBA图像应用平面变换(逆单应性),并将已转换的图像按前后顺序组合成单个图像的阿尔法组合,最终合成得到一个新奇视图I^t​。

平面变换和阿尔法组合都是可微的,因此可以很容易地融入到学习流程的其余部分中

4. 损失函数

结果展示:

CLIP-NeRF:文本和图像驱动的NeRF编辑框架

论文:(CVPR 2022) CLIP-NeRF: Text-and-Image Driven Manipulation of Neural Radiance Fields

项目主页:https://cassiepython.github.io/clipnerf/

Overview

  • 提出了第一个统一文本和图像驱动的NeRF编辑框架,使得用户可以使用文本提示或示例图像对3D内容进行灵活编辑。
  • Zs 和Za,分别控制形状和外观。
  • 提出feed forward mapper,能够实现快速推理出用户输入对物体形状和外观的改变量。
  • 提出了一种反演方法,利用EM算法从真实的图像中推断出相机位姿、Zs 和 Za,进而实现编辑现有物体的形状、外观、姿态。

Network architecture

  • GRAF不同,该网络并不是将 Zs 直接与positional encoding拼接起来送入神经辐射场,而是先用deformation network(受到Nerfies启发)生成原始位置编码的偏移量并与原始位置编码相加,再送入后续辐射场中。优点:使用tanh函数限制deformation network输出的偏移量的值域,提升了shape操控的鲁棒性与稳定性,同时使得对shape的操纵对于appearance无影响(传统conditional NeRF,如GRAF,实际上改变 Zs会对appearance产生一些影响)。
  • 先预训练好一个解耦条件NeRF,使得NeRF能够充分学习到场景的3D信息以及生成出真实的场景物体。
  • 然后利用CLIP distance训练CLIP分支中的shape mapper and appearance mapper(固定其他模块的参数)。使得mapper能够正确学习到如何将用户输入的modify 信息映射为 Zs, Za 的改变量以使得NeRF正确生成目标结果

Inverse Manipulation

为了获得某物体对应的latent code以便对其实施编辑,作者根据EM算法( Expectation Maximization Algorithm。期望最大算法)设计了一种迭代方法来交替优化 、Zs、Za 和相机位姿 v ,本质是优化各项参数,使得在该组参数下得到的生成结果接近于实际结果。

其中Zn是扰动,用于提升优化过程的鲁棒性, 入n 从1decay到0,表示越往后参数已经优化得差不多了,那么扰动也就相应地减小。

当获得某物体对应的 Zs,Za,v 后,输入文本便可以输出编辑后的物体。

Experiment results

  • Text-Driven
  • Exemplar-Driven Editing Results
  • Convert real image into corresponding latent code and camera pose, then use prompt to edit real image

实现编辑应该就是先反演出目标图像对应的各个latent codes,然后向CLIP encoder输入参考图像/文字,再通过shape/appearancce mapper得到相应的编辑改变量,将改变量和原始推算出的latent codes相加,再利用NeRF前向渲染出最终编辑后的图像。

Limitations

  • 无法进行细粒度的物体修改,比如修改车轮为红色。根源在于隐空间和预训练CLIP的固有局限性,比如CLIP就没有学到轮胎的语义信息。
  • 局限于使用文本和示例图像对于单个物体进行修改,没有扩展到复杂场景(如object-nerf处理的现实场景)。如何实现多物体场景的text/img guided modify?结合object-nerf和clip-nerf
  • 先训练好NeRF,再训练mapping network,那就限定了模型只能用参考文字/参考图像编辑固定场景中的物体,而且通过模型结构不难推测出,该模型迁移到多物体数据集上是不可行的。在多物体场景下,由于文本只能影响全局的 Za,Zs ,因此编辑会影响场景中的所有物体。

PixelNeRF–具有泛化性的NeRF

pixelNeRF: Neural Radiance Fields from One or Few Images

代码链接:https://github.com/sxyu/pixel-nerf

论文链接:https://alexyu.net/pixelnerf/

Nerf提出以来,收到了大量关注。但是,它仍存在以下缺点:

  1. Nerf的训练需要很多标准化的照片
  2. 训练花费大量时间

因此,本文提出了一种基于全卷积的Nerf:pixelNerf。当经过大量训练后,pixelNerf可以仅通过几张(甚至一张)照片进行良好的视图合成,同时这种方式也不需要精确的3D监督(2D监督即可)。

作者提出了pixelNeRF,一个只需要输入单张或多张图像,就能得到连续场景表示的学习框架。由于现存的构建神经辐射场的方法涉及到独立优化每个场景的表示,这需要许多校准的视图和大量的计算时间,因此作者引入了一种新的网络架构。实验结果表明,在所有情况下,pixelNeRF在新视图合成和单图像三维重建方面都优于当前最先进的工作。

该项目主要研究的问题是如何从一个稀疏的输入视图集中合成这个场景的新视图,在可微神经渲染出现之前,这个长期存在的问题一直没有得到进展。同时,最近的神经渲染场NeRF通过编码体积密度和颜色,在特定场景的新视图合成方面表现出很好的效果。虽然NeRF可以渲染非常逼真的新视图,但它通常是不切实际的,因为它需要大量的位姿图像和冗长的场景优化。

在这篇文章中,作者对上述方法进行了改进,与NeRF网络不使用任何图像特征不同的是,pixelNeRF将与每个像素对齐的空间图像特征作为输入。这种图像调节允许框架在一组多视图图像上进行训练,学习场景先验,然后从一个或几个输入图像中合成视图,如下图所示。

PixelNeRF具有很多特点:首先,Pixel可以在多视图图像的数据集上面进行训练,而不需要任何额外的监督;其次,PixelNeRF预测输入图像的摄像机坐标系中的NeRF表示,而不是标准坐标系,这是泛化看不见的场景和物体类别的必要条件,因为在有多个物体的场景中,不存在明确的规范坐标系;第三,它是完全卷积的,这允许它保持图像和输出3D表示之间的空间对齐;最后,PixelNeRF可以在测试时合并任意数量的输入视图,且不需要任何优化。

相关工作

新视图合成:这是一个长期存在的问题,它需要从一组输入视图中构建一个场景的新视图。尽管现在有很多工作都已经取得了逼真的效果,但是存在比较多的问题,例如需要密集的视图和大量的优化时间。其他方法通过学习跨场景共享的先验知识,从单个或少数输入视图进行新的视图合成,但是这些方法使用2.5D表示,因此它们能够合成的摄像机运动范围是有限的。在这项工作中,作者提出了PixelNeRF,能够直接从相当大的基线中合成新视图。

基于学习的三维重建:得益于深度学习的发展,单视图或多视图的三维重建也得到快速的发展。问题是,很多表示方法都需要3D模型进行监督,尽管多视图监督限制更小、更容易获取,其中的很多方法也需要物体的mask。相比之下,PixelNeRF可以单独从图像中训练,允许它应用到含有两个对象的场景而不需要修改。

以观察者为中心的三维重建:对于3D学习任务,可以在以观察者为中心的坐标系(即视图空间)或以对象为中心的坐标系(即规范空间)中进行预测。大多数现存的方法都是在规范空间中进行预测,虽然这使得学习空间规律更加容易,但是会降低不可见对象和具有多个对象场景的预测性能。PixelNeRF在视图空间中操作,这在【2】中已经被证明可以更好地重建看不见的对象类别,并且不鼓励对训练集的记忆。下表是PixelNeRF和其他方法的对比:

NeRF的缺点:

虽然NeRF实现了最新的视图合成,但它是一种基于优化的方法,每个场景必须单独优化,场景之间没有知识共享。这种方法不仅耗时,而且在单个或极稀疏视图的限制下,无法利用任何先验知识来加速重建或完成形状。

基于图像的NeRF:pixelNeRF

为了克服上面提到的关于NeRF的问题,作者提出了一种基于空间图像特征的NeRF结构。该模型由两个部分组成:一个完全卷积的图像编码器E (将输入图像编码为像素对齐的特征网格)和一个NeRF网络f (给定一个空间位置及其对应的编码特征,输出颜色和密度)。

单视图pixelNeRF:首先固定坐标系为输入图像的视图空间,并在这个坐标系中指定位置和摄像机光线。给定场景的输入图像I,首先提取出它的特征量W=E(I)。然后,对于相机光线上的一个点x,通过使用已知的内参,将x投影到图像坐标π(x)上,然后在像素特征之间进行双线性插值来提取相应的图像特征向量W(π(x))。最后把图像特征连同位置和视图方向(都在输入视图坐标系统中)传递到NeRF网络:其中γ()是x上的位置编码。

合并多个视图:多个视图提供了有关场景的附加信息,并解决了单视图固有的三维几何歧义。作者扩展了该模型,不同于现有的在测试时只使用单个输入视图的方法,它允许在测试时有任意数量的视图。

在有多个输入视图的情况下,只假设相对的相机姿态是已知的,为了便于解释,可以为场景任意固定一个世界坐标系。把输入图像记为I,其相关联的摄像机记为P=[R t]。对于新的目标摄影机光线,将视图方向为d的点x转换到每个输入视图i的坐标系,转换如下:

为了获得输出的密度和颜色,作者独立地处理每个视图坐标帧中的坐标和相应的特征,并在NeRF网络中聚合视图。将NeRF网络的初始层表示为f1,它分别处理每个输入视图空间中的输入,并将最终层表示为f2,它处理聚合视图。

和单视图类似,作者将每个输入图像编码成特征体积W(i)=E(I(i))。对于点x(i),在投影图像坐标π(x(i))处从特征体W(i)中提取相应的图像特征,然后将这些输入传递到f1,以获得中间向量:

最后用平均池化算子ψ将中间向量V(i)聚合并传递到最后一层f2,得到预测的密度和颜色:

个人觉得,这篇文章其实就是对原始Nerf做了一个效果很好的改进:将图片进行特征提取后再输入Nerf,(似乎并没有很多的创新),但是从图中展现的效果来看,这一改进是卓有成效的,或许也为我们提供了一种新的思路。

目前的缺陷:

1) Like NeRF, our rendering time is slow, and in fact, our runtime increases linearly when given more input views. Further, some methods (e.g. [28, 21]) can recover a mesh from the image enabling fast rendering and manipulation afterwards, while NeRF based representations cannot be converted to meshes very reliably. Improving NeRF’s efficiency is an important re-search question that can enable real-time applications.

2) As in the vanilla NeRF, we manually tune ray sampling bounds tn,tf and a scale for the positional encoding. Making NeRF-related methods scale-invariant is a crucial challenge.
3) While we have demonstrated our method on real data from the DTU dataset, we acknowledge that this dataset was captured under controlled settings and has matching camera poses across all scenes with limited viewpoints. Ultimately,our approach is bottlenecked by the availability of largescale wide baseline multi-view datasets, limiting the applicability to datasets such as ShapeNet and DTU. Learning
a general prior for 360◦ scenes in-the-wild is an exciting direction for future work

参考文献:

【1】Ben Mildenhall, Pratul P. Srinivasan, Matthew Tancik,Jonathan T. Barron, Ravi Ramamoorthi, and Ren Ng. Nerf: Representing scenes as neural radiance fields for view synthesis. In Eur. Conf. Comput. Vis., 2020

【2】Daeyun Shin, Charless Fowlkes, and Derek Hoiem. Pixels, voxels, and views: A study of shape representations for single view 3d object shape prediction. In IEEE Conf. Comput.Vis. Pattern Recog., 2018.