• 1 图像拼接基础知识
    • 1.1 特征匹配 原理及代码示例
    • 1.2 单应性矩阵 原理及代码示例
  • 2 图像拼接(一)(直接拼接)
  • 3 图像拼接(二)(单应性矩阵 + 图像变换 + 拼接)
    • 3.1 单应性矩阵函数
    • 3.2 拼接函数 实现 及细节测试验证
    • 3.3 图像拼接
  • 4 后续完善(拼接缝隙过度、裁剪)
    • 4.1 输入图像大小一致 优化
    • 4.2 后续完善
  • 5 进阶实战--图像拼接 (实战项目)

P87 11

1 图像拼接基础知识

图像关系,有重叠部分

原始图像

在这里插入图片描述

拼接结果

在这里插入图片描述
在这里插入图片描述

第二张图像的左上角是原点(0,0),左边和上边的都是负值,不显示
在这里插入图片描述
在这里插入图片描述

左边的图片变换后,超出尺寸的就不显示了,
事实上显示出来的那一部分,是与第二张图重叠的部分

放大窗口

在这里插入图片描述

从左上往右下拉
在这里插入图片描述

之后将右边的图,平移过来

在这里插入图片描述

1.1 特征匹配 原理及代码示例

超详细教程:特征点检测与匹配(Harris角点检测、Shi-Tomasi角点检测、SIFT关键点检测、SURF特征检测、 ORB特征检测、暴力特征匹配、FLANN特)

1.2 单应性矩阵 原理及代码示例

教程:图像查找(特征匹配 + 单应性矩阵)

2 图像拼接(一)(直接拼接)

import cv2
import numpy as np #第一步,读取文件,将图片设置成一样大小640*480
#第二步,找特征点,描述子,计算单应性矩阵
#第三部,根据单应性矩阵对图像进行变换,然后平移
#第四部,拼接并输出结果img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')#设置成一样大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))#将两图横向压入栈中,即直接拼接
inputs=np.hstack((img1,img2))
cv2.imshow('input',inputs)cv2.waitKey(0)

可见,直接拼接只是简单的把两个图像拼在一起,不可用
在这里插入图片描述

3 图像拼接(二)(单应性矩阵 + 图像变换 + 拼接)

3.1 单应性矩阵函数

def get_homo(img1,img2):#1 创建特征转换对象#2 通过特征转换获得特征点和描述子#3 创建特征匹配器#4 进行特征匹配#5 验证过滤特征,找出有效的特征匹配点sift = cv2.xfeatures2d.SIFT_create()k1,d1=sift.detectAndCompute(img1,None)k2,d2=sift.detectAndCompute(img2,None)#创建特征匹配器bf=cv2.BFMatcher()matches=bf.knnMatch(d1,d2,k=2)verify_matches=[]verify_ratio = 0.8 #过滤器阈值 for m1,m2 in matches:if m1.destance < 0.8*m2.distance:verify_matches.append(m1)min_matches=8if len(verify_matches)>=min_matches:img1_pts=[] #img1特征坐标点img2_pts=[]for m in verify_matches:img1_pts.append(k1[m.queryIdx].pt)img2_pts.append(k2[m.trainIdx].pt)#img_pt数组格式 [(x1,y2),(x2,y2)....]#findHomography需要的数组坐标[[x1,y1],[x2,y2]...]img1_pts=np.float(img1_pts).reshape(-1,1,2)img2_pts=np.float(img2_pts).reshape(-1,1,2)H,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)return Helse :print('err: Not enough matches')exit()

3.2 拼接函数 实现 及细节测试验证

#定义拼接函数
def stitch_image(img1,img2,H):#1 获得每张图片的四个角点#2 对图片进行变换(单应性矩阵使图进行旋转,平移)#3 创建一张大图,将两张图拼接到一起#4 输出结果w1,h1=img1.shape[:2]#shape有3个值(高,宽,通道数),这里只取前两个值w2,h2=img2.shape[:2]#获取第一张图的四个角点,OpenCV里图像四个点顺序通常喜欢逆时针#获取的角点要变称浮点型,数组不能是二维的,要变成能三维img1_dism=np.float32([[0,0],[0,h1-1],[w1-1,h1-1],[w1-1,0]]).reshape(-1,1,2)img2_dism=np.float32([[0,0],[0,h2-1],[w2-1,h2-1],[w2-1,0]]).reshape(-1,1,2)img1_transform=cv2.perspectiveTransform(img1_dism,H)print(img1_dism)print(img2_dism)print(img1_transform)

