MVSnet:非结构化多视图立体视觉的深度推断
1、前置知识
1.1 Homography(单应性)

如上图,有两台摄像机OL和OR,这两台摄像机在不同的视角去拍摄同一个平面上的同一个物体,可以看到对于这个物体的一个点(图中黄色的点)在两台摄像机的成像位置并不相同,假设这个点在OL上成像为PL点,在OR上成像为PR点,那么homography(单应性)研究的就是这两个点之间存在的转换关系矩阵H^(3x3矩阵),即PL=H^PR。这是针对于2D来说的,除此之外还有3D的情况,即物体的同一个点在摄像机OL和OR下的3D的坐标也不同,记为qL和qR,这两个点之间也存在着单应性的关系,即qL=HqR
那么单应性矩阵到底应该怎样表示?先看3D的情况,首先要先知道这几个量:①R旋转矩阵,维度为3x3,代表两个摄影机中心点的旋转矩阵;②平移矩阵t,维度为3x1,代表两个摄影机中心点的平移矩阵;③摄像机O到物体的距离d;④物体法向量n在O摄像机坐标系下的向量,这个向量是单位向量,值为1。其次就可以用这些量来表示H矩阵,即H=R±tnT/d,当±为-时代表物体的法向量指向摄像机,为+时就代表物体的法向量指向与摄像机相反的方向。这就是3D情况单应性矩阵,对于2D像素平面的单应性矩阵,要先知道这两个量:①摄像机OL的内参矩阵K1;②摄像机OR的内参矩阵K2,那么对于2D平面的单应性矩阵就可以表示为H^=K1HK2-1。
总结:也就是说一个物体上的点在不同视角下的不同摄像机中的不同成像点之间的转换关系可以表示为PL=K1(R±tnT/d)K2-1PR>
1.2 CNN卷积神经网络
1.2.1 卷积神经网络的任务

如上图检测任务,也就是将图中指定的物体找到,并进行标识,常见的标识方法是方框和文本。

如上图分类和检索任务,分类也就是给定一张图片,判断这张图片是给定类别的百分率(上图左侧);检索也就是给定一张图片,返回与这张图片类似的图片,例如淘宝的相似物品搜索,同款衣服搜索功能(上图右侧)。

如上图超分辨率重构,给定模糊的图片和清晰的标签图片作为训练集,能够使得模糊的图片变得清晰。

如上图医学任务:细胞检测;字体识别,路标识别。

如上图无人驾驶。

如上图人脸识别。
1.2.2 卷积神经网络与传统神经网络的区别

可以看到上图左边是传统神经网络,标准的输入层、隐藏层、输出层,右边就是CNN。传统神经网络看起来是二维的,而CNN看起来像三维的,也就是输入一张图片时,传统神经网络是将像素值全部取出来组成一个w×h维的列向量,而CNN是直接输入w×h这样一张图片。
1.2.3 卷积神经网络的整体架构

如上图卷积神经网络的整体架构是输入层,卷积层(提取特征),池化层(压缩特征),全连接层
1.2.4 卷积层是干什么的

看到上图左边是输入的一个32×32×3的图片(3代表的是RGB三色通道,如果是灰度图就是1),做的第一件事情就是把这个图划分为很多个小块,假设第一个小块为5×5×3,然后用一个3×3的卷积权重矩阵(蓝图中的暗色部分,右下角的值就是权重值)对这一小块的每一个3×3的部分进行处理(像素值与权重值相乘之后全部相加),提取这一块的特征值矩阵,上图中的绿色矩阵就是第一个小块的特征值矩阵。也可以将卷积神经网络看做是在找一个非常适合模型的权重矩阵,为了得到更好的特征值。

A但是可以看到我们划分的小块是三维的5×5×3,但是权重矩阵却是二维的3×3,这是怎么回事?由于图片是RGB图像,有三个颜色通道,因此RGB图像是三维的,那么在做卷积时,是分别对三个颜色通道做卷积,然后将三个通道卷积得到的特征值进行相加。

上图是卷积神经网络的卷积层的具体工作步骤。上图左边第一列是输入的一个7x7x3的图片,图片的分辨率是7x7,有三个颜色通道R、G、B。左边第二列是卷积核,也就是随机初始化的卷积权重矩阵,刚刚讲的是二维的,实际上卷积权重矩阵也是3维的3x3x3,其中最后一个维度的值必须和图片最后一个维度的值相同。可以看出对于每个颜色通道的随机初始化权重矩阵都是不一样的,对于每个颜色通道都单独处理,我们的卷积核可以是3x3x3的,也可以是4x4x3的,区别就是一次性卷积9个像素点得到一个特征值,还是一次性卷积16个像素点得到一个特征值,特征值的计算也就是将权重值与每个像素值相乘之后相加起来,列如上图的第一个特征值就等于0+2+0+b=2+1=3,其中b是一个偏置项,3就是一个特征值,也就是上图右边一列图中用绿框圈住的值,右边一列的图就是所谓的特征图。

