惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
J
Java Code Geeks
博客园_首页
Scott Helme
Scott Helme
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
V
Visual Studio Blog
Cloudbric
Cloudbric
Jina AI
Jina AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
A
Arctic Wolf
C
Cybersecurity and Infrastructure Security Agency CISA
S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
W
WeLiveSecurity
K
Kaspersky official blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hacker News: Ask HN
Hacker News: Ask HN
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
量子位
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 三生石上(FineUI控件)
Recent Commits to openclaw:main
Recent Commits to openclaw:main

又见苍岚

COLMAP PatchMatch Stereo 算法详解 事件驱动的状态机框架:从理论到工程实践 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录 2025 Python 版本性能测试 Flutter Hello World Flutter 安装环境配置 Ubuntu VMware 硬盘扩容后 SMBus Host controller not enabled 报错问题解决 Python NetworkX 教程 Docker GPU 报错 - Failed to initialize NVML Unknown Error 解决方案 Python matplotlib 图表绘制 cuda-toolkit 安装替代 Cuda 与 Cudnn Jinja2 Python 利用 docxtpl 和 Jinja2 生成基于模板的 Word 文档 Docker 实现 CPU 核心隔离 LoFTR 基于 Transformer 的特征提取匹配算法 OmniGlue 特征匹配 Ubuntu 下将 xlsx 文件按照 sheet 转换为 图片 Python 使用 SQLAlchemy Python FastAPI 教程 openwrt 软路由配置安装 Nav2 地图文件(PGM/YAML)规范标准 3D OBJ 模型转换为 glb 瓦片格式 Python 源码 Redis 数据库介绍 Ubuntu 22.04 内核自动升级导致 MongoDB 7.0.12 错误记录 ubuntu 20.04 安装 ROS Noetic ubuntu 18.04 安装 ROS Melodic VMware Workstation Pro 个人免费版下载、安装、使用指南 Hybrid A-star 路径规划 Reeds-Shepp 曲线 Dubins 曲线 Linux kvm 虚拟机网络不通的问题解决方法 Ubuntu 自动内存清理 BiliBili 缓存视频转 mp4 Python 求解线性规划 3D Gaussian Splatting 官方源码实践记录 ImageMagick 教程 Ubuntu 22.04 安装 Colmap 对数几率 odds Ubuntu nmcli 网络管理工具使用指南 SuperPoint 自监督深度学习特征点提取 SyncTV Music Tag Web 在线音乐信息整理工具 ncm 格式转 mp3 MusicBrainz 音乐元数据百科数据库 Ubuntu 网络流量监控工具 私人云音乐平台 Navidrome 入门 手眼标定 四元数(Quaternions) OHTTPS 实现免费自动 https 证书申请、更新、部署 ubuntu 22.04 安装 CloudCompare 单机 KVM 虚拟机冷迁移 Ubuntu 22.04 使用 mdadm 实现软 raid 小鱼 一键安装 ROS-humble Fluid -46- 基于 Simpletex API 构建公式识别页面 公式识别 API 简介 -- Simpletex 使用 Python web 部署库 waitress 3D Gaussian Splatting for Real-Time Radiance Field Rendering Ubuntu Swap 简介与空间扩展 Ubuntu 24.04 安装 forticlient Clash Verge 使用 MongoDB 7.0.17 集群 Docker 构建源码 Error code - 2013. Lost connection to MySQL server during query 问题解决 Python 日志记录库 loguru 使用指北 Python 实现 Web 日志查看服务 MySQL LOAD DATA LOCAL INFILE 极速数据加载 Image size exceeds limit of 89478485 pixels 解决方案 Docker 使用 NVIDIA GPU 驱动错误解决 阿里云 docker 镜像仓库 Ubuntu中没有wired connected的解决方案 MinIO 简介 subconverter 代理订阅格式转换 修复 node –openssl-legacy-provider is not allowed in NODE_OPTIONS 错误 Ubuntu 使用 Clash For Linux 客户端
SuperGlue 使用图神经网络学习特征匹配
Yiwei Zhang · 2025-08-05 · via 又见苍岚

SuperGlue 是 Magic Leap 完成的 CVPR 2020 研究项目, 用于为 SuperPoint 基础上进行描述子特征匹配。本文记录相关内容。

简介