输出img1,img2和变换后img1的四个角点

    print(img1_dism)print(img2_dism)print(img1_transform)

变换后img1_transform的四个角点,坐标有负值,原因超出了边界,超出的部分不显示;

在这里插入图片描述
在这里插入图片描述

    result_dism=np.concatenate((img2_dism,img1_transform),axis=0)aa=result_dism.min()#获取最小值print(aa)

在这里插入图片描述

只输出了一个数,是所有数据的最小值;
要输出最小的x值和最小y值,axis=0表示按x轴获取数据

aa=result_dism.min(axis=0)#axis=0表示按x轴获取数据

在这里插入图片描述

ravel()将二维数组转换成一维
可以看到双括号,变成了单括号
在这里插入图片描述
转换为整形,

aa=np.int32(result_dism.min(axis=0).ravel())

在这里插入图片描述

#最小值,向下取整-0.5,最大值向上取整,+0.5a=np.int32(result_dism.min(axis=0).ravel()-0.5)b=np.int32(result_dism.max(axis=0).ravel()+0.5)print(a)print(b)

在这里插入图片描述

变换之后的图,需要平移

#单应性矩阵变换,未平移
result_img=cv2.warpPerspective(img1,H,(max_x-min_x,max_y-min_y))#min是负值,减号,就相当于加

在这里插入图片描述

#平移,即乘以一个齐次坐标
#[1,0,dx]
#[0,1,dy]
#[0,0,1 ]

transform_array=np.array([[1,0,transform_dist[0]],[0,1,transform_dist[1]],[0,0,1]])#单应性矩阵变换result_img=cv2.warpPerspective(img1,transform_array.dot(H),(max_x-min_x,max_y-min_y))#min是负值,减号,就相当于加return result_img

至此,图像一的变换与平移完成
在这里插入图片描述

3.3 图像拼接

#找到合适的位置把图二拼接过来result_img[transform_dist[1]:transform_dist[1]+h2,transform_dist[0]:transform_dist[0]+w2]=img2

到此处,拼过工作已经完后,证情况下运行出拼接结果。

但是结果却出错了,图像的宽高颠倒了
在这里插入图片描述
跳转到45行,是最后写的一个拼接位置,,好像也没有什么毛病。
在这里插入图片描述

反复的全文检查了好几遍,也没发现问题。

最后发现这里,不一致;
在这里插入图片描述

shape数据存储顺序为:高,宽,通道数;
而写的是w,h,颠倒了,调换过来问题解决!

 #获取原始图像的高宽h1,w1=img1.shape[:2]#shape有3个值(高,宽,通道数),这里只取前两个值h2,w2=img2.shape[:2]

运行结果;
在这里插入图片描述

完整代码:

