分类 深度学习 下的文章

  • cat操作经常用于将特征联合多个卷积特征提取框架提取的特征融合或者是将输出层的信息进行融合;而add层更像是信息之间的叠加
  • add是在一个特征上增加其语义信息,对最终的图像的分类是有益;cat导致的结果改进可能是由于cat操作通过组合高级和低级特征来增加特征空间,因此后续的卷积运算能够学习依赖于高级和低级特征的新功能。

代码

def getPoolingKernel(kernel_size=25):
    half_size = float(kernel_size) / 2.0
    xc2 = []
    for i in range(kernel_size):
        xc2.append(half_size - abs(float(i) + 0.5 - half_size))
    xc2 = np.array(xc2)
    kernel = np.outer(xc2.T, xc2)
    kernel = kernel / (half_size ** 2)
    return kernel

def get_bin_weight_kernel_size_and_stride(patch_size, num_spatial_bins):
    ks = 2 * int(patch_size / (num_spatial_bins + 1));
    stride = patch_size // num_spatial_bins
    pad = ks // 4
    return ks, stride, pad

class SIFTNet(nn.Module):
    def CircularGaussKernel(self, kernlen=21, circ=True, sigma_type='hesamp'):
        halfSize = float(kernlen) / 2.
        r2 = float(halfSize ** 2)
        if sigma_type == 'hesamp':
            sigma_mul_2 = 0.9 * r2
        elif sigma_type == 'vlfeat':
            sigma_mul_2 = kernlen ** 2
        else:
            raise ValueError('Unknown sigma_type', sigma_type, 'try hesamp or vlfeat')
        disq = 0
        kernel = np.zeros((kernlen, kernlen))
        for y in range(kernlen):
            for x in range(kernlen):
                disq = (y - halfSize + 0.5) ** 2 + (x - halfSize + 0.5) ** 2
                kernel[y, x] = math.exp(-disq / sigma_mul_2)
                if circ and (disq >= r2):
                    kernel[y, x] = 0.
        return kernel

    def __repr__(self):
        return self.__class__.__name__ + '(' + 'num_ang_bins=' + str(self.num_ang_bins) + \
               ', ' + 'num_spatial_bins=' + str(self.num_spatial_bins) + \
               ', ' + 'patch_size=' + str(self.patch_size) + \
               ', ' + 'rootsift=' + str(self.rootsift) + \
               ', ' + 'sigma_type=' + str(self.sigma_type) + \
               ', ' + 'mask_type=' + str(self.mask_type) + \
               ', ' + 'clipval=' + str(self.clipval) + ')'

    def __init__(self,
                 patch_size=65,
                 num_ang_bins=8,
                 num_spatial_bins=4,
                 clipval=0.2,
                 rootsift=False,
                 mask_type='CircularGauss',
                 sigma_type='hesamp'):
        super(SIFTNet, self).__init__()
        self.eps = 1e-10
        self.num_ang_bins = num_ang_bins
        self.num_spatial_bins = num_spatial_bins
        self.clipval = clipval
        self.rootsift = rootsift
        self.mask_type = mask_type
        self.patch_size = patch_size
        self.sigma_type = sigma_type

        if self.mask_type == 'CircularGauss':
            self.gk = torch.from_numpy(
                self.CircularGaussKernel(kernlen=patch_size, circ=True, sigma_type=sigma_type).astype(np.float32))
        elif self.mask_type == 'Gauss':
            self.gk = torch.from_numpy(
                self.CircularGaussKernel(kernlen=patch_size, circ=False, sigma_type=sigma_type).astype(np.float32))
        elif self.mask_type == 'Uniform':
            self.gk = torch.ones(patch_size, patch_size).float() / float(patch_size * patch_size)
        else:
            raise ValueError(self.mask_type, 'is unknown mask type')

        self.bin_weight_kernel_size, self.bin_weight_stride, self.pad = get_bin_weight_kernel_size_and_stride(
            patch_size, num_spatial_bins)
        self.gx = nn.Conv2d(1, 1, kernel_size=(1, 3), bias=False)
        self.gx.weight.data = torch.tensor(np.array([[[[-1, 0, 1]]]], dtype=np.float32))

        self.gy = nn.Conv2d(1, 1, kernel_size=(3, 1), bias=False)
        self.gy.weight.data = torch.from_numpy(np.array([[[[-1], [0], [1]]]], dtype=np.float32))
        nw = getPoolingKernel(kernel_size=self.bin_weight_kernel_size)

        self.pk = nn.Conv2d(1, 1, kernel_size=(nw.shape[0], nw.shape[1]),
                            stride=(self.bin_weight_stride, self.bin_weight_stride),
                            padding=(self.pad, self.pad),
                            bias=False)
        new_weights = np.array(nw.reshape((1, 1, nw.shape[0], nw.shape[1])))
        self.pk.weight.data = torch.from_numpy(new_weights.astype(np.float32))
        return

    def forward(self, x):
        gx = self.gx(F.pad(x, (1, 1, 0, 0), 'replicate'))
        gy = self.gy(F.pad(x, (0, 0, 1, 1), 'replicate'))
        mag = torch.sqrt(gx * gx + gy * gy + self.eps)
        ori = torch.atan2(gy, gx + self.eps)
        mag = mag * self.gk.expand_as(mag).to(mag.device)
        o_big = (ori + 2.0 * math.pi) / (2.0 * math.pi) * float(self.num_ang_bins)
        bo0_big_ = torch.floor(o_big)
        wo1_big_ = o_big - bo0_big_
        bo0_big = bo0_big_ % self.num_ang_bins
        bo1_big = (bo0_big + 1) % self.num_ang_bins
        wo0_big = (1.0 - wo1_big_) * mag
        wo1_big = wo1_big_ * mag
        ang_bins = []
        for i in range(0, self.num_ang_bins):
            out = self.pk((bo0_big == i).float() * wo0_big + (bo1_big == i).float() * wo1_big)
            ang_bins.append(out)
        ang_bins = torch.cat(ang_bins, 1)
        ang_bins = ang_bins.view(ang_bins.size(0), -1)
        ang_bins = F.normalize(ang_bins, p=2)
        ang_bins = torch.clamp(ang_bins, 0., float(self.clipval))
        ang_bins = F.normalize(ang_bins, p=2)
        if self.rootsift:
            ang_bins = torch.sqrt(F.normalize(ang_bins, p=1) + 1e-10)
        return ang_bins

