论文解读:SuperPoint: Self-Supervised Interest Point Detection and Description

Posted 万里鹏程转瞬至

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了论文解读:SuperPoint: Self-Supervised Interest Point Detection and Description相关的知识,希望对你有一定的参考价值。

发表时间: 2018年
项目地址:https://arxiv.org/abs/1712.07629
论文地址:https://github.com/magicleap/SuperPointPretrainedNetwork

本文提出了一种用于训练计算机视觉中大量多视点几何问题的兴趣点检测器和描述符的自监督框架。与patch-based的神经网络相比,我们的全卷积模型处理全尺寸的图像,并在一次前向传播中计算像素级的特征点和特征描述符。我们引入单应性自适应,这是一种多尺度、多单应性方法,用于提高兴趣点检测重复性并执行跨域自适应(例如,合成到真实)。我们的模型在使用同态的MS-COCO通用图像数据集自适应,能够重复检测更丰富的集合与初始预适应深度模型相比以及任何其他传统的角检测器。最终系统结果与LIFT、SIFT和ORB相比,产生了最先进的单应性估计。

1、 关键点

一种使用自我训练的自监督(self-training)解决方案,而不是使用人类监督来定义真实图像中的兴趣点。不需要人工标记数据,仅依赖于模型的迭代提升性能。

1.1 训练机制

通过对单个图像进行单适应性变化(透视变化),形成Image Pair,以孪生网络的机制进行训练

1.2 训练步骤

所有的训练都是使用PyTorch [19]完成的,默认参数为lr = 0.001和β =(0.9,0.999)的ADAM求解器(0.999)。我们还使用了标准的数据增强技术,如随机高斯噪声、运动模糊、亮度水平的变化,以提高网络对照明和视点变化的鲁棒性。

1、兴趣点预训练

先基于点和线等基本图形伪造了一个基本数据集:synthetic data,然后使用该数据集训练模型。synthetic data包含上百万的合成形状的数据集,形状都是由简单的几何图形(多边形、弧形、圆等图形)组成,在兴趣点(角点、定点)上是没有争议的(如)。该步骤训练出来的模型称之为MagicPoint,只能提取特征点,在该步骤进行了超过 200,000次的迭代训练(实时动态训练数据不重复)

synthetic data:

  • 基本形状:四边形、三角形、线和椭圆的合成数据渲染,由简化的二维几何组成
  • 歧义消除:使用Y连接、T连接、L连接、椭圆中心点、线条端点为特征点,以此消除歧义
  • 鲁棒性增强:对每个随机样本进行单适应变化,数据全为动态生成不重复

数据集示意如下:

2、兴趣点自监督

MagicPoint在shape上效果较好,但是域适应能力有限(在伪数据上训练,在真实数据上测试),与经典检测器相比,遗失了不少兴趣点。 为此,提出一种多尺度、多变换技术–Homographic Adaptation方法。将多尺度变化、仿射变化所生成的伪标签还原到原始图像中,用于生成最终的伪标签,用于提升检测器的特征点提取能力。该步骤训练出来的模型为SuperPoint,只能提取特征点,在COCO 2014数据集上训练,约80000多个图像(读取为灰度图),size为240x320,自监督时数据单适应变化参数 N h N_h Nh=100,该操作重复了两次。

方法的具体运算流程如下:

3、联合训练

在检测到鲁棒和可重复的兴趣点后,最常见的步骤是在每个点上附加一个固定的维度描述符向量,用于更高层次的语义任务,例如,图像匹配。最后将SuperPoint与一个描述符子网络结合起来(参见图2c)。由于超级点架构由一个深层的卷积层组成,它可以提取多尺度特征,因此可以直接将兴趣点网络与一个计算兴趣点描述符的附加子网络结合起来。该步骤训练出来的模型为SuperPoint,在提取提取特征点的基础上增加了特征描述符。

2、网络结构

SuperPoint在全尺寸的图像上运行,一次前向传播即可输出特征点和特征描述符(以往的方法需要两次,特征点与描述符要分开计算,缺乏共享),网络结构如下图所示,有一个共享的encoder部分。

