OpenCV完美实现两张图片的全景拼接(详细教程)

目录

1,主要步骤

1.1  导入需要的包和模块,并读取两张待拼接的图片,这里我们假设它们为 left.jpg 和 right.jpg。

1.2  创建SIFT检测器

1.3 创建一个基于 FLANN 的匹配器

1.4  筛选过程删除掉一些不合适的匹配点,只保留最好的匹配点

1.5透视变换

1.6  消除重叠的效果,对两张图片进行加权处理

2,代码展示

3,效果展示


应用场景主要有两个方面:

  1. 风景或建筑物的拍摄

对于一些风景或建筑物的拍摄,有时候需要的画面宽度超出了单张图片所能提供的视野范围。这时可以通过拍摄多张图片并将它们拼接成一张更加宽阔的全景图来达到所需的效果。

  1. 科学研究

在一些科学研究中,需要对一定的区域进行高精度测量,例如地形测量、海洋测量等。这时候就需要一些宽视野相机来实现拍摄。但是,由于一张图片所能覆盖的区域有限,因此通常还需要将多张图片拼接成一张更大的全景图像,方便科学家们进行研究和分析。

1,主要步骤

  1. 读入待拼接的图片并调整大小;
  2. 使用 SIFT 或 SURF 算法提取图片的关键点和描述符;
  3. 使用基于 FLANN 的匹配器进行关键点匹配,并筛选出较好的匹配点;
  4. 计算视角变换矩阵,并使用透视变换对右边的图片进行变换;
  5. 消除重叠的效果,对两张图片进行加权处理;
  6. 输出拼接后的结果。

1.1  导入需要的包和模块,并读取两张待拼接的图片,这里我们假设它们为 left.jpgright.jpg

左视图:

右视图:

1.2  创建SIFT检测器

cv2.xfeatures2d.SIFT_create() 创建一个 SIFT 检测器。

也可以选择使用 cv2.SIFT_create()

不过前者是更新的版本,可能会更好一些

然后,在两张图片上分别使用这个检测器进行关键点检测和特征提取,获得关键点集合和描述符集合。

surf=cv2.xfeatures2d.SIFT_create()#可以改为SIFT
#sift = cv2.SIFT_create()
sift = cv2.xfeatures2d.SIFT_create()kp1,descrip1 = sift.detectAndCompute(imageA,None)
kp2,descrip2 = sift.detectAndCompute(imageB,None)

1.3 创建一个基于 FLANN 的匹配器

调用 cv2.FlannBasedMatcher() 创建一个基于 FLANN 的匹配器,并使用 knnMatch() 处理两张图片的特征描述符,得到最佳匹配。

indexParams = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
searchParams = dict(checks=50)
flann=cv2.FlannBasedMatcher(indexParams,searchParams)
match=flann.knnMatch(descrip1,descrip2,k=2)
good=[]

1.4  筛选过程删除掉一些不合适的匹配点,只保留最好的匹配点

for i,(m,n) in enumerate(match):if(m.distance<0.75*n.distance):good.append(m)

1.5透视变换

判断满足条件的匹配点数量是否大于阈值 MIN,如果大于,则进行视角变换矩阵的计算,将右边的图片 imageB 对其进行透视变换,得到 warpImg

if len(good) > MIN:src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)ano_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)M,mask = cv2.findHomography(src_pts,ano_pts,cv2.RANSAC,5.0)warpImg = cv2.warpPerspective(imageB, np.linalg.inv(M), (imageA.shape[1]+imageB.shape[1], imageB.shape[0]))direct=warpImg.copy()direct[0:imageA.shape[0], 0:imageB.shape[1]] =imageAsimple=time.time()show('res',warpImg)

同时,将左边的图片覆盖在变换后的图片上,得到 direct。最后,显示结果。