从刚刚了解的知识可以知道一个卷积核得到对应的一个特征图,如果我们想要更加丰富的特征,就可以制定多个卷积核,如上图制定了6个卷积核对32x32x3的图像进行卷积,就可以的得到28x28x6的一个特征图,也就是一个卷积核对应一个特征图,将6个28x28的特征图进行组合就得到了28x28x6的特征图,这个特征图里面就包含了更丰富的特征数据。

可以看到上图就是利用两个权重值不同的卷积核(左边第2.3列),对同一张7×7×3图像进行卷积,得到3×3×2的特征图,最后一个维度代表特征图的数量,也就是说输出结果的最后一个维度代表的是特征图的数量,输入图像的最后一个维度代表的颜色通道数。值得注意的是两个卷积核权重值不同,但是维度必须相同,都是3×3×3的。

经过上述描述,已经简单知道了一次卷积的大致流程:①将原始图像划分为多个小块;②制定卷积核,卷积核第三个维度必须和输入图像的颜色通道维度相同,卷积核的个数根据任务而定;③利用制定好的卷积核对图像的各个小块进行卷积,最后得到特征图,特征图的第三个维度就是卷积核的个数。那么对于一张图像进行特征提取,一次卷积是不够的,只有经过多次卷积提取出来的特征值才是比较准确的。这里的多次卷积不是指多个不同维度卷积核对同一张图像进行卷积,而是一张图像经过一次卷积之后得到一个特征图,然后再用另外的卷积核对这个特征图进行卷积。如上图就是先利用6个5×5×3的卷积核对32×32×3的图像进行卷积,得到28×28×6的特征图,再用10个5×5×6的卷积核对28×28×6的特征图进行卷积,得到24×24×10的特征图。
1.2.5 卷积层涉及参数

①滑动窗口步长,上图是步长分别为1和2的滑动窗口,可以看到步长不同的滑动窗口对同一7×7图像进行处理会得到不同维度的特征图,步长为1的结果为5×5的特征图,步长为2的结果为3×3的特征图。步长为1的滑动窗口提取的特征更加丰富,是更细致的特征提取,那么步长为2的滑动窗口提取的特征就比较粗糙。一般来说图像就是用步长为1的滑动窗口,但是这样得到的特征就比较多,那么计算效率就会低一点,文本等数据就是用步长为2的滑动窗口。
②卷积核大小,与滑动窗口步长对结果的影响差不多,卷积核越小得到的特征就越多提取特征就越细腻,相反就越粗糙。一般的CNN滑动窗口步长都设置为1,卷积核尺寸为3×3。

③边界填充,可以看到上图中间紫色部分才是真正的图像值,外面一圈灰色的就是填充的值。那么为什么要进行填充?窗口进行滑动计算特征值时有些像素点的值被重复利用,也就是对特征值的计算贡献比较大,最后可以发展越靠近中间的值贡献越大,越靠近边界的贡献就越小。但是图片的边界也是很重要的,因此为了使得整张图片都在特征提取过程中得到充分的利用,就在图像外面扩充一圈灰度值,这样就使得图像的边界也变成了中间值,对特征计算的贡献比较大。值得注意的是进行边缘扩充的时候只能添加一圈为0的灰度值,因为0与任何值相乘都是0,也就是说扩充的部分不会对最后计算的特征值有影响。边界填充几圈根据任务而定,一般一圈即可。
④卷积核个数,一般是根据结果所需多少个特征图而定,需要几个特征图就需要几个卷积核,这些卷积核维度是一样的,但是其初始化的权重值和更新的权重值都是不同的,并且是各管各的。
1.2.6 卷积结果计算公式

上图是卷积结果特征图分辨率的计算公式,也就是卷积得到的特征图的长宽。值得注意的是卷积后的特征图不一定就是变小的,根据上述公式,也可能不变。
1.2.7 卷积参数共享

卷积的参数共享:卷积的第一步就是将图片划分为很多个比较小的区域对于每个区域都要使用卷积核进行卷积,每个区域如果使用不同的卷积核,可能效果会好一点,但是极大的增加了计算和内存的负担,因此卷积决定进行参数共享,也就是每个区域都使用相同的卷积核,如上图的列子,最后只需要760个权重参数。
1.2.8 池化层

之前提及过的池化层的作用就是压缩特征,或者叫做下采样。如上图卷积层卷积后得到一个224×224×64的特征图,这是一个特征值特别丰富的组合,但过于太大了,因此池化层就对其进行下采样(压缩),变为一个112×112×64的特征图,长宽都减半(第三个维度特征图的个数是不能改变的),最终结果体积减小四倍,但是这个下采样是有选择的下采样,也就是留下重要的特征,去除不重要的特征。上图右边就是池化的一个简单步骤,也是选定一个窗口,然后从窗口中选定一个值留下,组成新的特征图。

