论文
Depth Map Prediction from a Single Image using a Multi-Scale Deep Network
摘要
直接从单张影像恢复深度是非常难搞定的,所以需要同时结合局部和全局信息来猜。在这篇论文里,作者攒了一个类似Encoder和Decoder网络,使用NYU Depth Dataset和KITTI Dataset训练,最终结果感觉还不错。
网络模型
以下展示了本篇论文中所用的网络结构,其包括两部分,分别是coarse-net和refine-net。早期的网络设计我感觉都非常粗糙,也没啥想说的,就随便贴贴。
1. coarse-net
coarse-net主要强调网络的全局视野,经过一系列卷积层从影像上提取特征,并最终生成一个粗略的深度图;并且其中有两个池化层,用于增强全局视野;由于2014年还没有出现UNet这种结构,所以coarse-net的最后两层是全连接,某种意义上也可以认为进一步增强了模型的全局视野。在github上找到一个对应的Pytorch代码,贴一下,加深印象。
其实看的时候感觉这个实现还是挺容易的,直接照着图片的参数填就行了。
# reference
# https://github.com/imran3180/depth-map-prediction/blob/master/model.py
class coarseNet(nn.Module):
def __init__(self,init_weights=True):
super(coarseNet, self).__init__()
self.conv1 = nn.Conv2d(3, 96, kernel_size = 11, stride = 4, padding = 0)
self.conv2 = nn.Conv2d(96, 256, kernel_size = 5, padding = 2)
self.conv3 = nn.Conv2d(256, 384, kernel_size = 3, padding = 1)
self.conv4 = nn.Conv2d(384, 384, kernel_size = 3, padding = 1)
self.conv5 = nn.Conv2d(384, 256, kernel_size = 3, stride = 2)
self.fc1 = nn.Linear(12288, 4096)
self.fc2 = nn.Linear(4096, 4070)
self.pool = nn.MaxPool2d(2)
self.dropout = nn.Dropout2d()
if init_weights:
self._initialize_weights()
def forward(self, x):
# [n, c, H, W ]
# [8, 3, 228, 304]
x = self.conv1(x) # [8, 96, 55, 74]
x = F.relu(x)
x = self.pool(x) # [8, 96, 27, 37] --
x = self.conv2(x) # [8, 256, 23, 33]
x = F.relu(x)
x = self.pool(x) # [8, 256, 11, 16] 18X13
x = self.conv3(x) # [8, 384, 9, 14]
x = F.relu(x)
x = self.conv4(x) # [8, 384, 7, 12]
x = F.relu(x)
x = self.conv5(x) # [8, 256, 5, 10] 8X5
x = F.relu(x)
x = x.view(x.size(0), -1) # [8, 12800]
x = F.relu(self.fc1(x)) # [8, 4096]
x = self.dropout(x)
x = self.fc2(x) # [8, 4070] => 55x74 = 4070
x = x.view(-1, 1, 55, 74)
return x
2. refine-net
由于coarse-net获取的深度图分辨率较低,且质量一般;所以在本文里还添加了refine-net,用于提高深度图质量。虽然refine-net的参数非常少,但是其在2014年设计时还是非常有特色的,其将coarse-net的输出嵌入到refine-net的第二层,有点skip-connection和resnet的意味。网络也有人实现了这部分的代码,继续贴一下。
class fineNet(nn.Module):
def __init__(self, init_weights=True):
super(fineNet, self).__init__()
self.conv1 = nn.Conv2d(3, 63, kernel_size = 9, stride = 2)
self.conv2 = nn.Conv2d(64, 64, kernel_size = 5, padding = 2)
self.conv3 = nn.Conv2d(64, 1, kernel_size = 5, padding = 2)
self.pool = nn.MaxPool2d(2)
if init_weights:
self._initialize_weights()
def forward(self, x, y):
# [8, 3, 228, 304]
x = F.relu(self.conv1(x)) # [8, 63, 110, 148]
x = self.pool(x) # [8, 63, 55, 74]
x = torch.cat((x,y),1) # x - [8, 63, 55, 74] y - [8, 1, 55, 74] => x = [8, 64, 55, 74]
x = F.relu(self.conv2(x)) # [8, 64, 55, 74]
x = self.conv3(x) # [8, 64, 55, 74]
return x
损失函数 公式(1)=公式(3)=公式(3)
这篇文章最有意思就是这个损失函数,但是不得不说论文里的损失函数让人看得乱七八糟的,根本搞不懂。
经过我两个夜晚的思考,终于把公式看懂了公式(1)、公式(3)、公式(3)的等价性,真是难得啊,特此记录一下。
首先记
d
i
=
l
o
g
y
i
∗
−
l
o
g
y
i
d_i=logy_i^*-logy_i
di=logyi∗−logyi,
d
‾
=
α
(
y
,
y
∗
)
=
1
n
∑
d
i
\overline{d}=\alpha(y,y^*)=\frac{1}{n}\sum{d_i}
d=α(y,y∗)=n1∑di以方便公式推导。
以下在推导时还充分利用了两个定则,分别是,第一个显而易见,第二个基于求和公式的理解。
1
n
∑
d
i
2
=
1
n
∑
d
j
2
∑
C
=
1
n
∑
∑
C
\frac{1}{n}\sum{d_i^2}=\frac{1}{n}\sum{d_j^2} \\ \sum C=\frac{1}{n} \sum\sum{C}
n1∑di2=n1∑dj2∑C=n1∑∑C
以下为完整的推导,证明了公式(1)、公式(3)、公式(3)的等价性,不难发现损失函数的两个视角其实是完全等价的(与文中差了一个2倍)
D
(
y
,
y
∗
)
=
1
n
∑
(
l
o
g
y
i
−
l
o
g
y
i
∗
+
α
(
y
,
y
∗
)
)
2
=
1
n
∑
(
d
i
−
d
‾
)
2
=
1
n
∑
(
d
i
2
−
2
d
‾
d
i
+
d
‾
2
)
=
1
n
∑
d
i
2
−
2
n
d
‾
∑
d
i
+
1
n
∑
d
‾
2
=
1
n
∑
d
i
2
−
2
d
‾
2
+
d
‾
2
=
1
n
∑
d
i
2
−
d
‾
2
=
1
2
(
1
n
∑
d
i
2
−
2
d
‾
2
+
1
n
∑
d
j
2
)
=
1
2
(
1
n
∑
d
i
2
−
2
1
n
∑
d
i
1
n
∑
d
j
+
1
n
∑
d
j
2
)
=
1
2
n
(
∑
d
i
2
−
2
1
n
∑
∑
d
i
d
j
+
∑
d
j
2
)
=
1
2
n
(
1
n
∑
∑
d
i
2
−
2
1
n
∑
∑
d
i
d
j
+
1
n
∑
∑
d
j
2
)
=
1
2
n
2
(
∑
∑
d
i
2
−
2
∑
∑
d
i
d
j
+
∑
∑
d
j
2
)
=
1
2
n
2
(
∑
∑
(
d
i
2
−
2
d
i
d
j
+
d
j
2
)
=
1
2
n
2
∑
∑
(
d
i
−
d
j
)
2
\begin{aligned} D(y,y^*) &=\frac{1}{n}\sum(logy_i-logy_i^*+\alpha(y,y^*))^2 \\ &=\frac{1}{n}\sum(d_i-\overline{d})^2 \\ &=\frac{1}{n}\sum(d_i^2-2\overline{d}d_i+\overline{d}^2) \\ &=\frac{1}{n}\sum{d_i^2}-\frac{2}{n}\overline{d}\sum{d_i}+\frac{1}{n}\sum{\overline{d}^2} \\ &=\frac{1}{n}\sum{d_i^2}-2\overline{d}^2+\overline{d}^2 \\ &=\frac{1}{n}\sum{d_i^2}-\overline{d}^2 \\ &=\frac{1}{2}(\frac{1}{n}\sum{d_i^2}-2\overline{d}^2+\frac{1}{n}\sum{d_j^2}) \\ &=\frac{1}{2}(\frac{1}{n}\sum{d_i^2}-2\frac{1}{n}\sum{d_i}\frac{1}{n}\sum{d_j}+\frac{1}{n}\sum{d_j^2}) \\ &=\frac{1}{2n}(\sum{d_i^2}-2\frac{1}{n}\sum{\sum{d_id_j}}+\sum{d_j^2}) \\ &=\frac{1}{2n}(\frac{1}{n}\sum\sum{d_i^2}-2\frac{1}{n}\sum{\sum{d_id_j}}+\frac{1}{n}\sum\sum{d_j^2}) \\ &=\frac{1}{2n^2}(\sum\sum{d_i^2}-2\sum{\sum{d_id_j}}+\sum\sum{d_j^2}) \\ &=\frac{1}{2n^2}(\sum\sum({d_i^2}-2d_id_j+d_j^2) \\ &=\frac{1}{2n^2}\sum\sum{(d_i-d_j)^2}\\ \end{aligned}
D(y,y∗)=n1∑(logyi−logyi∗+α(y,y∗))2=n1∑(di−d)2=n1∑(di2−2ddi+d2)=n1∑di2−n2d∑di+n1∑d2=n1∑di2−2d2+d2=n1∑di2−d2=21(n1∑di2−2d2+n1∑dj2)=21(n1∑di2−2n1∑din1∑dj+n1∑dj2)=2n1(∑di2−2n1∑∑didj+∑dj2)=2n1(n1∑∑di2−2n1∑∑didj+n1∑∑dj2)=2n21(∑∑di2−2∑∑didj+∑∑dj2)=2n21(∑∑(di2−2didj+dj2)=2n21∑∑(di−dj)2
最后再来说一下这个损失函数有什么意义吧。公式(1)想表达的是在单张影像深度估计下,深度的尺度其实是一个没有确定的问题,如果放任这个问题不管,会对深度估计产生负面影响;作者后续的实验也证明了这个问题。公式(3)则从另外一个角度说明,对于尺度未知的情况下,两个像素间的相对深度值更有价值。
训练
对于训练,有三点需要格外提醒一下:
1. 原文作者训练的时候,其实是先训练coarse-net;然后固定coarse-net,训练refine-net;最后再同时训练两个网络;
2. 训练时肯定要做数据增强啦,这个不用多说了
3. 训练时的损失函数其实稍微有一点变化,其公式如下,其中
α
=
0.5
\alpha=0.5
α=0.5,保证网络还是有一定能力学习全局尺度的,反正作者这样说了。
D
(
y
,
y
∗
)
=
1
n
∑
d
i
2
−
α
d
‾
2
D(y,y^*)=\frac{1}{n}\sum{d_i^2}-\alpha\overline{d}^2 \\
D(y,y∗)=n1∑di2−αd2
结果
以下是最终的一些结果,论文里有,我也贴一下。