图像拼接——基于homography的特征匹配算法

news2025/7/9 11:53:03

目录

  • 1. 任务要求
  • 2. 数据集
  • 3. 基于homography的特征匹配算法
  • 4. 拼接流程展示
    • 4.1 图片实例
    • 4.2 特征点位图
    • 4.3 特征点匹配结果
    • 4.4 相机校准结果
    • 4.5 拼接结果
  • 5. 部分图像拼接结果展示


1. 任务要求

  • 输入:同一个场景的两张待拼接图像(有部分场景重合)。
  • 任务:从输入的两张图像中提取特征点和描述子,可以使用现有的图像处理库来执行此任务。自己实现特征匹配算法,将来自两张图像的特征点进行匹配。最后根据匹配的特征点估计单应性变换,从而通过映射拼接成一张全景图。
  • 输出
    • 显示两张图像上提取的特征点的位置;
    • 显示特征点匹配对应的结果;
    • 显示经过几何变换后的图像叠加的结果;
    • 显示最终拼接的结果。

2. 数据集

  • 其中两组图像“cat”和“bridge”拍摄于杭州。
  • 其他图像分别来自测试数据链接和其他来源。

3. 基于homography的特征匹配算法

基于homography的特征匹配算法在图像拼接中起着关键作用,它能够定位和匹配两张待拼接图像中的特征点,从而实现图像的对齐和融合。该算法主要包括以下实现步骤:

  • 特征点提取和描述:使用ORB和SIFT等特征检测器对待拼接图像进行特征点提取。这些特征点具有在不同尺度和旋转下的不变性。对每个特征点计算其对应的特征描述子,用于后续的特征匹配。
  • 特征匹配:对两幅待拼接图像中的特征点进行匹配。我们使用基于最近邻的匹配,其中对于每个特征点,找到其在另一幅图像中的最佳匹配点。通过计算特征描述子之间的距离或相似度,确定最佳匹配点。
  • 计算homography矩阵:使用筛选后的特征点匹配对应的坐标,计算homography矩阵。homography矩阵可以将一个图像上的点映射到另一个图像上,从而实现图像的对齐。
  • 图像校准和拼接:使用计算得到的homography矩阵对多张图像进行透视变换,使其对齐。将校准后的图像进行融合,生成拼接结果图像。
import math

import cv2 as cv
import numpy as np


class FeatureMatcher:
    def __init__(
        self, matcher_type="homography", range_width=-1, **kwargs
    ):
        if matcher_type == "homography":
            if range_width == -1:
                self.matcher = cv.detail_BestOf2NearestMatcher(**kwargs)
            else:
                self.matcher = cv.detail_BestOf2NearestRangeMatcher(range_width, **kwargs)
        else:
            raise ValueError("Unknown matcher type")

    def match_features(self, features, *args, **kwargs):
        pairwise_matches = self.matcher.apply2(features, *args, **kwargs)
        self.matcher.collectGarbage()
        return pairwise_matches

    @staticmethod
    def draw_matches_matrix(
        imgs, features, matches, conf_thresh=1, inliers=False, **kwargs
    ):
        matches_matrix = FeatureMatcher.get_matches_matrix(matches)
        for idx1, idx2 in FeatureMatcher.get_all_img_combinations(len(imgs)):
            match = matches_matrix[idx1, idx2]
            if match.confidence < conf_thresh:
                continue
            if inliers:
                kwargs["matchesMask"] = match.getInliers()
            yield idx1, idx2, FeatureMatcher.draw_matches(
                imgs[idx1], features[idx1], imgs[idx2], features[idx2], match, **kwargs
            )
            
	@staticmethod
    def get_confidence_matrix(pairwise_matches):
        matches_matrix = FeatureMatcher.get_matches_matrix(pairwise_matches)
        match_confs = [[m.confidence for m in row] for row in matches_matrix]
        match_conf_matrix = np.array(match_confs)
        return match_conf_matrix


4. 拼接流程展示

4.1 图片实例

为了演示图像拼接的整体实现流程,这里我们选择一组我本人拍摄的玉泉校内的两只猫和周边环境图——“cat”,两幅有重叠画面的原图如下图所示。其中下面那只白猫几乎是静止不动的,上面的带橘色斑点的白猫在两幅图中的位置有相对移动。

from stitching.images import Images

# 1. load
images, low_imgs, medium_imgs, final_imgs = load_images(img_path)
images_to_match = medium_imgs

# 2. plot original images
plot_images(images_to_match, (20, 20), save=f'{save_path}/1-original.png')