import cv2
import numpy as np #根据单应性矩阵对图像进行变换,及拼接
def stitch_image(img1,img2,H):#1 获得每张图片的四个角点#2 对第二张图片进行变换(单应性矩阵使图进行旋转,平移)#3 创建一张大图,将两张图拼接到一起#4 输出结果#获取原始图像的高宽h1,w1=img1.shape[:2]#shape有3个值(高,宽,通道数),这里只取前两个值h2,w2=img2.shape[:2]#获取第一张图的四个角点,OpenCV里图像四个点顺序通常喜欢逆时针#获取的角点要变称浮点型,数组不能是二维的,要变成能三维img1_dism=np.float32([[0,0],[0,h1],[w1,h1],[w1,0]]).reshape(-1,1,2)img2_dism=np.float32([[0,0],[0,h2],[w2,h2],[w2,0]]).reshape(-1,1,2)img1_transform=cv2.perspectiveTransform(img1_dism,H)#print(img1_dism)#print(img2_dism)#print(img1_transform)result_dism=np.concatenate((img2_dism,img1_transform),axis=0)print(result_dism)#axis=0表示按x轴获取数据,获得最小x值y值,ravel()将二维数组转换成一维,转换为整形,#最小值,向下取整-0.5,最大值向上取整,+0.5[min_x,min_y]=np.int32(result_dism.min(axis=0).ravel()-0.5)[max_x,max_y]=np.int32(result_dism.max(axis=0).ravel()+0.5)#图像变换之后,部分数均已经超出显示范围,需要平移到大窗口中#平移的距离transform_dist = [-min_x,-min_y] #加负号,变成正值#平移,即乘以一个齐次坐标#[1,0,dx]#[0,1,dy]#[0,0,1 ]transform_array=np.array([[1,0,transform_dist[0]],[0,1,transform_dist[1]],[0,0,1]])#单应性矩阵变换#到此处图像一的变换与平移完成result_img=cv2.warpPerspective(img1,transform_array.dot(H),(max_x-min_x,max_y-min_y))#min是负值,减号,就相当于加#到此处图像一的变换与平移完成#找到合适的位置把图二拼接过来result_img[transform_dist[1]:transform_dist[1]+h2,transform_dist[0]:transform_dist[0]+w2]=img2return result_img#定义单应性矩阵函数
def get_homo(img1,img2):#1 创建特征转换对象#2 通过特征转换获得特征点和描述子#3 创建特征匹配器#4 进行特征匹配#5 验证过滤特征,找出有效的特征匹配点sift = cv2.xfeatures2d.SIFT_create()k1,d1=sift.detectAndCompute(img1,None)k2,d2=sift.detectAndCompute(img2,None)#创建特征匹配器bf=cv2.BFMatcher()matches=bf.knnMatch(d1,d2,k=2)#过滤特征,找出有效的特征匹配点verify_matches=[]verify_ratio = 0.8 #过滤器阈值 for m1,m2 in matches:if m1.distance < 0.8 * m2.distance:verify_matches.append(m1)min_matches=8if len(verify_matches)>min_matches:img1_pts=[] #img1特征坐标点img2_pts=[]for m in verify_matches:img1_pts.append(k1[m.queryIdx].pt)img2_pts.append(k2[m.trainIdx].pt)#img_pt数组格式 [(x1,y2),(x2,y2)....]#findHomography需要的数组坐标[[x1,y1],[x2,y2]...]img1_pts=np.float32(img1_pts).reshape(-1,1,2)img2_pts=np.float32(img2_pts).reshape(-1,1,2)H,mask=cv2.findHomography(img1_pts,img2_pts,cv2.RANSAC,5.0)return Helse :print('err: Not enough matches')exit()#第一步,读取文件,将图片设置成一样大小640*480
#第二步,找特征点,描述子,计算单应性矩阵
#第三部,根据单应性矩阵对图像进行变换,然后平移
#第四部,拼接并输出结果img1=cv2.imread('map1.png')
img2=cv2.imread('map2.png')#设置成一样大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))#将两图横向压入栈中,即直接拼接
inputs=np.hstack((img1,img2))#获得单应性矩阵
H=get_homo(img1,img2)#根据单应性矩阵对图像进行变换,及拼接
result_image=stitch_image(img1,img2,H)cv2.imshow('input',result_image)
cv2.waitKey(0)

4 后续完善(拼接缝隙过度、裁剪)

4.1 输入图像大小一致 优化

上面的代码示例,手动设置img1,和img的尺寸

#设置成一样大小640*480
img1=cv2.resize(img1,(640,480))
img2=cv2.resize(img2,(640,480))

现在改为,自动确定尺寸。

判断图片尺寸是否一致,
如果 一样大,不做resize;
如果不一样大,就要resize,选择两幅图中最小的宽高作为resize后的尺寸。

#判断图片尺寸是否一致,如果不一样大,就要resize,这里选择两幅图中最小的宽高
if (imageA.shape[0]==imageB.shape[0] and imageA.shape[1]==imageB.shape[1])!=1:h=min(imageA.shape[1],imageB.shape[1])w=min(imageA.shape[0],imageB.shape[0])imageA=cv2.resize(imageA,(h,w))#注意这里尺寸(高,宽),和平时的习惯宽高有点不一样imageB=cv2.resize(imageB,(h,w))print('修改后尺寸:',imageA.shape,imageB.shape)#输出调整后的尺寸

下图输出信息分别为:

两张图片原始尺寸;
resize后的尺寸;
拼接后的尺寸;
在这里插入图片描述

