深度估计

深度估计字面意思而已。

同样从人来讲的话,是一件很简单的事情。对于眼睛捕捉的一张图像,大脑自然而然解析出的是一个三维关系,而对于网络呢,他可能需要根据整个图像的信息、物体周围信息、透视法、物体本身的大小形状位置等信息,或者在图像序列中会加上时序关系,亦或者相机的变动根据焦距的关系等等判断出这个物体在整张图中的相对深度(其实很多时候一直在做的就是探索大脑是怎么做的,也不完全是)。

现在我看到的更多的是单目深度估计,可能是我检索有些问题,也可能是钱不够用,所以在技术上探索?单目深度估计的问题即MDE,Monocular Depth Estimation。单目问题在Godard e t c., 2017中指出,所谓单目是指在测试的时候只用了一张图片。一般来说,单目数据会是一个序列,因此也有很多方法是利用了时间的关系。所以在训练的时候可以用两个摄像机的图像来训练咯?人来说的话都是双目深度估计吧,双眼之间是有距离的,然后根据几何关系感觉就可以;但是人闭上一只眼睛,却也可以较准确地给出深度关系,这么一看刚出生的我就很机智哎

Paper

从论文来学习,慢慢体会为什么这个是一个难的问题,以及在3D目标检测上他的作用点。

Depth Map Prediction from a Single Image using a Multi-Scale Deep Network——2014

pdf:https://arxiv.org/pdf/1406.2283

Eigen的一篇经典之作,单目(from a single image)深度估计。看之后的文章知道了这一篇的一个巨大的贡献就是不用手动设置特征或者有初始的语义信息,估计这又是第一篇运用卷积网络的文章

首先他指出从单张图片上要得到深度预测图,需要整合全局和局部的信息。因此他用了两个网络:Coarse和fine,分别来获取全局和局部的预测。

显然,先用Coarse获得全局的一个预测,这个预测比较粗糙;然后将这个预测和原图再次通过fine网络精细化,reshape得到一个相对精细的深度预测图。这个思路很好理解它工作的原理。具体网络信息如下图。

Coarse网络前五层是在做特征提取后面两层其实就是在整合整个图片的信息。然后将输出与Fine的第一层输出整合后作为第二层的输入。所有的低-中层,隐藏层是用了线性整流单元以及第6层的全连接部分采用了dropout来减少过拟合的现象。

第二个点就是设计的Scale-Invariant error。设计这个点的由来也很直观,场景的全局规模是不可预测,而现在许多的error是由于在elementwise方法上累积下来的。然后它在Make3D方法上做了两个实验,发现oracle代替场景的平均尺寸可以提高20%,说明场景的平均规模在总error中占据了一大部分的比例。这个设计的理由我看了好多遍,网上的也没有讲的很清楚,英语so poor;反正从名字上来看就是更加关注于深度预测而不是尺寸,忽略尺寸的影响。

RMSE: root mean squared error

场景的平均尺寸:

用orale代替:

由此启发设计了Scale-Invariant error来表示场景中点之间的关系。用的是在log级别上的平均平方差。

这里,是预测的深度图,每一个里面还有n个pixels。

D(y,y^)=\frac 1 n\sum_id_i^2-\frac 1 {n^2}(\sum_id_i)^2\\
d_i=\log y_i-\log y_i^

L(y,y^)=\frac 1 n\sum_id_i^2-\frac \lambda {n^2}(\sum_id_i)^2\\
d_i=\log y_i-\log y_i^

L_{depth}(D,D^*)=\frac 1 n\sum_id_i^2-\frac 1 {2n^2}(\sum_id_i)^2\+\frac 1 n \sum_i[(\nabla_xd_i)^2+(\nabla_yd_i)^2]

>

第二种:

就变成第一种更好了。

增加行和列的差异部分之后,使得我们的损失函数不仅使得两者的值更加接近,而且在局部结构上也更加相似。

  • 表面法向量:

    把输出通道由1通道变成3通道,分别预测在图中每个像素点法向量的x,y,z。用L2norm来归一化法向量,方向传播也通过这个归一化。至于损失函数则是很简单的逐像素的点乘。

  • 语义标签

    用了pixelwise softmax classifier来预测每个点的类标签,pixelwise cross-entropy loss;最后的输出channels是和类别一样多的。


训练的话同样有数据增强,learning rate也是确定的,但是不同任务略有区别。


作者提供的代码是theano的,不太好看,还有就是这个好像只有test,没有train

Unsupervised Monocular Depth Estimation with Left-Right Consistency——2017

这篇文章首先它是unsupervised,没有用到ground truth depth(自认为这个标注应该比目标检测的还要难),之前大部分都是通过监督回归来做的;然后用到了Left-Right Consistency,首先需要得到视差图,对于同一时刻两个摄像机的视差图应该是对应的,用极线几何约束,可以根据公式$\hat d=bf/d$得到深度;而对于第二个图像,在这里它用了图像重建,重建出的图片用真实的图片监督,这样子在测试的时候才可以适用于单目的情况;最后提出了新的loss,首先是不同规模s的output,然后每一个规模的Cs由三个部分组成,重建的匹配误差、左右视差图一致误差、原始视差图应该是光滑的惩罚