# 3. print image size
print(f'Original image size: {images_to_match[0].shape}')

################ Load images ####################
def load_images(img_path):
    images = Images.of(img_path)

    medium_imgs = list(images.resize(Images.Resolution.MEDIUM))
    low_imgs = list(images.resize(Images.Resolution.LOW))
    final_imgs = list(images.resize(Images.Resolution.FINAL))

    return images, low_imgs, medium_imgs, final_imgs

################ Plot function####################
def plot_image(img, figsize_in_inches=(10, 10), save=None):
"""N_image = 1"""

def plot_images(imgs, figsize_in_inches=(10, 10), save=None):
"""N_images > 1"""

在这里插入图片描述

4.2 特征点位图

根据特征检测器提取的特征点,生成特征点位置图。这里我们以ORB特征检测器为例,下图中的绿色小圈展示了待拼接图像中检测到的特征点的分布情况。

from stitching.feature_detector import FeatureDetector

# 4. Feature detection: ORB, SIFT
finder = FeatureDetector(detector=detector)
features = [finder.detect_features(img) for img in images_to_match]

key_points_img = []
for i in range(len(images_to_match)):
    key_points_img.append(finder.draw_keypoints(images_to_match[i], features[i]))

plot_images(key_points_img, (20, 20), save=f'{save_path}/2-key_points.png')

在这里插入图片描述

4.3 特征点匹配结果

通过homography特征匹配算法(具体代码见第3节),将两张待拼接图像中匹配的特征点进行连接,生成特征点匹配结果图。下图中的绿色线段展示了特征点之间的对应关系。

from Feature_matcher import *

# 5. Feature matching: homography
matcher = FeatureMatcher()
matches = matcher.match_features(features)

print(matcher.get_confidence_matrix(matches))

# 6. plot matching
all_relevant_matches = matcher.draw_matches_matrix(images_to_match, features, matches, conf_thresh=1,
                                                   inliers=True, matchColor=(0, 255, 0))

for idx1, idx2, img in all_relevant_matches:
    print(f"Matches Image {idx1 + 1} to Image {idx2 + 1}")
    plot_image(img, (20, 10), save=f'{save_path}/3-matching.png')

4.4 相机校准结果

根据homography矩阵,对两张图像进行透视变换,使其对齐,生成校准结果图。下图的子图a为校准过程得到的mask图,子图b展示了经过校准后的待拼接图像,最终拼接图的大小与待拼接图像的大小一致。

from stitching.camera_estimator import CameraEstimator
from stitching.camera_adjuster import CameraAdjuster
from stitching.camera_wave_corrector import WaveCorrector
from stitching.warper import Warper
from stitching.timelapser import Timelapser

# 7. Camera Estimation, Adjustion and Correction
cameras = camera_correction(features, matches)

# 8. Warp images
(warped_low_imgs, warped_low_masks, low_corners, low_sizes,
 warped_final_imgs, warped_final_masks, final_corners, final_sizes, frame) \
    = warp_image(images, cameras, low_imgs, final_imgs)

plot_images(warped_low_imgs, (10, 10), save=f'{save_path}/4-warped_low_imgs.png')
plot_images(warped_low_masks, (10, 10), save=f'{save_path}/4-warped_low_masks.png')
plot_images(frame, (20, 10), save=f'{save_path}/4-warped_final_imgs.png')

################ Camera Estimation ##################
def camera_correction(features, matches):
    camera_estimator = CameraEstimator()
    camera_adjuster = CameraAdjuster()
    wave_corrector = WaveCorrector()

    cameras = camera_estimator.estimate(features, matches)
    cameras = camera_adjuster.adjust(features, matches, cameras)
    cameras = wave_corrector.correct(cameras)

    return cameras
################ Warp images ####################
def warp_image(images, cameras, low_imgs, final_imgs):
    warper = Warper()
    warper.set_scale(cameras)

    low_sizes = images.get_scaled_img_sizes(Images.Resolution.LOW)
    camera_aspect = images.get_ratio(Images.Resolution.MEDIUM,
                                     Images.Resolution.LOW)  # since cameras were obtained on medium imgs

    warped_low_imgs = list(warper.warp_images(low_imgs, cameras, camera_aspect))
    warped_low_masks = list(warper.create_and_warp_masks(low_sizes, cameras, camera_aspect))
    low_corners, low_sizes = warper.warp_rois(low_sizes, cameras, camera_aspect)

    final_sizes = images.get_scaled_img_sizes(Images.Resolution.FINAL)
    camera_aspect = images.get_ratio(Images.Resolution.MEDIUM, Images.Resolution.FINAL)

    warped_final_imgs = list(warper.warp_images(final_imgs, cameras, camera_aspect))
    warped_final_masks = list(warper.create_and_warp_masks(final_sizes, cameras, camera_aspect))
    final_corners, final_sizes = warper.warp_rois(final_sizes, cameras, camera_aspect)

    # Timelapser
    timelapser = Timelapser('as_is')
    timelapser.initialize(final_corners, final_sizes)

    frame = []
    for img, corner in zip(warped_final_imgs, final_corners):
        timelapser.process_frame(img, corner)
        frame.append(timelapser.get_frame())

    return (warped_low_imgs, warped_low_masks, low_corners, low_sizes,
            warped_final_imgs, warped_final_masks, final_corners, final_sizes, frame)