4.2 后续完善

5 进阶实战–图像拼接 (实战项目)

上面的图像拼接,重在展示基本原理,但存在拼接缝隙过度等一些问题。只用于实验,不能满足实际项目要求。

下面是,实战项目,实现。

实战项目:进阶实战–图像拼接(二) (实战一:图像拼接 附完整代码、实战二:图像拼接 附完整代码)

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git 中切换到指定 tag

在 Git 中切换到指定 tag&#xff08;比如 v1.22.1&#xff09;的正确做法如下&#xff1a;1️⃣ 查看已有的 taggit tag会列出所有可用的版本&#xff0c;比如&#xff1a;v1.21.0 v1.22.0 v1.22.1 v1.23.02️⃣ 切换到指定 taggit checkout tags/v1.22.1 -b v1.22.1解释&…

rust 从入门到精通之变量和常量

变量和常量 随着软件系统安全的重要性与日俱增, rust这门集聚高并发, 安全, 适配云环境的编程语言在市场上得到了越来越高的认可和关注。但其复杂的机制使其难以学习。且其很多特性对于其他语言是全新的&#xff0c;这加剧了学习的困难程度。教程主要针对rust基础进行讲解, 虽然…

2508C++,支持rdma通信的高性能rpc库

原文 [重磅]支持rdma通信的高性能的rpc库–yalantinglibs.coro_rpc yalantinglibs的coro_rpc是基于C20的协程的高性能的rpc库,提供了简洁易用的接口,让用户几行代码就可实现rpc通信,现在coro_rpc除了支持tcp通信之外还支持了rdma通信(ibverbs). 通过简单示例来感受一下rdma通…

FastAPI + React:现代 Web 前后端分离开发的全栈实践指南

一、为什么选 FastAPI React&#xff1f; 性能&#xff1a;FastAPI 基于 Starlette Uvicorn&#xff0c;QPS 与 Node/Go 同级&#xff0c;实测 3 倍于 Flask&#xff1b;React 虚拟 DOM 代码分割&#xff0c;首屏 < 1.2 s。效率&#xff1a;FastAPI 内置 Swagger/OpenAPI…

嵌入式硬件篇---电平转换电路

电平转换电路是电子电路中用来实现不同电压信号之间转换的关键电路&#xff0c;比如把 3.3V 的信号转换成 5V&#xff0c;或者把 5V 转换成 1.8V&#xff0c;确保不同电压的芯片、模块能正常通信。下面用通俗易懂的方式介绍几种常见的电平转换电路&#xff1a;一、电阻分压电路…

SAP ABAP IS SUPPLIED

效果 此谓词表达式用于检查过程的某个形式参数“para”是否已赋值或被请求使用。如果在调用时实际参数被赋值给了该形式参数&#xff0c;则该表达式为真。 这种关系表达式仅能在函数模块和方法中使用。而对于“para”而言&#xff0c;所有可选的形参都可以进行指定。 加上“NOT…

视频内容提取与AI总结:提升学习效率的实用方法

文章目录1、前言2、方法介绍2.1 B站视频处理方案2.2 通用视频处理方案2.3 AI内容总结3、实际效果4、使用建议5、技术发展趋势6、总结&#x1f343; 作者介绍&#xff1a;25届双非本科网络工程专业&#xff0c;阿里云专家博主&#xff0c;专注于 AI 原理、AI 应用开发、AI 产品设…

JVM 面试精选 20 题

目录1. 什么是 JVM、JDK 和 JRE&#xff1f;它们之间的关系是什么&#xff1f;2. Java 内存区域&#xff08;运行时数据区&#xff09;有哪些&#xff1f;3. 说说你对 JVM 垃圾回收机制的理解。4. 常用的垃圾回收算法有哪些&#xff1f;5. 什么是 Minor GC、Major GC 和 Full G…

CMIP6 气候模式核心特性解析

在全球气候变化研究中&#xff0c;CMIP6&#xff08;第六次耦合模式比较计划&#xff09;的气候模式是关键工具。以下从研发背景与核心能力角度&#xff0c;解析五类主流模式的技术特点与适用场景。 一、主流模式技术特性 1. CanESM5/CanESM5-1&#xff08;加拿大环境与气候变…

