前言
Cascade Cost Volume for High-Resolution Multi-View Stereo and Stereo Matching
出自CVPR 2020,github链接casmvsnet
下图是我修正后的网络图,更好的帮助大家理解网络
一、论文先进之处
使用多阶段策略,由粗到细的推断深度图
DTU准确度提升了35%,GPU和运行时间降了50%
二、网络结构
1.引入特征金字塔fpn
本网络的输入为原图像与标定的相机位姿,其中位姿信息用于单应性变换原图像用于特征提取。特征提取网络包括两部分,特征金字塔和多尺度聚合模块。特征金字塔可视为一个编码解码结构,通过特征金字塔可以得到三个不同尺度的特征图,顶层特征图包含高层语义特征但缺乏底层的细节;高层的特征图虽然包含特征细节,但缺失足够的语义信息。因此从多个尺度上提取特征能够描述准确的图像特征。根据特征金字塔的上采样及下采样结构,输出的特征图有三个尺度,顶层的特征输出大小为W/4×H/4×32,其中W和H为原图像尺度;中间层尺度大小为W/2×H/2×16;最底层尺度为W×H×8。这三个尺度可成为3个阶段
2.多阶段推断深度图
每一阶段都类似MVSNet,不过第二三阶段估计的是残差深度图,第一阶段估计的是稀疏的深度图
3.多阶段刨析
通过特征提取以后得到了一张参考图像的特征和N-1张源图像的特征,再根据单应性变换,将每一张源图像投影到每一层深度上构成特征体,最后利用插值法使每张投影尺寸相同。理论上每一张参考图像会有N-1个对应的特征体,将这些特征体基于方差的形式构建一个代价体。由于在第一阶段生成的代价体尺度为W/4×H/4×32×48是稀疏的,因此使用的正则网络是3D CNN生成粗糙的深度图并且将场景的深度范围进行了一个估计。
第一阶段得到的深度图作为参考,进行第二阶段的深度图估计。使用中层特征图在剩余深度范围内进行采样深度平面构成剩余代价体,其尺度为W/2×H/2×16×32,使用3DCNN残差深度图,最终生成第二阶段深度图。值得一提的是,在构成剩余代价体时需要使用残差量的单应性变换,公式如下:
最终的深度图是依靠第二阶段估计的深度图用相同的方式构成剩余代价体,其尺度大小为W×H×8×8,生成最终的深度图。
4.一些问题
本网络获取残差深度图的方式为通过缩小剩余深度获取,这个范围是通过一个系数实现的
我在这里有详细阐述
三、代码刨析
class CascadeMVSNet(nn.Module): #返回各个阶段的输出深度图与置信度图(附件一个优化深度图) 2022/6/21
def __init__(self, refine=False, ndepths=[48, 32, 8], depth_interals_ratio=[4, 2, 1], share_cr=False,
grad_method="detach", arch_mode="fpn", cr_base_chs=[8, 8, 8]):
super(CascadeMVSNet, self).__init__()
self.refine = refine
self.share_cr = share_cr
self.ndepths = ndepths
self.depth_interals_ratio = depth_interals_ratio
self.grad_method = grad_method
self.arch_mode = arch_mode
self.cr_base_chs = cr_base_chs
self.num_stage = len(ndepths)
print("**********netphs:{}, depth_intervals_ratio:{}, grad:{}, chs:{}************".format(ndepths,
depth_interals_ratio, self.grad_method, self.cr_base_chs))
assert len(ndepths) == len(depth_interals_ratio)
self.stage_infos = {
"stage1":{
"scale": 4.0,
},
"stage2": {
"scale": 2.0,
},
"stage3": {
"scale": 1.0,
}
}
self.feature = FeatureNet(base_channels=8, stride=4, num_stage=self.num_stage, arch_mode=self.arch_mode)
if self.share_cr:
self.cost_regularization = CostRegNet(in_channels=self.feature.out_channels, base_channels=8)
else:
self.cost_regularization = nn.ModuleList([CostRegNet(in_channels=self.feature.out_channels[i],
base_channels=self.cr_base_chs[i])
for i in range(self.num_stage)])
if self.refine:
self.refine_network = RefineNet()
self.DepthNet = DepthNet()
def forward(self, imgs, proj_matrices, depth_values):
depth_min = float(depth_values[0, 0].cpu().numpy())
depth_max = float(depth_values[0, -1].cpu().numpy())
depth_interval = (depth_max - depth_min) / depth_values.size(1)
# step 1. feature extraction
features = []
for nview_idx in range(imgs.size(1)): #imgs shape (B, N, C, H, W)
img = imgs[:, nview_idx]
features.append(self.feature(img)) #提取参考源图像特征 金字塔结构 2022/6/20
outputs = {
} #各个阶段的输出深度图与置信度图(附件一个优化深度图) 2022/6/21
depth, cur_depth = None, None
for stage_idx in range(self.num_stage): #step 2. 分几个阶段,循环执行多阶段 2022/6/20
# print("*********************stage{}*********************".format(stage_idx + 1))
#stage feature, proj_mats, scales
features_stage = [feat["stage{}".format(stage_idx + 1)] for feat in features] #feat是一个包含特征金字塔三个阶段的特征图列表,故此步得到所有图片第i层的特征图 2022/6/20
proj_matrices_stage = proj_matrices["stage{}".format(stage_idx + 1)]
stage_scale = self.stage_infos["stage{}".format(stage_idx + 1)]["scale"]
if depth is not None: #除了第一阶段 ,都会走这 2022/6/21
if self.grad_method == "detach":
cur_depth = depth.detach()
else:
cur_depth = depth
cur_depth = F.interpolate(cur_depth.unsqueeze(1),
[img.shape[2], img.shape[3]], mode='bilinear',
align_corners=Align_Corners_Range).squeeze(