24/10/14 视觉笔记 图像拼接融合

图像拼接分为四步

1.特征点提取

2.特征点匹配

3.仿射矩阵计算

4.图像拼接与融合

1.特征提取

找到图像中具有显著性信息点,并计算该点的特征表达

def detectAndDescrible(img):
    #构建STFT特征检测器
    sift = cv2.SIFT_create()
    #特征提取
    kps,features = sift.detectAndCompute(img,None)
    return kps,features

SIFT 是一种流行的计算机视觉算法,用于检测和描述图像中的关键点,这些关键点对于图像的旋转、尺度变换和亮度变化具有不变性。

SIFT 算法的主要原理可以概括为以下几个步骤:

  1. 尺度空间极值检测

    • SIFT 算法首先在不同尺度的空间中检测关键点。这是通过构建高斯尺度空间并计算每个像素点的高斯差分来实现的。在每个尺度上,算法会寻找能够成为关键点的局部极值点,这些点在高斯差分图中既是局部最大值也是局部最小值。
  2. 关键点定位

    • 一旦检测到潜在的关键点,算法会使用泰勒展开来更精确地定位这些关键点。这一步骤确保了关键点的位置和尺度的准确性。
  3. 方向分配

    • 为了使关键点具有旋转不变性,SIFT 算法为每个关键点分配一个或多个方向。这是通过计算图像局部区域的梯度方向直方图来实现的。每个关键点的方向由其周围区域的主方向决定。
  4. 关键点描述

    • SIFT 算法为每个关键点生成一个独特的描述子,该描述子捕捉了关键点周围区域的局部图像特征。描述子是通过将关键点周围的区域划分为16个子区域,并为每个子区域计算一个8维的梯度方向直方图来构建的,从而生成一个128维的向量。
  5. 匹配和筛选

    • 最后,SIFT 算法使用欧氏距离来匹配两幅图像中的关键点描述子。为了提高匹配的稳健性,算法通常会使用最近邻和次近邻距离比率测试来筛选匹配对。

存在问题:

会发现有高度相似的特征点

我们使用阈值ratio,用于确定好的匹配点对

完整代码

def matchKeyPoint(kps_l,kps_r,features_l,features_r,ratio):
    Match_idxAndDist = []#存储最近点位置,最近点距离,次近点位置,次近点距离
    for i in range(len(features_l)):
        #从features_r中找到与i距离最近的2个点
        min_IdxDis = [-1,np.inf]#距离最近的点
        secMin_IdxDis = [-1,np.inf]#距离第二近的点
        for j in range(len(features_r)):
            dist = np.linalg.norm(features_l[i] - features_r[j])
            if (min_IdxDis[1]>dist):
                secMin_IdxDis = np.copy(min_IdxDis)
                min_IdxDis = [j,dist]
            elif(secMin_IdxDis[1]>dist and secMin_IdxDis[1] != min_IdxDis[1]):
                secMin_IdxDis = [j,dist]
        Match_idxAndDist.append([min_IdxDis[0],min_IdxDis[1],secMin_IdxDis[0],secMin_IdxDis[1]])

    goodMatches = []
    #如果i与最近的2个点的距离差较大,那么就不是好的匹配点
    for i in range(len(Match_idxAndDist)):
        if(Match_idxAndDist[i][1]<=Match_idxAndDist[i][3]*ratio):
            goodMatches.append((i,Match_idxAndDist[i][0]))
    #获取匹配较好的点对
    goodMatches_pos = []
    for(idx,correspondingIdx) in goodMatches:
        psA = (int(kps_l[idx].pt[0]),int(kps_l[idx].pt[1]))
        psB = (int(kps_r[correspondingIdx].pt[0]),int(kps_r[correspondingIdx].pt[1]))
        goodMatches_pos.append([psA,psB])
    return goodMatches_pos

显示匹配点

#显示匹配点
def drawMatches(img_left,img_right,matches_pos):
    hl,wl = img_left.shape[:2]
    hr,wr = img_right.shape[:2]
    vis = np.zeros((max(hl,hr),wl + wr,3),dtype= np.uint8)
    vis[0:hl,0:wl] = img_left #将两幅图像复制到画布上
    vis[0:hr,wl:] = img_right

    for(img_left_pos,img_right_pos)in matches_pos: #绘制匹配点连线
        pos_l = img_left_pos
        pos_r = img_right_pos[0] + wl,img_right_pos[1]
        cv2.circle(vis,pos_l,3,(0,0,255),1)    #绘制红色和绿色的圆圈来标记匹配点
        cv2.circle(vis,pos_r,3,(0,255,0),1)
        cv2.line(vis,pos_l,pos_r,(255,0,0),1) #绘制黄色线
    return vis