网络整体是对称的,整体来说还是清晰的。思路来源于DispNet。整体训练的是一个视差预测网络。


loss部分比较复杂,每一个output scale s有一个损失,这个损失又由三个部分组成,每个部分又有左右两边。

  • 图像重建损失:结合L1和SSIM(结构相似性)

    其中,SSIM(Structural Similarity)的定义如下:

  • 视差光滑:L1惩罚,exponential linear units(在code部分,左右视差也是个监督的)

  • 左右视差一致:L1惩罚


同样使用了数据增强:随机翻转、颜色变换。

使用了ResNet作为编码器;解码器部分以每个分辨率附加预测,以便以下一个更高的分辨率进行预测。

为了减少立体遮挡的影响,在测试的时候做了一个后处理,即原来视差图翻转原图后得到视差图翻转得到——即翻转图片的翻转视差图。这样应该是对齐的,but where the disparity ramps are located on the right of occluders as well as on the right side of the image大概就是减少遮挡视差的影响吧,感觉是因为测试的时候没有右图的监督了,所以弄了个翻转再翻转?。最后的视差图用。但是会增加两倍测试的时间。


缺点:遮挡部分的像素在两个图像中都不可见;训练数据集必须有两个view;由于依赖图像重构,对于反射透视的深度判别是不一致的。

Deeper Depth Prediction with Fully Convolutional Residual Networks——2016

主要贡献:

  1. 受到Encoder-decoder结构的影响,发现它使用了高层和低层的特征来优化预测,而深度预测正好需要全局和局部的特征,因此作者使用Encoder-decoder结构。

  2. 使用了ResNet的卷积神经网络架构作为编码器的部分,一是因为最后一层的卷积层的感受野越大的话我们可以从模型中得到更好的基础特征,而是因为他们输入图片的尺寸大小为304*228,而ResNet最后尺寸大小为483*483,这样就可以获取足够多的图片信息了那也就是说原图如果是大分辨需要先压缩

  3. 使用了向上卷积代替了全连接层,全连接层相互之间是有些独立的,不适用于回归问题上,而且全连接层需要很多参数,因此会消耗过多的内存。

  4. 解码部分使用了向上投影,同时比较了不同的向上投影up-projection模块的效果

    • Vanilla UP-Convolution,使用解池层un-pooling layer,之后跟上一个卷积层。但是解池层是通过将每一个cell变为2*2的输出,简单来说左上的cell为输入的值,其他三个均为零。这样子的话整个output map会变得稀疏,从而这个feature map就比较weak了,存在较大的棋盘效应(就是生成的图像是由深深浅浅的方块组成,像棋盘,当卷积核大小不能被步长整除时,反卷积就会出现重叠问题,插零的时候,输出结果会出现一些数值效应,就像棋盘一样),边缘模糊,噪声较大,学起来也比较难了。

    • Vanilla UP-Projection,和上一个很像,受ResNet的影响,增加了投影,使得模型更易学习。通过合并两个独立的分支来获取密集的feature map;但是结果仍然是不理想的。

    • Fast UP-Convolution,作者提出的一个可以提高训练速度15%的向上卷积模块。具体来说就是把原来5*5的卷积分解为四个部分A:3*3,B:2*3, C:3*2, D:2*2,然后每一个部分各自卷积完成后,将结果插值后得到新的feature map就是和分布式的意思差不多吧,分别计算output中每一个格子,然后最后组成成output;小卷积代替大卷积,实现上采样。而且这样得到的结果可以大量减少0的出现;作者说叠加到第五个卷积块之后,没什么效果提升了,我还以为是因为刚好可以凑出来呢,不过有1*1在应该没什么问题,但是这样子的话不就对0部分还是有影响的吧

    • Fast UP-Projection,就是将Vanilla UP-Projection中的卷积部分计算用插值的技术来算

  5. 使用了reverse Huber loss function,这是因为作者不想要数据集中不符合常理的数据影响梯度流的传递,因此当深度差没有超过一定阈值的时候,使用l1 loss,对于小误差更加敏感一些。

  6. 没有后处理的步骤,如CRF

Two Stream Networks for Self-Supervised Ego-Motion Estimation——2019

概述:论文的点从标题就可以知道,用了自监督的思想、Two stream network完成自我运动估计任务。首先通过深度预测网络获取深度作为几何流的部分,同时图片本身作为appearance流的部分进入PoseNet,预测两帧内自我运动估计。然后利用第一帧和第二帧的深度以及预测出的自我运动估计的信息 重新生成第二帧,与真实值进行photometric loss的运算。

