当前位置: 首页 > news >正文

OpenCV Python——图像拼接(一)(图像拼接原理、基础知识、单应性矩阵 + 图像变换 + 拼接)

  • 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.dtcms.com/a/334108.html

相关文章:

  • 国外护理学专业期刊Top10分析评介
  • 知识点汇总LinuxC高级 -1
  • 【嵌入式FreeRTOS#7】中断管理实验
  • 《C++进阶之继承多态》【多态:概念 + 实现 + 拓展 + 原理】
  • MoE及其优化技术->COMET(字节)
  • Spring MVC 九大组件源码深度剖析(三):ThemeResolver - 动态换肤的奥秘
  • 国产碳化硅模块及顶部散热的11种封装产品介绍应用
  • 标准瓦片层级0~20,在EPSG:4326坐标系下,每个像素点代表的度数
  • Spring AI Starter和文档解读
  • AI应用安全 - Prompt注入攻击
  • HTTP 代理服务器的 C++ 实现与分析:客户端通过代理访问 HTTP 站点的主页劫持流程(软件实现+流程演示+原理讲解)
  • 【昇腾】单张48G Atlas 300I Duo推理卡MindIE+WebUI方式跑7B大语言模型_20250816
  • 护理学新境界
  • Tello无人机与LLM模型控制 ROS
  • 力扣hot100 | 矩阵 | 73. 矩阵置零、54. 螺旋矩阵、48. 旋转图像、240. 搜索二维矩阵 II
  • RK3568 NPU RKNN(二):RKNN-ToolKit2环境搭建
  • 人工智能中的(特征选择)数据过滤方法和包裹方法
  • C++ 内存管理(内存分布 , 管理方式 , new和delete实现原理)
  • 前端开发入门书籍推荐:Vue.js 3与前端基础的完美组合
  • 在openEuler24.03 LTS上高效部署Apache2服务的完整指南
  • Vue3从入门到精通:5.2 Vue3构建工具与性能优化深度解析
  • InfluxDB 数据迁移工具:跨数据库同步方案(二)
  • 美国服务器环境下Windows容器工作负载智能弹性伸缩
  • NVIDIA ORIN AGX编译烧写镜像操作步骤
  • 集成运算放大器(反向比例,同相比例)
  • Hadoop面试题及详细答案 110题 (16-35)-- HDFS核心原理与操作
  • Spark Shuffle中的数据结构
  • 《MySQL 数据库备份与视图创建全流程:从数据迁移到高效查询实战》
  • MySQL 全文索引指南
  • 机器学习 [白板推导](十二)[卡曼滤波、粒子滤波]