仿射矩阵计算

在图像拼接中,仿射矩阵的计算是一个关键步骤,它用于将图像从一个视角转换到另一个视角,从而实现图像的对齐。

以下是计算仿射矩阵的步骤:

  1. 选择对应点: 通常需要至少三个非共线的点对来唯一确定一个仿射变换。这些点对可以是图像中的角点、边缘或其他特征点。

  2. 建立方程组: 对于每一对对应点 (x1,y1) 和(x2​,y2​),我们可以建立以下方程组:  其中,(a11,a12,a13) 和 (a21,a22,a23) 是仿射矩阵的元素。

  3. 构建矩阵A和向量B: 将上述方程组扩展到所有点对,并将它们写成矩阵形式:对于每个点对,我们添加一行到矩阵A中,并添加对应的 x2,y2​ 到向量B中。

  4. 求解仿射矩阵: 使用最小二乘法或其他数值方法求解方程组 ATAX=ATB,其中 AT 是矩阵A的转置。这将给出仿射矩阵 XX 的元素。

  5. 归一化和平移: 由于仿射矩阵的最后一行通常是 [0,0,1],我们可以通过归一化前两行来确保矩阵的正确性。然后,如果需要,我们可以添加平移向量。

def solve_homography(P, m):
    try:
        A = []
        for r in range(len(P)):
            A.append([-P[r, 0], -P[r, 1], -1, 0, 0, 0, P[r, 0] * m[r, 0], P[r, 1] * m[r, 0], m[r, 0]])
            A.append([0, 0, 0, -P[r, 0], -P[r, 1], -1, P[r, 0] * m[r, 1], P[r, 1] * m[r, 1], m[r, 1]])
        u, s, vt = np.linalg.svd(A)
        H = np.reshape(vt[-1], (3, 3))  # Corrected index from 8 to -1
        H = H / H[2, 2]  # Normalize the matrix
    except Exception as e:
        print("Error:", e)
        return None  # Return None or a default value if an error occurs
    return H

RANSAC(随机抽样一致性)

是一种用于从包含异常值(外点)的数据集中估计数学模型参数的迭代方法。在图像拼接中,RANSAC算法尤其有用,因为它可以有效地剔除错误匹配的特征点对,从而提高拼接的准确性。

  1. RANSAC估计: 使用RANSAC算法从匹配点对中估计最佳单应性矩阵(Homography)。这个过程涉及到随机选择匹配点对,计算单应性矩阵,然后评估这个矩阵的一致性。如果一个匹配点对与单应性矩阵的投影误差小于设定的阈值,则被认为是内点(正确的匹配点);否则,被认为是外点(错误的匹配点)。

def fitHomoMat(matches_pos, nIter=1000, th=5.0):
    dstPoints = []
    srcPoints = []
    for dstPoint, srcPoint in matches_pos:
        dstPoints.append(list(dstPoint))
        srcPoints.append(list(srcPoint))
    dstPoints = np.array(dstPoints)
    srcPoints = np.array(srcPoints)

    # 利用RANSAC算法,获取最优矩阵
    NumSample = len(matches_pos)
    threshold = th
    NumIter = nIter
    NumRandomSubSample = 4
    MaxInlier = 0
    Best_H = None
    for run in range(NumIter):
        SubSampleIdx = random.sample(range(NumSample), NumRandomSubSample)

        # 计算
        H = solve_homography(srcPoints[SubSampleIdx], dstPoints[SubSampleIdx])
        
        NumInlier = 0  # 初始化内点计数
        pos_Inlier = []  # 初始化内点位置列表
        for i in range(NumSample):
            if i not in SubSampleIdx:
                concateCoor = np.hstack((srcPoints[i], [1]))
                dstCoor = H @ concateCoor.T
                if dstCoor[2] <= 1e-8:
                    continue
                dstCoor = dstCoor / dstCoor[2]
                
                # 如果计算的目标点和匹配的目标点距离较近,则将这一对点定义为Inlier
                if np.linalg.norm(dstCoor[:2] - dstPoints[i]) < threshold:
                    NumInlier += 1  # 正确的更新方式
                    pos_Inlier.append((srcPoints[i], dstPoints[i]))
        
        if MaxInlier < NumInlier:
            MaxInlier = NumInlier
            Best_H = H
            save_Inlier_pos = pos_Inlier
    return Best_H, save_Inlier_pos

