跳转到内容
ForeverYoung
返回

图像局部区域(Patch)的描述符学习策略

一些计算机视觉任务会依赖图像的几何关系,比如相机定位,三维重建等等。一种方法用于求解图像的几何关系是通过图像局部特征的相关匹配来求解。图像的局部特征则是从图像局部区域中抽取的特征,包括边缘、角点、线、曲线和特别属性的区域等。一般来说,图像的局部特征包含两个部分,局部特征点的位置,局部特征的描述符。在一方面上,描述符可分为基于人工的特征符和基于学习的特征符。

文本将关注于在特征点已知的情况下,如何通过学习去生成描述符去描述局部区域(Patch)的。相较于网络架构,本文主要关注于不同论文的数据处理和学习策略。

数据和任务

基本Patch的描述符(local descriptor)即给定Patch生成local descriptor。对于神经网络来说,即意味着网络的输入是图像局部区域(Patch),网络的输出是该Patch的特征。

其中图像局部区域(Patch)是基于图像的特征点所提取的的。如下图所示,下面五个图像来自于同一场景,图上的圆圈的中心即是特征点,而圆圈所选择的区域即是Patch。

{%asset_img images_hard.jpg HPatch原始和特征点 %}

需要注意的是,由于五个图像来自于同一场景,图像中的特征点也一一匹配。如下图所示,下图的每一列都是从匹配的特征点所提取的局部区域,而每一行则代表了从每幅图像所提取的全部Patch。

{%asset_img patches_hard.jpg HPatch提取的局部区域 %}

理想的描述符(local descriptor)是使得匹配的特征点的描述符一致或距离相近,不匹配的特征点的描述符距离较远。对应于上图,即每一列样本之间的描述符应距离相近,而每一行的样本之间的描述符应距离较远。

在数学上,假设有一特征点,其描述符标为,称之参考样本或锚点(anchor)。与之匹配的特征点可被称为正样本(positive sample),其描述符标为。与之匹配的特征点可被称为负样本(negative sample),其描述符标为。理想情况下,参考样本与正样本之间的距离应远小于参考样本与负样本之间的距离,即

训练流程

对于基本Patch的描述符(local descriptor),一个常见的训练流程如下图所示。

{%asset_img general_pipeline.jpg Pipeline %}

数据采样

具体而言,随机选取对匹配的patches组成最终的training batch,所以最终的training batch为。其中 表示对应的一对匹配的patch对。

描述子和距离矩阵

通过神经网络,可以生成样本所对应的描述子。针对于同一batch中的参考样本和正样本俩俩组合计算距离,可得到上图右侧所示的距离矩阵。当组合的参考样本和正样本匹配时,则该距离为正样本距离,不匹配时,则该距离为负样本样本距离。

对于描述子和距离矩阵,可采用不同的损失函数,进而学习网络。

学习策略

L2Net | L2-Net: Deep Learning of Discriminative Patch Descriptor in Euclidean Space

L2Net是相对早期的Patch-Based基于深度学习的描述符。其损失函数基本已不再使用,但其模型直至现在仍被广泛使用。

L2Net损失函数主要分为三个部分:

L2Net模型如下图所示。L2Net采用单路全卷积框架,图中3x3 Conv代表Conv+BN+Relu,8×8 Conv代表Conv+BN,在第三层和第五层的卷积层步长为2用于下采样。LRN(Local Response Normalization layer)用于归一化输出,等价于L2Norm。需要注意的是,由于区域图像变化较大,为了消除光照等其他因素的影响,一般也会在模型初始加InstanceNorm。

flowchart LR
    Input[Patch] --> B(3x3 Conv 32):::Conv
    B --> C(3x3 Conv 32):::Conv
    C --> D(3x3 Conv 64/2):::ConvDown
    D --> E(3x3 Conv 64):::Conv
    E --> F(3x3 Conv 128/2):::ConvDown
    F --> G(3x3 Conv 128):::Conv
    G --> H(8x8 Conv 128):::ConvBN
    H --> J(LRN):::LRN
    J --> Descriptor(Descriptor)

    classDef Conv fill:#cfc;
    classDef ConvDown fill:#cff;
    classDef ConvBN fill:#ffc;
    classDef LRN fill:#fcc;

该网络简单直接,特征提取速度在ms级,在低端gpu上基本在1ms左右。

HardNet | Working hard to know your neighbor’s margins: Local descriptor learning loss

HardNet的训练流程如下图所示。

{%asset_img hardnet_pipeline.jpg HardNet_Pipeline %}

简单而讲,HardNet采用了度量学习中的triplet loss去最大化training batch中正负样本之间的距离。

具体而言,当距离矩阵已知时,对于匹配的Patch对,可以找到:

这样对于每个匹配的 patch pair 在都可以生成一个四元组 。而在四元组的距离包括:

由于最后的目标是最大化training batch中正负样本之间的距离。所以将选取最难的样本进行训练,对应于anchor-negative 距离中,即意味着距离最小的样本。因此,最终的损失函数为:

损失函数对应的代码为