在这里插入图片描述

4.5 拼接结果

将经过校准的两张图像进行融合,生成拼接结果图。根据用户的选择,可以提供剪裁相机校准结果的选项(stitching(crop = True),默认为False)。图1分别展示了未剪裁和剪裁后的校准图(5a&c)和拼接图时的接缝(5b&d)。最后拼接图结果见图2,上面三幅图不包括剪裁步骤,下面三幅存在剪裁步骤。可以看到,在拼接之前剪裁至规则的四边形对拼接时的seam line的选取有较大的影响,有一定概率导致最终的拼接图像不符合预期。

from stitching.cropper import Cropper
from stitching.seam_finder import SeamFinder

# 9. Crop images
if crop:
    (cropped_low_imgs, cropped_low_masks, cropped_final_imgs,
     cropped_final_masks, final_corners, final_sizes, frame) = (
        crop_image(images, warped_low_imgs, warped_low_masks, low_corners, low_sizes,
                   warped_final_imgs, warped_final_masks, final_corners, final_sizes))

    plot_images(frame, (20, 10), save=f'{save_path}/5-cropped_final_imgs.png')
else:
    cropped_low_imgs = warped_low_imgs
    cropped_low_masks = warped_low_masks
    cropped_final_imgs = warped_final_imgs
    cropped_final_masks = warped_final_masks

# 10. Seam Masks
seam_finder, seam_masks_plots, compensated_imgs, seam_masks = (
    seam(cropped_low_imgs, low_corners, cropped_low_masks,
         cropped_final_masks, cropped_final_imgs, final_corners))
plot_images(seam_masks_plots, (15, 10), save=f'{save_path}/6-seam_masks.png')

# 11. Matching result
blender = Blender()
blender.prepare(final_corners, final_sizes)
for img, mask, corner in zip(compensated_imgs, seam_masks, final_corners):
    blender.feed(img, mask, corner)
panorama, _ = blender.blend()
blended_seam_masks = seam_finder.blend_seam_masks(seam_masks, final_corners, final_sizes)

plot_image(panorama, (20, 20), save=f'{save_path}/7-matched_result.png')
plot_image(seam_finder.draw_seam_lines(panorama, blended_seam_masks, linesize=3), (15, 10),
           save=f'{save_path}/8-seam_lines.png')
plot_image(seam_finder.draw_seam_polygons(panorama, blended_seam_masks), (15, 10),
           save=f'{save_path}/9-seam_polygons.png')

# 12. Done
print('Done!')

################ Crop images ####################
def crop_image(images, warped_low_imgs, warped_low_masks, low_corners, low_sizes,
               warped_final_imgs, warped_final_masks, final_corners, final_sizes):
    cropper = Cropper()

    mask = cropper.estimate_panorama_mask(warped_low_imgs, warped_low_masks, low_corners, low_sizes)

    lir = cropper.estimate_largest_interior_rectangle(mask)

    low_corners = cropper.get_zero_center_corners(low_corners)
    rectangles = cropper.get_rectangles(low_corners, low_sizes)

    overlap = cropper.get_overlap(rectangles[1], lir)

    intersection = cropper.get_intersection(rectangles[1], overlap)

    cropper.prepare(warped_low_imgs, warped_low_masks, low_corners, low_sizes)

    cropped_low_masks = list(cropper.crop_images(warped_low_masks))
    cropped_low_imgs = list(cropper.crop_images(warped_low_imgs))
    low_corners, low_sizes = cropper.crop_rois(low_corners, low_sizes)

    lir_aspect = images.get_ratio(Images.Resolution.LOW, Images.Resolution.FINAL)  # since lir was obtained on low imgs
    cropped_final_masks = list(cropper.crop_images(warped_final_masks, lir_aspect))
    cropped_final_imgs = list(cropper.crop_images(warped_final_imgs, lir_aspect))
    final_corners, final_sizes = cropper.crop_rois(final_corners, final_sizes, lir_aspect)

    # Redo the timelapse with cropped Images:
    timelapser = Timelapser('as_is')
    timelapser.initialize(final_corners, final_sizes)

    frame = []
    for img, corner in zip(cropped_final_imgs, final_corners):
        timelapser.process_frame(img, corner)
        frame.append(timelapser.get_frame())

    return (cropped_low_imgs, cropped_low_masks, cropped_final_imgs,
            cropped_final_masks, final_corners, final_sizes, frame)