几个重点

  • self-supervised pre-training:实验发现,大量未标记的驾驶数据可以替代同一domain中精选的数据集的效果。使用了80K images of CityScapes (CS) or the 1M urban driving dataset (D1M)分别训练后作为pretraining。数据集大的结果更好。

  • Two stream network combing images and inferred depth:之前的自我运动估计一般处理的是3个帧,而此方法只用了两个帧。

    Input首先经过DepthNet得到深度估计图,然后这两个作为两个流的输入进入Two-stream PoseNet,分别来预测structure(geometry)、appearance。之后预测的pose(就是过程中的自我运动变换,6-DOFfor translation, for rotation using the Euler parameterization)、以及进行view synthesis。合成后的预测viewphotometric loss

    每一个stream包括8个卷积层和最后的一个平均池化层。

    • Robust appearance-based loss(per-pixel loss)

      like many works use linear combination between an L1 loss and the SSIM loss)

      SSIM方法对于遮挡或者动态目标susceptible;这里采用了auto-masking appraoch的方法。定义masking term关注静态像素和有细微photometric变化的部分

      So Robust appearance-based loss is:

    • final loss:

  • DepthNet is based on the DispNet.(4 scales, depth at each scale is upsampled by a factor of 2 and concatenated with the decoder features to help resolve the depth and the next scale.)

    • Depth smoothness loss:引入多尺度边缘感知项,以规范无纹理区域的深度预测
  • sparsity-inducing data augmentation policy:大概就是在原图上按设定的比例随机增加噪声块(块的大小也是超参数),作为一个正则化,avoid overfitting。(实验发现,在深度估计上也有所提升)

    为什么要丢失一部分信息呢?

    • pixel-level计算量很大
    • 密集的信息容易overfit
    • 只有一部分的输入pixels是可信赖的可能因为mask是缓解不是解决?

    两个超参数:

    • 图片被随机噪声覆盖的比例
    • 噪声块的尺寸

评估指标:Absolute Trajectory Error (ATE)(轨迹图看起来很不错呢)

3D Packing for Self-Supervised Monocular Depth Estimation——2020

这篇论文的总体思想就是,很可能灵感是因为3D其实是有速度的,而目前标注框有一个速度偏差,不太准确;正好利用了这一个思考。不过新出的cityscapes 3D数据集已经解决了这一问题,用stereo代替lidar作为生成的来源,同时加上了pitch、roll、yaw这三个信息使得这个标注框更加贴合实际。当前时间段的图片预估自我运动的速度,下一时间段的图片预估深度,然后用预估的速度和深度可以复原出下一时间段的图片;约束一个就是下一时间段图片的复原情况,另一个是速度评估情况。

这篇文章三个贡献:

  1. 提出了一个新的自监督网络结构PackNet对称的 (3D Packing and unpacking)blocks,适用于高分辨率的单目深度估计,使用了一个Pose ConvNet,自我的运动估计;最终生成密集的appearance and geometric information

    • PackNet:标准卷积结构通过渐进式的步长和池化增加感受野的大小而这里是通过Packing and unpacking blocks:大概是一个数据重构的过程,比最大池化+双线性插值的效果更加好,用l1 loss来横量

      • Packing: Space2Depth(折叠空间维度,减少分辨率,且是可逆的), 3D Conv., Reshape, 2D Conv(压缩特征空间).
      • Unpacking: 2D Conv.(变换为所需特征channel个数), 3D Conv.(扩展被压缩的空间), Reshape, Depth2Space(via nearest-neighbor or with learnable transposed convolutional weights.代替上采样的过程)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      class PackNet01(nn.Module):
      def __init__(self,...):
      pass
      def forward(self, x):
      x = self.pre_calc(x) # Conv2D(in_channels, 64, 5, 1)
      # Encoder
      x1 = self.conv1(x)
      x1p = self.pack1(x1)
      # ...
      x5 = self.conv5(x4p)
      x5p = self.pack5(x5)
      # Skips
      skip1 = x
      # ...
      skip5 = x4p
      # Decoder
      unpack5 = self.unpack5(x5p)
      # A for concatenation and B for addition
      if self.version == 'A':
      concat5 = torch.cat((unpack5, skip5), 1)
      else:
      concat5 = unpack5 + skip5
      iconv5 = self.iconv5(concat5)
      unpack4 = self.unpack4(iconv5)
      if self.version == 'A':
      concat4 = torch.cat((unpack4, skip4), 1)
      else:
      concat4 = unpack4 + skip4
      iconv4 = self.iconv4(concat4)
      disp4 = self.disp4_layer(iconv4)
      udisp4 = self.unpack_disp4(disp4)
      unpack3 = self.unpack3(iconv4)
      if self.version == 'A':
      concat3 = torch.cat((unpack3, skip3, udisp4), 1)
      else:
      concat3 = torch.cat((unpack3 + skip3, udisp4), 1)
      iconv3 = self.iconv3(concat3)
      disp3 = self.disp3_layer(iconv3)
      udisp3 = self.unpack_disp3(disp3)
      unpack2 = self.unpack2(iconv3)
      if self.version == 'A':
      concat2 = torch.cat((unpack2, skip2, udisp3), 1)
      else:
      concat2 = torch.cat((unpack2 + skip2, udisp3), 1)
      iconv2 = self.iconv2(concat2)
      disp2 = self.disp2_layer(iconv2)
      udisp2 = self.unpack_disp2(disp2)
      unpack1 = self.unpack1(iconv2)
      if self.version == 'A':
      concat1 = torch.cat((unpack1, skip1, udisp2), 1)
      else:
      concat1 = torch.cat((unpack1 + skip1, udisp2), 1)
      iconv1 = self.iconv1(concat1)
      disp1 = self.disp1_layer(iconv1)
      if self.training:
      return [disp1, disp2, disp3, disp4]
      else:
      return disp1
  2. 提出了一个新的loss photometricLoss,可以附加上相机自身的速度等内在信息;目标是:

    其中,表示mask,使得计算loss的时候关注有效部分的关系

    包括:

    • Appearance Matching Loss(SSIM)
      1
      2
      3
      photometric_loss = [self.ssim_loss_weight * ssim_loss[i].mean(1, True) +
      (1 - self.ssim_loss_weight) * l1_loss[i].mean(1, True)
      for i in range(self.n)]
 $$\mathcal M_p$$去除了静态场景和目标没有移动的部分~~感觉是为了避免最后学成所有均没有移动,然后这一部分的loss永远为0~~
 $$
 \mathcal M_p = min \mathcal L_p (I_t, I_s ) > min \mathcal L_p (I_t, \hat I_t )
 $$
 SSIM:
 $$
 SSIM(x,y)=\frac{(2\mu_x\mu_y+C_1)(2\sigma_{xy}+C_2)}{(\mu_x^2+\mu_y^2+C_1)(\sigma_x^2+\sigma_y^2+C_2)}
 $$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def SSIM(x, y, C1=1e-4, C2=9e-4, kernel_size=3, stride=1):