【牛客刷题】BM63 跳台阶:三种解法深度解析(递归/DP动态规划/记忆化搜索)

文章目录 一、题目介绍 1.1 题目描述 1.2 示例 二、算法设计思路 2.1 核心问题分析 2.2 斐波那契数列关系 三、流程图 解法1:递归法(自顶向下) 解法2:动态规划(自底向上) 解法3:记忆化搜索(递归优化) 解法4: 优化DP流程(推荐) 四、解法实现 五、复杂度分析对比 六、…

《解构WebSocket断网重连:指数退避算法的前端工业级实践指南》

WebSocket作为客户端与服务器双向通信的核心载体,支撑着从在线协作、金融行情到即时通讯等各类高实时性场景。然而,网络环境的动态变化—从用户设备的Wi-Fi与蜂窝网络切换,到公共网络的临时拥塞,再到服务器的短暂重启—都可能导致WebSocket连接中断,进而引发数据传输停滞、…

医疗洁净间的“隐形助手”:富唯智能复合机器人如何重塑手术器械供应链

当手术刀片在无影灯下传递时&#xff0c;0.01mm的抓取偏差可能意味着感染风险——而富唯智能复合机器人以0.02mm的重复定位精度与99.999%无菌操作的硬实力&#xff0c;正成为高端医疗产线中替代人力的关键技术支点。一、医疗上下料的三大痛点&#xff1a;精度、洁净与连续性1.毫…

《设计模式》工厂方法模式

1.工厂方法模式&#xff08;Factory Method&#xff09;定义 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 1.1 UML图&#xff1a; 主要有4个对象&#xff1a; 抽象工厂&#xff08;Abstract Creator&#xf…

冒泡排序——简单理解和使用

阅前声明&#xff1a;如果想直接了解冒泡排序的简化思想&#xff0c;请跳至文章尾部在介绍之前&#xff0c;我们先看一个用到该功能的实战训练&#xff08;本人也是从中开始认识到冒泡排序这个函数定义&#xff09;对于小白来说&#xff0c;我的思路如下&#xff1a;1.题目中涉…

AI应用商业化加速落地 2025智能体爆发与端侧创新成增长引擎

今年以来&#xff0c;人工智能 (AI) 正在进入从算力投入到云服务消耗再到商业化收入&#xff0c;最终回到算力再投入的良性循环&#xff0c;而 AI 应用的起量正是推动这一飞轮效应的关键。7 月 31 日&#xff0c;国务院常务会议审议通过了《关于深入实施 “人工智能 ” 行动的意…

Pytest测试框架基础及进阶

Pytest测试框架基础# Pytest测试框架介绍# Pytest是Python一款三方测试框架&#xff0c;用于编写和运行单元测试、集成测试和功能测试。Pytest测试框架具有简单、灵活、易于扩展等特点&#xff0c;被广泛应用于Python项目的测试工作中。 Pytest主要特点&#xff1a; 简单易用…

航空装备先进加工工艺与制造技术论坛——2025成都航空装备展

300参展企业 11500㎡展区面积 7大专业展区 12000观众规模15同期会议 160发言嘉宾 5000参会嘉宾 100媒体报道航空工业飞速发展&#xff0c;先进加工工艺与制造技术成为了支撑航空装备性能提升、质量保障和产能优化的核心基石。为探索前沿技术路径、凝聚行业创新力量&#xff0c;…

为什么品牌更愿意为新品打广告?

品牌资源向新品广告倾斜&#xff0c;可以说是市场上的普遍现象。尤其对于没有明星产品的品牌而言&#xff0c;新品推广时企业的重要曝光节点。下面就让我们一同来了解下&#xff0c;为什么品牌更愿意为新品打广告。一、市场需求更充分新品广告往往承担着市场教育的功能&#xf…

电子电气架构 --- 关于整车信息安全的一些思考

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

报错:Eplan无法打开数据库的解决方法

详细报错信息&#xff1a;无法打开数据库 E:\eplan\部件\Microsoft\ESS_part001.mdb。针对64位版本的EPLAN 平台需要使用64位版本的Microsoft Office. 一、报错及解决方法 报错信息&#xff1a;无法打开数据库 E:\eplan\部件\Microsoft\ESS_part001.mdb。针对64位版本的EPLAN 平…