那么在窗口选定留下的特征值是怎么选择的。可以看到上图,是一个名为最大池化的方法,假如有一个4×4×1的特征图,用一个2×2的窗口对其进行池化,那么就有4个窗口,选择每个窗口最大的值留下,这就是最大池化,这样做的原因就是卷积后越大的特征值就越重要,因为最后的特征值是权重值和各个像素值乘积的和,只有权重和像素都很高,最后的特征值才会大。值得注意的是池化没有涉及任何的矩阵运算,只是一个筛选,过滤的过程。
1.2.9 卷积神经网络的整体架构

上图是一个卷积神经网络的整体架构,可以看到每经历两次卷积(CONV)就要进行一次池化(POOL),并且每次卷积都与Relu非线性激活函数绑在一起的,这与传统的神经网络一样,在进行了线性计算之后进行非线性激活函数Relu的计算。在卷积池化完之后,假如得到一个32x32x10的特征图,如何将这个特征图转化为最后设定五个类别的概率值,这时候就需要用到全连接层(FC),也就是将32x32x10的特征图变为一个10240的列向量,然后经过全连接层转为设定的五个类别的概率值,全连接层的维度就为[10240,5]。
上述的卷积神经网络为7层神经网络,只有带参数的才能被叫做层,只有卷积层和全连接层才带参数。

上图是卷积神经网络工作流程的可视化视图。首先给定一个图像,然后用多个卷积核进行卷积,得到多个特征图的组合,在用多个卷积核进行卷积,特征图的个数在次增加,这是就利用池化进行下采样,一般是变为原始体积的1/4,然后在卷积池化,最后的一个转换是指将特征图的组合转换为一个列向量,最后利用全连接层进行分类。
1.2.10 经典的卷积神经网络

上图是经典的卷积神经网络AlexNet,它是12年的卷积神经网络,现在看来问题是很大的,第二行中看到有11x11的卷积核,过于太大了,导致提取的特征很粗糙,步长为4,也过于大了。总体来说它是一个8层的神经网络,5层卷积,3层全连接,简单了解一下就可以了。

上图是CNN另一个经典网络VGG。这是14年的卷积神经网络,上图右边是vgg的各个版本,红框中的内容是比较常用的一个版本。那么vgg和AlexNet相比起来,它的卷积核都是3x3的,因此vgg提取的特征是比较细致的,并且vgg是一个16层的网络,提取的特征更准确。根据上图右边观察可以看到每次池化之后,特征图的个数都会翻倍,这是因为池化会消减掉一些信息,也就是缩短长宽,那么vgg为了弥补这些消减的信息,就通过将特征图的数量翻倍来进行弥补。

AlexNet是8层神经网络,VGG是16层神经网络,那么是不是层数大,效果就越好,因为深度学习也就是用更深层次的网络去提取特征,但是实验效果显示并不是这样的,原因是这样的,因为每次卷积都是对上次卷积的结果继续进行卷积,我们不敢保证每次卷积的结果都很好,如果一次卷积的效果不好,那么下次在此基础上卷积的效果也不会怎么样。如上图56层的CNN无论在训练集还是测试集上都比26层CNN效果差。
针对上述问题,提出了残差网络Resnet,它的一个思想是可以增加CNN的层数,无论增加多少层都没关系,但是加进来效果不好的层,就将其权重设置为0,也就是不会对最后的结果产生影响,加进来效果好的层,就留下对结果产生优化效果。如上图下面的解决方案,H(x)=F(x)+x,其中F(x)代表中间的两层卷积,其效果并不好,将其权重设为0,然后将x直接跳过这两层传到最后的H(x)。因此Rsnet它有一个优点就是有前面比较好的效果兜底,如上面的x,总之不会比之前的效果差。

上图是Resnet文献里面的原图,左边可以看作是VGG,可以看到层数越多反而效果越差,而右边的Resnet层数越多效果越好。
Resnet是一个非常经典的CNN,它可以用于特征提取、分类、回归等很多的问题,所以现在常用的CNN都是Resnet,其最常见的层数是50和101。
1.2.11 感受野

上图中输入是一个5x5的图像,使用3x3的卷积核进行卷积得到3x3的特征图,在使用3x3的卷积核进行卷积得到一个特征值,那么从最后一个特征值能够感受到上一个输入是3x3的,从3x3的特征图可以感受到上一个输入是5x5的。这就是感受野。在进行特征提取是如果最后一个特征值来自一个比较大的感受野,也就是融合了较多的特征,就是越好的,也就是感受野越大越好。
给上图在添加一个卷积层,那么就会有3个含有3x3卷积核的卷积层了,那么最后一个特征值的感受野就是7x7,那么为什么不使用一个含有7x7卷积核的卷积层对图像进行处理,这与3个含有3x3卷积核的卷积层处理得到的特征值一样。