"""
x,y : torch.Tensor [B,3,H,W]Input images
C1,C2 : float SSIM parameters
"""
pool2d = nn.AvgPool2d(kernel_size, stride=stride)
refl = nn.ReflectionPad2d(1)
x, y = refl(x), refl(y)
mu_x = pool2d(x)
mu_y = pool2d(y)
mu_x_mu_y = mu_x * mu_y
mu_x_sq = mu_x.pow(2)
mu_y_sq = mu_y.pow(2)
sigma_x = pool2d(x.pow(2)) - mu_x_sq
sigma_y = pool2d(y.pow(2)) - mu_y_sq
sigma_xy = pool2d(x * y) - mu_x_mu_y
v1 = 2 * sigma_xy + C2
v2 = sigma_x + sigma_y + C2
ssim_n = (2 * mu_x_mu_y + C1) * v1
ssim_d = (mu_x_sq + mu_y_sq + C1) * v2
ssim = ssim_n / ssim_d
return ssim
  • Depth Smoothness Loss , decayed by a factor of 2 on down-sampling, starting with a weight of 1 for the 0 th pyramid level.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def calc_smoothness_loss(...):
    inv_depths_norm = inv_depths_normalize(inv_depths)
    inv_depth_gradients_x = [gradient_x(d) for d in inv_depths_norm]
    inv_depth_gradients_y = [gradient_y(d) for d in inv_depths_norm]
    image_gradients_x = [gradient_x(image) for image in images]
    image_gradients_y = [gradient_y(image) for image in images]
    weights_x = [torch.exp(-torch.mean(torch.abs(g), 1, keepdim=True)) for g in image_gradients_x]
    weights_y = [torch.exp(-torch.mean(torch.abs(g), 1, keepdim=True)) for g in image_gradients_y]
    # Note: Fix gradient addition
    smoothness_x = [inv_depth_gradients_x[i] * weights_x[i] for i in range(num_scales)]
    smoothness_y = [inv_depth_gradients_y[i] * weights_y[i] for i in range(num_scales)]
    smoothness_loss = sum([(smoothness_x[i].abs().mean() +
    smoothness_y[i].abs().mean()) / 2 ** i
    for i in range(self.n)]) / self.n
    smoothness_loss = self.smooth_loss_weight * smoothness_loss
  • Velocity Supervision Loss

  • scale-aware self-supervised objective loss

  1. 提出了一个新的数据集:range从之前的80m提升到了200m

效果:STA,尤其在远距离;可以和监督学习方法媲美;在unseen data(遮挡?)上也通用;可以很好地扩展:参数数量、输入分辨率、未标记的训练数据;在高分辨率上可以达到实时;不需要大型的监督学习作为pretrained

Deep Ordinal Regression Network for Monocular Depth Estimation——2018

看到CVPR2020Learning Depth-Guided Convolutions for Monocular 3D Object Detection获取深度图的时候使用了DORN方法,于是过来看看,使用它的优点。

首先之前的方法从image-level information, hierarchical features来探索,主要是用回归的方法以及最小平方差来约束得到depth map,由于使用大量的空间池化的操作,得到的深度图的分辨率较低。这样得到的结果:收敛较慢且在局部local上结果并不好。如果要提高分辨率,势必要加入一些skip-connections以及多层反卷积的操作(还有比如Eigen那篇利用了多规模的网络),但会增加大量的计算和存储空间