print(rows)
print(cols)
for col in range(0,cols):# 开始重叠的最左端if imageA[:, col].any() and warpImg[:, col].any():left = colprint(left)breakfor col in range(cols-1, 0, -1):#重叠的最右一列if imageA[:, col].any() and warpImg[:, col].any():right = colprint(right)break

1.6  消除重叠的效果,对两张图片进行加权处理

根据图片相对位置的不同,左边的图片和右边的图片有可能会在某些列出现重叠部分,为了消除这种不自然的效果,需要实现像素级的混合。首先找到左右图片开始重叠的位置和结束的位置,然后对两张图片进行加权处理,最后将加权后的图片输出。

#加权处理
res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):for col in range(0, cols):if not imageA[row, col].any():  # 如果没有原图,用旋转的填充res[row, col] = warpImg[row, col]elif not warpImg[row, col].any():res[row, col] = imageA[row, col]else:srcImgLen = float(abs(col - left))testImgLen = float(abs(col - right))alpha = srcImgLen / (srcImgLen + testImgLen)res[row, col] = np.clip(imageA[row, col] * (1 - alpha) + warpImg[row, col] * alpha, 0, 255)warpImg[0:imageA.shape[0], 0:imageA.shape[1]]=res
show('res',warpImg)
final=time.time()
print(final-starttime)

2,代码展示

import cv2
import numpy as np
from matplotlib import pyplot as plt
import time
def show(name,img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()
MIN = 10
FLANN_INDEX_KDTREE = 0
starttime = time.time()
img1 = cv2.imread('left.jpg') #query
img2 = cv2.imread('right.jpg') #train
imageA = cv2.resize(img1,(0,0),fx=0.2,fy=0.2)
imageB = cv2.resize(img2,(0,0),fx=0.2,fy=0.2)
surf=cv2.xfeatures2d.SIFT_create()#可以改为SIFT
#sift = cv2.SIFT_create()
sift = cv2.xfeatures2d.SIFT_create()kp1,descrip1 = sift.detectAndCompute(imageA,None)
kp2,descrip2 = sift.detectAndCompute(imageB,None)
#创建字典
indexParams = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
searchParams = dict(checks=50)
flann=cv2.FlannBasedMatcher(indexParams,searchParams)
match=flann.knnMatch(descrip1,descrip2,k=2)
good=[]
#过滤特征点
for i,(m,n) in enumerate(match):if(m.distance<0.75*n.distance):good.append(m)# 当筛选后的匹配对大于10时,计算视角变换矩阵
if len(good) > MIN:src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)ano_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)M,mask = cv2.findHomography(src_pts,ano_pts,cv2.RANSAC,5.0)warpImg = cv2.warpPerspective(imageB, np.linalg.inv(M), (imageA.shape[1]+imageB.shape[1], imageB.shape[0]))direct=warpImg.copy()direct[0:imageA.shape[0], 0:imageB.shape[1]] =imageAsimple=time.time()show('res',warpImg)
rows,cols=imageA.shape[:2]
print(rows)
print(cols)
for col in range(0,cols):# 开始重叠的最左端if imageA[:, col].any() and warpImg[:, col].any():left = colprint(left)breakfor col in range(cols-1, 0, -1):#重叠的最右一列if imageA[:, col].any() and warpImg[:, col].any():right = colprint(right)break
#加权处理
res = np.zeros([rows, cols, 3], np.uint8)
for row in range(0, rows):for col in range(0, cols):if not imageA[row, col].any():  # 如果没有原图,用旋转的填充res[row, col] = warpImg[row, col]elif not warpImg[row, col].any():res[row, col] = imageA[row, col]else:srcImgLen = float(abs(col - left))testImgLen = float(abs(col - right))alpha = srcImgLen / (srcImgLen + testImgLen)res[row, col] = np.clip(imageA[row, col] * (1 - alpha) + warpImg[row, col] * alpha, 0, 255)warpImg[0:imageA.shape[0], 0:imageA.shape[1]]=res
show('res',warpImg)
final=time.time()
print(final-starttime)