class SIFTNetFeature2D:
    def __init__(self):
        self.model = SIFTNet(patch_size=32, mask_type='Gauss', sigma_type='hesamp')
        device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
        torch.set_grad_enabled(False)
        if torch.cuda.is_available():
            self.model.cuda()
        self.model.eval()

    def patch2des_once(self, patches):
        if len(patches.shape) == 4:
            new_patches = []
            for i in range(len(patches)):
                new_patches.append(cv2.cvtColor(patches[i] * 255, cv2.COLOR_BGR2GRAY) / 255.0)
            new_patches = torch.from_numpy(numpy.array(new_patches).astype(np.float32)).to(device)
            data_a = new_patches.permute(0, 2, 1).unsqueeze(dim=1)
        else:
            data_a = torch.from_numpy(numpy.array(patches).astype(np.float32)).to(device).unsqueeze(dim=1)
        with torch.no_grad():
            out_a = self.model(data_a)
        return out_a

    def compute(self, patches):
        batch_size = 128
        dim = 128
        descriptors_for_net = np.zeros((len(patches), dim), dtype=np.float32)
        for i in range(0, len(patches), batch_size):
            data_a = patches[i: i + batch_size, :, :].astype(np.float32)
            out_a = self.patch2des_once(data_a)
            descriptors_for_net[i: i + batch_size] = out_a.cpu().detach().numpy().reshape(-1, dim)
        return descriptors_for_net / np.max(np.abs(descriptors_for_net))

用法