这篇文章主要的贡献在于提出了spacing-increasing discretization[离散化] (SID)的策略(instead of the uniform discretization (UD) strategy),to discretize depth and recast depth network learning as an ordinal有序 regression loss(因为之前对于深度估计是经过回归数值然后变成多分类问题,丢失了深度之间是有序的关系;也就是说有序指的是相对深浅的关系,在此篇论文中深度的结果是用相对顺序来表达的)。另外使用了多尺度网络结构,避免了不必要的空间池化以及获取多尺度的信息的操作。主要是通过去除局部采样,使用dilated convolutions(增大感受野但同时不会减小分辨率);并将fully-connected full-image encoders重新设计。


整个网络由两个部分组成,Dense feature extractor和Scene understanding modular.

  • Dense feature extractor(use dilated convolution
  • Scene understanding modular
    • ASPP(atrous spatial pyramid pooling):ASPP is employed to extract features from multiple large receptive fields via dilated convolutional operations. The dilation rates are 6, 12 and 18, respectively.
    • a cross-channel leaner:pure 1 × 1 convolutional branch
    • a full-image encoder:The full-image encoder captures global contextual information and can greatly clarify local confusions in depth estimation. 和之前的方法相比,参数更加少。首先利用一个平均池化层来减少空间维度,然后通过fc层获得C维度的特征向量,之后使用1x1的卷积作为cross-channel parametric pooling structure,最后将F copy为原空间维度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class DORN(nn.Module):
def __init__(self,...):
pass
def forward(self, image, target=None):
N, C, H, W = image.shape
feat = self.backbone(image) # ResNet and so on...
feat = self.SceneUnderstandingModule(feat)
if self.training:
prob = self.regression_layer(feat)
loss = self.criterion(prob, target)
return loss
prob, label = self.regression_layer(feat)
if self.discretization == "SID":
t0 = torch.exp(np.log(self.beta) * label.float() / self.ord_num) # alpha=1; i=label
t1 = torch.exp(np.log(self.beta) * (label.float() + 1) / self.ord_num)
else:
t0 = 1.0 + (self.beta - 1.0) * label.float() / self.ord_num
t1 = 1.0 + (self.beta - 1.0) * (label.float() + 1) / self.ord_num
depth = (t0 + t1) / 2 - self.gamma
return {"target": [depth], "prob": [prob], "label": [label]}
class SceneUnderstandingModule(nn.Module):
def __init__(self, pyramid=[6, 12, 18],...):
# ...
self.encoder = FullImageEncoder(h // 8, w // 8, kernel_size)
self.aspp1 = nn.Sequential(
conv_bn_relu(batch_norm, 2048, 512, kernel_size=1, padding=0),
conv_bn_relu(batch_norm, 512, 512, kernel_size=1, padding=0)
)
self.aspp2 = nn.Sequential(
conv_bn_relu(batch_norm, 2048, 512, kernel_size=3, padding=pyramid[0], dilation=pyramid[0]),
conv_bn_relu(batch_norm, 512, 512, kernel_size=1, padding=0)
)
self.aspp3 = nn.Sequential(
conv_bn_relu(batch_norm, 2048, 512, kernel_size=3, padding=pyramid[1], dilation=pyramid[1]),
conv_bn_relu(batch_norm, 512, 512, kernel_size=1, padding=0)
)
self.aspp4 = nn.Sequential(
conv_bn_relu(batch_norm, 2048, 512, kernel_size=3, padding=pyramid[2], dilation=pyramid[2]),
conv_bn_relu(batch_norm, 512, 512, kernel_size=1, padding=0)
)
self.concat_process = nn.Sequential(
nn.Dropout2d(p=0.5),
conv_bn_relu(batch_norm, 512*5, 2048, kernel_size=1, padding=0),
nn.Dropout2d(p=0.5),
nn.Conv2d(2048, ord_num * 2, 1)
)
def forward(self, x):
N, C, H, W = x.shape
x1 = self.encoder(x)
x1 = F.interpolate(x1, size=(H, W), mode="bilinear", align_corners=True)
x2 = self.aspp1(x)
x3 = self.aspp2(x)
x4 = self.aspp3(x)
x5 = self.aspp4(x)
x6 = torch.cat((x1, x2, x3, x4, x5), dim=1)
out = self.concat_process(x6)
out = F.interpolate(out, size=self.size, mode="bilinear", align_corners=True)
return out
class FullImageEncoder(nn.Module):
def __init__(self, h, w, kernel_size):
# ...
self.global_pooling = nn.AvgPool2d(kernel_size, stride=kernel_size, padding=kernel_size // 2)
self.global_fc = nn.Linear(2048 * self.h * self.w, 512)
self.conv1 = nn.Conv2d(512, 512, 1)
# ...
def forward(self, x):
x1 = self.global_pooling(x)
x2 = self.dropout(x1)
x3 = x2.view(-1, 2048 * self.h * self.w) # kitti 4x5
x4 = self.relu(self.global_fc(x3))
x4 = x4.view(-1, 512, 1, 1)
x5 = self.conv1(x4)
return x5

主要是因为拥有很多depth的时候,平均分配会导致每一个depth之间的距离就很小,这样总的depth loss就会很大(over-strengthened loss);这里用SID,在log空间上,相对来说区分度会更高一些,更加准确一些(感觉就是depth相差近的会比较接近,但是远的话会远的更加明显一些)

实现的时候,在上增加了一个shift


训练阶段:

图片,经过参数为的dense feature extractor,得到特征图。然后将特征图的每一个空间区域计算深度排序,并加上对应的权重,得到序列输出。对于每一个空间区域,通过SID策略得到的离散深度标签为。有此整个图像域有序的损失定义为pixelwise ordinal loss 的平均值

其中,$\hat l_{(w,h)}$是估计的离散值。目标就是最小化损失,它对的偏导为:

测试阶段:预测的深度值为:

pyramid stereo matching network (PSMNet)——2018

论文《Pseudo-LiDAR from Visual Depth Estimation: Bridging the Gap in 3D Object Detection for Autonomous Driving》中使用的深度估计模型,所以来看看。

Current architectures rely on patch-based Siamese networks, lacking the means to exploit context information for finding correspondence in ill-posed regions基于图块匹配intensity-consistency,缺少利用上下文信息去寻找不适定区域(遮挡区域、弱纹理区域等). To tackle this problem, we propose PSMNet, a pyramid stereo matching network consisting of two main modules: spatial pyramid pooling and 3D CNN. The spatial pyramid pooling module takes advantage of the capacity of global context information by aggregating context in different scales and locations to form a cost volume单独从一个像素的强度(灰度或RGB值)很难判断环境关系。因此借助物体的环境信息来丰富图像特征能够有助于一致性估计,尤其对于不适定区域。物体(例如汽车)和次级区域(车窗,轮胎等)的关系由SPP模块学习来结合多层级的环境信息. The 3D CNN learns to regularize cost volume using stacked multiple hourglass networks in conjunction with intermediate supervision.

The left and right input stereo images are fed to two weight-sharing pipelines consisting of a CNN for feature maps calculation, an SPP module for feature harvesting by concatenating representations from subregions with different sizes, and a convolution layer for feature fusionSPP模块使用自适应平均池化把特征压缩到四个尺度上,并紧跟一个11的卷积层来减少特征维度,之后低维度的特征图通过双线性插值的方法进行上采样以恢复到原始图片的尺寸。不同级别的特征图都结合成最终的SPP特征图。SPP模块通过结合不同级别的特征有助于立体匹配。. The left and right image features are then used to form a 4D cost volume, which is fed into a 3D CNN for cost volume regularization and disparity regression.为了在视差维度和空间维度聚合特征信息,我们提出两种类型的3D CNN结构来调整匹配代价卷:基础结构和堆叠的沙漏结构。使用一个沙漏(编码解码)结构,由多个重复的带有中间层监督的由精到粗再由粗到精的过程构成。这个堆叠的沙漏结构有*三个主要的沙漏网络,每个都会生成一个视差图。这样三个沙漏结构就会由三个输出和三个损失。

使用视差回归的方式来估算连续的视差图。根据由softmax操作得到预测代价Cd来计算每一个视差值d的可能性。预测视差值d’由每一个视差值*其对应的可能性求和得到。

Use Smooth L 1 loss which is widely used in bounding box regression for object detection because of its robustness and low sensitivity to outliers.

Digging Into Self-Supervised Monocular Depth Estimation——2019

Research on self-supervised monocular training usually explores increasingly complex architectures, loss functions, and image formation models. We show that a surprisingly simple model, and associated design choices, lead to superior predictions. In particular, we propose

  • a minimum reprojection loss, designed to robustly handle occlusions
  • a full-resolution multi-scale sampling method that reduces visual artifacts
  • an auto-masking loss to ignore training pixels that violate camera motion assumptions. (training a pose estimation network) 判断出pose再得到下一步的运动。

Takes a single color input and produces a depth map .

where

通过深度图、相对pose关系、内参K来复原出target的图像;然后minimization of a photometric reprojection error(pe) at training time,加上边缘光滑化。

In mixed training (MS), includes the temporally adjacent frames and the opposite stereo view.

  • Per-Pixel Minimum Reprojection Loss: reduces artifacts at image borders, improves the sharpness of occlusion boundaries, and leads to better accuracy

  • Auto-Masking Stationary Pixels: This has the effect of letting the network ignore objects which move at the same velocity as the camera, and even to ignore whole frames in monocular videos when the camera stops moving.

    where is the Iverson bracket.(如果方括号内的条件满足则为1,不满足则为0)

  • Multi-scale Estimation

  • Final Training Loss:

Evaluation

论文《Depth Map Prediction from a Single Image using a Multi-Scale Deep Network》(4.3中提到)

3D object detection with depth

只是个人总结,不确保准确性

在MonoGRNet部分的说明文字中简要说明了2D目标检测的局限性以及深度对于目标检测来说增加了许多几何感知信息,也由此展开了3D object detection的研究。3D检测同样可以分为一阶段和两阶段的方法,从简单角度来讲,只是boxes外包围框,变成了3D corners框,这也就是说从原来的四点变成了八点。有一种方法就是通过学习的深度来完成3D映射。对于人眼来说简单的3D关系,但是对于计算机视觉来说却是一个挑战,主要是因为遮挡、以及由于距离所带来的尺寸变化

在3D目标检测中,我们主要关注的是用什么方法来获取3D信息,而对于检测部分的研究则仍然采用2D已有的方法。看到一个博主整理的table,感觉自己还是很乱。

2D目标检测4个自由度degrees of freedom (DoF) ,center point 和尺寸;3D目标检测7个自由度,3D physical size , 3D center location and yaw。

2D-3D

3D到2D基本就是3D框的外围框作为2D框,如a图。

这样,就有等式:

因此3D-2D只需要选择4个点,就可以知道2D框了,也就是说4个等式,求3个未知量(?是指三个维度如何投影的吗),是一个over-determined problem。

而2D到3D是一个不可确定的问题,如下图。

可以看到2D是需要yaw(车子的角度)以及obervation angle两者所确定pose的。

method

use lidar

lidar:使用雷达的信息,lidar-based的结果明显高于image-based(尤其在深度信息上),but expensive,并且在不利的天气条件下,效果不好(是因为像雨、雾环境下,空气中有别的分子所干扰么?)

雷达的信息(不是很清楚这一部分的研究,但感觉也是一个不是100%确定的信息提取,应该是包括point cloud部分的研究)转换出来可以变为yaw(绕y轴的旋转角度),position,dimension,以及固定的投影矩阵project matrix信息的组合。yaw基本就可以确定车头的方向(也就是说我希望得到的立体框是可以包含左右前后的信息的);假设一个单位正方体目标在xy平面上关于z轴对称,通过旋转(yaw)、拉伸变化(dimension)、偏移(position)可以得到三维的corners。

雷达的方法基本可以概括为直接用雷达和生成伪雷达两种。

伪雷达方法主要有两个缺点,一个是它对于深度图的准确率有很强的依赖,如果深度图有所偏差,对于雷达信息的构建有很大的影响;另一个是雷达提取的是空间信息,对于语义信息方面有所丢失,因此对于同一形状的物体,雷达无法辨别,需要结合RGB图像。

伪雷达方法的优点也很明显,虽然直接用lidar的方法准确性更高,但是比较稀疏(而且贵);但是伪雷达是根据图像来的,相对比较密集,而且还含有RGB信息

最终是要在2D平面上完成3D检测(那对于无人驾驶来说,如果一直用lidar的话,以及可以重建出3D世界了,那摄像机有什么用处呢?因为lidar只是告诉你这个3D位置上有东西,对于语义信息是缺失的,同时当前雷达仍然会被环境有所干扰,而且相机图像对之后的追踪等下游问题仍然是有有用信息的),于是通过摄像机、投影平面、3D坐标系之间的关系,用投影矩阵完成3D->2D的一个变换,就可以得到2D画面中真实的3D标注了(如果单目深度估计做的好的话,它生成伪雷达的方法本质上还是image-base吧!感觉这个有些可行)

In LiDAR coordinate system (x: front, y: left, z: up, and (0, 0, 0) is the location of the LiDAR sensor). The elevation angle (仰角) to the LiDAR sensor as

首先深度图可以通过视差图得到,也可以直接通过网络预测得到;然后深度图转伪雷达时用公式:

代码大致如下:

1
2
3
4
5
6
7
8
rows, cols = depth_map.shape
c, r = np.meshgrid(np.arange(cols), np.arange(rows))
point_cloud = np.stack([c, r, depth_map])
point_cloud = point_cloud.reshape((3, -1))
x = ((point_cloud[:, 0]-cu)*point_cloud[:, 2])/fu
y = ((point_cloud[:, 1]-cv)*point_cloud[:, 2])/fv
point_cloud[:, 0] = x
point_cloud[:, 1] = y

方法分类:

  • 3D volume-based
  • projection-based:使用3D-2D投影之间的关系
    • Bird’s Eye View (BEV):在这个视野当中即使距离不同但是规模都是相同的,并且没有重叠。3D信息转换到从上往下看到的2D图像中:宽度和深度变成了空间维度高度通道信息记录。首先需要根据原图生成BEV,常用的是通过Inverse perspective mapping (IPM)方法(但是它假设所有像素都在地面上,并且相机online时时准确地知道外部(和内部)信息,外在的信息还需要标定)这……我可能需要了解一下,一下子没有变换的想法;另外用来生成BEV的方法有Orthographic Feature Transform (OFT)、BirdGAN(这个方法对于距离有所限制,只能在10-15m。
      • YOLO3D
      • HDNet
      • BirdNet
    • frontal view
      • frontal view FCN

RGB picture add depth

RGB-D:即RGB图片加上depth的信息

主要包括从图像明暗、不同视角、光度、纹理信息等获取场景深度形状的Shape from X方法,还有结合SFM(Structure from motion)和SLAM(Simultaneous Localization And Mapping)等方式预测相机位姿的算法。也可以利用双目进行深度估计,但是由于双目图像需要利用立体匹配进行像素点对应和视差计算,所以计算复杂度也较高,尤其是对于低纹理场景的匹配效果不好。而单目深度估计则相对成本更低,更容易普及。

常在loss上增加object shape, ground plane, and key points等部分的限制。一般来说Image-based方法效果很难去获取目标的尺寸和结构信息,主要是因为两个原因:

  1. 在某点周围的像素与这一点的深度并不一定是相同的,因此难以区分背景与目标。在Image-based的方法中在卷积核的某一感受野之内,不同物体实际上是physically incoherent, 但相邻的像素点之间并没有显式表示这种关系(Focal Loss的提出也是为了解决类似的前景、背景不平衡的问题)。
  2. 由于相机图片上遵循近大远小的规则,我们并不知道实际目标的大小;在front view后简单叠加depth map, 虽然能够提供重要的深度信息,但是仍然没有直观地展示真实三维空间的分布属性。除此之外,越远的物体在front view中是越小的,而检测小物体本身就是一个比较难的任务。因此,深度图中不同尺度的物体也增加了Image-based 3D Perception的难度。
  • 2D-3D geometry-based 这种方法就是在平面和立体两个维度上增加关联限制
    • MonoGRNet
    • MonoDIS
  • 3D shape-based:用3D框的shape或者keypoints完成绘制

    • MonoGRNet:首先找中心点(2d),然后通过深度变为3d中心点,最后找corners(中间有一些delta回归的部分)
    • Mono3D++
    • MonoDIS
    • Deep MANTA:最终获得,包括bbox(cls and regression), S(parts coordinates), V(parts visibility), T(Template similarity)。感觉就是先找目标,然后找它的方向,然后找是否可见什么的,最后就变成完整的3D框了
    • 3D RCNN:四个部分(amodal-bbox,center-proj,pose angles,shape parameters)这个感觉先确定框和中心点,然后看它的旋转角度,从而知道pose,回归它的shape,知道整体标注部分
    • RTM-3D:大概就是找目标的边界转折点,然后再变到立体框上,从复杂变减
  • depth map-based:增加depth map,也就是原本是3 channel的,现在变更到4 channels了,通过深度来复原。

    • Pseudo-lidar(CVPR 2019):提到了一个观点——卷积在深度图中用处不大,因为2D距离很近,在3D上可能相距甚远(这样子一看IDA的思想其实就避免了这个问题,一个物体的depth应该是相同的)其实感觉伪lidar就是先让模型学习一个中间产物,然后再由这个中间产物去学习一个更高层的信息。提出这个想法的契机,应该是因为lidar的信息捕捉更加准确或者说更多;并切lidar信息进行3D检测的方法应该是比较成熟了;而且lidar的价格贵以及缺少语义信息其实是可以从image上获取的;大部分的方法是通过2D image -> depth -> lidar -> 3D detection这样的一个过程;PL:是用来减小lidar与相机之间的gap而产生的一个新方向。

      pseudo-LiDAR first utilizes an image-based depth estimation model to obtain predicted depth of each image pixel . The resulting depth is then projected to a “pseudo-LiDAR” point in 3D by

      is the camera center and and are the horizontal and vertical focal lengths.

      First, a depth estimator is learned to estimate generic depths for all pixels in a stereo image; then a LiDAR-based detector is trained to predict object bounding boxes from depth estimates, generated by the frozen depth network.比较好奇的一点是单目深度估计可以做好这个吗?感觉可能结果不太好,一个是因为单目深度估计本来不确定性更大,双目会有几何关系的限制;二是钱贵的应该有他自己的道理

    • MLF(Multi-Level Fusion)(CVPR 2018)

    • MonoDepth(Unsupervised Monocular Depth Estimation with Left-Right)

  • proposal-based
    • Mono3D
    • MonoPSR
  • transform-based
  • stereo-based:使用双目,然后通过几何关系

可视化

将相机图像转换为BEV的方法通常称为逆变换角度映射(IPM),IPM假设世界是扁平的。

观察depth的变化情况,会绘制BEV图,但是一般不转变相机图像,只是绘制出俯视图。

1
2
3
4
5
6
7
def draw_overlook(fx, width, length):
img = np.zeros((width, length, 3), np.uint8)
img.fill(255)
cv2.line(img, (0, 1280-(int)(width/2/alpha)), (width/2, length), (0, 0, 0), 3)
cv2.line(img, (width, 1280-(int)(width/2/alpha)), (width/2, length), (0, 0, 0), 3)
cv2......

转载请注明出处,谢谢。

愿 我是你的小太阳

买糖果去喽