重叠部分的融合处理

def warp(img_left, img_right, HomoMat,blending_mode="linearrBlending"):

    hl, wl= img_left.shape[:2]
    (hr, wr) = img_right.shape [ :2]
    stitch_img = np.zeros( (max(hl, hr), wl + wr, 3), dtype=np.uint8)
    
    if (blending_mode == "noBlending"): #选择无混合模式,将左图复制到画布左侧
        stitch_img[:hl, :wl] =img_left
    
    # 从right img 转换到 left img
    inv_H=np.linalg.inv (HomoMat)  #计算给定单应性矩阵的逆矩阵
    for i in range(stitch_img.shape[0]):#遍历画布的每个像素,使用逆矩阵将右图的坐标转换到左图的坐标中,并复制相应的像素值
        for j in range(stitch_img.shape[1]):
            #计算左图 i,j 处 对应右图哪个坐标点
            coor = np.array([j, i, 1])
            img_right_coor = inv_H @ coor # the coordination of right image
            img_right_coor /= img_right_coor[2]
        
            #y为宽x为高
            y, x = int (round (img_right_coor[0])), int(round(img_right_coor[1]))
        
            #超出范围 不处理
            if (x < 0 or x >= hr or y < 0 or y >= wr) :
                continue
        
            stitch_img[i, j] = img_right[x, y]
    
    if (blending_mode == "linearBlending"):  #选择线性混合模式,调用相应的函数来处理图像重叠区域的融合
        stitch_img = linearBlending([img_left, stitch_img])
    elif (blending_mode == "linearBlendingWithConstant"):
        stitch_img = linearBlendingWithConstantWidth([img_left, stitch_img])
    # 去除黑边
    stitch_img = removeBlackBorder (stitch_img) 
    return stitch_img

去除黑边

通过检查图像的每一列和每一行是否全为黑色(即所有像素值均为0),然后移除全黑的列和行来实现。

def removeBlackBorder(img):
    h,w = img.shape[:2]
    reduced_h,reduced_w = h,w
    for col in range(w - 1,-1,-1):
        all_black = True
        for i in range(h):
            if(np.count_nonzero(img[i,col])>0): #检查是否为黑色
                all_black = False
                break
        if (all_black ==True):
            reduced_w = reduced_w-1

    for row in range(h-1,-1,-1):
        all_black = True,
        for i in range(reduced_w):
            if(np.count_nonzero(img[row,i])>0):
                all_black = False,
                break
            if(all_black == True):
                reduced_h = reduced_h-1
        return img[:reduced_h,:reduced_w]

图像融合

def linearBlending (imgs) :

    img_left, img_right = imgs
    (hl, wl) = img_left.shape[:2]
    (hr, wr) = img_right.shape[:2]
    img_left_mask = np.zeros((hr, wr), dtype=np.uint8)
    img_right_mask =np.zeros((hr, wr), dtype=np.uint8)
    
    #找到img_left 和 img_right 的mask部分 即非0部分
    for i in range (hl):
        for j in range (wl):
            if np.count_nonzero(img_left[i, j]) > 0:
                img_left_mask[i, j] = 1
    
    for i in range (hr) :
        for j in range (wr) :
            if np.count_nonzero(img_right[i, j]) > 0:
                img_right_mask[i, j] = 1
    
    #找到两图重合的部分
    overlap_mask = np.zeros((hr, wr), dtype=np.uint8)
    for i in range (hr) :
        for j in range (wr) :
            if (np.count_nonzero(img_left_mask[i, j]) > 0 and np.count_nonzero(img_right_mask[i, j]) >0):
                overlap_mask[i, j] = 1
    #计算重叠区域的线性alph值,即将色彩从img_left 到 img_right 逐步过度
    alpha_mask = np.zeros((hr, wr)) # alpha value depend on left image,
    for i in range (hr) :
        minIdx = maxIdx = -1
        for j in range (wr) :
            if (overlap_mask[i, j] == 1 and minIdx == -1):
                minIdx = j
            if (overlap_mask[i, j] == 1):
                maxIdx = j
    
        if (minIdx == maxIdx):#融合区域过小
            continue
        
        decrease_step = 1 / (maxIdx - minIdx)
        for j in range (minIdx, maxIdx + 1) :
            alpha_mask[i, j] =1- (decrease_step * (j - minIdx))
        
    linearBlending_img = np.copy(img_right)
    linearBlending_img[:hl,:wl]=np.copy(img_left)
    #线性混合
    for i in range (hr) :
        for j in range (wr) :
            if (np.count_nonzero(overlap_mask[i, j]) > 0):
                linearBlending_img[i, j] = alpha_mask[i, j] * img_left[i, j] + (1 - alpha_mask[i, j]) * img_right[i, j]
    
    return linearBlending_img

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

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