sift_net = SIFTNetFeature2D()
sift_net.compute(patches)

效果
比OpenCV的经典SIFT效果略差。

其他
当然也可以直接使用kornia写好的,文档网址:
https://kornia.readthedocs.io/en/latest/_modules/kornia/feature/siftdesc.html#SIFTDescriptor


本文只提供网络图,具体原理内容请移步对应论文

单图像超分辨率的多尺度特征融合残差网络

论文:
https://www.sciencedirect.com/science/article/pii/S0925231219315140
代码:https://github.com/QinJinghui/MSFFRN

  • 多尺度特征融合残差网络(MSFFRN)的网络架构
  • 简单的扩展规则可生成具有 P 交织路径的多尺度特征融合残差架构
  • 具有4个交织路径特征融合的多尺度特征融合残余块(MSFFRB)结构。使用此块作为模型中的默认块。
  • 最终效果:

基于自适应空间并行卷积和快速多尺度融合的小目标检测方法

https://www.researchgate.net/publication/357885975_Small_Object_Detection_Method_Based_on_Adaptive_Spatial_Parallel_Convolution_and_Fast_Multi-Scale_Fusion

  • SODNet的结构。SODNet 由四个组件组成。(1)ASPConv模块,从输入图像中提取出丰富的物体空间信息。(2)所提出的骨干模块,由子骨干和FPN两部分组成。子主干进一步提取 ASPConv模块的输出特征,通过 FPN 生成多尺度特征图。(3)FMF模块,快速融合多尺度特征图中的语义信息和空间信息,生成有利于小目标检测的高分辨率特征图。(4)Predictor,对融合的多尺度特征图进行分类和定位。
  • ASPConv的结构。首先在分裂融合子模块中自适应学习小目标的多尺度局部上下文信息,然后融合形成详细的空间信息。
  • 骨干模块的结构。
  • FMF的结构。低分辨率特征映射P2和P3将丰富的语义信息映射到高分辨率空间,并与特征图C1融合,生成高分辨率的地物图P1具有丰富的语义和空间信息。P2和P3由FPN生成,C1为APSConv模块的输出特征图。
  • 测试数据集的样本测试结果,TinyPerson (a-c)、清华-腾讯100K (d-f)、UAVDT (g-i)、MS COCO (j-1)。

目前仅供内部阅读,非常抱歉~


[hide]
知道大家最关心什么,所以,相关的Github链接:

基线与评估框架

基线:

评估:

流程:

指标:
1、0.95召回率时假阳性率(通常用于UBC PhotoTour)

  观察到这个度量与描述符的实际性能几乎没有关联,因此它的可用性仅限于模型训练期间的收敛性检查

2、平均精度(MAP)

  其中sij是查询i中第j大的分配置信度得分,V表示样本Sij和样本sij-1的召回率的差,P表示精度,考虑样本si, 1,…, Sij。APi的值是在查询Q中取的平均值,其中每个查询包含Ni个样本。这个度量在HPatches中有三个任务:

  • 验证:描述符用于为一对补丁分配一个分数,以指示它们是否对应。
  • 检索:有一个查询补丁,描述符用于从一组给定的补丁中检索相应的补丁,该补丁包含几个正数和大量的负数。
  • 匹配:与图像匹配管道最相关的任务是给定一个查询patch和一组包含一个正数的patch,找到对应的patch。