SuperGlue (SG) 是 Magic Leap 完成的 CVPR 2020 研究项目。SuperGlue 网络是一个图神经网络,结合了最优匹配层,经过训练可以对两组稀疏图像特征进行匹配。该存储库包括 PyTorch 代码和预训练权重,用于在 SuperPoint 关键点和描述符之上运行 SuperGlue 匹配网络。

SuperGlue 是一种图神经网络,可同时执行上下文聚合、匹配和局部特征过滤,以实现宽基线姿态估计。它速度快、可解释,并且在室内和室外都非常可靠。它通过共同寻找对应关系并拒绝不可匹配的点来匹配两组局部特征。 通过求解可微最优传输问题来估计分配,其成本由图神经网络预测。我们引入了一种基于注意力的灵活上下文聚合机制,使 SuperGlue 能够联合推理底层 3D 场景和特征分配。与传统的手工设计启发式方法相比,我们的技术通过图像对的端到端训练来学习 3D 世界的几何变换和规律性。SuperGlue 优于其他学习方法,并在具有挑战性的现实世界室内和室外环境中的姿势估计任务中取得了最先进的结果。所提出的方法在现代 GPU 上实时进行匹配,并且可以很容易地集成到现代 SfM 或 SLAM 系统中。

功能定位

上图所示是完整的图片匹配任务流程,被分为前端、中端和后端三部分,其中SG主要在中端发挥作用。

SG将学习特征匹配(learning feature matching)视为求解两个本地特征集合之间的部分分配(partial assignment)问题。

作者从经典图论的线性分配(linear assignment)出发,之后拓展到可以用可微方法求解的最优运输问题(optimal transport),其中成本函数通过图神经网络预测获得。启发自Transformer,即注意力机制可以同时利用关键点空间联系和视觉表征。除此之外SG还通过大量标记过的图像对学习到了用于姿态重建的先验知识。

核心思想

提出一种基于图神经网络的特征点匹配方法,把图像中的特征点看作图的节点,通过注意力机制聚合特征信息,得到用于匹配的特征向量。然后把匹配问题看作一个可微的最优运输问题(differentiable optimal transport problem),利用 Sinkhorn Algorithm 算法进行求解。

整个框架由两个主要模块组成:注意力GNN以及最优匹配层。其中注意力GNN将特征点以及描述子编码成为一个向量(该向量可以理解为特征匹配向量),随后利用自我注意力以及交叉注意力来回增强(重复𝐿次)这个向量𝑓的特征匹配性能;随后进入最优匹配层,通过计算特征匹配向量的内积得到匹配度得分矩阵,然后通过Sinkhorn算法(迭代𝑇次)解算出最优特征分配矩阵。

整个算法的处理流程如下图所示:

设有一对输入图片 $A, B$ ,其中各自具有关键点集合 $p$ 和相应的视觉描述子(descriptor) $d$ 。将这两者合并起来 $(p,d)$ 作为本地特征。每个关键点包含点坐标 $x,y$ 和可信度 $c$ ,视觉描述子由 SuperPoint 之类的CNN获得(或者是传统特征SIFT)。第 $i$ 个位置坐标 $p_i:=(x,y,c)$ 经过一个由多层感知机构成的编码器处理后与特征描述向量 $d_i$ 相加,得到图神经网络的一个节点 $x_i$ ,该节点被称作局部特征图 . $A$ 有 $M$ 个本地特征,而图 $B$ 有 $N$ 个。

$$ \mathbf{x}_{i}=\mathbf{d}_{i}+\mathrm{MLP}_{\mathrm{enc}}\left(\mathbf{p}_{i}\right) $$

为了获取上下文环境信息,本文采用图神经网络对特征信息进行聚合。本文提出一种多重图神经网络结构,将原图和待匹配图像中的特征点放在一起构成一个完整的图(graph),图中包含两种类型的无向边,一种是图像内部的边(Intra-image edges)也就是原图或待匹配图像内部特征点之间的连线,另一种是图像之间的边(Inter-image edges),即原图中的特征点和待匹配图像中的特征点之间的连线。因为包含两种类型的无向边,因此称为多重图神经网络。