def triplet_loss(x, label, margin=0.1):
    # x is D x N
    dim = x.size(0) # D
    nq = torch.sum(label.data==-1).item() # number of tuples
    S = x.size(1) // nq # number of images per tuple including query: 1+1+n

    xa = x[:, label.data==-1].permute(1,0).repeat(1,S-2).view((S-2)*nq,dim).permute(1,0)
    xp = x[:, label.data==1].permute(1,0).repeat(1,S-2).view((S-2)*nq,dim).permute(1,0)
    xn = x[:, label.data==0]

    dist_pos = torch.sum(torch.pow(xa - xp, 2), dim=0)
    dist_neg = torch.sum(torch.pow(xa - xn, 2), dim=0)

    return torch.sum(torch.clamp(dist_pos - dist_neg + margin, min=0)) / nq

SOSNet | SOSNet: Second Order Similarity Regularization for Local Descriptor Learning

HardNet的loss是由距离度量直接组成的,其可以被认为是基于一阶相似距离的loss。SOSNet在此之上又增加了基于二阶相似距离的loss。

所谓二阶距离,这里指在四元组 中两个 anchor-negative 距离的距离, 即 。通过最小化二阶距离,可使得local descriptor更加聚积,如下图所示。

{%asset_img second_order_dist.jpg Second_Order_Distance %}

{%asset_img sosnet.jpg SOSNet_output %}

二阶距离损失函数对应的代码为

def sos_loss(x, label):
    # x is D x N
    dim = x.size(0) # D
    nq = torch.sum(label.data==-1).item() # number of tuples
    S = x.size(1) // nq # number of images per tuple including query: 1+1+n

    xa = x[:, label.data==-1].permute(1,0).repeat(1,S-2).view((S-2)*nq,dim).permute(1,0) # D * (B * num_neg)
    xp = x[:, label.data==1].permute(1,0).repeat(1,S-2).view((S-2)*nq,dim).permute(1,0)
    xn = x[:, label.data==0]

    dist_an = torch.sum(torch.pow(xa - xn, 2), dim=0)
    dist_pn = torch.sum(torch.pow(xp - xn, 2), dim=0)

    return torch.sum(torch.pow(dist_an - dist_pn, 2)) ** 0.5 / nq

Log Polar Transformation | Beyond Cartesian Representations for Local Descriptors

另一篇对局部特征很有帮助的工作是Log Polar Transformation。其核心是通过在图像预处理中引入Log Polar Transformation,将图像从欧式坐标系转换为对数极坐标系表示。由于不同的旋转和尺度变化在对数极坐标系下对图像不会有太大影响,因此Log Polar Transformation会使得模型对旋转和尺度变化的鲁棒性较好。

{%asset_img log_polar_transformation.jpg Rotation and Scaling transformations to a Euclidean image can be read as horizontal and vertical shift respectively, after a log-polar transformation. The log-polar transformation translates rotation and scale in Euclidean images into vertical and horizontal translations (respectively) in the log-polar model. %}

如上图所示,在欧式图像上的旋转和尺度变化对应到对数极坐标系下是竖直和水平方向上的平移变化。而卷积操作的滑动窗口计算方式使得其对平移变化非常鲁棒。需要注意的是,由于一般Patch-based卷积神经网络都非常浅,导致其无法很好的消除由旋转的尺度变化所导致的偏差。甚至为了消除这种偏差,在工程上常常使用重力的方向来旋转输入CNN的Patch。因此,Log Polar Transformation通过增强模型对旋转和尺度变化的鲁棒性,进而提升了模型的鲁棒性和准确度。

更多的旋转和尺度变化在对欧式坐标系和对数极坐标系的表现如下图所示。

{%asset_img log_poloar_samples.jpg Example of rotation and scale transformations for a single MNIST image. %}

Log Polar Transformation在图像预处理时可以实现为

def to_log_polar(img, dsize, max_radius):
    assert isinstance(img, Image.Image)

    # dsize = img.size
    center = [s // 2 for s in img.size]
    flags = cv2.WARP_POLAR_LOG
    out = cv2.warpPolar(
        np.asarray(img), dsize=dsize, center=center, maxRadius=max_radius, flags=flags
    )
    return Image.fromarray(out)


def from_log_polar(polar_img, dsize, max_radius):
    assert isinstance(polar_img, Image.Image)

    # dsize = polar_img.size
    center = [s // 2 for s in polar_img.size]
    flags = cv2.WARP_POLAR_LOG | cv2.WARP_INVERSE_MAP
    out = cv2.warpPolar(
        np.asarray(polar_img),
        dsize=dsize,
        center=center,
        maxRadius=max_radius,
        flags=flags,
    )
    return Image.fromarray(out)

需要注意的是由于pytorch GridSample 的计算方式不同,用opencv实现的log polar transform会与论文原始PTN网络输出不一致,对于原始PTN网络实现可以参考论文代码

参考


分享这篇文章:

上一篇
深度学习模型大小与模型推理速度的探讨
下一篇
pybind: 为cpp/cuda代码提供python接口