相关文章

3-3 AUTOSAR RTE 对SR Port的实现

返回总目录->返回总目录<- 目录 一、前言 二、显式访问 三、隐式访问 四、队列调用(Queued) 五、无效数据元素 一、前言 RTE作为SWC和BSW之间的通信机构,支持Sender-Receiver方式实现ECU内及ECU间的通信。 对于Sender-Receiver Port支持三种模式: 显式访问:若…

京东零售数据湖应用与实践

作者&#xff1a;陈洪健&#xff1a;京东零售大数据架构师&#xff0c;深耕大数据 10 年&#xff0c;2019 年加入京东&#xff0c;主要负责 OLAP 优化、大数据传输工具生态、流批一体、SRE 建设。 当前企业数据处理广泛采用 Lambda 架构。Lambda 架构的优点是保证了数据的完整性…

LeetCode|70.爬楼梯

这道题很像斐波那契数列&#xff0c;但是初始值不同&#xff0c;也有动态规划的解法&#xff0c;但是一开始我想到的是递归写法。现在我们站在第n阶台阶&#xff0c;那么&#xff0c;我们上一步就有两种可能&#xff1a;1、我们从第n-1阶台阶走一步上来的&#xff1b;2、我们从…

弹出“xinput1_3.dll文件缺失”的错误要怎么处理?一键修复xinput1_3.dll

当你尝试打开游戏或应用时&#xff0c;如果弹出“xinput1_3.dll文件缺失”的错误&#xff0c;请记得及时处理。这个文件是DirectX中用于处理游戏控制器输入的关键组件。这个问题可以通过几个简单的步骤轻松解决。本指南将教你如何快速恢复或替换丢失的xinput1_3.dll文件&#x…

免费Excel工作表同类数据合并工具

下载地址&#xff1a;https://pan.quark.cn/s/81b1aeb45e4c 在 Excel 表格里&#xff0c;当我们试图手动将多行同类数据合并为一行时&#xff0c;会遭遇诸多棘手的困难以及繁杂的操作流程。在确定哪些数据属于可合并的同类数据时&#xff0c;单纯依靠人工进行对比&#xff0c;极…

SQL数据库刷题sql_day33(连续3次为球队得分的球员名单)

描述 两支篮球队进行了激烈的比赛&#xff0c;比分交替上升。比赛结束后&#xff0c;你有一个两队分数的明细表&#xff08;名称为“分数表”&#xff09;。 表中记录了球队、球员号码、球员姓名、得分分数及得分时间。现在球队要对比赛中表现突出的球员进行奖励。 问题&#x…

用最短长度的绳子把整个花园围起来

给定一个数组 trees&#xff0c;其中 trees[i] [xi, yi] 表示树在花园中的位置。 你被要求用最短长度的绳子把整个花园围起来&#xff0c;因为绳子很贵。只有把 所有的树都围起来&#xff0c;花园才围得很好。 返回恰好位于围栏周边的树木的坐标。 示例 1: 输入: points […

MySQL 的数据类型

1.整数类型 1.1 tinyint tinyint 为小整数类型&#xff0c;存储空间为1个字节&#xff08;8位&#xff09;&#xff0c;有符号范围-128 ~ 127&#xff0c;无符号范围 0 ~ 255,此类型通常在数据库中表示类型的字段&#xff0c;如某一字段 type 表示学科,其中 “type1” 表示语文…

实战篇:(二)React 创建项目并连接 MySQL 后台的实战教程

React 创建项目并连接 MySQL 后台的实战教程 一、项目概述 本篇博客将介绍如何使用 React 搭建前端项目&#xff0c;并通过 Node.js 和 MySQL 实现简单的后台数据连接。通过这个项目&#xff0c;你将掌握从前端到后端数据库的基础开发流程&#xff0c;适合初学者或正在项目实…

中国各大一线及二线省会城市程序员收入大比拼,看看你所在的城市的统计是否准确