3,效果展示

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

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

相关文章

Hadoop3教程(十二):MapReduce中Shuffle机制的概述

文章目录 &#xff08;95&#xff09; Shuffle机制什么是shuffle&#xff1f;Map阶段Reduce阶段 参考文献 &#xff08;95&#xff09; Shuffle机制 面试的重点 什么是shuffle&#xff1f; Map方法之后&#xff0c;Reduce方法之前的这段数据处理过程&#xff0c;就叫做shuff…

利用ChatGPT练习口语

目录 ChatGPT 这两天发布了一个激动人心的新功能&#xff0c;App端&#xff08;包括iOS和Android&#xff09;开始支持语音对话以及图片识别功能。 这两个功能一如既往的优先开放给Plus用户使用&#xff0c;现在将App更新到最新版本&#xff0c;就能体验。 为什么说激动人心&a…

解决 vscode使用Prettier格式化js文件报错:Cannot find module ‘./parser-babylon‘

报错如下&#xff1a; ["ERROR" - 11:48:58] Error formatting document. ["ERROR" - 11:48:58] Cannot find module ./parser-babylon Require stack: - d:\VueCode\VueProject\myqqmusic\node_modules\prettier\index.js - c:\Users\Administrator.SKY-2…

ROS键盘遥控机器人,通过参数服务器指定速度

1、引言 在上节的驱动机器人&#xff0c;我们知道是cmd_vel话题发布一串Twist类型消息来控制&#xff0c;我们可以输入如下命令查看这个Twist的详细信息&#xff1a;rosmsg show geometry_msgs/Twist geometry_msgs/Vector3 linear float64 x float64 y float64 z geome…

1-图像读取

skimage import skimage from skimage import io, color# 读取灰度图&#xff0c;能做到16bit无损 img io.imread(CT-220s_681.tif) # 直接就是numpy类型&#xff0c;dtype根据图片格式决定,np默认float64格式 print(img.shape, type(img), img.dtype) print(img)# 读取彩色…

IDEA spring-boot项目启动,无法加载或找到启动类问题解决

问题描述&#xff1a;找不到或无法加载主类 xxx.xxx.xxx.Classname 解决方案&#xff1a; 1.检查启动设置&#xff1a; 启动类所在包运行环境&#xff08;一般选择默认即可&#xff09;设置完成即可进行运行测试 2.如果第一步没有解决问题&#xff0c;试着第二步&#xff1a…

广州华锐互动:炼钢工厂VR仿真实训系统

随着科技的发展&#xff0c;我们的教育体系和职业培训方法也在迅速变化。其中&#xff0c;虚拟现实&#xff08;VR&#xff09;技术的出现为我们提供了一种全新的学习和培训方式。特别是在需要高度专业技能和安全性的领域&#xff0c;如钢铁冶炼。本文将探讨如何使用VR进行钢铁…

机器人控制算法——两轮差速驱动运动模型

1.Introduction 本文主要介绍针对于两轮差速模型的逆运动学数学推导。因为在机器人控制领域&#xff0c;决策规划控制层给执行器输出的控制指令v(车辆前进速度)和w(角速度)&#xff0c;因此&#xff0c;我们比较关心&#xff0c;当底层两个驱动电机接收到此信息&#xff0c;如何…

Rust 基础

文章目录 一、变量1.1 不可变变量/可变变量/常量1.2 变量的可覆盖性 二、数据类型2.1 数据类型 & 编译器自动推导机制2.2 标量与复合 三、函数3.1 普通函数3.2 匿名函数/闭包3.3 函数指针3.4 高阶函数3.5 函数部分完整代码&#xff1a; 一、变量 1.1 不可变变量/可变变量/…

自定义安装Redhat8.6镜像:

