TensorRT – 使用trtexec工具转换模型、运行模型、测试网络性能

转换模型将onnx转换为TensorRT:

方法一、trtexec

trtexec是在tensorrt包中自带的转换程序,该程序位于bin目录下,用起来比较方便,也是最简单的trt模型转换方式,在使用之前需要系统安装好cuda和cudnn,否则无法正常运行。使用示例如下:

首先将pytorch模型先转换成onnx模型,示例代码如下:

def torch2onnx(model_path,onnx_path):
model = load_model(model_path)
test_arr = torch.randn(1,3,32,448)
input_names = ['input']
output_names = ['output']
tr_onnx.export(
model,
test_arr,
onnx_path,
verbose=False,
opset_version=11,
input_names=input_names,
output_names=output_names,
dynamic_axes={"input":{3:"width"}} #动态推理W纬度,若需其他动态纬度可以自行修改,不需要动态推理的话可以注释这行
)
print('->>模型转换成功!')

trtexec转换命令如下:

固定尺寸模型转换:将ONNX模型转换为静态batchsize的TensorRT模型,启动所有精度以达到最佳性能,工作区大小设置为1024M

./trtexec --onnx=repvgg_a1.onnx --saveEngine=repvgg_a1.engine --workspace=1024  --fp16 --verbose

动态尺寸模型转换:将ONNX模型转换为动态batchsize的TensorRT模型,启动所有精度以达到最佳性能,工作区大小设置为1024M

./trtexec --onnx=repvgg_a1.onnx --saveEngine=repvgg_a1.engine --workspace=1024 --minShapes=input:1x3x32x32 --optShapes=input:1x3x32x320 --maxShapes=input:1x3x32x640 --fp16

注意:
–minShapes,–optShapes ,–maxShapes必须全部设置,设置的形式为:batchsize x 通道数 x 输入尺寸x x 输入尺寸y

例如:
--minShapes=input:1x3x416x416
--optShapes=input:8x3x416x416
--maxShapes=input:8x3x416x416

参看命名详解: ./trtexec –help, -h

trtexec的参数使用说明

1.1 Model Option 模型选项

–uff : UFF模型文件名–onnx : ONNX模型文件名–model : Caffe模型文件名,模式时无模型,使用随机权重–deploy : Caffe prototxt 文件名–output : 输出名称(可多次指定);UFF和Caffe至少需要一个输出–uffInput : 输入blob名称及其维度(X、Y、Z=C、H、W),可以多次指定;UFF型号至少需要一个–uffNHWC : 设置输入是否在NHWC布局中而不是NCHW中(在–uffInput中使用X、Y、Z=H、W、C顺序)

1.2 Build Options 构建选项