原因如上图,可以看到一个含有C个7x7xC卷积核的卷积层的参数比三个含有C个3x3xC卷积核的卷积层的参数多。
1.2.12 基于CNN构建识别模型
下面的代码是一个简单的CNN模型,先通过这个模型了解CNN的框架。
第一步导入软件包。CNN的输入和层都与传统神经网络不一样需要重新设计,但是大致模块是一样的。
#导入pytorch软件包,用于训练和构建神经网络
import torch
#导入pytorch中的神经网络nn模块,该模块中定义了构建神经网络和操作张量相关的函数和类
import torch.nn as nn
#导入pytorch中的神经网络optim模块,该模块包含了常见训练神经网络的优化算法,如随机梯度下降(SDG)、Adam、RMSprop 等等
import torch.optim as optim
#导入pytorch中的神经网络nn.functional模块,该模块里面定义了训练神经网络大量的函数,包括损失函数、激活函数、池化函数和归一化函数
import torch.nn.functional as F
#torchvision库是用于处理图像任务的,里面包含了训练神经网络常见的数据集、模型架构和图像处理工具,从中导入datasets和transforms模块,datasets包含了常见的数据集,如MNIST、ImageNet等,transforms用于图像转换任务,包括图像的随机裁剪、大小调整和归一化等,这些转换通常用于数据预处理,以便在模型训练之前对输入数据进行标准化或增强。
from torchvision import datasets, transforms
#导入matplotlib.pyplot模块,用于绘制各种类型的图像,用于可视化数据,绘制损失曲线,以及模型输出等
import matplotlib.pyplot as plt
#导入numpy库,用于处理数组和矩阵等数据
import numpy as np
#是Jupyter Notebook的魔法命令,它告诉Jupyter在notebook中内联显示matplotlib绘制的图形,而不是在外部窗口或新标签页中打开。
%matplotlib inline
第二步读取数据
读取训练集和测试集的数据
用download迭代取出数据
# 定义超参数
input_size =28#图像的总尺寸28*28
num_classes=10#标签的种类数
num_epochs=3 #训练的总循环周期
batch size=64 #一个撮(批次)的大小,64张图片
#训练集
#MNIST是datasets模块内置的数据集,首先检查是否存在路径./data,存在则跳过该命令,否则创建该路径
train_dataset =datasets.MNIST(root='./data',
#指定下载的数据集是训练数据集
train=True,
#将导入的图像利用transforms.ToTensor()转为张量
transform=transforms.ToTensor(),
#下载训练集
download=True)
# 测试集
test_dataset = datasets.MNIST(root='./data',
train=False,
transform=transforms.ToTensor())
#构建batch数据
#调用torch.utils.data.DataLoader构建训练数据加载器train_loader,其中dataset是要加载数据集的名称,batch_size是每次从数据集中加载一次的批次大小,shuffle指定是否在每个 epoch 之前随机打乱数据集顺序,这样可以增加数据的随机性,有助于模型学习更好的特征。
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)
test_loader = torch.utils.data.Dataloader(dataset=test_dataset, batch_size=batch_ize,
shuffle=True)
第三步构建CNN网络模块
将卷积层、relu和池化看作是一个套餐
注意一个套餐之后输出的是一个特征图,只有将其将转为向量才能用于分类和回归任务。
#定义了一个CNN类,是nn. Module(Pytorch中的一切自定义类都要继承于它,因为里面有很多关于自定义的函数和方法,能够便于自定义构建类)的子类
class CNN(nn. Module):
#CNN的初始化构造函数
def init (self):
#父类nn. Module的构造函数
super(CNN, self)._init_()
#用nn. Sequential定义第一个套餐conv1
self.convl = nn. Sequential(
#用nn. Conv2d定义卷积层
nn. Conv2d(
#输入图像的颜色通道数,或者是输入的特征图数量,这里输入的图像是灰度图,因此颜色通道为1,输入图像大小为28x28x1
in_channels=1,
#输出的特征图的数量,也就是卷积核的个数
out_channels=16,
#卷积核的大小,为5x5x1的
kernel_size=5,
#滑动窗口步长
stride=l,
#边缘填充数量
padding=2,
),
#调用Relu激活函数
nn. ReLU(),
#nn.MaxPool2d构建池化层,池化窗口大小为2x2
nn.MaxPool2d(kernel size=2),
)
#根据公式卷积层输出的特征图为28x28x16,池化层输出的特征图为14x14x16
#用nn. Sequential定义第二个套餐conv2
self.conv2 = nn.Sequential(
#用nn. Conv2d定义卷积层,其参数和上述顺序一样
nn.Conv2d(16,32,5,1,2),
nn.ReLu().
nn. MaxPool2d(2).
)
#根据公式卷积层输出的特征图为14x14x32,池化层输出的特征图为7x7x32
#利用nn.Linear创建全连接层,利用全连接层的权重参数W和偏置参数b将特征图转为向量。向量的大小就是32x7x7,最后标签的类别数量是10,那么全连接层中权重参数W的大小就是(32*7*7,10)
self.out=nn.Linear(32*7*7,10)
#定义了类CNN的前向传播函数
def forward(self, x):
#计算卷积、Relu和池化的套餐一
x= self.convl(x)
#计算卷积、Relu和池化的套餐一
x= self.conv2(x)
#将得到的特征图重塑成32x7x7的向量
x=x.view(x.size(0),-1)
#利用全连接层计算向量所占10个类别的概率
output = self.out(x)
return output
第四步用准确率评估模型性能
#定义了一个名为accuracy的函数,参数predictions代表CNN模型预测的概率值,labels是标签值
def accuracy(predictions, labels):
#利用torch.max获取预测值每行最大的索引值,即预测类别
pred = torch.max(predictions.data, 1)[1]
#pred.eq(labels.data.view_as(pred))用于比较预测类别和真实标签是否相等,然后利用sum()统计预测正确的数量
rights = pred.eq(labels.data.view_as(pred)).sum()
#返回了正确预测的数量和总样本数量
return rights, len(labels)
第五步训练模型
#将定义的类CNN实例化为net对象
net=CNN
#调用Pytoch中定义好的损失函数CrossEntropyLoss
criterion =nn.CrossEntropyLoss()
#使用Adam梯度下降优化器
optimizer = optim.Adam(net.parameters(),1r=0.001)
#循环遍历每一个训练轮次
for epoch in range(num epochs):
#创建一个train_right列表,用于将当前epoch的结果保存下来
train_rights=[]
#循环遍历训练数据加载器中的每一个批次数据
for batch_idx,(data,target)in enumerate(train_loader):
#调为神经网络模型的训练模式
net.train()
#计算神经网络的模型输出,即预测的概率值
output = net(data)
#计算损失值
loss = criterion(output,target)
#对优化器里面的梯度清零
optimizer.zero grad()
#反向传播
loss.backward()
#利用得到的参数梯度更新参数
optimizer.step()
#计算当前轮次正确率
right = accuracy(output, target)
#将所有轮次的准确率加入列表train_rights
train_rights.append(right)
#在每个训练周期中的100个批次数据之后都要对模型进行评估,并输出在训练集和测试集上面的损失和准确率
if batch_idx % 100 == 0:
#调为模型的评估模式
net.eval()
#创建一个val_rights列表,用于将当前epoch的结果保存下来
val_rights =[]
for(data, target)in test_loader:
output = net(data)
right =accuracy(output,target)
val_rights.append (right)
#准确率计算
#计算所有批次正确预测的数量和总样本数
train_r=(sum([tup[0] for tup in train_rights[]), sum([tup[1] for tup in train_rights]))
#计算测试集中所有batch的预测正确数量和样本总数。
val_r=(sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))
#输出每个轮次100个批次数据后的损失值,训练集和测试集上的准确率
print('当前epoch: {} [{}/0}(:.0f]%)]\t损失:{:.6f}\t训练集准确率:{:.2f}%t测试集正确率:{:.2f}%'.format(epoch, batch_idx * batch_size, len(train_loader.dataset),
100.* batch_idx/len(train_loader),
loss. data,
100.*train_r[0].numpy()/train_r[1]
100.*val_r[0].numpy()/val_r[1]))