2.1 实现代码

实现代码如下所示,是一个vgg风格的模型

class SuperPointNet(torch.nn.Module):
  """ Pytorch definition of SuperPoint Network. """
  def __init__(self):
    super(SuperPointNet, self).__init__()
    self.relu = torch.nn.ReLU(inplace=True)
    self.pool = torch.nn.MaxPool2d(kernel_size=2, stride=2)
    c1, c2, c3, c4, c5, d1 = 64, 64, 128, 128, 256, 256
    # Shared Encoder.
    self.conv1a = torch.nn.Conv2d(1, c1, kernel_size=3, stride=1, padding=1)
    self.conv1b = torch.nn.Conv2d(c1, c1, kernel_size=3, stride=1, padding=1)
    self.conv2a = torch.nn.Conv2d(c1, c2, kernel_size=3, stride=1, padding=1)
    self.conv2b = torch.nn.Conv2d(c2, c2, kernel_size=3, stride=1, padding=1)
    self.conv3a = torch.nn.Conv2d(c2, c3, kernel_size=3, stride=1, padding=1)
    self.conv3b = torch.nn.Conv2d(c3, c3, kernel_size=3, stride=1, padding=1)
    self.conv4a = torch.nn.Conv2d(c3, c4, kernel_size=3, stride=1, padding=1)
    self.conv4b = torch.nn.Conv2d(c4, c4, kernel_size=3, stride=1, padding=1)
    # Detector Head.
    self.convPa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)
    self.convPb = torch.nn.Conv2d(c5, 65, kernel_size=1, stride=1, padding=0)
    # Descriptor Head.
    self.convDa = torch.nn.Conv2d(c4, c5, kernel_size=3, stride=1, padding=1)
    self.convDb = torch.nn.Conv2d(c5, d1, kernel_size=1, stride=1, padding=0)

  def forward(self, x):
    """ Forward pass that jointly computes unprocessed point and descriptor
    tensors.
    Input
      x: Image pytorch tensor shaped N x 1 x H x W.
    Output
      semi: Output point pytorch tensor shaped N x 65 x H/8 x W/8.
      desc: Output descriptor pytorch tensor shaped N x 256 x H/8 x W/8.
    """
    # Shared Encoder.
    x = self.relu(self.conv1a(x))
    x = self.relu(self.conv1b(x))
    x = self.pool(x)
    x = self.relu(self.conv2a(x))
    x = self.relu(self.conv2b(x))
    x = self.pool(x)
    x = self.relu(self.conv3a(x))
    x = self.relu(self.conv3b(x))
    x = self.pool(x)
    x = self.relu(self.conv4a(x))
    x = self.relu(self.conv4b(x))
    # Detector Head.
    cPa = self.relu(self.convPa(x))
    semi = self.convPb(cPa)

    # Descriptor Head.
    cDa = self.relu(self.convDa(x))
    desc = self.convDb(cDa)
    dn = torch.norm(desc, p=2, dim=1) # Compute the norm.
    desc = desc.div(torch.unsqueeze(dn, 1)) # Divide by norm to normalize.

    output = 'semi': semi, 'desc': desc
    self.output = output
    return output

2.2 Shared Encoder

SuperPoint使用了一个VGG风格的[27]编码器来降低图像的size。该编码器由卷积层、池化空间降采样和非线性激活函数组成。编码器使用三个最大池层,其输出的特征图size在长宽上均为原图的1/8,及输出图的一个坐标点对应原图8x8的区域.此时,特征图的channel为256,相比于原图,具有更小的空间维度和更大的通道深度。

2.3 Interest Point Decoder

对于兴趣点检测,输出的每个像素对应于输入中该像素的“关键点”概率。head为两个conv的堆叠,实现代码为self.convPb(self.relu(self.convPa(x))),具体输出的特征维度为65维,即特征图上的一个像素点有65维,其中有64维对应着原图中一个不重叠的8x8区域,第65维作为垃圾桶(表示该区域没有特征点)。在后续步骤中,可以通过reshape操作,即可得到原图大小的特征点概率图,具体运算步骤如下:
H c = H / 8 R H c × W c × 64 ⇒ R H × W H_c=H/8 \\\\ \\mathbbR^H_c \\times W_c \\times 64 \\Rightarrow \\mathbbR^H \\times W Hc=H/8RHc×Wc×64RH×W