在这里插入图片描述

图1. Crop images. a, Warpped. b, Wrapped to seam. c, Warp and crop. b, Cropped to seam.

在这里插入图片描述

图2. Stitching results. a, Original result. b, Result with seam line. c, Result with seam ploygons. d-f, Cropped results.

5. 部分图像拼接结果展示

在这里插入图片描述

Fig. S1. Examples using ORB detector and without crop&mask. a, bridge. b, building. c, sportfield. d, door. e, barcode. f, exposure_error. Left, Original. Middle, Stitching results. Right, Result with seam ploygons.

创作不易,麻烦点点赞和关注咯!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

统信系统常见问题解决方法

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 背景说明 本文所说的问题&#xff0c;是基于浪潮统信UOS的环境存在的问题。 一、WPS新建文档默认保存格式不对 解决办法&#xff1a; 1.编辑/opt/apps/cn.wps.wps-office-pro/files/kingsoft/wps-office/…

计算机网络【Google的TCP BBR拥塞控制算法深度解析】

Google的TCP BBR拥塞控制算法深度解析 宏观背景下的BBR 慢启动、拥塞避免、快速重传、快速恢复&#xff1a; 说实话&#xff0c;这些机制完美适应了1980年代的网络特征&#xff0c;低带宽&#xff0c;浅缓存队列&#xff0c;美好持续到了2000年代。 随后互联网大爆发&#x…

【中南林业科技大学】计算机组成原理复习包括题目讲解(超详细)

来都来了点个赞收藏关注一下再走呗&#x1f339;&#x1f339;&#x1f339;&#x1f339; 第1章&#xff1a;绪论 1.冯诺依曼机特点&#xff0c;与现代计算机的区别 冯诺依曼计算机的基本思想是&#xff1a;程序和数据以二进制形式表示&#xff0c;存储程序控制。在计算机中&…

Android14新特性 开启前台service服务

1. Android14新特性 1.1. 场景 在Android14&#xff08;targetSDK34&#xff09;系统手机开启前台service服务崩溃 ATAL EXCEPTION: mainProcess: com.inspur.lbrd, PID: 15634java.lang.RuntimeException: Unable to create service com.inspur.lbrd.service.KeepAliveServi…

[GDOUCTF 2023]泄露的伪装

[GDOUCTF 2023]泄露的伪装 wp 进入页面&#xff0c;会发现什么也没有&#xff1a; 目录扫描&#xff1a; dirsearch -u “http://node4.anna.nssctf.cn:28588/” 扫出了两个文件&#xff0c;都去访问一下&#xff0c;test.txt 是源码的副本&#xff0c;由于是文本文件&…

STL——集合算法

算法简介&#xff1a; set_intersection // 求两个容器的交集set_union // 求两个容器的并集set_difference // 求两个容器的差集 1.set_intersection 函数原型&#xff1a; set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest);…

NFS的基本使用

#江南的江 #每日鸡汤&#xff1a;岁月匆匆&#xff0c;时光荏苒&#xff0c;感悟人生路漫漫&#xff0c;不忘初心方得始终。 #初心和目标&#xff1a;和从前的自己博弈。 NFS(存储共享服务) 本文要点摘要&#xff1a; 下面将讨论什么是NFS&#xff0c;如何配置NFS&#xff0c;…

AI产品经理-借力

AI产品经理-借力&#xff1a;学会善用供应商改造自有产品 1.整个项目的工作方法 2.项目启动-行业调研 3.项目启动-供应商选型

【数据结构——二叉树】二叉树及其应用2023(头歌习题)【合集】

目录 第1关&#xff1a;括号表示法创建二叉树任务描述相关知识编程要求测试说明完整代码 第2关&#xff1a;先序序列创建二叉树任务描述相关知识二叉树的前序遍历如何创建一颗二叉树伪代码如下&#xff1a; 二叉树的中序遍历 编程要求测试说明完整代码 第3关&#xff1a;计算二…

