利用SuperGlue算法实现跨尺度金字塔特征点的高效匹配(含py代码)

       在计算机视觉领域,特征点匹配是一个基础而关键的任务,广泛应用于图像拼接、三维重建、目标跟踪等方向。传统的特征点匹配方法通常基于相同尺度下提取的特征进行匹配,然而在实际场景中,由于成像距离、分辨率等因素的差异,待匹配图像间存在显著的尺度变化,直接利用原始尺度的特征难以获得理想的匹配效果。为了克服这一难题,构建图像金字塔并在不同层级进行特征提取和匹配成为一种行之有效的策略。本文将给出如何使用图神经网络匹配算法SuperGlue的代码,实现跨金字塔层级的特征点高效匹配,充分利用不同尺度信息,显著提升匹配的准确性和鲁棒性。

1. 文件结构

2. 具体代码 

#! /usr/bin/env python3
import cv2
import torch   # 这一句
torch.set_grad_enabled(False) # 这一句
from models.matching import Matching # 这一句
from models.utils import (frame2tensor) # 这一句
import numpy as np

config = {
    'superpoint': {
        'nms_radius': 4,
        'keypoint_threshold': 0.005,
        'max_keypoints': -1
    },
    'superglue': {
        'weights': 'outdoor',
        'sinkhorn_iterations': 20,
        'match_threshold': 0.2,
    }
}
#
# device = 'cuda' if torch.cuda.is_available() else 'cpu'
device = 'cuda'
matching = Matching(config).eval().to(device)     # 这一句
keys = ['keypoints', 'scores', 'descriptors']
######################################################################################################
def match_frames_with_super_glue(frame0,frame1):
    print("正在调用基于 superGlue 匹配的函数进行特征点匹配...")  # 添加了print语句
    # 将参考帧和当前帧转换为PyTorch张量格式
    frame_tensor0 = frame2tensor(frame0, device)
    frame_tensor1 = frame2tensor(frame1, device)

    # 使用SuperPoint网络提取参考帧的特征点
    last_data = matching.superpoint({'image': frame_tensor0})

    # 将提取到的参考帧特征点数据转换为字典格式
    last_data = {k + '0': last_data[k] for k in keys}
    last_data['image0'] = frame_tensor0

    # 获取参考帧的特征点坐标
    kpts0 = last_data['keypoints0'][0].cpu().numpy()

    # 使用SuperGlue网络在参考帧和当前帧之间进行特征点匹配
    pred = matching({**last_data, 'image1': frame_tensor1})

    # 获取当前帧的特征点坐标
    kpts1 = pred['keypoints1'][0].cpu().numpy()

    # 获取特征点匹配结果和匹配置信度
    matches = pred['matches0'][0].cpu().numpy()
    confidence = pred['matching_scores0'][0].cpu().numpy()

    # 筛选出有效的匹配对
    valid = matches > -1
    mkpts0 = kpts0[valid]
    mkpts1 = kpts1[matches[valid]]

    # 打印匹配结果
    #
    # print(f"----已经完成帧间的关键点匹配----")
    for i, (kp0, kp1) in enumerate(zip(mkpts0, mkpts1)):
        print(f"Match {i}: ({kp0[0]:.2f}, {kp0[1]:.2f}) -> ({kp1[0]:.2f}, {kp1[1]:.2f})")

        # 确保两个图像都是三通道
    if len(frame0.shape) == 2:
        vis_frame0 = cv2.cvtColor(frame0, cv2.COLOR_GRAY2BGR)
    else:
        vis_frame0 = frame0.copy()

    if len(frame1.shape) == 2:
        vis_frame1 = cv2.cvtColor(frame1, cv2.COLOR_GRAY2BGR)
    else:
        vis_frame1 = frame1.copy()

    # 绘制第一个输入图像及其特征点
    vis_frame0_with_kpts = vis_frame0.copy()
    for kp in kpts0:
        cv2.circle(vis_frame0_with_kpts, (int(kp[0]), int(kp[1])), 3, (0, 255, 0), -1)
    cv2.imshow("Input Frame 0 with Keypoints", vis_frame0_with_kpts)

    # 绘制第二个输入图像及其特征点
    vis_frame1_with_kpts = vis_frame1.copy()
    for kp in kpts1:
        cv2.circle(vis_frame1_with_kpts, (int(kp[0]), int(kp[1])), 3, (0, 255, 0), -1)
    cv2.imshow("Input Frame 1 with Keypoints", vis_frame1_with_kpts)

        # 绘制特征点
    for kp in mkpts0:
        cv2.circle(vis_frame0, (int(kp[0]), int(kp[1])), 3, (0, 255, 0), -1)
    for kp in mkpts1:
        cv2.circle(vis_frame1, (int(kp[0]), int(kp[1])), 3, (0, 255, 0), -1)

        # 调整高度一致,通过在较短的图像上下填充黑色背景
    max_height = max(vis_frame0.shape[0], vis_frame1.shape[0])
    if vis_frame0.shape[0] < max_height:
        diff = max_height - vis_frame0.shape[0]
        pad_top = np.zeros((diff // 2, vis_frame0.shape[1], 3), dtype=np.uint8)
        pad_bottom = np.zeros((diff - diff // 2, vis_frame0.shape[1], 3), dtype=np.uint8)
        vis_frame0 = np.vstack((pad_top, vis_frame0, pad_bottom))
    if vis_frame1.shape[0] < max_height:
        diff = max_height - vis_frame1.shape[0]
        pad_top = np.zeros((diff // 2, vis_frame1.shape[1], 3), dtype=np.uint8)
        pad_bottom = np.zeros((diff - diff // 2, vis_frame1.shape[1], 3), dtype=np.uint8)
        vis_frame1 = np.vstack((pad_top, vis_frame1, pad_bottom))

        # 计算右侧图像的垂直偏移量
        right_pad_top = pad_top.shape[0]

        # 绘制匹配线段
        concat_frame = np.hstack((vis_frame0, vis_frame1))
        for kp0, kp1 in zip(mkpts0, mkpts1):
            pt0 = (int(kp0[0]), int(kp0[1]))
            pt1 = (int(kp1[0]) + vis_frame0.shape[1], int(kp1[1]) + right_pad_top)
            cv2.line(concat_frame, pt0, pt1, (0, 255, 0), 1)

        # 缩小可视化窗口大小
        scale_factor = 1
        resized_frame = cv2.resize(concat_frame, None, fx=scale_factor, fy=scale_factor)

        # 显示可视化结果
        cv2.imshow("Matched Features", resized_frame)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    return mkpts0, mkpts1, confidence[valid]

def build_pyramid(image, scale=1.2, min_size=(30, 30)):
    pyramid = [image]
    while True:
        last_image = pyramid[-1]
        width = int(last_image.shape[1] / scale)
        height = int(last_image.shape[0] / scale)
        if width < min_size[0] or height < min_size[1]:
            break
        next_image = cv2.resize(last_image, (width, height))
        pyramid.append(next_image)
    return pyramid

if __name__ == "__main__":
    # 读取两帧图像
    frame0 = cv2.imread("/home/fairlee/786D6A341753F4B4/KITTI/sequences_kitti_00_21/01/image_0/000630.png", 0)
    frame1 = cv2.imread("/home/fairlee/786D6A341753F4B4/KITTI/sequences_kitti_00_21/01/image_0/000631.png", 0)

    # 构建 frame1 的金字塔
    pyramid1 = build_pyramid(frame1, scale=1.2)

    # # # 显示金字塔层
    # for i, layer in enumerate(pyramid1):
    #     cv2.imshow(f"Layer {i}", layer)
    #     cv2.waitKey(500)  # 显示500毫秒
    # cv2.destroyAllWindows()

    # 选择合适的金字塔层作为 frame1 的替代
    frame1_substitute = pyramid1[2]  # 例如,选择第二层

    # 调用match_frames_with_super_glue函数进行特征点匹配
    mkpts0, mkpts1, confidence = match_frames_with_super_glue(frame0, frame1_substitute)

    # 打印匹配结果
    print(f"第一帧的特征点匹配到的特征点数量: {len(mkpts0)}")
    print(f"第二帧的特征点匹配到的特征点数量: {len(mkpts1)}")
    print(f"匹配置信度的长度为: {len(confidence)}")

3. 运行结果

       代码实现展示了该方法的具体流程,通过选取合适的金字塔层作为待匹配图像的替代,实现了跨尺度的特征点匹配。实验结果表明,该方法能够有效地处理存在显著尺度变化的图像,获得数量可观且置信度较高的匹配点对,为后续的图像拼接、三维重建等任务提供了重要的基础。该方法的优越性在于巧妙地结合了图像金字塔的多尺度表示和SuperGlue的强大匹配能力,为解决复杂场景下的特征匹配难题提供了新的思路和方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/693527.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【个人博客搭建练手版】Windows环境下使用tale,mblog,halo三种框架搭建个人博客,适合准备搭建博客的练手之作

昨天发了一篇搭建博客的教程&#xff0c;竟然上新人榜了。 博客链接&#xff1a;https://editor.csdn.net/md/?articleId139392436 趁热打铁把该文章中说的使用在Windows环境使用docker desktop搭建halo博客练手的教程也写一下。 这篇文章不只有halo的搭建&#xff0c;还有ta…

堆排序讲解

前言 在讲堆的删除时&#xff0c;我们发现一步一步删除堆顶的数据&#xff0c;排列起来呈现出排序的规律&#xff0c;所以本节小编将带领大家进一步理解堆排序。 1.堆排序概念 那么什么是堆排序&#xff1f; 堆排序&#xff08;Heap Sort&#xff09;是一种基于堆数据结构的排…

JS-Fetch

Fetch 是一种用于进行网络请求的现代 JavaScript API。它提供了一种简单、灵活且功能强大的方式&#xff0c;用于从服务器获取资源并处理响应。 Fetch API 在浏览器中原生支持&#xff0c;并且以 Promise 为基础&#xff0c;使得异步请求更加直观和易用。使用 Fetch API&#…

C++ Qt实现http url启动本地应用程序

更多Qt文章,请访问《深入浅出C++ Qt开发技术专栏》:https://blog.csdn.net/yao_hou/category_9276099.html 文章目录 1、注册自定义协议2、编写web页面3、编写C++应用程序我们在使用腾讯会议时经常会通过http链接打开本地的腾讯会议,例如下图: 打开会议发起人给的链接,会出…

C语言小例程10/100

题目&#xff1a;要求输出国际象棋棋盘。 程序分析&#xff1a;国际象棋棋盘由64个黑白相间的格子组成&#xff0c;分为8行*8列。用i控制行&#xff0c;j来控制列&#xff0c;根据ij的和的变化来控制输出黑方格&#xff0c;还是白方格。 #include<stdio.h>int main() {…

Elasticsearch:ES|QL 查询 TypeScript 类型(二)

在我之前的文章 “Elasticsearch&#xff1a;ES|QL 查询 TypeScript 类型&#xff08;一&#xff09;”&#xff0c;我们讲述了如何在 Nodejs 里对 ES|QL 进行查询。在今天的文章中&#xff0c;我们来使用一个完整的例子来进行详细描述。更多有关如何使用 Nodejs 来访问 Elasti…

【马琴绿绮】马维衡古琴之马氏汉风 明代杉木制;周身髹朱红色漆

【马琴绿绮式】马维衡古琴之马氏汉风 明代杉木制&#xff1b;琴体周身髹朱红色漆&#xff0c;鹿角霜灰胎&#xff1b;形体壮硕、风格高古&#xff1b;音色松透、浑厚&#xff0c;音质纯净&#xff0c;按弹舒适&#xff0c;手感丝滑。

AI视频教程下载:如何用ChatGPT来求职找工作?

这是一个关于使用ChatGPT找工作的课程&#xff0c;作者分享了自己的求职经验和技巧&#xff0c;介绍了如何使用人工智能来改进个人资料和简历&#xff0c;以及如何研究公司和面试。通过细节处理职业目标、分享个人兴趣和技能、寻求导师和专业发展机会&#xff0c;以及在行业内建…

LLVM Cpu0 新后端 系列课程总结

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

状态方程ABCD矩阵如何确定例子

状态方程ABCD矩阵如何确定 确定状态空间表示中的状态矩阵A、输入矩阵 B、输出矩阵C 和直通矩阵D,需要从系统的动力学方程出发,并将其转换为状态方程的形式。我们可以通过一个具体的物理系统(如倒立摆系统)来说明这一过程 例子:倒立摆系统 系统描述 考虑一个倒立摆系统…

车用柴油氧化安定性检测 GB 19147-2009全项检测

柴油分为轻柴油&#xff08;沸点范围约180-370℃&#xff09;和重柴油&#xff08;沸点范围约350-410℃&#xff09;两大类。柴油使用性能中最重要的是着火性和流动性&#xff0c;其技术指标分别为十六烷值和凝点&#xff0c;我国柴油现行规格中要求含硫量控制在0.5%-1.5%。 检…

Linxu: Dynamic debug 简介

文章目录 1. 前言2. 什么是 Dynamic debug (dyndbg) ?3. Dynamic debug (dyndbg) 的使用3.1 开启 Dynamic debug (dyndbg) 功能3.2 使用 Dynamic debug (dyndbg) 功能 4. Dynamic debug (dyndbg) 的实现4.1 内核接口 dynamic_pr_debug() 的实现4.2 debugfs 导出控制节点 contr…

stm32中外部中断控制Led亮灭

说明&#xff1a;外部中断的方式通过按键来实现&#xff0c;stm32的配置为江科大stm32教程中的配置。 1.内容&#xff1a; 通过中断的方式&#xff0c;按下B15按键Led亮&#xff0c;按下B13按键Led灭。 2.硬件设计&#xff1a; 3.代码&#xff1a; 3.1中断底层 EXTI.c #i…

Apple - Quartz 2D Programming Guide

本文翻译自&#xff1a;Quartz 2D Programming Guide&#xff08;更新时间&#xff1a;2017-03-21 https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html#//apple_ref/doc/uid/TP300010…

高考之后第一张大流量卡应该怎么选?

高考之后第一张大流量卡应该怎么选&#xff1f; 高考结束后&#xff0c;选择一张合适的大流量卡对于准大学生来说非常重要&#xff0c;因为假期期间流量的使用可能会暴增。需要综合考虑多个因素&#xff0c;以确保选到最适合自己需求、性价比较高且稳定的套餐。以下是一些建议…

Mysql(一):深入理解Mysql索引底层数据结构与算法

众所众知&#xff0c;MySql的查询效率以及查询方式&#xff0c;基本上和索引息息相关&#xff0c;所以&#xff0c;我们一定要对MySql的索引有一个具体到数据底层上的认知。 这一次也是借着整理的机会&#xff0c;和大家一起重新复习一下MySql的索引底层。 本节也主要有一下的…

电脑缺失msvcp110.dll文件的解决方法,总结5种靠谱的方法

在计算机使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp110.dll”。这个错误提示通常出现在运行某些软件时&#xff0c;那么&#xff0c;它究竟会造成哪些问题呢&#xff1f; 一&#xff0c;msvcp110.dll文件概述 msvcp110.dll是Mic…

【二叉树】Leetcode 103. 二叉树的锯齿形层序遍历【中等】

二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a; 输入&#xff1a;roo…

vite常识性报错解决方案

1.导入路径不能以“.ts”扩展名结束。考虑改为导入“xxx.js” 原因&#xff1a;当你尝试从一个以 .ts 结尾的路径导入文件时&#xff0c;ESLint 可能会报告这个错误&#xff0c;因为它期望导入的是 JavaScript 文件&#xff08;.js 或 .jsx&#xff09;而不是 TypeScript 文件&…

nest入门教程

1.介绍&#xff1a; Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用的框架。它使用渐进式 JavaScript&#xff0c;构建并完全支持 TypeScript&#xff08;但仍然允许开发者使用纯 JavaScript 进行编码&#xff09;并结合了 OOP&#xff08;面向对象编程&am…