前面讲的很详细,这里可以清楚的看到,整个网络的流程
superglue代码中作者只是提供了已经训练好的权重文件和网络流程但是对于,训练的细节,代码也没有给出,后续我想自己设计一下loss试一下能不能自己训练,我看到今年的匹配比赛都是自己在superglue上面改的东西
SuperGlue feature matching middle-end
Given two sets of keypoints and locations, we determine the
correspondences by:
1. Keypoint Encoding (normalization + visual feature and location fusion)
2. Graph Neural Network with multiple self and cross-attention layers
3. Final projection layer
4. Optimal Transport Layer (a differentiable Hungarian matching algorithm)
5. Thresholding matrix based on mutual exclusivity and a match_threshold
The correspondence ids use -1 to indicate non-matching points. Paul-Edouard Sarlin, Daniel DeTone, Tomasz Malisiewicz, and Andrew
Rabinovich. SuperGlue: Learning Feature Matching with Graph Neural
Networks. In CVPR, 2020. https://arxiv.org/abs/1911.11763
"""Run SuperGlue on a pair of keypoints and descriptors""" desc0, desc1 = data['descriptors0'], data['descriptors1'] kpts0, kpts1 = data['keypoints0'], data['keypoints1']
这个输入是SuperPoint的输出作为这个网络的输入,分别是描述子和关键帧的位置信息,这里稍微看一下就好了。
前面有一部对于描述子做正则化这狐狸就不多赘述了,主要作用就是讲位置信息和描述子信息的权重进肯能的一样。
最重要的是MLP模块其实就是融合模块,这里相当于将位置信息和描述子信息一起的融合编码,中间经过了卷积运算。
这个应该是主要的点,但是我认为可以改动的地方不多我就没有仔细看,
我想要做研究的主要就是这个位置,我找了好多资料来看,这里的知识还是比较深奥的。按照作者的逻辑,我大概理解到作者在上面的矩阵中生成了一个二维的成本矩阵,这个和最优传输理论还是有一点区别的。这里我调试的一个scores是【1*239*244】维的二维矩阵
scores = torch.einsum('bdn,bdm->bnm', mdesc0, mdesc1) scores = scores / self.config['descriptor_dim']\*\*.5
在进入了最优传输模块之前,作者相当于通过上面的运算,直接就得到了一个耦合矩阵,这个和最优传输里面能够对应上去。
scores = log_optimal_transport( scores, self.bin_score, iters=self.config['sinkhorn_iterations'])
这个里面的参数scores就是上面的,然后是这个bin_score,我一开始也没有理解到是啥东西,我又看了他的论文,才发现,他这个东西其实就是一个常量。
def log_optimal_transport(scores, alpha, iters: int): """ Perform Differentiable Optimal Transport in Log-space for stability""" b, m, n = scores.shape one = scores.new_tensor(1) k= m \* one ms, ns = (k).to(scores), (n\*one).to(scores) # 初始化一个值为alpha的bmn矩阵 bins0 = alpha.expand(b, m, 1) bins1 = alpha.expand(b, 1, n) alpha = alpha.expand(b, 1, 1) # 连接出来一个新的矩阵 couplings = torch.cat([torch.cat([scores, bins0], -1), torch.cat([bins1, alpha], -1)], 1)
这里的alpha就是对应上面的bin_score,这里的主要作用我看作者写的就是他定义的一个垃圾桶的东西,我也没搞明白有啥作用,这个东西就是在原本的二维矩阵里面又外扩了一行一列,这个里面的初始值就是alpha,得到了一个新的矩阵就是couplings矩阵变成了【1*240*245】
然后就是那个ns和ms,其实这个东西我感觉特别的累赘,这个东西说白了就是对应最优传输问题中的那个概率函数,这里我们真的没有用,因为我们的每个点分配的概率肯定是一样的(虽然我也不清楚这里如何对应上最优传输的作用)。
norm = - (ms + ns).log() log_mu = torch.cat([norm.expand(m), ns.log()[None] + norm]) log_nu = torch.cat([norm.expand(n), ms.log()[None] + norm]) log_mu, log_nu = log_mu[None].expand(b, -1), log_nu[None].expand(b, -1) Z = log_sinkhorn_iterations(couplings, log_mu, log_nu, iters)
这里的骚操作我也没搞得特别清楚,说白了我理解就是,为了筹叠加所需要的概率函数,这里就是log_mu和log_nu这两个应该分别大小就是1*240和245*1.
后面就是可以得到这个Z
def log_sinkhorn_iterations(Z, log_mu, log_nu, iters: int): """ Perform Sinkhorn Normalization in Log-space for stability""" u, v = torch.zeros_like(log_mu), torch.zeros_like(log_nu) for _ in range(iters): u = log_mu - torch.logsumexp(Z + v.unsqueeze(1), dim=2) v = log_nu - torch.logsumexp(Z + u.unsqueeze(2), dim=1) return Z + u.unsqueeze(2) + v.unsqueeze(1)
作者说他用了一个啥(a differentiable Hungarian matching algorithm)东西求解的,在我理解的看来这个东西好像就是放大匹配的差异
这里面叠加了100次好像,这个u和v,我应该可以这样理解就是在联合概率分布就相当于矩阵Z,而log_mu和log_nu就是相当于边界概率分布,通过不断的叠加边界概率分布的方法,去加大差异性。
(这里悄悄的说一嘴,我感觉这个东西没有啥卵用,因为我把这个东西去掉以后,匹配在我的肉眼来看,一点差异都没有)也可能是我太肤浅了,没看到作者加这个东西的作用,而且我因为想把这里修改一下去提升这个性能,我看了很多的相关工作。这个Sinkhorn主要的作用就是叠加的效率比较高,这样的效果肯定比像orb-slam2用的暴力匹配好很多,而且前面加了注意力,尤其是自注意力,肯定能够提高这里描述子的信息。但是我感觉这个sinkhorn_iterations在像有一些纹理特别重复的地区,他的作用没有前面的作用大,而且Sinkhorn并不能求解出一个准确解,只能求个大概的解。
这个图片描述的很直观,这个迭代过程其实很广泛,我想要改进一下。而且slam当中场景的相邻两帧图片其实差别不大,尤其是对于位置信息来讲,所以我感觉这个里面其实大体上来讲取点的顺序其实就是和匹配顺序差不了太多的,除了一些消失的匹配点。
mutual0 = arange_like(indices0, 1)[None] == indices1.gather(1, indices0) mutual1 = arange_like(indices1, 1)[None] == indices0.gather(1, indices1) zero = scores.new_tensor(0) #torch.where() #函数的作用是按照一定的规则合并两个tensor类型。 #torch.where(condition,a,b)其中 #输入参数condition:条件限制,如果满足条件,则选择a,否则选择b作为输出。 mscores0 = torch.where(mutual0, max0.values.exp(), zero) mscores1 = torch.where(mutual1, mscores0.gather(1, indices1), zero) valid0 = mutual0 & (mscores0 > self.config['match_threshold']) valid1 = mutual1 & valid0.gather(1, indices1) indices0 = torch.where(valid0, indices0, indices0.new_tensor(-1)) indices1 = torch.where(valid1, indices1, indices1.new_tensor(-1)) return { 'matches0': indices0, # use -1 for invalid match 'matches1': indices1, # use -1 for invalid match 'matching_scores0': mscores0, 'matching_scores1': mscores1, }
这里就不用将太多了,作者最后把垃圾桶给扔掉了。我突然发现这个垃圾桶的作用就是让那些匹配不上的点能够有个地方可以去,不让他鸠占鹊巢了,让这些错误的点实在不行都给他搞到匹配垃圾桶去。最后在这里面要去垃圾桶的时候就将这些到垃圾桶的点都给他变成了无效点去了。