SG希望求解一种分配方法,实现从A的本地特征到B的本地特征之间的关联,于是构造一个M×N矩阵,矩阵元素是[0,1]的的值描述关键点之间的关联可信度。并定义了两个限制条件:

  1. 每个关键点最多只能和另图中一个点关联
  2. 由于感知器的融合和失误,允许有的点没有关联。

我们给出一个软分配矩阵 $𝑃∈[0,1], 𝑀×𝑁$,根据上述约束,我们有如下关系:

$$ \mathbf{P} \mathbf{1}_{N} \leq \mathbf{1}_{M} \quad \text { and } \quad \mathbf{P}^{\top} \mathbf{1}_{M} \leq \mathbf{1}_{N} $$

首先要理解分配矩阵𝑃是什么。𝑃的每一行代表来自于图𝐴中的某个特征点对应图𝐵的𝑁种匹配的可能性。如上图所示,图𝐴有3个特征点,图𝐵有4个特征点,那么这个分配矩阵的维度就是𝑃∈𝑅3×4。对于第0行,即图A的第0号特征点,它可能在图B中有4个匹配点,由上图种给出的软分配矩阵𝑃的第0行数据可以看到,最大是数字是0.6,即图𝐴的第0号特征与图B的第1号特征是匹配的。相应的,对于𝑃的第0列,最大是数字是0.5,即图𝐵的第0号特征与图𝐴的第1号特征是匹配的,这看起来非常合理。

这里需要注意的是,这个𝑃是一个所谓的“软分配”矩阵,所以其中的元素并非是绝对的0或1。对于𝑃还需要满足规定:在理想情况下𝑃的行之和或者列之和等于1。此处的“理想情况”指的是图𝐴与图𝐵中的所有特征都可以在对方的图像上找到对应关系。但是实际情况下,可能存在遮挡/视角变化/检测噪声等因素的干扰,大概率会出现图𝐴在图𝐵中找不到对应的匹配点,反之亦然。

如上图所示,对于𝑃的第3列,即图𝐵的第3号特征而言,它并没有找到于其对应的特征匹配,所以对应的第3列之和是小于1的,确切来说对于一个调校的特别好的网络而言,第3列之和接近于0。

但是我们在做优化问题时并不喜欢不等于,所以作者后续对得分矩阵以及对应的分配矩阵进行了增广,让上述约束变成了=。

注意力GNN

这里有个有意思的说法:特征点的位置以及视觉外观能够提高其特异性。另外一个具有启发性的观点是人类在寻找匹配点过程是具有参考价值的。想一下人类是怎样进行特征匹配的,人类通过来回浏览两个图像试探性筛选匹配关键点,并进行来回检查(如果不是匹配的特征,观察一下周围有没有匹配的更好的点,直到找到匹配点/或没有匹配)。上述过程人们通过主动寻找上下文来增加特征点特异性,这样可以排除一些具有奇异性的匹配。本文的核心就是利用基于注意力机制的GNN实现上述过程,即模拟了人类进行特征匹配。

特征点Encode

首先根据上述说法,特征点位置+描述会获得更强的特征匹配特异性,所以这里将特征点的位置以及描述子合并成每个特征点𝑖的初始表示 $𝑥_𝑖$

$$ ^{(0)} \mathbf{x}_{i}=\mathbf{d}_{i}+\mathbf{M L P}_{\mathrm{enc}}\left(\mathbf{p}_{i}\right) $$

其中MLP表示多层感知机(Multilayer Perceptron ,MLP)此处用于对低维特征升维,上式实际上是将视觉外观以及特征点位置进行了耦合,正因如此,这使得该Encode形式使得后续的注意力机制能够充分考虑到特征的外观以及位置相似度。

特征点编码代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class KeypointEncoder(nn.Module):
""" Joint encoding of visual appearance and location using MLPs"""
""" feature_dim: 256, layers: [32, 64, 128, 256]"""
def __init__(self, feature_dim, layers):
super().__init__()
self.encoder = MLP([3] + layers + [feature_dim]) # MLP([3, 32, 64, 128, 256, 256])
nn.init.constant_(self.encoder[-1].bias, 0.0)

def forward(self, kpts, scores):
inputs = [kpts.transpose(1, 2), scores.unsqueeze(1)]
return self.encoder(torch.cat(inputs, dim=1)) # DIM: 1x256xM