1.2.13 迁移学习

深度学习有一个非常常用的策略叫做迁移学习。神经网络训练过程会遇到很多的问题,第一个是数据量不够,第二个是训练时间过长,第三个是调参数太费时间,那么迁移学习就可以解决这些问题。假如我们需要训练的模型与前人的数据集以及模型都很像,如上图第一个假如是前人训练的识别模型的100w自行车数据集,下面一个是我们自己的1000的自行车数据集,同样是识别自行车,数据集也相似,那么我们这个模型就可以使用前任训练好的识别模型的参数,这就叫做迁移学习,也就是将别人训练好的与自己训练模型相似的参数拿过来使用。但是在迁移学习的过程中,一定注意将我们自己的数据集改为适用于迁移模型的格式。

那么迁移学习的过程中就有一个问题,就是前人模型的参数完全适用于我们自己的模型吗?这是不确定的,假如前任模型要做1000个分类,而我们只做100个分类,那就不一样了。那么对于卷积、relu、池化组成的套餐的这些层级来说进行迁移学习是有两种选择,一种是使用前人模型的参数进行初始化,然后自己训练在不断的更新,另一种是完全使用前人模型的参数作为结果,不在更新,这种叫做冻住,这种迁移学习方法优点之一就是非常快,那么对于全连接层在迁移学习是一般是要根据自己的任务进行修改的。对于卷积、relu、池化组成的套餐的这些层级在迁移学习时,后面一种方法是比较好的,但是也根据实际情况而定,如果数据集偏少,就冻住多一点,数据集偏多就采用第一种方法。
2、MVSnet关键算法(tensorflow版本,论文中这个关键算法是错误,但是代码中是正确的)
2.1 深度图计算原理

如上图有两个摄像机,一个物体,两个摄像机在不同视角下对同一物体进行拍照。已知量有物体上同一点(红点)在两个摄影机下的3D坐标为p和p’。要求物体上的红点离左边一个摄影机的距离D是多少?