相关数据集

  • HPatches:此dataset是本地特性描述符基准测试的一部分。它包含116个场景,每个场景6张图像,根据主要的妨害因素,这些图像被分割为光照(57个场景)和视点子集。在光照分割中,场景是由静态网络摄像机在一天中的不同时间捕获的,视点分割包含显著的摄像机姿态变化。图像通过单应性与参考图像相关联。采用DoG、Hessian-Hessian和Harris-Laplace检测器进行关键点提取。参考图像中最小的patch大小为16x16像素,而提取的patch大小为65x65。补丁根据数量被进一步划分为难度级别(简单,困难和非常困难)
  • PhotoSynth(PS):数据集包含了来自30个场景的光照和视角变化的补丁。使用COLMAP SfM软件进行三维重建,并验证补丁的对应关系。每个patch的缩放和旋转都是标准化的。尺寸最小为20x20像素,最大为128x128像素。裁剪后的补丁大小调整为48x48px。从每个场景中提取大约40万个patch。经过训练的描述符在视点分割的HPatches基准测试中达到了最先进的性能。然而,它牺牲了对光度变化的鲁棒性。虽然场景包含光度变化,训练的描述符只有在HPatches的视点分割上才能获得更好的结果。
  • UBC Phototour(也叫Brown):由3个场景组成:Liberty、Notredame和Yosemite。利用Bundler structure-from-motion库对原始图像构造点云三维重建和密集深度图,获得相应的patch集。利用高斯差和哈里斯特征点检测器对每个场景进行重构。Liberty序列经常被用作一个单独的数据集来训练。它也被用于测试,但随着学习描述符的兴起,通常使用的FPR95指标已经低于1%,使这个基准饱和。
  • IMW Phototourism 2020:这个数据集是最近为即将到来的CVPR图像匹配研讨会创建的,是广泛的评估软件的一部分。目前它包含13个训练场景,3个验证场景和10个测试场景。使用COLMAP SfM软件进行三维重建,并验证补丁的对应关系。每个文件夹的3D重建场景包含2D到3D点通信。它不提供尺度和旋转的信息,提取的小块不易归一化。然而,我们观察到,就训练过的描述符的性能而言,这并不是一个大问题。由于这些图像是由Phototourism数据获得的,它涵盖了一些天气和照明的变化。提取的小块的数量与所有图像中关键点的总数成正比,并且在整个场景中变化很大。如Brandenburg Gate有超过200万个补丁,而Trevi Fountain有超过1500万个补丁
  • AMOS Patches:可用于提高局部描述符对光度变化的鲁棒性的训练数据很少。我们建议通过引入AMOS Patches数据集来填补这一空白。它是由AMOS全球静态网络摄像机集合创建的,涵盖了各种照明和季节相关的采集条件。AMOS是一个从户外网络摄像机中收集的持续增长的公开可用数据集,目前包含超过10亿(或20 TB)的图像。它被组织成单独的相机目录,这些目录根据收购的年份和月份被分成文件夹。图像的大小不同,它们的质量和每个相机目录中的图像数量也不同。典型的AMOS相机是静态的,大约有300x 300像素大小。许多相机可以在所有季节和全天存储图像。

相关模型

  • L2NET:介绍了相对简单的卷积网络架构,输出长度为128的向量。
  • HardNet:网络采用L2Net架构并引入了Hard采样的三态损耗函数。
  • GeoDESC:使用来自多视图重建的几何约束。
  • SOSNet:引入了二阶相似性正则化,该正则规则化被添加到loss函数。

(总结:以上其实都是L2Net结构,只是变了loss函数)

  • VGG:HardNet是一种VGG风格的网络,它由空间大小逐渐减小的卷积层块组成,并且通道数量不断增加。
  • HardNet变体:与原始版本相比:

    • HardNet7x2每层通道数增加了两倍。
    • HardNet8在第块中增加了一个卷积层。
    • HardNet9在第和第块中增加了两个层。
    • HardNet8x2每层的通道数是HardNet8的两倍。
    • 注意:HardNet架构的输入大小为32x32像素。由于观察到从HardNet8进一步扩大模型体系结构并不能带来更好的性能,因此调整HardNet8架构,使其预期输入更大的尺寸。首先,在倒数第二层将stride改为2,然后输入大小为64x64像素。如果我们还将最后一个卷积层的内核大小设置为6x6像素,则输入大小为48 x48像素。请注意两个相反的趋势:在HPatches和AMOS Patches上,更大的输入大小导致更好的性能,但是这样的模型在IMW PT基准上给出了较差的结果
  • ResNet:另一种流行的体系结构类型是ResNet。它由称为ResBlock的卷积层块组成。每个块的输出与它的输入相加,因此网络学习的是增量变化,而不是直接转换。然而,在我们的所有实验中,它都不是更好的,而且它对GPU内存的要求更高,所以我们不建议使用这种架构类型来学习一个局部特征描述符。