在中国&#xff0c;程序员的收入因城市、经验、学历等因素而有所不同。本文将对一线及二线省会城市的程序员收入进行详细比较&#xff0c;帮助大家了解各地的薪资水平。 一线城市程序员收入 上海 上海作为中国的经济中心&#xff0c;程序员收入最高。根据最新数据&#xff…

新生编程入门的方式探讨

关于如何编程入门&#xff0c;这是一个很好的问题。在上大学之前&#xff0c;并没有怎么接触电脑的我&#xff0c;也许可以谈一谈。 还记得在高中的时候&#xff0c;因为很多同学去网吧玩电脑打游戏&#xff0c;被学校开除&#xff0c;老师谆谆教诲大家不要去网吧&#xff0c;所…

Word粘贴时出现“文件未找到:MathPage.WLL”的解决方案

解决方案 一、首先确定自己电脑的位数&#xff08;这里默认大家的电脑都是64位&#xff09;二、右击MathType桌面图标&#xff0c;点击“打开文件所在位置”&#xff0c;然后分别找到MathPage.WLL三、把这个文件复制到该目录下&#xff1a;C:\Program Files\Microsoft Office\r…

SQLAlchemy入门:详细介绍SQLAlchemy的安装、配置及基本使用方法

SQLAlchemy是一个流行的Python SQL工具包和对象关系映射&#xff08;ORM&#xff09;框架&#xff0c;它为开发人员提供了一种高效、灵活的方式来与数据库进行交互。本文将详细介绍SQLAlchemy的安装、配置及基本使用方法&#xff0c;并通过代码示例和案例分析&#xff0c;帮助新…

SSL---SSL certificate problem

0 Preface/Foreword 0.1 SSL certificate problem 开发过程中&#xff0c;gitlab-runner连接gitlab时候出现SSL 证书问题。 场景&#xff1a;公司的gitlab runner服务器引入了SSL证书&#xff0c;每年都会主动更新一次。当前的gitlab-runner运行在PC机器上&#xff0c;但是g…

论文翻译 | OpenICL: An Open-Source Framework for In-context Learning

摘要 近年来&#xff0c;上下文学习&#xff08;In-context Learning&#xff0c;ICL&#xff09;越来越受到关注&#xff0c;并已成为大型语言模型&#xff08;Large Language Model&#xff0c;LLM&#xff09;评估的新范式。与传统微调方法不同&#xff0c;ICL无需更新任何参…

如何用好 CloudFlare 的速率限制防御攻击

最近也不知道咋回事儿,群里好多站长都反映被CC 攻击了。有人说依旧是 PCDN 干的,但明月感觉不像,因为有几个站长被 CC 攻击都是各种动态请求(这里的动态请求指的是.php 文件的请求)。经常被攻击的站长们都知道,WordPress /Typecho 这类动态博客系统最怕的就是这种动态请求…

C++模板初阶,只需稍微学习;直接起飞;泛型编程

&#x1f913;泛型编程 假设像以前交换两个函数需要&#xff0c;函数写很多个或者要重载很多个&#xff1b;那么有什么办法实现一个通用的函数呢&#xff1f; void Swap(int& x, int& y) {int tmp x;x y;y tmp; } void Swap(double& x, double& y) {doubl…

【自然语言处理】多头注意力Multi-Head Attention机制

多头注意力&#xff08;Multi-Head Attention&#xff09;机制是Transformer模型中的一个关键组件&#xff0c;广泛用于自然语言处理任务&#xff08;如机器翻译、文本生成等&#xff09;以及图像处理任务。它的核心思想是通过多个不同的注意力头来捕获输入的不同特征&#xff…

MedSAM2调试安装与使用记录

目录 前言一、环境准备多版本cuda切换切换cuda版本二 安装CUDNN2.1 检查cudnn 二、使用步骤1.安装虚拟环境2.测试Gradio3.推理 总结 前言 我们在解读完MedSAM之后&#xff0c;迫不及待想尝尝这个技术带来的福音&#xff0c;因此验证下是否真的那么6。这不&#xff0c;新鲜的使…

使用 KVM 在 Xubuntu 上创建 Windows 10 虚拟机

目录 前言说明注意准备 iso官网思博主(嘻嘻)拖动到虚拟机里面启动 virt-manager创建虚拟机选择本地安装介质选择 iso配置 内存 和 CPU选择 创建的虚拟机 保存的位置启动虚拟机看到熟悉的 Win10界面点击现在安装点击我没有产品密钥选择 Win10 专业工作站版勾选接受许可条款选择自…