MVSnet求物体上的红点离左边一个摄影机的距离D的方法如上图,由左边一个摄像机为出发点做平面,分别做了五个平面,这五个平面离摄像机的距离分别是d1,d2,d3,d4,d5,最后第五个平面与物体的红点相交,那么d5就是这个物体上的红点离摄影机的距离,那么MVSnet是如何判定平面是否与物体的红点相交的,其思想是通过这五个平面离摄像机的距离d1,d2,d3,d4,d5去计算各个平面对应3D场景下的单应性矩阵,然后利用3D场景下这五个平面对应的单应性矩阵H对P点进行转换,如果转换后的点与p'很相似,则说明这个平面上的点就与物体相交,这样就可以得到距离d5,否则不相交,继续上述步骤。
那么MVSnet求深度的过程可以归纳为:①保证已知量p、p’;②以摄影机为原点画平面,得到每个平面离摄影机的距离为di;③根据公式H=R-tnT/di计算每个平面对应的单应性矩阵;④根据每个平面对应的单应性矩阵转换p坐标为p’‘,查看p'’与p‘的相似性;⑤若p'’与p‘极其相似,则结束上述步骤,得到物体上点到摄影机的深度di,否则继续上述步骤。
上述阐述的是一个3D的版本,而MVSnet最终需要得到的是同一物体的同一点在不同视角下摄影机不同2D成像点之间的关系。因此这里的步骤要稍作修改:①保证已知量q、q’、K1、K2,q、q’为两个摄影机下的成像点,K1、K2为摄影机的内参矩阵;②以摄影机为原点画平面,得到每个平面离摄影机的距离为di;③根据公式H^=K1(R-tnT/di)K2-1计算每个平面对应的2D版本的单应性矩阵;④根据每个平面对应的单应性矩阵转换q坐标为q’‘,查看q'’与q‘的相似性;⑤若q'’与q‘极其相似,则结束上述步骤,得到物体上点到摄影机的深度di,否则继续上述步骤。

式子H^=K1(R-tnT/di)K2-1中的R,t代表的是两个摄影机中心的旋转和平移矩阵,那么R,t如何获得?如上图的世界坐标是固定的,并且我们在制定数据集的时候就知道了各个摄像机与世界坐标系对应的旋转平移矩阵,如上图R1、t1、R2、t2是已知的,那么就可以通过这些量求得R和t。





上图中代码fronto_direction代表的值是R1最后一行的值,其中[0,2,0]就代表从R1的第(2,0)个元素开始取,[-1,1,3]代表取一行三列,故最终取的就是R1的最后一行的值。那么这行代码有什么用?实际上fronto_direction的负值就等于nTR1,起计算可以看到下图:

那么可以看出代码中的式子与推论的式子一样,故论文中的式子是错误的,但是代码中式子却是正确的。
其实论文中的这个式子是比较复杂且难理解的,其实在后续对于MVSnet的研究中都没有使用上述论文源码中的式子,如果是要研究两个视角下不同成像点的关系都是使用的另外一个式子,如下图:

2.2 CASMVSnet对于MVSnet的改进