HardNet变体-结构:

HardNet变体-评估:

Resnet-结构:

Resnet-评估:

HardNet结构:

  HardNet描述符在各种基准测试中的性能很好。HardNet描述符是一个实值卷积网络,输出向量长度为128,输入patch大小为32x32p像素。该体系结构采用VGG风格,由卷积层组成,每个层后面依次是批处理归一化ReLU激活。使用p=0.3Dropout。输入到网络的是均值方差归一化loss函数:

  M是margin,如果后一段距离与前一段距离之差至少为M,则损失为零。

各个实验比较

不同图像缩放程度下

不同模态数据集


  G为灰度,D为(估计单)深度。

不同数据集大小


  ei为补丁集i在其包含的所有补丁上的这些差异的平均值。

较小数据集下的HardNet:

多数据集训练


  结合这些数据集可以带来一些改进。然而,有几件事你必须要小心。首先,在某些情况下,我们经常会经历相反的行为,例如:在Colosseum训练的HardNet8获得70.75 mAA(100),而添加Liberty则获得70.21 mAA(109)。从某种意义上说,这种负面影响可以通过描述符的压缩来恢复,因为我们发现,如果模型在更多的数据集上训练,降维效果会更好。

不同输入大小

不同的输出大小


  输出向量变长,带来内存占用率高、最近邻搜索速度慢等缺点。如果我们将HardNet8的输出大小减少到128以匹配SIFT的向量长度,我们将得到较差的结果:在IMW PT基准上68.75 mAA(109)和69.43 mAA(109)。另一种减少输出大小的方法是使用降维技术。在此,我们利用主成分分析(PCA)来压缩特征嵌入。降维是有益的。我们可以看到,最好的组合是HardNet8,输出大小为512,然后压缩为128。在这个实验中,我们在UBC Phototour的Liberty数据集上进行训练,我们在训练数据的模型输出上拟合PCA。

最后的池化影响


替换最后一个卷积层为:

  • 增加接收域:不优于基线。
  • local pool:性能比基线更糟糕。
  • Non-learned Pooling:MaxPool实现更糟糕的结果,AvGPool无法学习。

不同的margin

不同的batch size

不同的epoch数

不同模型的评估

结论

1、更多数据集的训练可能会使描述符质量恶化。
2、resnet类架构和模型不如那些vgg类模型。类似的观察也发生在切换到局部池或增加接受域。
3、更大的训练批量是有益的。
4、使用PCA压缩网络输出可以在IMW PT基准测试中获得更好的结果。
5、在减少了一个数量级的数据集上进行训练,可以获得类似的性能。
6、通过切换到RGB或用估计的单深度映射连接补丁来丰富数据模式,会导致有限或没有任何改进。
7、在手动选择摄像机进行训练时,小而多样化的子集比具有相似视图或不精确对齐的图像的大子集更好。
8、在训练过程中对一批patch进行采样时,使用较少的源摄像机是有益的。

参考文献

1、Improving the HardNet Descriptor

[/hide]


CNN上加Attention可以加在这几方面:
a. 在卷积操作前做attention,比如Attention-Based BCNN-1,这个任务是文本蕴含任务需要处理两段文本,同时对两段输入的序列向量进行attention,计算出特征向量,再拼接到原始向量中,作为卷积层的输入。
b. 在卷积操作后做attention,比如Attention-Based BCNN-2,对两段文本的卷积层的输出做attention,作为pooling层的输入。
c. 在pooling层做attention,代替max pooling。比如Attention pooling,首先我们用LSTM学到一个比较好的句向量,作为query,然后用CNN先学习到一个特征矩阵作为key,再用query对key产生权重,进行attention,得到最后的句向量。