2.4 Descriptor Decoder

特征描述符的提取与特征点提取类似,也为两个conv的堆叠,head实现代码为desc = self.convDb( self.relu(self.convDa(x)))。该head输出的特征图最终为N x 256 x H/8 x W/8,可以理解为原图8x8的区域像素都共用一个特征描述符。在输出时,对desc进行了L2归一化(整体值除以某个维度的2范数)。计算过程中shape变化如下:

import torch
desc=torch.rand((8,256,40,40)) # 模拟Descriptor Head的输出
dn = torch.norm(desc, p=2, dim=1) #torch.Size([8, 40, 40])   计算desc在第一个维度的二范数
dn = torch.unsqueeze(dn, 1)  #torch.Size([8, 1, 40, 40])
desc = desc.div(dn)  #torch.Size([8, 256, 40, 40])

3、loss设计

最终的损失是两个中间损失的和:一个用于兴趣点检测器Lp,另一个用于描述符Ld。使用成对的合成扭曲图<具有两个伪标签(兴趣点位置)和来自图像随机扭曲中的单应性矩阵H>。这允许我们同时优化两个损失,给出一对图像,。我们使用λ= 0.0001来平衡最终损失为:
L ( X , X ′ , D , D ′ ; Y , Y ′ , S ) = L p ( X , Y ) + L p ( X ′ , Y ′ ) + λ L d ( D , D ′ , S ) \\beginarrayl \\mathcalL\\left(\\mathcalX, \\mathcalX^\\prime, \\mathcalD, \\mathcalD^\\prime ; Y, Y^\\prime, S\\right)= \\\\ \\quad \\mathcalL_p(\\mathcalX, Y)+\\mathcalL_p\\left(\\mathcalX^\\prime, Y^\\prime\\right)+\\lambda \\mathcalL_d\\left(\\mathcalD, \\mathcalD^\\prime, S\\right) \\endarray L(X,X,D,D;Y,Y,S)=Lp(X,Y)+Lp(X,Y)+λLd(D,D,S)
最终的输入有X、D、Y、S、X’、D’、Y’(两组XDY和单适应矩阵H),其中X为输入特征点,Y为特征点标签,D为输出的特征描述符,S为特征点的对应性(在特征描述符loss中会描述)。
兴趣点检测器loss Lp的计算公式如下,其代码实质就是在输出chanel上做了一次softmax激活。
L p ( X , Y ) = 1 H c W c ∑ h = 1 w = 1 H c , W c l p ( x h w ; y h w ) w h e r e l p ( x h w ; y ) = − log ⁡ ( exp ⁡ ( x h w y ) ∑ k = 1 65 exp ⁡ ( x h w k ) ) . \\mathcalL_p(\\mathcalX, Y)=\\frac1H_c W_c \\sum_\\substackh=1 \\\\ w=1^H_c, W_c l_p\\left(\\mathbfx_h w ; y_h w\\right) \\\\ where \\\\ l_p\\left(\\mathbfx_h w ; y\\right)=-\\log \\left(\\frac\\exp \\left(\\mathbfx_h w y\\right)\\sum_k=1^65 \\exp \\left(\\mathbfx_h w k\\right)\\right) . Lp(X,Y)=HcWc1h=1w=1Hc,Wclp(xhw;yhw)wherelp(xhw;y)=log(k=165exp(xhwk)exp(xhwy)).
特征描述符loss Ld的计算公式如下,可以看到是一个加权的hinge loss(因为正样本对数量与负样本对的数量是不一样的,故对正样本用 λ d \\lambda_d λ视觉SLAM总结——SuperPoint / SuperGlue

基于SuperPoint与SuperGlue实现图像配准

图像匹配天花板:SuperPoint+SuperGlue复现

BERT - 论文解读

LipNet 论文解读

AlexNet论文解读与代码实现