–maxBatch : 设置最大批处理大小并构建隐式批处理引擎(默认值=1)–explicitBatch :构建引擎时使用显式批量大小(默认 = 隐式)–minShapes=spec : 使用提供的最小形状的配置文件构建动态形状–optShapes=spec : 使用提供的 opt 形状的配置文件构建动态形状–maxShapes=spec : 使用提供的最大形状的配置文件构建动态形状–minShapesCalib=spec : 使用提供的最小形状的配置文件校准动态形状–optShapesCalib=spec : 使用提供的 opt 形状的配置文件校准动态形状–maxShapesCalib=spec :使用提供的最大形状的配置文件校准动态形状注意:必须提供所有三个 min、opt 和 max 形状。但是,如果只提供了 opt 形状,那么它将被扩展,以便将最小形状和最大形状设置为与 opt 形状相同的值。此外,使用 动态形状意味着显式批处理。 输入名称可以用转义单引号括起来(例如:‘Input:0’)。示例输入形状规范:input0:1x3x256x256,input1:1x3x128x128 每个输入形状都作为键值对提供,其中 key 是输入名称 值是用于该输入的维度(包括批次维度)。 每个键值对都使用冒号 (😃 分隔键和值。 可以通过逗号分隔的键值对提供多个输入形状。–inputIOFormats=spec : 每个输入张量的类型和格式(默认所有输入为fp32:chw)注意:如果指定此选项,请按照与网络输入ID相同的顺序为所有输入设置逗号分隔的类型和格式(即使只有一个输入需要指定IO格式)或设置一次类型和格式以进行广播。–outputIOFormats=spec : 每个输出张量的类型和格式(默认所有输入为fp32:chw)注意:如果指定此选项,请按照与网络输出ID相同的顺序为所有输出设置逗号分隔的类型和格式(即使只有一个输出需要指定IO格式)或设置一次类型和格式以进行广播。–workspace=N : 以M为单位设置工作区大小(默认值 = 16)–noBuilderCache : 在构建器中禁用时序缓存(默认是启用时序缓存)–nvtxMode=mode : 指定 NVTX 注释详细程度。 mode ::= default|verbose|none–minTiming=M : 设置内核选择中使用的最小迭代次数(默认值 = 1)–avgTiming=M : 为内核选择设置每次迭代的平均次数(默认值 = 8)–noTF32 : 禁用 tf32 精度(默认是启用 tf32,除了 fp32)–refit : 将引擎标记为可改装。这将允许检查引擎内的可改装层和重量。–fp16 : 除 fp32 外,启用 fp16 精度(默认 = 禁用)–int8 : 除 fp32 外,启用 int8 精度(默认 = 禁用)–best : 启用所有精度以达到最佳性能(默认 = 禁用)–calib= : 读取INT8校准缓存文件–safe : 仅测试安全受限流中可用的功能–saveEngine= : 保存序列化模型的文件名–loadEngine= : 加载序列化模型的文件名–tacticSources=tactics : 通过从默认策略源(默认 = 所有可用策略)中添加 (+) 或删除 (-) 策略来指定要使用的策略。

1.3 Inference Options 推理选项

–batch=N : 为隐式批处理引擎设置批处理大小(默认值 = 1)–shapes=spec : 为动态形状推理输入设置输入形状。注意:使用动态形状意味着显式批处理。 输入名称可以用转义的单引号括起来(例如:‘Input:0’)。 示例输入形状规范:input0:1x3x256x256, input1:1x3x128x128 每个输入形状都作为键值对提供,其中键是输入名称,值是用于该输入的维度(包括批次维度)。 每个键值对都使用冒号 (😃 分隔键和值。 可以通过逗号分隔的键值对提供多个输入形状。–loadInputs=spec :从文件加载输入值(默认 = 生成随机输入)。 输入名称可以用单引号括起来(例如:‘Input:0’)–iterations=N : 至少运行 N 次推理迭代(默认值 = 10)–warmUp=N : 在测量性能之前运行 N 毫秒以预热(默认值 = 200)–duration=N : 运行至少 N 秒挂钟时间的性能测量(默认值 = 3)–sleepTime=N : 延迟推理以启动和计算之间的 N 毫秒间隔开始(默认 = 0)–streams=N : 实例化 N 个引擎以同时使用(默认值 = 1)–exposeDMA : 串行化进出设备的 DMA 传输。 (默认 = 禁用)–noDataTransfers : 在推理过程中,请勿将数据传入和传出设备。 (默认 = 禁用)–useSpinWait : 主动同步 GPU 事件。 此选项可能会减少同步时间,但会增加 CPU 使用率和功率(默认 = 禁用)–threads : 启用多线程以驱动具有独立线程的引擎(默认 = 禁用)–useCudaGraph : 使用 cuda 图捕获引擎执行,然后启动推理(默认 = 禁用)–separateProfileRun : 不要在基准测试中附加分析器; 如果启用分析,将执行第二次分析运行(默认 = 禁用)–buildOnly : 跳过推理性能测量(默认 = 禁用)

1.4 Build and Inference Batch Options 构建和推理批处理选项
使用隐式批处理时,引擎的最大批处理大小(如果未指定)设置为推理批处理大小; 使用显式批处理时,如果仅指定形状用于推理,它们也将在构建配置文件中用作 min/opt/max; 如果只为构建指定了形状,则 opt 形状也将用于推理; 如果两者都被指定,它们必须是兼容的; 如果启用了显式批处理但都未指定,则模型必须为所有输入提供完整的静态维度,包括批处理大小

1.5 Reporting Options 报告选项

–verbose : 使用详细日志记录(默认值 = false)–avgRuns=N : 报告 N 次连续迭代的平均性能测量值(默认值 = 10)–percentile=P : 报告 P 百分比的性能(0<=P<=100,0 代表最大性能,100 代表最小性能;(默认 = 99%)–dumpRefit : 从可改装引擎打印可改装层和重量–dumpOutput : 打印最后一次推理迭代的输出张量(默认 = 禁用)–dumpProfile : 每层打印配置文件信息(默认 = 禁用)–exportTimes= : 将计时结果写入 json 文件(默认 = 禁用)–exportOutput= : 将输出张量写入 json 文件(默认 = 禁用)–exportProfile= : 将每层的配置文件信息写入 json 文件(默认 = 禁用)

1.6 System Options 系统选项

–device=N :选择 cuda 设备 N(默认 = 0)–useDLACore=N : 为支持 DLA 的层选择 DLA 核心 N(默认 = 无)–allowGPUFallback : 启用 DLA 后,允许 GPU 回退不受支持的层(默认 = 禁用)–plugins : 要加载的插件库 (.so)(可以多次指定)

1.7 Help 帮助
–help, -h : 打印以上帮助信息

方法2、使用python脚本

参考官方给到的demo写一个脚本转:官方脚本位于下载的目录:TensorRT-7.2.3.4/samples/python/yolov3_onnx/onnx_to_tensorrt.py

import os 
import tensorrt as trt
os.environ["CUDA_VISIBLE_DEVICES"]='0'
TRT_LOGGER = trt.Logger()
onnx_file_path = 'Unet375-simple.onnx'
engine_file_path = 'Unet337.trt'

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_workspace_size = 1 << 28 # 256MiB
            builder.max_batch_size = 1
            # Parse model file
            if not os.path.exists(onnx_file_path):
                print('ONNX file {} not found, please run yolov3_to_onnx.py first to generate it.'.format(onnx_file_path))
                exit(0)
            print('Loading ONNX file from path {}...'.format(onnx_file_path))
            with open(onnx_file_path, 'rb') as model:
                print('Beginning ONNX file parsing')
                if not parser.parse(model.read()):
                    print ('ERROR: Failed to parse the ONNX file.')
                    for error in range(parser.num_errors):
                        print (parser.get_error(error))

            network.get_input(0).shape = [1, 3, 300, 400]
            print('Completed parsing of ONNX file')
            print('Building an engine from file {}; this may take a while...'.format(onnx_file_path))
            #network.mark_output(network.get_layer(network.num_layers-1).get_output(0))
            engine = builder.build_cuda_engine(network)
            print("Completed creating Engine")
            with open(engine_file_path, "wb") as f:
                f.write(engine.serialize())

运行ONNX模型

  • 在具有静态输入形状的全维模式下运行 ONNX 模型
trtexec --onnx=model.onnx
  • 使用给定的输入形状在全维模式下运行 ONNX 模型

trtexec –onnx=model.onnx –shapes=input:32x3x244x244

  • 使用一系列可能的输入形状对 ONNX 模型进行基准测试
trtexec --onnx=model.onnx --minShapes=input:1x3x244x244 --optShapes=input:16x3x244x244 --maxShapes=input:32x3x244x244 --shapes=input:5x3x244x244



trtexec --onnx=depth_feat_model.onnx --minShapes=input:1x4x128x128 --maxShapes=input:1x4x896x896 --shapes=input:1x4x512x512 --saveEngine=depth_feat_model.engine --verbose --workspace=1024 --fp32

网络性能测试

  • 加载转换后的TensorRT模型进行性能测试,指定batch大小
trtexec --loadEngine=mnist16.trt --batch=1

打印输出:
trtexec会打印出很多时间,这里需要对每个时间的含义进行解释,然后大家各取所需,进行评测。总的打印如下:
[09/06/2021-13:50:34] [I] Average on 10 runs - GPU latency: 2.74553 ms - Host latency: 3.74192 ms (end to end 4.93066 ms, enqueue 0.624805 ms)  # 跑了10次,GPU latency: GPU计算耗时, Host latency:GPU输入+计算+输出耗时,end to end:GPU端到端的耗时,eventout - eventin,enqueue:CPU异步耗时
[09/06/2021-13:50:34] [I] Host Latency
[09/06/2021-13:50:34] [I] min: 3.65332 ms (end to end 3.67603 ms)
[09/06/2021-13:50:34] [I] max: 5.95093 ms (end to end 6.88892 ms)
[09/06/2021-13:50:34] [I] mean: 3.71375 ms (end to end 5.30082 ms)
[09/06/2021-13:50:34] [I] median: 3.70032 ms (end to end 5.32935 ms)
[09/06/2021-13:50:34] [I] percentile: 4.10571 ms at 99% (end to end 6.11792 ms at 99%)
[09/06/2021-13:50:34] [I] throughput: 356.786 qps
[09/06/2021-13:50:34] [I] walltime: 3.00741 s
[09/06/2021-13:50:34] [I] Enqueue Time
[09/06/2021-13:50:34] [I] min: 0.248474 ms
[09/06/2021-13:50:34] [I] max: 2.12134 ms
[09/06/2021-13:50:34] [I] median: 0.273987 ms
[09/06/2021-13:50:34] [I] GPU Compute
[09/06/2021-13:50:34] [I] min: 2.69702 ms
[09/06/2021-13:50:34] [I] max: 4.99219 ms
[09/06/2021-13:50:34] [I] mean: 2.73299 ms
[09/06/2021-13:50:34] [I] median: 2.71875 ms
[09/06/2021-13:50:34] [I] percentile: 3.10791 ms at 99%
[09/06/2021-13:50:34] [I] total compute time: 2.93249 s

Host Latency gpu: 输入+计算+输出 三部分的耗时
Enqueue Time:CPU异步的时间(该时间不具有参考意义,因为GPU的计算可能还没有完成)
GPU Compute:GPU计算的耗时
综上,去了Enqueue Time时间都是有意义的
  • 收集和打印时序跟踪信息
trtexec --deploy=data/AlexNet/AlexNet_N2.prototxt --output=prob --exportTimes=trace.json
  • 使用多流调整吞吐量

调整吞吐量可能需要运行多个并发执行流。例如,当实现的延迟完全在所需阈值内时,我们可以增加吞吐量,即使以一些延迟为代价。例如,为批量大小 1 和 2 保存引擎并假设两者都在 2ms 内执行,延迟阈值:

trtexec --deploy=GoogleNet_N2.prototxt --output=prob --batch=1 --saveEngine=g1.trt --int8 --buildOnly
trtexec --deploy=GoogleNet_N2.prototxt --output=prob --batch=2 --saveEngine=g2.trt --int8 --buildOnly
  • 保存的引擎可以尝试找到低于 2 ms 的组合批次/流,以最大化吞吐量:
trtexec --loadEngine=g1.trt --batch=1 --streams=2
trtexec --loadEngine=g1.trt --batch=1 --streams=3
trtexec --loadEngine=g1.trt --batch=1 --streams=4
trtexec --loadEngine=g2.trt --batch=2 --streams=2

python调用 TensorRT模型的推理

推理依旧分为动态尺寸的和固定尺寸的,动态推理这一块C++版本的资料比较多,python接口的比较少,固定尺寸的推理官方也有demo,分为异步同步推理。

python推理接收numpy格式的数据输入。

动态推断

import tensorrt as trt
import pycuda.driver as cuda
#import pycuda.driver as cuda2
import pycuda.autoinit
import numpy as np
import cv2
def load_engine(engine_path):
    #TRT_LOGGER = trt.Logger(trt.Logger.WARNING)  # INFO
    TRT_LOGGER = trt.Logger(trt.Logger.ERROR)
    with open(engine_path, 'rb') as f, trt.Runtime(TRT_LOGGER) as runtime:
        return runtime.deserialize_cuda_engine(f.read())
 
path ='/home/caidou/trt_python/model_1_-1_-1_3.engine'
#这里不以某个具体模型做为推断例子.
 
# 1. 建立模型,构建上下文管理器
engine = load_engine(path)
context = engine.create_execution_context()
context.active_optimization_profile = 0
 
#2. 读取数据,数据处理为可以和网络结构输入对应起来的的shape,数据可增加预处理
imgpath = '/home/caidou/test/aaa.jpg'
image = cv2.imread(imgpath)
image = np.expand_dims(image, 0)  # Add batch dimension.  
 
 
#3.分配内存空间,并进行数据cpu到gpu的拷贝
#动态尺寸,每次都要set一下模型输入的shape,0代表的就是输入,输出根据具体的网络结构而定,可以是0,1,2,3...其中的某个头。
context.set_binding_shape(0, image.shape)
d_input = cuda.mem_alloc(image.nbytes)  #分配输入的内存。
 
 
output_shape = context.get_binding_shape(1) 
buffer = np.empty(output_shape, dtype=np.float32)
d_output = cuda.mem_alloc(buffer.nbytes)    #分配输出内存。
cuda.memcpy_htod(d_input,image)
bindings = [d_input ,d_output]
 
#4.进行推理,并将结果从gpu拷贝到cpu。
context.execute_v2(bindings)  #可异步和同步
cuda.memcpy_dtoh(buffer,d_output)  
output = buffer.reshape(output_shape)
 
#5.对推理结果进行后处理。这里只是举了一个简单例子,可以结合官方静态的yolov3案例完善。

静态推断:

静态推断和动态推断差不多,只不过不需要每次都分配输入和输出的内存空间。

import tensorrt as trt
import pycuda.driver as cuda
#import pycuda.driver as cuda2
import pycuda.autoinit
import numpy as np
import cv2
path ='/home/caidou/trt_python/model_1_4_256_256.engine'
engine = load_engine(path)
imgpath = 'aaa.jpg'
context = engine.create_execution_context()
image1 = cv2.write(imgpath)
image1 = cv2.resize(image1,(256,256))
image2 = image1.copy()
image3 = image1.copy()
image4 = image1.copy()
image = np.concatenate((image1,image2,image3,image4))
image = image.reshape(-1,256,256)
 
# image = np.expand_dims(image, axis=1)
image = image.astype(np.float32)
 
image = image.ravel()#数据平铺
outshape= context.get_binding_shape(1) 
output = np.empty((outshape), dtype=np.float32)
d_input = cuda.mem_alloc(1 * image.size * image.dtype.itemsize)
d_output = cuda.mem_alloc(1*output.size * output.dtype.itemsize)
bindings = [int(d_input), int(d_output)]
stream = cuda.Stream()
for i in tqdm.tqdm(range(600)):
    cuda.memcpy_htod(d_input,image)
    context.execute_v2(bindings)
    cuda.memcpy_dtoh(output, d_output)

更新:

SLAM-同时定位与地图构建

同时定位与地图构建(英语:Simultaneous localization and mapping,一般直接称SLAM)是一种概念:希望机器人从未知环境的未知地点出发,在运动过程中通过重复观测到的地图特征(比如,墙角,柱子等)定位自身位置和姿态,再根据自身位置增量式的构建地图,从而达到同时定位和地图构建的目的。

一、SLAM的典型应用领域

机器人定位导航领域:地图建模。SLAM可以辅助机器人执行路径规划、自主探索、导航等任务。国内的科沃斯、塔米以及最新面世的岚豹扫地机器人都可以通过用SLAM算法结合激光雷达或者摄像头的方法,让扫地机高效绘制室内地图,智能分析和规划扫地环境,从而成功让自己步入了智能导航的阵列。国内思岚科技(SLAMTEC)为这方面技术的主要提供商,SLAMTEC的命名就是取自SLAM的谐音,其主要业务就是研究服务机器人自主定位导航的解决方案。目前思岚科技已经让关键的二维激光雷达部件售价降至百元,这在一定程度上无疑进一步拓展了SLAM技术的应用前景。

VR/AR方面:辅助增强视觉效果。SLAM技术能够构建视觉效果更为真实的地图,从而针对当前视角渲染虚拟物体的叠加效果,使之更真实没有违和感。VR/AR代表性产品中微软Hololens、谷歌ProjectTango以及MagicLeap都应用了SLAM作为视觉增强手段。

无人机领域:地图建模。SLAM可以快速构建局部3D地图,并与地理信息系统(GIS)、视觉对象识别技术相结合,可以辅助无人机识别路障并自动避障规划路径,曾经刷爆美国朋友圈的Hovercamera无人机,就应用到了SLAM技术。

无人驾驶领域:视觉里程计。SLAM技术可以提供视觉里程计功能,并与GPS等其他定位方式相融合,从而满足无人驾驶精准定位的需求。例如,应用了基于激光雷达技术Google无人驾驶车以及牛津大学MobileRoboticsGroup11年改装的无人驾驶汽车野猫(Wildcat)均已成功路测。

二、SLAM框架

SLAM系统框架如图所示,一般分为五个模块,包括传感器数据、视觉里程计、后端、建图及回环检测。

传感器数据:主要用于采集实际环境中的各类型原始数据。包括激光扫描数据、视频图像数据、点云数据等。

视觉里程计:主要用于不同时刻间移动目标相对位置的估算。包括特征匹配、直接配准等算法的应用。

后端:主要用于优化视觉里程计带来的累计误差。包括滤波器、图优化等算法应用。

建图:用于三维地图构建。

回环检测:主要用于空间累积误差消除

其工作流程大致为:

传感器读取数据后,视觉里程计估计两个时刻的相对运动(Ego-motion),后端处理视觉里程计估计结果的累积误差,建图则根据前端与后端得到的运动轨迹来建立地图,回环检测考虑了同一场景不同时刻的图像,提供了空间上约束来消除累积误差。

三、SLAM分类(基于传感器的SLAM分类)

目前用在SLAM上的传感器主要分为这两类,一种是基于激光雷达的激光SLAM(Lidar SLAM)和基于视觉的VSLAM(Visual SLAM)。

1.激光SLAM

激光SLAM采用2D或3D激光雷达(也叫单线或多线激光雷达),2D激光雷达一般用于室内机器人上(如扫地机器人),而3D激光雷达一般使用于无人驾驶领域。激光雷达的出现和普及使得测量更快更准,信息更丰富。激光雷达采集到的物体信息呈现出一系列分散的、具有准确角度和距离信息的点,被称为点云。通常,激光SLAM系统通过对不同时刻两片点云的匹配与比对,计算激光雷达相对运动的距离和姿态的改变,也就完成了对机器人自身的定位。

激光雷达测距比较准确,误差模型简单,在强光直射以外的环境中运行稳定,点云的处理也比较容易。同时,点云信息本身包含直接的几何关系,使得机器人的路径规划和导航变得直观。激光SLAM理论研究也相对成熟,落地产品更丰富。

对比相机、ToF 和其他传感器,激光可以使精确度大大提高,常用于自动驾驶汽车和无人机等高速移动运载设备的相关应用。激光传感器的输出值一般是二维 (x, y) 或三维 (x, y, z) 点云数据。激光传感器点云提供了高精确度距离测度数据,特别适用于 SLAM 建图。一般来说,首先通过点云匹配来连续估计移动。然后,使用计算得出的移动数据(移动距离)进行车辆定位。对于激光点云匹配,会使用迭代最近点 (ICP) 和正态分布变换 (NDT) 等配准算法。二维或三维点云地图可以用栅格地图或体素地图表示。

但就密度而言,点云不及图像精细,因此并不总能提供充足的特征来进行匹配。例如,在障碍物较少的地方,将难以进行点云匹配,因此可能导致跟丢车辆。此外,点云匹配通常需要高处理能力,因此必须优化流程来提高速度。鉴于存在这些挑战,自动驾驶汽车定位可能需要融合轮式测距、全球导航卫星系统 (GNSS) 和 IMU 数据等其他测量结果。仓储机器人等应用场景通常采用二维激光雷达 SLAM,而三维激光雷达点云 SLAM 则可用于无人机和自动驾驶。

2.视觉SLAM

眼睛是人类获取外界信息的主要来源。视觉SLAM也具有类似特点,它可以从环境中获取海量的、富于冗余的纹理信息,拥有超强的场景辨识能力。早期的视觉SLAM基于滤波理论,其非线性的误差模型和巨大的计算量成为了它实用落地的障碍。近年来,随着具有稀疏性的非线性优化理论(Bundle Adjustment)以及相机技术、计算性能的进步,实时运行的视觉SLAM已经不再是梦想。

视觉SLAM的优点是它所利用的丰富纹理信息。例如两块尺寸相同内容却不同的广告牌,基于点云的激光SLAM算法无法区别他们,而视觉则可以轻易分辨。这带来了重定位、场景分类上无可比拟的巨大优势。同时,视觉信息可以较为容易的被用来跟踪和预测场景中的动态目标,如行人、车辆等,对于在复杂动态场景中的应用这是至关重要的。

通过对比我们发现,激光SLAM和视觉SLAM各擅胜场,单独使用都有其局限性,而融合使用则可能具有巨大的取长补短的潜力。例如,视觉在纹理丰富的动态环境中稳定工作,并能为激光SLAM提供非常准确的点云匹配,而激光雷达提供的精确方向和距离信息在正确匹配的点云上会发挥更大的威力。而在光照严重不足或纹理缺失的环境中,激光SLAM的定位工作使得视觉可以借助不多的信息进行场景记录。

近年来,SLAM导航技术已取得了很大的发展,它将赋予机器人和其他智能体前所未有的行动能力,而激光SLAM与视觉SLAM必将在相互竞争和融合中发展,使机器人从实验室和展厅中走出来,做到真正的服务于人类。

顾名思义,视觉 SLAM(又称 vSLAM)使用从相机和其他图像传感器采集的图像。视觉 SLAM 可以使用普通相机(广角、鱼眼和球形相机)、复眼相机(立体相机和多相机)和 RGB-D 相机(深度相机和 ToF 相机)。

视觉 SLAM 所需的相机价格相对低廉,因此实现成本较低。此外,相机可以提供大量信息,因此还可以用来检测路标(即之前测量过的位置)。路标检测还可以与基于图的优化结合使用,这有助于灵活实现 SLAM。

使用单个相机作为唯一传感器的 vSLAM 称为单目 SLAM,此时难以定义深度。这个问题可以通过以下方式解决:检测待定位图像中的 AR 标记、棋盘格或其他已知目标,或者将相机信息与其他传感器信息融合,例如测量速度和方向等物理量的惯性测量单元 (IMU) 信息。vSLAM 相关的技术包括运动重建 (SfM)、视觉测距和捆绑调整。

视觉 SLAM 算法可以大致分为两类。稀疏方法:匹配图像的特征点并使用 PTAM 和 ORB-SLAM 等算法。稠密方法:使用图像的总体亮度以及 DTAM、LSD-SLAM、DSO 和 SVO 等算法。

SIFT–尺度不变特征变换

https://docs.opencv.org/4.1.2/da/df5/tutorial_py_sift_intro.html

SIFT,即尺度不变特征变换(Scale-invariant feature transform,SIFT),是用于图像处理领域的一种描述。这种描述具有尺度不变性,可在图像中检测出关键点,是一种局部特征描述子。

尺度不变特征变换 (Scale-invariant feature transform, SIFT) 是计算机视觉中一种检测、描述和匹配图像局部特征点的方法,通过在不同的尺度空间中检测极值点或特征点 (Conrner Point, Interest Point) ,提取出其位置、尺度和旋转不变量,并生成特征描述子,最后用于图像的特征点匹配。SIFT 特征凭借其良好的性能广泛应用于运动跟踪 (Motion tracking) 、图像拼接 (Automatic mosaicing) 、3D 重建 (3D reconstruction) 、移动机器人导航 (Mobile robot navigation) 以及目标识别 (Object Recognition) 等领域。

SIFT特征的特点

SIFT是一种检测、描述、匹配图像局部特征点的算法,通过在尺度空间中检测极值点,提取位置、尺度、旋转不变量,并抽象成特征向量加以描述,最后用于图像特征点的匹配。SIFT特征对灰度、对比度变换、旋转、尺度缩放等保持不变性,对视角变化、仿射变化、噪声也具有一定的鲁棒性。但其实时性不高,对边缘光滑的目标无法准确提取特征点。

SIFT算法主要包括四个步骤。

1. 尺度空间极值检测

从上图可以明显看出,我们不能使用相同的窗口来检测具有不同比例的关键点。即便小拐角可以。但是要检测更大的拐角,我们将需要更大的窗口。为此,使用了比例空间滤波。在其中,找到具有各种σ值的图像的高斯拉普拉斯算子。LoG用作斑点检测器,可检测由于σ的变化而导致的各种大小的斑点。简而言之,σ用作缩放参数。例如,在上图中,低σ的高斯核对于较小的拐角给出较高的值,而高σ的高斯核对于较大的拐角而言非常合适。因此,我们可以找到整个尺度和空间上的局部最大值,这给了我们(x,y,σ)值的列表,这意味着在(x,y在σ尺度上有一个潜在的关键点。

但是这种LoG代价昂贵,因此SIFT算法使用的是高斯差值,它是LoG的近似值。高斯差是作为具有两个不同σ的图像的高斯模糊差而获得的,设为σ和kσ。此过程是针对高斯金字塔中图像的不同八度完成的。如下图所示:

一旦找到该DoG,便会在图像上搜索比例和空间上的局部极值。例如,将图像中的一个像素与其8个相邻像素以及下一个比例的9个像素和前一个比例的9个像素进行比较。如果是局部极值,则可能是关键点。从根本上说,关键点是最好的代表。如下图所示:

对于不同的参数,本文给出了一些经验数据,可以概括为:octaves=4,缩放尺度=5,初始σ=1.6,k=√2等作为最佳值。

2. 关键点定位

一旦找到潜在的关键点位置,就必须对其进行优化以获取更准确的结果。他们使用了标度空间的泰勒级数展开来获得更精确的极值位置,如果该极值处的强度小于阈值(根据论文为0.03),则将其拒绝。在OpenCV DoG中,此阈值称为**ContrastThreshold**,它对边缘的响应较高,因此也需要删除边缘。

为此,使用类似于哈里斯拐角检测器的概念。他们使用2×2的Hessian矩阵(H)计算主曲率。从哈里斯拐角检测器我们知道,对于边缘,一个特征值大于另一个特征值。因此,这里他们使用了一个简单的函数。

如果该比率大于一个阈值(在OpenCV中称为**edgeThreshold**),则该关键点将被丢弃。论文上写的值为10。

因此,它消除了任何低对比度的关键点和边缘关键点,剩下的就是很可能的目标点。

3. 方向分配

现在,将方向分配给每个关键点,以实现图像旋转的不变性。根据比例在关键点位置附近采取邻域,并在该区域中计算梯度大小和方向。创建了一个具有36个覆盖360度的bin的方向直方图(通过梯度幅度和σ等于关键点比例的1.5的高斯加权圆窗加权)。提取直方图中的最高峰,并且将其超过80%的任何峰也视为计算方向。它创建的位置和比例相同但方向不同的关键点。它有助于匹配的稳定性。

4. 关键点描述

现在创建了关键点描述符。在关键点周围采用了16×16的邻域。它分为16个4×4大小的子块。对于每个子块,创建8 bin方向直方图。因此共有128个bin值可用。它被表示为形成关键点描述符的向量。除此之外,还采取了几种措施来实现针对照明变化,旋转等的鲁棒性。

5. 关键点匹配

通过识别两个图像的最近邻,可以匹配两个图像之间的关键点。但是在某些情况下,第二个最接近的匹配可能非常接近第一个。它可能是由于噪音或其他原因而发生的。在那种情况下,采用最接近距离与第二最接近距离之比。如果大于0.8,将被拒绝。根据论文,它可以消除大约90%的错误匹配,而仅丢弃5%的正确匹配。 因此,这是SIFT算法的总结。有关更多详细信息和理解,强烈建议阅读原始论文。记住一件事,该算法已申请专利。所以这个算法包含在opencv contrib repo中.

OpenCV中的SIFT

现在,让我们来看一下OpenCV中可用的SIFT功能。让我们从关键点检测开始并进行绘制。首先,我们必须构造一个SIFT对象。我们可以将不同的参数传递给它,这些参数是可选的,它们在docs中已得到很好的解释。

import numpy as np
import cv2 as cv
img = cv.imread('home.jpg')
gray= cv.cvtColor(img,cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d.SIFT_create()
kp = sift.detect(gray,None)
img=cv.drawKeypoints(gray,kp,img)
cv.imwrite('sift_keypoints.jpg',img)

sift.detect()函数在图像中找到关键点。如果只想搜索图像的一部分,则可以通过掩码。每个关键点是一个特殊的结构,具有许多属性,例如其(x,y)坐标,有意义的邻域的大小,指定其方向的角度,指定关键点强度的响应等。

OpenCV还提供**cv.drawKeyPoints**()函数,该函数在关键点的位置绘制小圆圈。 如果将标志**cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS**传递给它,它将绘制一个具有关键点大小的圆,甚至会显示其方向。 请参见以下示例。

img=cv.drawKeypoints(gray,kp,img,flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) cv.imwrite('sift_keypoints.jpg',img)

查看下面的结果: 

现在要计算描述符,OpenCV提供了两种方法。 1. 由于已经找到关键点,因此可以调用**sift.compute**(),该函数根据我们找到的关键点来计算描述符。例如:kp,des = sift.compute(gray,kp) 2. 如果找不到关键点,则可以使用**sift.detectAndCompute**()函数在单步骤中直接找到关键点和描述符。

我们将看到第二种方法:

sift = cv.xfeatures2d.SIFT_create() 
kp, des = sift.detectAndCompute(gray,None)

这里的kp将是一个关键点列表,而des是一个形状为NumberofKeypoints×128的数字数组。

这样我们得到了关键点,描述符等。

MVS学习–《A Comparison and Evaluation of Multi-View Stereo Reconstruction Algorithms》

1. 前言

Middlebury是计算机视觉和三维中间领域著名的高校,特别是提供了著名的立体匹配benchmark数据库,并不断提供新数据的更新。在MVS领域,也同样提供了经典的benchmark数据库,包含两个物体-Temple和Dino,其中Temple有312张相片,Dino有363张相片,如下图所示。并且每个物体还提供了由激光Lidar测量得到的地面真值(Groud Truth)数据,因此可以用来准确的衡量不同MVS算法的准确性(重建的三维模型与真值的差异)和完整性(有多少真值包含在重建的三维模型中)。

在建立该数据库的过程中,Middlebury的研究团队分类总结了当时(2006年)的state-of-art的算法,提出了算法有效性评价标准。基于该标准,并使用该数据库验证这些算法的有效性,最终形成该文章1。这篇文章是后来几乎每一篇研究MVS算法的文章的必引参考文献,其中对于算法的分类介绍和有效性验证规则十分经典,下面分别进行总结。

2. MVS算法分类

MVS是指Multiview Stereo,具体来说是通过多幅已知拍摄方位信息(外方位元素)的图像来估计目标三维信息的算法,数据基于图像的三维重建中一大类非常重要和实用的算法。文章中提到类似的方法还有双目或者三目立体匹配方法,这一类方法能够获得单一的视差图,但是受限于照片数量和拍摄角度,无法覆盖物体的全部表面。另一类方法是多基线立体重建方法,可以构建稀疏特征点集。

一般来说,MVS可以按照如下6个方面的标准进行分类:

  1. 场景表达方式(scene representation);
  2. 图像一致性计算方法(photo consistency measure);
  3. 可见性模型(visible model);
  4. 在重建时优先考虑的形状约束(shape prior);
  5. 重建算法(reconstruction algorithm);
  6. 初始化条件(initialization requirements)。

下面分别对每个方面进行简单的描述。

2.1 场景表达(Scene Representation)

场景表达是指重建得到的三维场景使用什么样的数学模型进行表达,一般来说有如下4种方式:

  1. 体素(Voxel)
  2. 层次级(level set):记录每个点到某个最近平面的距离
  3. 多边形实体(polygon mesh):这是应该是我们最熟悉的表达方式,也是人工三维建模最常见的数据表达方式
  4. 深度图(depth map):一般基于像方立体匹配算法算法生成的结果就是深度图,每个像素的灰度值代表该像素距离当前图像平面的距离。

2.2 图像一致性计算方法(Photo Consistency Measure)

这部分和双目立体匹配中用到的图像一致性计算方法类似,但是考虑到MVS本身的特殊性,一般来说,MVS中图像一致性计算根据搜索内容的不同分为以下两种方法

1. 基于物方的图像一致性计算方法

通常使用体素表达方法,搜索空间中的每个体素在对应两幅图像中的投影位置的图像一致性,如果该一致性计算值小于某个阈值,则该体素可以认为是代表了真实物体。

2. 基于像方的图像一致性计算方法

根据极线约束,对于一幅图像的某个点,搜索其对应极线上最相似的匹配点(一致性最高),这种方法通常在双目立体视觉中使用。

需要注意的是这两种方法都是基于物体表面为Lambartian的假设,但是也有进一步的研究利用BRDF进行计算,或者考虑物体的阴影,消除物体阴影对于一致性计算的影响。

2.3 可见性模型(Visible Model)

可见性模型是在计算图像一致性时,决定究竟哪些图像和参考图像有共视区域,可以进行图像一致性计算的方法。一般来说,有如下三种模型

  • 几何模型。
  • 准几何模型。
  • 基于粗差(outlier)的模型,通常是将遮挡视为粗差,因为对于一个点来说,在两视中被看到的可能性大于被遮挡的可能性。

2.4 在重建时优先考虑的形状约束(Shape Prior)

由于常见的弱纹理(大范围区域颜色相同或者相近)或者无纹理等原因,导致在匹配是在这些区域无法得到良好的匹配结果,因此需要引入形状约束来近似约束这些区域的可能形状,可以使得最终得到的场景具有某种特殊的性质。这种方法在双目立体匹配的研究中是极为常见的方法,但是在MVS中,由于多幅图像提供更强的约束,较少使用这种方法。常见的形状约束方法如下:

  1. 基于场景重建的技术通常采用“最少平面数”约束,因为过多的多边形面片会使得场景过于破碎。
  2. 基于体素和Space carve的重建方法通常增加“最多平面数”约束,使得表面具有更加丰富的细节。
  3. 在基于像方的匹配方法中,通常添加局部平滑约束:例如双目立体匹配中常见的piece-wise smothness,假设场景中的弱纹理区域是与摄影平面平行的小平面。

2.5 重建算法(Reconstruction Algorithm)

  1. 体素着色算法:从一个volumn中提取一个平面出来
  2. 通过递推的方法展开一个平面:在过程中最小化代价函数(based on voxels, level-set, mesh)
  3. 基于像方的匹配,生成深度图,并对不同图像间的深度图进行融合
  4. 提取特征点,拟合一个面来重建特征

2.6 初始化条件(Initialization Requirements)

  1. 需要图像集(毕竟是基于图像的三维重建,需要尽可能多的多角度拍摄的同一场景的图像)
  2. 几乎所有的算法都要求或者假设待重建三维目标的空间范围或者scene geometry
  3. 基于像方的方法要求最大/最小视差(这一点要求和2类似)

3. MVS算法的评价

文章中提出,对于MVS算法应该从一下两个方面进行评价

1. 准确性

准确性是指重建结果与真值间的差距,一般方法是,对于重建结果中的一个三维空间点,寻找其对应真值中的点,计算其距离,最后统计所有点距离真值的距离。 根据统计结果来评价重建结果的准确性。

2. 完整性

完整性是指有多少真值被包含在重建结果中。一般方法与准确性计算类似,但是是计算真值中的点到重建结果中最近点的距离,统计所有真值点的计算结果来评价重建的完整性。需要注意的是,如果真值中的点距离重建结果中最近点的距离大于某个阈值,则认为是没有找打匹配点,也就是该真值点没有被覆盖。

SFM的重建成果是稀疏三维点云,而MVS可以获得更好的结果

(1)如何理解密集点云的生成原理

  MVS是生成密集点云的方法,事实上,为什么我们在SFM中不能得到密集点云?因为,SFM中我们用来做重建的点是由特征匹配提供的!这些匹配点天生不密集!而使用计算机来进行三维点云重建,我们必须认识到,点云的密集程度是由人为进行编程进行获取的。SFM获得点的方式决定了它不可能直接生成密集点云。

  而MVS则几乎对照片中的每个像素点都进行匹配,几乎重建每一个像素点的三维坐标,这样得到的点的密集程度可以较接近图像为我们展示出的清晰度。

  其实现的理论依据在于,多视图照片间,对于拍摄到的相同的三维几何结构部分,存在极线几何约束。

描述这种几何约束:

  想象,对于在两张图片中的同一个点。现在回到拍摄照片的那一刻,在三维世界中,存在一条光线从照片上这一点,同时穿过拍摄这张照片的相机的成像中心点,最后会到达空间中一个三维点,这个三维点同时也会在另一张照片中以同样的方式投影。

  这个过程这样看来,很普通,就如同普通的相机投影而已。但是因为两张图片的原因,他们之间存在联系,这种联系的证明超过了能力范围,但是我们只需要知道,此种情况下,两张照片天然存在了一种约束。

  X表示空间中的一点,x1、x2为X在两张图片中的同一点。由于天然的约束,已知x1,想要在另一张图片中找到x2,可以在直线L2上进行一维寻找。  MVS主要做的就是如何最佳搜索匹配不同相片的同一个点。

2)初步探究MVS中的点匹配方法

  在有了约束的基础上,接下来就是在图片上的一条线上进行探测,寻找两张图片上的同一点。主要方法为逐像素判断,两个照片上的点是否是同一点——为此提出图像点间的“一致性判定函数”

   π (p)是使得点p投影到照片上一点的函数, Ω(x) 函数定义了一个点x周围的区域,I(x) 函数代表了照片区域的强度特征,ρ(f, g) 是用来比较两个向量之间的相似程度的

  ρ函数和Ω函数的具体选择决定这个”一致性判别“的准确度。这个函数的具体实现,由编程实现。

  

参考文献


  1. Seitz, S. M., Curless, B., Diebel, J., Scharstein, D., & Szeliski, R. (n.d.). A Comparison and Evaluation of Multi-View Stereo Reconstruction Algorithms. In 2006 IEEE Computer Society Conference on Computer Vision and Pattern Recognition – Volume 1 (CVPR’06) (Vol. 1, pp. 519–528). IEEE. https://doi.org/10.1109/CVPR.2006.19

基于SfM(Structure from motion)的三维重建

SfM(Structure from motion) 是一种三维重建的方法,用于从motion中实现3D重建。也就是从时间系列的2D图像中推算3D信息。

人的大脑可以从动的物体中取得其三维的信息,是因为大脑在动的2D图像中找到了匹配的地方,即Corresponding area (points)。然后通过匹配点之间的视差得到相对的深度信息,在这一点上,原理和基于Stereo的三维重建相同。

SfM的输入是一段motion或者一时间系列的2D图群,如下图所示 [1],这里不需要任何相机的信息。然后通过2D图之间的匹配可以推断出相机的各项参数Corresponding points可以用SIFT,SURF来匹配,也可以用最新的AKAZE(SIFT的改进版,2010)来匹配。而Corresponding points的跟踪则可以用Lucas-Kanede的Optical Flow来完成。

在SfM中,误匹配会造成较大的Error,所以要对匹配进行筛选,目前流行的方法是RANSAC(Random Sample Consensus)。2D的误匹配点可以应用3D的Geometric特征来进行排除。

Bundler [2] 就是一种SfM的方法,Bundler使用了基于SIFT的匹配算法,并且对匹配进行了过滤去噪处理。下图显示了一组测试数据(一时间系列的2D图群):

将这些图片保存到同一个文件夹,然后将文件夹的目录输入,Bundler会自行处理,之后会得到一群Corresponding points。比如其中的一组Corresponding points (A1,A2,A3,…Am),其实他们来自同一个三维点A的Projection。所以通过这些点可以重建三维点A。然后将很多组Corresponding points 进行重建,则得到了一群三维的点,这里称为3D点阵。

然后3D点阵可以通过MeshLab(开源Source,支持Windows/Linux/Mac)来重建稀疏的Mesh。也可以通过PMVS(Patch-based Multi-view Stereo)来重建Dense的Mesh[3]。

[1] 満上育久 ”Structure from Motion – Osaka University“ 映像情报メディア学会志 Vol.65, No.4, pp.479-482, 2011.

[2] N.Snavely, S.M. Seitz, R.Szeliski, “Modeling the World from Internet Photo Collections”, International Journal of Computer Vision, vol.80, no.2, 2008.

[3] Y. Furukawa, J.Ponce, “Accurate, Dense and Robust Multi-view Stereopsis” IEEE Transactions on Pattern Analysis and Machine Intelligence, 2009.

补充:通过视差d求解深度图:

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

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

通过同一水平线的两个视图获得深度图

神奇的达尔文进化定律告诉我们,单个眼睛的自然界生物大都灭绝了。自然界的大多物种都是和人一样,需要两只眼睛来做三维空间定位。那为什么需要两只眼睛呢?

因为一只眼睛看到的图像是二维的,二维的信息是无法用来表示三维的空间的,如上图所示,虽然处于同一水平面上的照相机L,R拍摄了同一个物体,两者之间产生的图片是不同的。并且这种不同是不能通过平移生成的图片所消除的。离照相机近的物体偏离的位置比较大,离照相机远的物体偏离的比较少。这种差异性的存在就是三维空间带来的。(这部分请参考双眼可以测距和建立立体环境,双摄像头可以吗?)。同时同一水平线上的两个照相机拍摄到的照片是服从以下物理规律的:

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

MVSNeRF:多视角立体图像的快速广义辐射场重建

MVSNeRF: Fast Generalizable Radiance Field Reconstruction
from Multi-View Stereo

https://github.com/apchenstu/mvsnerf

提出了一种新的神经渲染方法neural rendering approach  MVSNeRF,它可以有效地重建用于视图合成的神经辐射场。与先前关于神经辐射场的工作不同,这些工作考虑对密集捕获的图像进行逐场景优化,我们提出了一种通用的深度神经网络,该网络可以通过快速网络推理仅从三个附近的输入视图重建辐射场。我们的方法利用平面扫描成本体plane-swept cost volumes(广泛用于多视图立体multi-view stereo)进行几何感知场景推理,并将其与基于物理的体渲染相结合进行神经辐射场重建。我们在DTU数据集中的真实对象上训练我们的网络,并在三个不同的数据集上测试它以评估它的有效性和可推广性generalizability我们的方法可以跨场景(甚至室内场景,完全不同于我们的对象训练场景)进行推广generalize across scenes,并仅使用三幅输入图像生成逼真的视图合成结果,明显优于目前的广义辐射场重建generalizable radiance field reconstruction工作。此外,如果捕捉到密集图像dense images are captured,我们估计的辐射场表示可以容易地微调easily fine-tuned;这导致快速的逐场景重建fast per-scene reconstruction,比NeRF具有更高的渲染质量和更少的优化时间。

我们利用最近在深度多视图立体(MVS)deep multi- view stereo (MVS)上的成功[50,18,10]。这一系列工作可以通过对成本体积应用3D卷积applying 3D convolutions on cost volumes来训练用于3D重建任务的可概括的神经网络。与[50]类似,我们通过将来自附近输入视图的2D图像特征(由2D CNN推断)扭曲warping到参考视图的平截头体中的扫描平面上sweeping planes in the reference view’s frustrum,在输入参考视图处构建成本体。不像MVS方法[50,10]仅在这样的成本体积上进行深度推断depth inference,我们的网络推理关于场景几何形状和外观reasons about both scene geometry and appearance,并输出神经辐射场(见图2),实现视图合成。具体来说,利用3D CNN,我们重建(从成本体)神经场景编码体neural scene encoding volume,其由 编码关于局部场景几何形状和外观的信息的每个体素神经特征per-voxel neural features 组成。然后,我们利用多层感知器(MLP)在编码体积encoding volume内使用三线性插值神经特征tri-inearly interpolated neural features来解码任意连续位置处的体积密度volume density和辐射度radiance。本质上,编码体是辐射场的局部神经表示;一旦估计,该体积可以直接用于(丢弃3D CNN)通过可微分射线行进differentiable ray marching(如在[34]中)的最终渲染。

我们的方法结合了两个世界的优点,基于学习的MVS和神经渲染。与现有的MVS方法相比,我们实现了可微分神经渲染differentiable neural rendering,允许在没有3D监督和推理时间优化的情况下进行训练,以进一步提高质量。与现有的神经渲染作品相比,我们的类MVS架构可以自然地进行跨视图对应推理cross-view correspondence reasoning,有利于推广到未知的测试场景,也导致更好的神经场景重建和渲染。因此,我们的方法可以明显优于最近的并行可概括NeRF工作concurrent generalizable NeRF work[54,46],该工作主要考虑2D图像特征,而没有明确的几何感知3D结构(参见表。1和图4)。 我们证明,仅使用三个输入图像,我们从DTU数据集训练的网络可以在测试DTU场景上合成照片级逼真的图像,甚至可以在具有非常不同的场景分布的其他数据集上生成合理的结果。此外,我们估计的三个图像辐射场(神经编码体积)可以在新的测试场景上进一步轻松优化,以在捕获更多图像的情况下改善神经重建,从而产生与每个场景过拟合NeRF相当甚至更好的照片级逼真结果,尽管我们的优化时间比NeRF少得多。

该方法利用深度MVS的成功,在成本体上应用3D卷积来训练用于3D重建任务的可泛化神经网络。与MVS方法不同的是,MVS方法仅对这样的成本体进行深度推断,而该网络对场景几何和外观进行推理,并输出神经辐射场,从而实现视图合成。具体而言,利用3D CNN,重建(从成本体)神经场景编码体,由编码局部场景几何和外观信息的体素神经特征组成。然后,多层感知器(MLP)在编码体内用三线性插值的神经特征对任意连续位置处的体密度和辐射度进行解码。本质上,编码体是辐射场的局部神经表征;其一旦估计,可直接用于(丢弃3D CNN)可微分光线行进(ray-marching)进行最终渲染。

与现有的MVS方法相比,MVSNeRF启用可微分神经渲染,在无3D监督的情况下进行训练,并优化推断时间,以进一步提高质量。与现有的神经渲染方法相比,类似MVS的体系结构自然能够进行跨视图的对应推理,有助于对未见测试场景进行泛化,引向更好的神经场景重建和渲染。

如图1是MVSNeRF的概览:(a)基于摄像头参数,首先将2D图像特征warp(单应变换)到一个平面扫描(plane sweep)上,构建成本体;这种基于方差的成本体编码了不同输入视图之间的图像外观变化,解释了由场景几何和视图相关明暗效果引起的外观变化;(b)然后,用3D CNN重建逐体素神经特征的一个神经编码体;3D CNN 是一个3D UNet,可以有效地推断和传播场景外观信息,从而产生有意义的场景编码体;注:该编码体是无监督预测的,并在端到端训练中用体渲染进行推断;另外,还将原图像像素合并到下一个体回归阶段,这样可恢复下采样丢失的高频;(c)用MLP,通过编码体插值的特征,在任意位置回归体密度和RGB辐射度,这些体属性由可微分光线行进做最终的渲染。

SLAM、实时三维重建、SfM、多视角立体视觉MVS

SLAM(Simultaneous Localization And Mapping) 同时定位与地图构建

SLAM是Simultaneous Location and Mapping,同时定位与地图构建。是指搭载特定传感器的主体,在没有环境先验信息的情况下,于运动过程中建立环境的模型,同时估计自己的运动。
目的是解决自主机器人“定位”和“建图”两个问题。同时要求能够实时地,没有先验知识地进行。

  • 一般假设相机的内参已知;
  • 实时处理;
  • 以定位为主要目标
  • 输入数据包括RGB、RGB-D、激光、IMU等
  • 输出稀疏或半稠密地图
  • 机器人和计算机视觉研究领域
  • 典型软件:x-SLAM

实时三维重建

  • 一般假设相机的内参已知;
  • 实时处理;
  • 以建图为主体
  • 输入数据以RGB-D图像为主
  • 输出稠密地图
  • 计算机视觉和计算机图形研究领域
  • 典型软件:xFusion

SfM(Structure from Motion) 运动恢复结构

  • 估计相机内参;
  • 不实时处理;
  • 输入数据以RGB图像为主
  • 输出稠密地图
  • 计算机视觉和计算机图形研究领域
  • 典型软件:Agisoft PhotoScan、Agisoft Metashape、COLMAP

多视角立体视觉MVS

多视角立体视觉(Multiple View Stereo,MVS)是对立体视觉的推广,能够在多个视角(从外向里)观察和获取景物的图像,并以此完成匹配和深度估计。某种意义上讲,SLAM/SFM其实和MVS是类似的,只是前者是摄像头运动,后者是多个摄像头视角。也可以说,前者可以在环境里面“穿行”,而后者更像在环境外“旁观”。

  • 收集图像;
  • 针对每个图像计算相机参数;
  • 从图像集和相应的摄像机参数重建场景的3D几何图形;
  • 可选择地重建场景的形状和纹理颜色。

共同点

都需要估计和优化相机的位姿

基础:三维运动、相机模型、非线性优化

裸眼3D–原理介绍

裸眼3D基本上都是针对双目视差来说的。

什么是双目视差:人有两只眼睛,它们之间大约相隔65mm。当我们观看一个物体,两眼视轴辐合在这个物体上时,物体的映像将落在两眼网膜的对应点上。这时如果将两眼网膜重叠起来,它们的视像应该重合在一起,即看到单一、清晰的物体。根据这一事实,当两眼辐合到空间中的一点时,我们可以确定一个假想的平面,这个平面上的所有各点都将刺激两眼网膜的对应区域。这个表面就叫做视觉单像区(horopter)。它可以定义为在一定的辐合条件下,在视网膜对应区域的成像空间中所有各点的轨迹。位于视觉单像区的物体,都将落在视网膜对应点而形成单个的映像。

如果两眼成像的网膜部位相差太大,那么人们看到的将是双像,即把同一个物体看成两个。例如,我们用右手举起一支铅笔,让它和远处墙角的直线平行。这时如果我们注视远处墙角的直线,那么近处的铅笔就将出现双像;如果我们注视近处的铅笔,远处的墙角直线就将出现双像。

正因为双目视差,才会让我们看到的物体有纵深感和空间感。

裸眼3D是怎么做到蒙骗双眼来营造空间和纵深感呢,现在的3D视频或者图像都是通过区分左右眼来拍摄的两幅图,视差距约为65mm,通过让你左眼看到左眼的图像,右眼看到右眼的图像就可以让你的大脑合成一副有纵深感的立体画面。

人的两只眼睛相距约6cm,就像两部相距6cm放置的照相机,拍出的照片角度会有一点点不同(侈开)。

这种侈开在大脑里就可以融合成立体的感觉。

我们再抽丝剥茧制作一个最简单的侈开立体图:

越简单的图越容易说明原理,但观看起来越消耗眼睛“内功”。请您用原理一的透视方法,让左眼看左图,右眼看右图,当您能看到三个双圈的时候,中间那个小圆就会凸出纸面呈现立体感

Geometry-Free View Synthesis:基于transformer的大尺度转换的新视角合成

Geometry-Free View Synthesis: Transformers and no 3D Priors

引入一种基于 transformers 的概率方法,用于从具有大视角变化的单一源图像中进行新视图合成。作者对transformers 的各种显式和隐式 3D 感应偏置进行比较,结果表明,在架构中显式使用 3D 变换对其性能没有帮助。此外,即使没有深度信息作为输入,模型也能学会在其内部表示中推断深度。这两种隐式 transformer 方法在视觉质量和保真度上都比目前的技术状态有显着的改进。

将开源:https://github.com/CompVis/geometry-free-view-synthesis

论文:https://arxiv.org/abs/2104.07652

自回归transformer模型
问题:大角度变换,CNNs需要几何模型。
解决:不需要任何手工设计的三维偏差:(i)一种隐式学习源视图和目标视图之间的远程三维对应关系的全局注意机制来实现。 (ii)捕捉从单个图像预测新视图所固有的歧义所必需的概率公式,从而克服了以前局限于相对较小的视角变化的方法的局限性。(iii)发现以显式深度图的形式提供它们几何信息的好处相对较小,并研究了从transformer的层中恢复显式深度表示的能力,它已经学会了隐式地表示几何变换,而无需任何深度监督,甚至可以学习以无监督的方式预测深度

相关工作:本文从GPT-2架构构建自适应transformer,即,多块多头自注意,层规范和位置向MLP。自回归两阶段方法:是基于在神经离散表示学习(VQVAE)中的工作,它旨在通过向量量化或离散分配的软松弛来学习一个离散的压缩表示。这种训练范式提供了一个合适的空间来训练潜在表示上的自回归似然模型,并已被用于训练生成模型来训练分层,类条件图像合成、文本控制图像合成和音乐生成。最近,证明了VQVAE的对抗性训练在保持高保真度重建的同时提高了压缩性能,随后,我们能够在学习的潜在空间上有效地训练自回归transformer模型,(产生一个所谓的VQGAN)。我们直接在这项工作的基础上,使用VQGANs来表示源视图和目标视图,并在需要时表示深度图。
方法:由于不确定性,我们遵循一种概率方法,并从以下分布中采样新视角。

潜在空间中的概率视图合成:
为了学习上式中分布,需要捕获源视图和目标视图之间的远程交互的模型,以隐式地表示几何变换。由于基于相似的模型已经被证明直接在像素空间中建模图像时,在像素的短程交互上花费了太多的容量,我们遵循VQGAN并采用了两个阶段训练。第一阶段执行反向(对抗性)引导的离散表示学习(VQGAN),获得一个抽象的潜在空间,已被证明非常适合有效地训练生成式transformer。
建模条件图像模型
VQGAN包括一个编码器E,解码器G和一个离散表征zi(dz)的codebook Z。训练后的VQGAN允许编码任意x(HxWx3)到离散隐空间E(x)(h x w x dz)。以栅格扫描的顺序展开,这个潜在的表示形式对应于一个序列s(h x w x dz),可以等价地表示为一个整数序列,索引已学习的码本。按照通常的名称,我们将序列元素称为“tokens”。一个嵌入函数g=g(s) (hw x de)将每个tokens映射到transformer的嵌入空间中,并添加了可学习的位置编码。类似地,为了编码输入视图xsrc和照相机转换T,两者都由一个函数f映射到嵌入空间中:

其中,n表示条件处理序列的长度。通过使用不同的函数不同的归纳偏差被纳入架构。然后,transformer T处理所连接的序列[f(xsrc,T),g(sdst)]去学习以xsrc和T为条件的合理的新观点的分布。

编码Inductive Biases:比较几何变换的方法被显式地构建到条件函数f中,和没有使用这种转换的方法。
几何图像扭曲:这一部分说明显式几何变换怎么运用。我们假设有一个针孔照相机模型,这样三维点到齐次像素坐标的投影通过相机固有矩阵K确定。源坐标和目标坐标之间的转换是由旋转R和平移t组成的刚性运动给出的。这些参数一起指定了对要生成的新视图的所需控制,如T=(K,R,T)。

此关系定义了一个从源到目标的前向流场作为深度和相机参数的函数。然后,流场可以通过扭曲操作S将源图像xsrc扭曲到目标视图中。由于从流中获得的目标像素不一定是整数值,因此我们遵循,并通过跨越四个最近的目标像素的双线性溅射特征来实现S。当多个源像素映射到相同的目标像素时,我们使用它们的相对深度给予离相机更近的点权重——这是z缓冲的软变体。
在最简单的情况下,我们现在可以描述显式方法和隐式方法在接收关于源图像和期望的目标视图的信息的方式之间的区别。此处,显式方法接收使用相机参数扭曲的源信息,而隐式方法接收原始源图像和相机参数本身,如:

显式几何变换
在下面,我们用transformer的条件函数f来描述所有被考虑的变体。此外,e还表示一个可学习的嵌入映射离散的VQGAN代码E(x)到转换器的嵌入空间。相似地,e pos(n x de)指一个可学习的位置编码。流场F总是从xsrc计算,以提高可读性,我们从扭曲操作的参数中忽略它,S(.)=S(Fsrc->dst(K,R,t,d)).
(1)我们的第一个显式变体,expl.-img,会扭曲源图像,并以与目标图像相同的方式对其进行编码:

(2)受之前的作品的启发,我们包含了一个expl.-feat变体,它首先编码原始源图像,然后在这些特征之上应用warp。我们再次使用了VQGAN编码器E,以获得:

(3)解释上式中扭曲的特征保持固定状态。

隐式几何变换:接下来,我们描述我们用来分析的隐式变量,transformer能否同样好地处理所有位置,是否需要在模型中内置一个显式的几何转换。我们使用与显式变体相同的符号。
(4)第一个变体impl.-catdepth为transformer提供了显式变体中使用的所有相同组件:相机参数K、R、t、估计深度d和源图像xsrc。相机参数被拉平并连接到T^,通过Wcam(de x 1)映射到嵌入空间。深度和源图像被VQGAN编码器Ed和E编码来获得

与其他变体相比,这个序列大约长32倍,这是计算成本的两倍。
(5)因此,我们还包括了一个impl.-depth变体,它连接了深度和源图像的离散代码,并用一个矩阵W(de x 2dz)映射它们到嵌入空间以避免序列长度增加:

(6)隐式方法提供了一个有趣的可能性:因为它们不需要明确地估计该深度来执行扭曲操作S,所以它们在没有这样的深度估计的情况下具有解决该任务的潜力。因此,输入深度仅使用相机参数和源图像-根据我们的任务描述的最低限度。Impl.nodepth:

(7)最后,我们分析了显式方法和隐式方法是否提供了互补的优势。因此,我们添加了一个混合变体,其条件函数是方法(3)中expl.-emb和方法(5)中impl.-depth的f的结合。
深度读数输出:为了研究学习不同视图之间几何关系的隐式模型的能力,我们建议从一个训练过的模型中提取一个深度的显式估计。为此,我们使用线性探测,通常用于研究无监督方法的特征质量。更具体地说,我们假设一个由L层和impl.nodepth类型组成的transformer模型,它仅基于源框架和变换参数。接下来,我们指定一个特定的层0≤l≤L(其中l=0表示输入),并提取它的潜在表示el,对应于所提供的源框架xsrc的位置。然后,我们训练一个逐位置线性分类器W来预测深度编码器Ed的离散的、潜在的表示,通过一个来自el的交叉熵目标。请注意,transformer和VQGAN的权重均保持不变。
隐式,显式transformer比较

数据集: RealEstate and ACID.
其他实验:比较密度估算的质量,实现预测的可视化熵,测量图像质量和保真度,与以前的方法进行比较,对几何图形的探测