目录 一、创建虚拟机 二、选择需要安装的镜像 三、选择正确的操作系统和版本 四、更改虚拟机名称和位置 五、配置处理器和内核数量以及内存 配置规则&#xff1a; 六、网络类型、I/O控制类型、磁盘类型使用推荐 即可 网络类型&#xff1a; I/O控制类型: 磁盘类型: 七…

报道 | 2023-2024年1月国际运筹优化会议汇总

2023年10月、11月、12月召开会议汇总&#xff1a; 2023 International Conference on Optimization and Applications (ICOA) Location: Abu Dhabi, United Arab Emirates Important dates: Conference: October 05-06, 2023 Details: https://lct.ac.ae/en/icoa/ 2023 INF…

21GA-ELM,遗传算法优化ELM预测,并和优化前后以及真实数值进行对比,确定结果,基于MATLAB平台,程序已经调通,可以直接运行,需要直接拍下。

GA-ELM&#xff0c;遗传算法优化ELM预测&#xff0c;并和优化前后以及真实数值进行对比&#xff0c;确定结果&#xff0c;基于MATLAB平台&#xff0c;程序已经调通&#xff0c;可以直接运行&#xff0c;需要直接拍下。 21matlab时间序列预测极限学习遗传优化算 (xiaohongshu.co…

【算法|前缀和系列No.1】牛客网 DP34 【模板】前缀和

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【牛客网刷题】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希…

如何从 Pod 内访问 Kubernetes 集群的 API

Kubernetes API 是您检查和管理集群操作的途径。您可以使用Kubectl CLI、工具(例如curl)或流行编程语言的官方集成库来使用 API 。 该 API 也可供集群内的应用程序使用。Kubernetes Pod 会自动获得对 API 的访问权限,并且可以使用提供的服务帐户进行身份验证。您可以通过使…

19 | 如何搞清楚事务、连接池的关系?正确配置是怎样的

事务的基本原理 在学习 Spring 的事务之前&#xff0c;你首先要了解数据库的事务原理&#xff0c;我们以 MySQL 5.7 为例&#xff0c;讲解一下数据库事务的基础知识。 我们都知道 当 MySQL 使用 InnoDB 数据库引擎的时候&#xff0c;数据库是对事务有支持的。而事务最主要的作…

Elasticsearch实现检索词自动补全(检索词补全,自动纠错,拼音补全,繁简转换) 包含demo

Elasticsearch实现检索词自动补全 自动补全定义映射字段建立索引测试自动补全 自动纠错查询语句查询结果 拼音补全与繁简转换安装 elasticsearch-analysis-pinyin 插件定义索引与映射建立拼音自动补全索引测试拼音自动补全测试繁简转换自动补全 代码实现demo结构demo获取 自动补…

【c语言】迷宫游戏

之前想写的迷宫游戏今天终于大功告成&#xff0c;解决了随机生成迷宫地图的问题&#xff0c;使用的是深度优先算法递归版本&#xff0c;之前的迷宫找通路问题用的是深度优先算法的非递归实现.之前写过推箱子&#xff0c;推箱子用到了人物的移动&#xff0c;以及碰到墙就不会走&…

【ALO-BP预测】基于蚁狮算法优化BP神经网络回归预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python学习----Day08

函数变量的作用域 全局作用域 全局作用域在程序执行时创建&#xff0c;在程序执行结束时销毁。所有函数以外的区域都是全局作用域。在全局作用域中定义的变量&#xff0c;都属于全局变量&#xff0c;全局变量可以在程序的任意位置被访问。 函数作用域 函数作用域在函数调用…

【Eclipse】解决插件下载速度太慢

解决方案&#xff1a;修改镜像 下面列出几个国内的镜像网站&#xff1a; 中国科学技术大学(5.6MB/s) http://mirrors.ustc.edu.cn/eclipse/ 北京理工大学&#xff08;600KB/s&#xff09; http://mirror.bit.edu.cn/eclipse/ 大连东软信息学院(400KB/s) http://mirrors.neuso…