def MLP(channels: list, do_bn=True):
""" Multi-layer perceptron """
n = len(channels)
layers = []
for i in range(1, n):
layers.append(
nn.Conv1d(channels[i - 1], channels[i], kernel_size=1, bias=True))
if i < (n-1):
if do_bn:
layers.append(nn.BatchNorm1d(channels[i]))
layers.append(nn.ReLU())
return nn.Sequential(*layers)

将描述子与编码后的特征点相加可以得到后续多层GNN的输入的初值:

1
2
3
# Keypoint MLP encoder.
desc0 = desc0 + self.kenc(kpts0, data['scores0']) #图A
desc1 = desc1 + self.kenc(kpts1, data['scores1']) #图B

多层GNN

考虑一个单一的完全图,它的节点是图像中每个特征点,这个图包括两种不同的无向边:一种是“Intra-image edges”(self edge)𝐸self ,它连接了来自图像内部特征点;另外一种是“Inter-image edges”(cross edge)𝐸cross ,它连接本图特征点𝑖与另外一张图所有特征点。

令$(ℓ)𝑥_𝑖^𝐴$表示为图像 𝐴 上第 𝑖 个元素在第 ℓ 层的中间表达形式。信息(message)$𝑚_{𝐸→𝑖}$是聚合了所有特征点 ${𝑗:(𝑖,𝑗)∈𝐸}$之后点结果(将自我注意力以及交叉注意力进行聚合),其中 $𝐸∈{𝐸_{self },𝐸_{cross} }$,所以图像 𝐴 中所有特征 𝑖 传递更新的残差信息(residual message)是:

$$ ^{(\ell+1)} \mathbf{x}_{i}^{A}=^{(\ell)} \mathbf{x}_{i}^{A}+\operatorname{MLP}\left(\left[^{(\ell)} \mathbf{x}_{i}^{A} \| \mathbf{m}_{\mathcal{E} \rightarrow i}\right]\right) $$

其中[⋅‖⋅]表示串联操作。同样的,图像𝐵上所有特征有类似的更新形式。可以看到self 以及cross edges绑在一起并交替进行更新,先self后cross,作者提到共有固定数量的𝐿层。

需要说明的是,这里的self/cross-attention实际上就是模拟了人类来回浏览匹配的过程,其中self-attention是为了使得特征更加具有匹配特异性,而cross-attention是为了用这些具有特异性的点做图像间特征的相似度比较。

  • 代码如下:
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
class AttentionalGNN(nn.Module):
def __init__(self, feature_dim: int, layer_names: list):
super().__init__()
self.layers = nn.ModuleList([
AttentionalPropagation(feature_dim, 4)
for _ in range(len(layer_names))])
self.names = layer_names

def forward(self, desc0, desc1):
for layer, name in zip(self.layers, self.names):
layer.attn.prob = []
if name == 'cross':
src0, src1 = desc1, desc0
else: # if name == 'self':
src0, src1 = desc0, desc1
delta0, delta1 = layer(desc0, src0), layer(desc1, src1)
desc0, desc1 = (desc0 + delta0), (desc1 + delta1)
return desc0, desc1

class AttentionalPropagation(nn.Module):
def __init__(self, feature_dim: int, num_heads: int):
super().__init__()
self.attn = MultiHeadedAttention(num_heads, feature_dim)
self.mlp = MLP([feature_dim*2, feature_dim*2, feature_dim]) #其中MLP隐含层配置[512,512,256]
nn.init.constant_(self.mlp[-1].bias, 0.0)

def forward(self, x, source):
message = self.attn(x, source, source) # q,k,v, DIM: 1x256xM
return self.mlp(torch.cat([x, message], dim=1)) #DIM: 1x256xM

Attentional Aggregation

这一小节介绍上述介绍种的message是如何获得的。

文章的亮点之一就是将注意力机制用于特征匹配,这到底是如何实现的呢?

作者提到,注意力机制将self以及cross信息聚合得到$𝑚_{𝐸→𝑖}$。其中 self edge 利用了self-attention,cross edge利用了cross-attention。类似于数据库检索,我们想要查询 $𝑞_𝑖$ 基于元素的属性即键 $𝑘_𝑖$,检索到了某些元素的值 $𝑣_𝑗$。