在MVSnet论文中就是通过上述的过程{①保证已知量p、p’;②以摄影机为原点画平面,得到每个平面离摄影机的距离为di;③根据公式H=R-tnT/di计算每个平面对应的单应性矩阵;④根据每个平面对应的单应性矩阵转换p坐标为p’‘,查看p'’与p‘的相似性;⑤若p'’与p‘极其相似,则结束上述步骤,得到物体上点到摄影机的深度di,否则继续上述步骤}来得到场景里面的深度的,那么在第二步当中划分平面的时候,MVSnet实际上是一次性划分了256个平面,平面之间的距离可能为2mm,这样会占用大量的CPU,并且导致神经网络训练时的训练子集(batch_size)为1,这样会导致非常低效(不能使用向量化)。
那么CASMVSnet就对此做了一个改进,它其实有点像Nerf那样采用的一个粗细策略来划分平面,故先划分48个平面,可能平面之间的距离为6mm,然后就可以简单的得出物体上的点离那个平面比较近(经过单应性转换后的点与实际映射的点比较相似),然后就在这个平面左右去更加细致的划分,第二次划分就是32个平面,平面之间的距离可能就是4mm了,那么CASMVSnet就不会占用大量的CPU,并且训练子集的里面的样本个数也增多了,因而就能展现出更好的性能,不管是效果还是速度,速度是因为训练子集的里面的样本个数增多了,效果是因为粗细这种策略能够更好的确定深度。
3、主要工作
基于多个视角摄像机拍摄的图片通过深度学习的方法估计未知几何结构的整个场景的深度(估计深度图 ),这里的深度指的就是真个场景里面的所有物体里摄像机的距离。
用深度学习进行多视图重建的开山之作。
4、 MVSnet整体文章框架结构
-
摘要:介绍文章整体概况,包括文章架构和主要内容等等。
-
第一章“简介”:对本文所做的工作和任务进行一个简介。
-
第二章“相关工作”:探讨本文研究方向在以前所做的一些工作。
-
第三章“MVSnet”:介绍本文所使用到的工具、数学公式、方法等等,以及如何通过这些工具、方法、公式等实现MVSnet。其中包括图片的特征提取、特征匹配、代价体、深度图、概率图、损失值等等。
-
第四章“实施”:将第三章的实现MVSnet的整体过程变为代码,并进行训练和后处理。
-
第五章“评估”:将训练好的模型在两个数据集上进行评估,并做了相容实验(一个变量变换,其余变量不变,看看这个变量对于结果的影响大小)。最后在这一章中还进行了讨论,也就是说明这篇论文的局限性,告诉研究者们引用时的注意事项。
-
第六章“结论”:对上述章节的所有内容进行概括总结。
-
第七章“致谢”
5.论文阅读
5.1 摘要
-
提出了一种端到端的深度学习架构,通过多视图图像推断出深度图。
-
网络:①提取图像特征(特征提取);②为每张参考图像的视锥构建3D代价体;③正则化和回归代价体;④通过卷积,得到初始的深度图,再用参考图进行细化,得到最终的输出。
-
创新点:①可以允许多张图像的输入;②使用方差作为损失的度量值;③在DTU上训练,在Tanks and Temples上评估;④通过后处理,生成点云,与之前的技术相比,方法更好、速度更快;⑤泛化性强,不通过微调也可以有较好的效果。
5.2 简介(第一章)
-
本文的方法是单次输出单张图像的深度图,而不是整个场景。即输入一张参考图(ref)和任意张张源图(src),参考图为主导,源图为协助,最终输出参考图的深度图。(自我理解:输入的参考图与每一张源图通过单应性变换构建代价体,将代价体进行多尺度CNN卷积得到多张深度图,然后进行深度图融合得到初始的深度图,最后通过参考图细化深度图的边缘部分,得到最后的输出)
-
可微单应性变换:通过相机几何结构(相机与物体之间的几何关系),基于二维图像构建视锥的代价体。
-
基于方差的损失度量:可以将多个特征映射到一个代价体里面作为损失特征。
-
将代价体进行3D多尺度卷积得到初始深度图。
-
利用参考图对初始的深度图进行细化,特别是边缘轮廓部分。
-
本文的方法相当于传统的MVS变为了逐视图的深度图估计,使得大规模的重建变得可能。
5.3 相关工作(第二章)
-
MVS(多视图)重建的相关工作:①直接点云重建:直接操作3D点,依靠传播策略逐渐加密重建,传播顺序进行,难以并行化,处理时间较长;②体积重建:将三维空间,划分为规则的网格,估计每个体素是否附着在曲面上,空间离散化错误,高内存消耗;③深度图重建:最灵活,将复杂的MVS问题解耦为相对较小的逐视图深度图估计问题,每次只关注一个参考和几个源图像。
-
代价体(cost volume)的计算:
首先我们要知道cost volume里面存储的就是参考图像上的像素点进行单应性变换后的点与匹配图上对应点相似性的误差。其具体计算过程如下:



5.4 MVSnet方法(第三章)

MVSnet工作流程:①提取参考图像和各个源图的特征,也就是深度特征,即di;②根据di计算参考图每个像素与各个源图之间的代价体;③由参考图的每个像素与每个源图的的代价体组合得到每张源图与参考图之间的代价体,最后再将每个源图对应的代价体进行融合,得到最终输入的所有图像的最终的一个代价体;④正则和归一化代价体,也就是利用CNN卷积神经网络处理代价体,得到初步的深度图;⑤利用参考图像进一步细化深度图。
5.4.1 特征提取
8层2D卷积提取图像深度特征。
5.4.2 构建代价体
前面已经讲过如何通过单应性变换构建参考图的单个像素与各个源图之间的代价体,将这些像素对应的代价体进行组合就可以得到参考图与特定源图之间的代价体,那么又如何将各个源图对应的代价体融合形成最终所有输入图像得到单个代价体,使用的公式是:
其中Vi就是各个源图对应的代价体,

代表所有源图对应代价体的平均值,N代表输入图像的个数,通过上述公式就将各个源图对应的代价体融合形成最终所有输入图像得到单个代价体。
上图公式是基于方差的,能够更好的体现多视图的特征差异。
但是上述构造的代价体是具有噪声的(如物体的遮挡),就要对其做平滑处理,也就是图像的正则化处理,从而得到概率体,概率体能够很好的帮助我们进行深度图的推断。
5.4.3 深度图
所谓的概率体可以帮助我们计算深度图,其原理就是:在我们最终的代价体中记录了参考图像每个像素在各个深度di下经单应性变换后与源图相应像素点的误差值,那么我们就通过这些值计算参考图每个经单应性变换后的像素点就是源图匹配点的概率,那么就组成了概率体。根据概率体P初步估计深度图D,是按照如下公式进行估计的,也就是计算沿深度方向的期望值,即所有假设的概率加权和:

最终由概率体输出的深度图是合格的,但是由于在计算概率体的时候运用了正则化,使得深度图中的轮廓边界信息被过度的平滑而不准确,因此本文利用含有边界信息的参考图对深度图进行进一步的细化,使得边界深度信息尽可能的准确。这里使用的是深度残差网络来进行的。
5.4.4 损失
本文考虑了两个损失,一个是真实深度标签值与初始深度图的损失,另一个是真实深度标签值与细化深度图的损失。

上图就是本文采用计算损失值的函数,其中d(p)表示真实深度标签值,但是真实深度标签值并不是完整的,只是部分像素有效,因此Pvalid代表的就是有效的深度标签像素,从这个集合里面计算损失,然后d^i(p)和d^r(p)分别表示初始的深度值和细化后的深度值,最后λ用来控制初始深度值的损失和细化后的深度值的损失在整个损失值中占的比例。
那么整个式子就可以解释为分别计算含有有效深度标签像素与初始深度值和细化深度值的差值的1范数,然后使得这两个1范数进行相加,用λ控制两者在整个损失里面的占比。
5.5 实施
5.5.1 训练
对于MVSnet训练过程,我们需要含有深度标签值的数据集。MVSnet中一般由点云或者网格直接表示出真实的场景,那么就需要计算出深度标签值。MVSnet中是利用SPSR生成网格表面,将网格表面渲染到每一个视点,然后产生每一个视点的深度值。这样就得到了场景的深度标签值。
除了准备含有标签值的数据,还要准备训练集,训练集中还含有DTU中79个物体,每个物体49个视角,每个视角7种不同光照条件的的样本,即训练集含有79x49x7个样本。
对于MVSnet模型的训练,其神经网络模型的输入是一张参考图像,两张源图。那么是如何选择这三张图片的?首先对于所有图片的位姿信息我们都是已知的,那么就可以先选定一个参考图,然后得到一系列与之相匹配的源图,但是只能选两张,就用过以下公式计算每个源图的得分值,最后选择与参考图匹配得分最高的两张源图作为输入:

注意在进行单应性变换时,所取的平面范围为425mm~635mm,2mm为一个间隔,D=256.
5.5.2 后处理
由概率体得到的深度图虽然合格,但是还是要过滤掉异常点(由于遮挡和背景造成),并且进行细化。
如何判定异常点,可以由一下定义来进行判定:

对于后处理完成后的深度图,要进行深度图的融合,即将不同的参考视图对应的深度图进行融合,表示成为稠密点云。MVSnet当中采用的是一种基于可见的融合算法,这种算法可以将不同视点的冲突和遮挡最小化,融合后的深度图直接投射到三维空间,形成稠密点云。
5.6 评估
对于训练好的MVSnet模型要进行评估,分别在两个数据集上进行了评估,一个是划分的DTU测试集,一个是Tanks and Temples dataset数据集作为测试集。
5.6.1 DTU测试集上的评估

上图为DTU作为测试集时评估指定的参数,即输入图像数量为5,输入图像的宽高分别为1600和1184,划分的平面数量为256。
有如下两个评估指标:
-
距离指标:重建点与参考点的距离。
-
百分比指标:重建点云与真实网格视点的距离。
5.6.2 Tanks and Temples dataset上的评估

上图为Tanks and Temples dataset作为测试集时评估指定的参数,即输入图像数量为5,输入图像的宽高分别为1920和1056,划分的平面数量为256。
论文种显示最后得到的MVSnet模型在Tanks and Temples dataset数据集上也有较好的泛化效果。
5.6.3 消融实验
消融实验是在验证集上面做的。
所谓的消融实验就是查看一个变量变化,其余变量不变,这个变化的变量对于结果有什么影响。
本文做了三个消融实验,分别是:①输入图像的张数N;②带不带深度图细化的网络;③基于方差和基于均值的损失值度量。
得出以下结论:
-
增加视图的数量可以降低验证集的损失。
-
带有深度图细化的网络、不带深度图细化的网络对验证集损失影响不大。
-
基于方差的收敛速度更快,验证损失更低。
5.6.4 讨论
讨论这一章探讨了MVSnet在运行时间、GPU内存、训练数据上存在的局限性。
对于运行时间:MVSNet效率更高,重建一次scan大概需要230s,平均每个view4.7s。比Gipuma快5倍,比COLMAP快100倍,比SurfaceNet快160倍 问题:这个是重建点云的速度吗?如果是的话,主要的时间是占用在生成点云上,还是估计深度上。后面的FastMVSNet提高的是估计深度的速度,还是生成点云的速度。如果主要时间是点云生成,而且FastMVSNet提高的只是深度估计的速度,那么应该找一个算法,能够加快点云的生成。
5.7 结论
提出一种用于MVS重构的DL框架
以非结构化图像作为输入,以端到端的方式,推断ref image的深度图。
核心贡献:
1)将相机参数,编码为,可微单应性,以在camera frustum上构建cost volume。
2)构建的cost volume可以连接二维特征提取和三维损失正则化网络。
MVSNet效果更优,速度更快,没有微调的情况下,在TAT上具有很强的泛化性。
更多推荐
所有评论(0)