蓝桥杯C/C++程序设计——单词分析

题目描述 小蓝正在学习一门神奇的语言&#xff0c;这门语言中的单词都是由小写英文字母组 成&#xff0c;有些单词很长&#xff0c;远远超过正常英文单词的长度。小蓝学了很长时间也记不住一些单词&#xff0c;他准备不再完全记忆这些单词&#xff0c;而是根据单词中哪个字母出…

Python魔法方法之__getattr__和getattribute

在Python中有这两个魔法方法容易让人混淆&#xff1a;__getattr__和getattribute。通常我们会定义__getattr__而从来不会定义getattribute&#xff0c;下面我们来看看这两个的区别。 __getattr__魔法方法 class MyClass:def __init__(self, x):self.x xdef __getattr__(self, …

HTML标签基础入门

HTML 基本语法概述标签关系HTML基础结构HTML常用标签标题标签示例 段落和换行标签示例 文本格式化标签示例 div和span标签示例 图像标签和路径示例 超链接标签示例 注释 ctrl/特殊字符示例 表格标签 表头单元格标签表格属性示例 合并单元格示例 列表标签无序列表有序列表自定义…

SpringBoot集成支付宝,看这一篇就够了。

前 言 在开始集成支付宝支付之前&#xff0c;我们需要准备一个支付宝商家账户&#xff0c;如果是个人开发者&#xff0c;可以通过注册公司或者让有公司资质的单位进行授权&#xff0c;后续在集成相关API的时候需要提供这些信息。 下面我以电脑网页端在线支付为例&#xff0c;介…

Linux系统使用yum安装MySQL

部署MySQL数据库有多种部署方式&#xff0c;常用的部署方式就有三种&#xff1a;yum安装、rpm安装以及编译安装。每一种安装方式都有自己的优势&#xff0c;那么企业当中通常情况下采用的是rpm和二进制安装的方式。 MySQL官网下载地址 Mysql 5.7的主要特性 更好的性能&#xf…

费曼学习法应用:谈自私和教育的引导

今天这个还是来源于我和九迁的对话&#xff0c;起因是中午吃饭的时候&#xff0c;九迁在学校与班主任老师和数学老师对话中带来的思考。 先听音频&#xff1a; 对话内容&#xff08;以下内容可以边听边看&#xff0c;属于语音转换过来的文字&#xff0c;最后有个总结&#xff0…

中文字符占用字节即相关原理(实现中文(中英混合)字符串的反转)

如有不对欢迎指正。 目录 一.ASCLL字符和中文字符 1.使用无符号数表示的原因(对于中文字符)&#xff1a; 2.但是并不是所有情况都是用无符号数(以下目前只是猜测,如有问题欢迎指正) &#xff1a; 1. 什么时候使用无符号数表示: 2. 不需要使用的情况&#xff1a; …

46、激活函数 - Relu 激活

本节介绍一个在神经网络中非常常见的激活函数 - Relu 激活函数。 什么是ReLU激活函数 ReLU 英文名为 Rectified Linear Unit,又称修正线性单元,是一种简单但很有效的激活函数,它的定义如下: 即当输入 x 大于零时,输出等于他自己;当输入小于等于零时,输出为零,下面是re…

2023年成都市中等职业学校学生技能大赛“网络搭建及应用”赛项竞赛样卷

2023年成都市中等职业学校学生技能大赛 “网络搭建及应用”赛项竞赛样卷 &#xff08;总分1000分&#xff09; 目录 2023年成都市中等职业学校学生技能大赛 “网络搭建及应用”赛项竞赛样卷 网络建设与调试项目&#xff08;500分&#xff09; 服务器搭建与运维项目&#xff08;…

2023年度业务风险报告:四个新风险趋势

目录 倒票的黄牛愈加疯狂 暴增的恶意网络爬虫 愈加猖獗的羊毛党 层出不穷的新风险 业务风险呈现四个趋势 防御云业务安全情报中心“2023年业务风险数据”统计显示&#xff0c;恶意爬虫风险最多&#xff0c;占总数的37.8%&#xff1b;其次是虚假账号注册&#xff0c;占18.79%&am…

负载均衡之LVS

LVS LVS 原理 IPVS LVS 的 IP 负载均衡技术是通过 IPVS 模块来实现的&#xff0c;IPVS 是 LVS 集群系统的核心软件&#xff0c;它的主要作用是&#xff1a;安装在 Director Server 上&#xff0c;同时在 Director Server 上虚拟出一个 IP 地址&#xff0c;用户必须通过这个虚…