$$ \mathbf{m}_{\mathcal{E} \rightarrow i}=\sum_{j:(i, j) \in \mathcal{E}} \alpha_{i j} \mathbf{v}_{j} $$ 其中注意力权重 $𝛼_{𝑖𝑗}$ 是查询与检索到对象键值相似度的Softmax即,$\alpha_{i j}=\operatorname{Softmax}_{j}\left(\mathbf{q}_{i}^{\top} \mathbf{k}_{j}\right)$ 这里需要解释一下键(key),query以及值(value)。令待查询点特征点𝑖位于查询图像𝑄上,所有的源特征点位于图像𝑆上,其中 $(𝑄,𝑆)∈\{𝐴,𝐵\}^2$,于是我们可以将 key,query 以及 value 写成下述形式: $$ \begin{aligned} \mathbf{q}_{i} &=\mathbf{W}_{1}^{(\ell)} \mathbf{x}_{i}^{Q}+\mathbf{b}_{1} \\\left[\begin{array}{l}\mathbf{k}_{j} \\ \mathbf{v}_{j}\end{array}\right] &=\left[\begin{array}{l}\mathbf{W}_{2} \\ \mathbf{W}_{3}\end{array}\right](\ell) \mathbf{x}_{j}^{S}+\left[\begin{array}{l}\mathbf{b}_{2} \\ \mathbf{b}_{3}\end{array}\right] \end{aligned} $$

每一层ℓ都有其对应的一套投影参数,这些参数被所有的特征点共享。理解一下:此处的𝑞𝑖对应于待查询图像上某个特征点𝑖的一种表示(self-attention映射),$𝑘_𝑗$以及 $𝑣_𝑗$ 都是来自于召回的图像特征点𝑗的一种表示(映射);$𝛼_{𝑖𝑗}$ 表示这两个特征相似度,它是由 $𝑞_𝑖$ 以及 $𝑘_𝑗$ 计算得到(在这里体现了cross-attention的思想),越大就表示这两个特征越相似,然后利用该相似度对 $𝑣_𝑗$ 加权求和得到 $𝑚_{𝐸→𝑖}$,这就是所谓的特征聚合

上面提到的这些概念有些难以理解,作者特意对上述过程进行了可视化,self-attention就是一张图像内部的边相连进行聚合,它能够更加关注具有特异性的所有点,且并不仅局限于其邻域位置特征;cross-attention做的就是匹配那些外观相似的两张图像见的特征。

下图展示了每层self-attention以及across-attention中权重 $𝛼_{𝑖𝑗}$的结果。按照匹配从难到易,文中画出了3个不同的特征点作为演示,绿色特征点(容易),蓝色特征点(中等)以及红色特征点(困难)。对于self-attention,初始时它(某个特征)关联了图像上所有的点(首行),然后逐渐地关注在与该特征相邻近的特征点(尾行)。同样地,cross-attention主要关注去匹配可能的特征点,随着层的增加,它逐渐减少匹配点集直到收敛。绿色特征点在第9层就已经趋近收敛,而红色特征直到最后才能趋紧收敛(匹配)。可以看到无论是self还是cross,它们关注的区域都会随着网络层深度的增加而逐渐缩小。

经过了𝐿次self/cross-attention后就可以得到注意力GNN的输出,对于图像𝐴我们有:

$$ \mathbf{f}_{i}^{A}=\mathbf{W} \cdot^{(L)} \mathbf{x}_{i}^{A}+\mathbf{b}, \quad \forall i \in \mathcal{A} $$

我们可以把$𝑓_𝑖^𝐴$理解为匹配描述子(类比特征描述子),专门为特征匹配服务,对于图像𝐵具有类似的形式。

匹配层(Optimal matching layer)

接下来的任务就是去构建软分配矩阵𝑃。对于一般的图匹配流程,这个分配矩阵可以通过计算一个得分矩阵$\mathbf{S} \in \mathbb{R}^{M \times
N}$(用来表示一些潜在的匹配)来实现。具体而言,通过最大化总体得分 $\sum_{i, j} \mathbf{S}{i, j} \mathbf{P}{i,
j}$ 即可得到这个分配矩阵𝑃,其中要注意的是𝑃是有约束的。

匹配得分预测

作者使用GNN聚合得到的$\mathbf{f}_{i}^{A}$以及$\mathbf{f}_{i}^{B}$计算内积得到得分: $$ \mathbf{S}_{i, j}=<\mathbf{f}_{i}^{A}, \mathbf{f}_{j}^{B}>, \forall(i, j) \in \mathcal{A} \times \mathcal{B} $$

遮挡以及可见性

类似于SuperPoint在提取特征点时增加了一层dustbin通道,专门为了应对图像中没有特征点情况。本文借鉴了该思想,在得分矩阵$𝑆$的最后一列/行设置为dustbins可以得到$\overline{\mathbf{S}}$,这样做的作用在于可以滤出错误的匹配点。

$$ \overline{\mathbf{S}}_{i, N+1}=\overline{\mathbf{S}}_{M+1, j}=\overline{\mathbf{S}}_{M+1, N+1}=z \in \mathbb{R} $$ 图像𝐴上的特征点被分配到图像𝐵上某个特征匹配或者被分配到dustbin,这就意味着每个dustbin有𝑁,𝑀个匹配,因此软分配矩阵有如下约束: $$ \overline{\mathbf{P}} \mathbf{1}_{N+1}=\mathbf{a} \quad\text { and } \quad \overline{\mathbf{P}}^{\top} \mathbf{1}_{M+1}=\mathbf{b} $$ 其中$\mathbf{a}=\left[\begin{array}{ll}\mathbf{1}_{M}^{\top} & N\end{array}\right]^{\top}$,$\mathbf{b}=\left[\begin{array}{ll}\mathbf{1}_{N}^{\top} & M\end{array}\right]^{\top}$。𝑎 **表示图𝐴中特征点以及dustbin的期望匹配数**,即正常情况下,图𝐴中每个特征点在图𝐵中仅且仅有1个匹配点,但是对于图𝐴的dustbin而言,它可能匹配到图B的任何一个特征点,即有𝑁种可能性,所以$\mathbf{a} = \left[\begin{array}{ll}\mathbf{1}_{M}^{\top} & N\end{array}\right]^{\top}$。

作者Sarlin在这个问题中提到,上式的约束项之所以被替换成=,是因为做了类似于松弛化的操作,这么做的好处是将原本难以优化的不等问题变为恒等约束(原话为"These are like slack variables that enforce the equality constraint"),同时可以比较好的应对没有匹配的特征或者错误的特征(这其实是增加dustbin通道的原因,它起到滤除外点的作用)。

Sinkhorn Algorithm

求解最大化总体得分可由“Sinkhorn Algorithm”进行求解。

如上图所示,蓝色背景区域的几个公式给出了针对该特征匹配问题的“代价函数”,准确来说应该是负的代价函数。因为原始的最优传输问题中$\mathbf{\bar{S}}$扮演的角色应该是cost matrix,即代价矩阵;对比之下,本例中 $\mathbf{\bar{S}}$ 为匹配描述子的余弦相似度,这正好与代价矩阵相反。正因如此,相较于原始最优传输问题的最小化代价函数,本例的目标是最大化匹配描述子的相似度,所以上式为max而非min。

至于如何求解最优传输问题,sinkhorn algorithm算法能够以一种迭代的方式对该问题进行求解,具体的方法是:

  • 给定:匹配描述子的余弦相似度矩阵 $\bar{S}$,两个分布 𝑎 和 𝑏,正则项𝜆(越大表示分配越均匀,默认为1)
  • 初始化:分配矩阵:$\mathbf{\bar{P}} =
    \exp^{-\lambda \mathbf{\bar{S}}}$ 重复:
    • 缩放行,使得行之和为𝑎.
    • 缩放列,使得列之和为𝑏. 直至收敛.

这里给出一个简单的例子,一步步阐述sinkhorn algorithm是如何运作的。

  • 首先给定输入,并初始化分配矩阵(注,为方便起见,分配矩阵被初始化为代价矩阵 $\mathbf{\bar{P}}
    =\mathbf{\bar{S}}$,实际情况下是 $\mathbf{\bar{P}} = \exp^{-\lambda
    \mathbf{\bar{S}}}$)

  • 步骤1:计算目前的行之和; 步骤2:对于P矩阵的每一行,分别除以上述行之和并乘目标行之和a;

  • 步骤3:计算目前的列之和; 步骤4:对于P矩阵的每一列,分别除以上述列之和并乘目标列之和b;

对行列重复执行1-4步,经过4次迭代后分配矩阵的行之和就与𝑎一致,列之和与𝑏一致。

作者Sarlin提供的代码如下:

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
def log_optimal_transport(scores: torch.Tensor, alpha: torch.Tensor, iters: int) -> torch.Tensor:
""" Perform Differentiable Optimal Transport in Log-space for stability"""
b, m, n = scores.shape
one = scores.new_tensor(1)
ms, ns = (m*one).to(scores), (n*one).to(scores)

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)

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)
Z = Z - norm # multiply probabilities by M+N
return 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)

损失函数

$$ \begin{aligned}\mathrm{Loss}&=-\sum_{(i,j)\in\mathcal{M}}\log\bar{\mathbf{P}}_{i,j}\\&-\sum_{i\in\mathcal{I}}\log\bar{\mathbf{P}}_{i,N+1}-\sum_{j\in\mathcal{J}}\log\bar{\mathbf{P}}_{M+1,j}.\end{aligned} $$

设计中图神经网络和最优匹配层都是可微的,这就让反向传播可以一直从最终的匹配结果一直到最初的视觉描述子。SG是基于ground truth(gt)匹配监督学习来的。argmin负log似然函数获得结果。

流程框图

实验

特征匹配的目的是为了解算出两帧之间的相对位姿,所以实验对比的一个指标就是单应矩阵估计,另外还有室内外的位姿估计。

单应矩阵估计

能够获得非常高的匹配召回率(98.3%)同时获得超高的精度,比传统的暴力匹配都好了一大截。

室内外位姿估计

下表看来,大基线室内位姿估计也是相当棒,完胜传统算法。

网络耗时

接下来放出大家比较关心的网络耗时,下图是在NVIDIA GeForce GTX 1080 GPU跑了500次的结果,512个点69ms(14.5fps),1024个点87ms(11.5fps)。

更多匹配结果

第一列是SuperPoint+暴力匹配结果,第二列是SuperPoint+OAnet(ICCV 2019)结果,第三列是SuperPoint+SuperGlue结果。能看到SuperGlue惊人的特征匹配能力,尤其是在大视角变化时优势明显(红线表示错误匹配,绿线表示正确匹配)。

改进点 (Update: 2022.05.17)

主要从改进特征编码形式,attention形式,改进最优传输问题的求解策略等角度进行展开。

  • A Unified Framework for Implicit Sinkhorn Differentiation,CVPR 2022,,分析了深度学习中任务中通用Sinkhorn层的隐含梯度的使用,文中指出SuperGlue中使用的是自动微分的sinkhorn algorithm,收敛速度可能较慢且容易出现OOM,而本算法能够解决该问题(不过并没有进行验证) [Code]
  • OpenGlue - Open Source Pipeline for Image Matching,arXiv 2022,复现了SuperGlue,包括训练以及推理过程,OpenGlue对基于图的匹配过程进行概况,形成了一套易替换和后续开发的模块和流程;此外基于上述流程对SuperGlue中attention等步骤进行改进;开源协议有更新,OpenGlue可商用 [Code]
  • MatchFormer: Interleaving Attention in Transformers for Feature Matching,arXiv 2022,改进的LoFTR, 将attention引入特征编码阶段,大幅度提升特征匹配在大视角变化时的匹配性能 [Code]
  • LoFTR: Detector-Free Local Feature Matching with Transformers, CVPR 2021,无特征匹配,匹配性能强劲 [Code]
  • valgur/SuperGluePretrainedNetwork, 实现了 C++ 调用 SuperGlue:包含使用 jit scripts 将SuperPoint以及SuperGlue序列化,然后使用pytorch的 C++ 接口进行调用 [Code]
  • SuperGlue with Physarum Dynamics, 最优传输问题的求解由Sinkhorn Algorithm 替换成 Physarum Dynamics solver [Code]
  • Superglue-Jittor,改进了图注意力机制中矩阵运算,采用分块优化的方式进行,降低了匹配过程中的显存占用;使用dual-Softmax 替换 sinkhorn algorithm [Code]
  • SuperGlue PyTorch Implementation,pytorch复现SuperGlue [Code]
  • MNNSuperGlue,MNN Superglue 关键点匹配C++实现 [Code]

原始论文

参考资料

文章链接:
https://www.zywvvd.com/notes/study/image-processing/feature-extraction/superglue/superglue/