基于OpenCv的图像全景拼接

⚠申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计6757字,阅读大概需要3分钟
🌈更多学习内容, 欢迎👏关注👀【文末】我的个人微信公众号:不懂开发的程序猿
个人网站:https://jerry-jy.co/

❗❗❗知识付费,🈲止白嫖,有需要请后台私信或【文末】个人微信公众号联系我

基于OpenCv的图像全景拼接

  • 基于OpenCv的图像全景拼接
    • 任务需求
    • 任务目标
      • 1、掌握基于OpenCv提取SIFT特征点
      • 2、掌握基于OpenCv使用SIFT描述子进行Brute-Force蛮力匹配
      • 3、掌握基于OpenCv计算单应性矩阵
    • 任务环境
      • 1、jupyter开发环境
      • 2、OpenCv
      • 3、python3.6
    • 任务实施过程
      • 一、图像全景拼接
        • 1.导入所需要的工具包和图像
        • 2.提取A、B图片SIFT特征描述子
        • 3.Brute-Force蛮力匹配两张图片所有特征点
        • 4当筛选后的匹配对大于4时,计算视角变换矩阵
        • 5.图片A进行视角变换并与B图结合
        • 6.可视化配对关键点
      • 二、拼接图像黑边去除
        • 1.定义图像黑边去除函数
        • 2.去除拼接后图像黑边
      • 三、多张图像全景拼接
        • 1.两张图像全景拼接
        • 2.多张图像全景拼接
      • 四、任务小结
  • 说明

基于OpenCv的图像全景拼接

任务需求

图像拼接的主要目的是为了解决相机视野限制,生成更宽的图像场景。简单来说图像拼接技术就是把若干幅有重叠部分的图像合成一幅大视角宽幅面的图像。图像拼接技术已普遍出现在海洋和矿产勘测、卫星遥感探索、医学成像、计算机特效生成以及近期热门的虚拟现实等领域。

本次实验是基于特征点匹配的全景图像拼接算法,首先提取各图像中的SIFT特征,通过特征点匹配完成两幅图像的配准;再根据图像配准结果计算出图像间的变换矩阵;最后通过视角变换矩阵变换图像进行拼接。

在这里插入图片描述

任务目标

1、掌握基于OpenCv提取SIFT特征点

2、掌握基于OpenCv使用SIFT描述子进行Brute-Force蛮力匹配

3、掌握基于OpenCv计算单应性矩阵

任务环境

1、jupyter开发环境

2、OpenCv

3、python3.6

任务实施过程

一、图像全景拼接

图像全景拼接具体地说就是使用多个摄像机对同一个场景在不同角度拍摄,把得到的多个图像进行校正、去噪、匹配、融合,最终构建成一个质量高、清晰、边缘平滑、分辨率高的图像。

1.导入所需要的工具包和图像
import cv2 # 导入opencv
import matplotlib.pyplot as plt # 导入绘图模块
import numpy as np # 导入numpy库
from utils import im_show # 导入显示图像函数
# 绘制图像直接展示,不用调用plt.show()
%matplotlib inline 
# 用来正常显示中文标签
plt.rc('font',family="SimHei")
# 读取拼接图像(右边,左边)
imgA = cv2.imread(r'./experiment/data/2.jpg')
imgB = cv2.imread(r'./experiment/data/1.jpg')
# 设置画布大小
plt.figure(figsize=(15,12))
# 显示图像
im_show('原图像',np.hstack((imgA,imgB)))

在这里插入图片描述

2.提取A、B图片SIFT特征描述子
# 先将两张照片转换成灰度图
grayA = cv2.cvtColor(imgA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imgB, cv2.COLOR_BGR2GRAY)
# 建立SIFT生成器
descriptor = cv2.SIFT_create()
# 检测两个图像的SIFT特征点,并计算描述子
kpsA, featuresA = descriptor.detectAndCompute(grayA, None)
kpsB, featuresB = descriptor.detectAndCompute(grayB, None)
print('图像A和B的提取的SIFT关键点个数:',len(kpsA),len(kpsB))
# 将得到的关键点坐标转换成NumPy数组
# kpsA = np.float32([kpA.pt for kpA in kpsA])
# kpsB = np.float32([kpB.pt for kpB in kpsB])

在这里插入图片描述

3.Brute-Force蛮力匹配两张图片所有特征点
# 创建BFMatcher对象,使用默认参数做蛮力匹配
matcher = cv2.BFMatcher()
# 使用KNN检测来自A、B图的SIFT特征匹配对,K=2表示一个特征点对应与它最佳匹配的2个特征点
rawMatches = matcher.knnMatch(featuresA, featuresB, k=2)
# 应用比率测试
# 使用for循环,设一个特征点对应的两个特征点的距离m和n
# 如果m<0.75n(m和n表示距离)表示距离相对较近,将满足条件的匹配保留下来
# queryIdx是一组关键点的索引,trainIdx是另一组关键点的索引
good = []
matches = []
for m, n in rawMatches:
    if m.distance < 0.75 * n.distance:
        matches.append((m.trainIdx, n.queryIdx))
        good.append([m])
print('两张图片一共匹配了{}对关键点'.format(np.array(matches).shape[0]))
# 使用KNN方法绘制匹配点,flags=0表示画特征点和连线,flags=2表示不画特征点。
img = cv2.drawMatchesKnn(imgA,kpsA,imgB,kpsB,good,None,flags=2)
# 设置画布大小
plt.figure(figsize=(15,12))
im_show('使用SIFT描述子的蛮力匹配的关键点',img)

在这里插入图片描述

4当筛选后的匹配对大于4时,计算视角变换矩阵

视角变换矩阵也被称为单应性矩阵(H矩阵)。图像拼接之前要先对图像进行角度变换的操作。得到H矩阵(3*3)最少需要4对配对点(x,y)。

# 将得到的关键点坐标转换成NumPy数组
kpsA = np.float32([kpA.pt for kpA in kpsA])
kpsB = np.float32([kpB.pt for kpB in kpsB])
if len(matches) > 4:
    # 获取匹配对的点坐标
    ptsA = np.float32([kpsA[i] for (_, i) in matches])
    ptsB = np.float32([kpsB[i] for (i, _) in matches])
    # findHomography()函数计算视角变换矩阵
    # ptsA, ptsB表示两幅图的sift特征点坐标
    # cv2.RANSAC表示用RANSAC选择最适合的配对特征点
    H, status = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, 4.0)
    # 得到的返回值H就是视角变换矩阵H(3*3)
    print(H.shape,H)

在这里插入图片描述

5.图片A进行视角变换并与B图结合
# 获取两个图像的高(h)和宽(w)
hA, wA = imgA.shape[:2]
print((hA, wA))
hB, wB = imgB.shape[:2]
print((hB, wB))
# warpPerspective()透视变换函数,放入源图像,H矩阵,设置变换后图像的形状
result = cv2.warpPerspective(imgA, H, (wA+wB, hA))
# 查看A图片变换后图像
plt.figure(figsize=(15,12))
im_show('图像A变换后', result)

在这里插入图片描述

在这里插入图片描述

# 将图片B传入result图片最左端
result[:hB, :wB] = imgB
# 查看拼接后的图像
plt.figure(figsize=(15,12))
im_show('图像拼接后', result)

在这里插入图片描述

6.可视化配对关键点
# 创建一个两个图像宽度的全是0的数组
vis = np.zeros((hA, wA+wB, 3), dtype=np.uint8)
# 对数组的切片赋值,左边为imgA,右边为imgB
vis[0:hA, 0:wA] = imgA
vis[0:hB, wA:] = imgB
# 联合遍历,画出匹配对
# status是计算h矩阵返回的掩码,值为1认为是匹配点,0认为是离群点
# queryIdx,trainIdx是两组关键点的索引
for ((trainIdx, queryIdx), s) in zip(matches, status):
    # 当点对匹配成功时,画到可视化图上
    if s == 1:
        # 画出匹配关键点对
        ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
        ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
        # line()绘制直线,放入直线起点和终点坐标,设置绘制颜色为绿色
        cv2.line(vis, ptA, ptB, (0, 255, 0))
plt.figure(figsize=(15,12))
im_show('图像拼接关键点配对', vis)

在这里插入图片描述

二、拼接图像黑边去除

因为设置拼接后的图像宽度是两张图像的宽度和,当两个图像拼接后可能会留下黑边,接下来定义函数去除黑边

1.定义图像黑边去除函数
def change_size(image):
    '''
    图像黑边去除函数
    image:源图像
    '''
    img = cv2.medianBlur(image, 5)  # 中值滤波,去除黑色边际中可能含有的噪声干扰
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换成灰度图
    a,binary_image = cv2.threshold(gray, 15, 255, cv2.THRESH_BINARY)  # 调整裁剪效果,阈值二值化来分离出前景和背景
    indexes = np.where(binary_image == 255)  # 提取白色像素点的坐标,输出的是宽(w)和高(h)
    # 获取得到的高和宽的最小值和最大值,得到上下左右四个边界和宽度和高度
    left = min(indexes[0])  # 左边界
    right = max(indexes[0])  # 右边界
    width = right - left  # 宽度
    bottom = min(indexes[1])  # 底部
    top = max(indexes[1])  # 顶部
    height = top - bottom  # 高度
    # 根据得到的边界对图像切片截取,去除黑边,这里列数减50是为了完全去除黑边
    pre1_picture = image[left:left + width, bottom:bottom + height-50]  
    # 返回图像截取后的结果
    return pre1_picture
2.去除拼接后图像黑边
result = change_size(result)
# 查看黑边去除后的图像
plt.figure(figsize=(15,12))
im_show('图像拼接(黑边去除)', result)

在这里插入图片描述

三、多张图像全景拼接

1.两张图像全景拼接

将之前两张图像全景拼接的代码放入Stitcher.py文件中。注意我们之前的代码imgA是拼接的右边图像,imgB是拼接的左边图像,Stitcher.py文件中是按照拼接从左到右放入图像。

from Stitcher import Stitcher
imageA = cv2.imread(r'./experiment/data/1.jpg')
imageB = cv2.imread(r'./experiment/data/2.jpg')
stitcher = Stitcher() # 实例化Stitcher类
# 放入两张图像,设置showMatches=True表示返回两个图像配对点可视化
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)
# 去除黑边
result = change_size(result)
plt.figure(figsize=(15,12))
im_show('全景图像拼接(两张)', result)
# 将拼接图像保存
cv2.imwrite(r'./experiment/data/12.jpg',result)

在这里插入图片描述

plt.figure(figsize=(15,12))
im_show('图像配对关键点', vis)

在这里插入图片描述

2.多张图像全景拼接
# 导入第三和第四张照片,将其依次拼接到上面两个图像拼接的结果中
imageC = cv2.imread(r'./experiment/data/3.jpg')
imageD = cv2.imread(r'./experiment/data/4.jpg')

stitcher = Stitcher() # 实例化Stitcher类
# 放入两张图像,设置showMatches=True表示返回两个图像配对点可视化
result = stitcher.stitch([result, imageC], showMatches=False)
result = change_size(result) # 去除黑边
result = stitcher.stitch([result, imageD], showMatches=False)
result = change_size(result) # 去除黑边
plt.figure(figsize=(15,12))
im_show('全景图像拼接(四张)', result)
# 将图像保存
cv2.imwrite(r'./experiment/data/1234.jpg',result)

在这里插入图片描述

四、任务小结

本次实验主要完成基于OpenCv根据两张图像的SIFT特征点进行全景拼接。

通过本次实验需要掌握以下内容:

  • 掌握基于OpenCv提取SIFT特征点
  • 掌握基于OpenCv使用SIFT描述子进行Brute-Force蛮力匹配
  • 掌握基于OpenCv计算单应性矩阵

–end–

说明

本实验(项目)/论文若有需要,请后台私信或【文末】个人微信公众号联系我

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

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

相关文章

【数据结构(十)】Map和Set

❣博主主页: 33的博客❣ ▶️文章专栏分类:数据结构◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你学更多数据结构知识 目录 1.前言2.搜索树2.1 概念2.2实现二叉搜索树 2.4性能分析3.搜索3.Map3.1Map说明3.2 M…

vue3使用el-autocomplete请求远程数据

服务器端 RestController RequestMapping("/teacher") public class TeacherController {Resourceprivate TeacherService teacherService;GetMapping({"/v1/getTop10TeacherByName/","/v1/getTop10TeacherByName/{name}"})public ResultBean&l…

论文笔记:(Security 22) 关于“二进制函数相似性检测”的调研

个人博客链接 注&#xff1a;部分内容参考自GPT生成的内容 [Security 22] 关于”二进制函数相似性检测“的调研&#xff08;个人阅读笔记&#xff09; 论文&#xff1a;《How Machine Learning Is Solving the Binary Function Similarity Problem》&#xff08;Usenix Securi…

C++多态特性详解

目录 概念&#xff1a; 定义及实现&#xff1a; 虚函数重写的两个例外&#xff1a; 1.协变&#xff1a; 2.析构函数的重写&#xff1a; final关键字&#xff1a; override关键字&#xff1a; 多态是如何实现的&#xff08;底层&#xff09;&#xff1a; 面试题&#xff1…

图像识别及分类

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

【网络编程下】五种网络IO模型

目录 前言 一.I/O基本概念 1.同步和异步 2.阻塞和非阻塞 二.五种网络I/O模型 1.阻塞I/O模型 2.非阻塞式I/O模型 ​编辑 3.多路复用 4.信号驱动式I/O模型 5. 异步I/O模型 三.五种I/O模型比较​编辑 六.I/O代码示例 1. 阻塞IO 2.非阻塞I/O 3.多路复用 (1)select …

Rust web简单实战

一、使用async搭建简单的web服务 1、修改cargo.toml文件添加依赖 [dependencies] futures "0.3" tokio { version "1", features ["full"] } [dependencies.async-std] version "1.6" features ["attributes"]2、搭…

【Leetcode每日一题】 综合练习 - 全排列 II(难度⭐⭐)(71)

1. 题目解析 题目链接&#xff1a;47. 全排列 II 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路梳理 为了生成给定数组nums的全排列&#xff0c;同时避免由于重复元素导致的重复排列&#xff0c;我们可以遵…

刷代码随想录有感(56):二叉搜索树的最小绝对差

题干&#xff1a; 代码:中序遍历成有序数组逐一比较相邻两个数之间的差值&#xff0c;注意这里是取最小值所以定义的初始值应该是非常大的INT_MAX&#xff01;&#xff01;&#xff01; class Solution { public:void traversal(TreeNode* root, vector<int>&a){if(…

OpenCV 为轮廓创建边界框和圆(62)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV检测凸包(61) 下一篇 :OpenCV如何为等值线创建边界旋转框和椭圆(62) ​ 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 cv::boundingRect使用 OpenCV 函数 cv::mi…

c++多线程2小时速成

简介 c多线程基础需要掌握这三个标准库的使用&#xff1a;std::thread,std::mutex, andstd::async。 1. Hello, world #include <iostream> #include <thread>void hello() { std::cout << "Hello Concurrent World!\n"; }int main() {std::th…

轻松应对数据恢复挑战:雷神笔记本,不同情况不同策略

在数字化时代&#xff0c;数据无疑是我们生活中不可或缺的一部分。无论是重要的工作文件、珍贵的家庭照片&#xff0c;还是回忆满满的视频&#xff0c;一旦丢失&#xff0c;都可能给我们的生活带来诸多不便。雷神笔记本作为市场上备受欢迎的电脑品牌&#xff0c;用户在使用过程…

ubuntu使用Remmina远程连接Windows桌面

概况 目的&#xff1a; 远程连接公司电脑写一点代码 之前的方案&#xff1a; 安装Win10虚拟机&#xff0c;虚拟机里连接 VPN&#xff0c; 然后用 mstsc 命令连接。 新的方案&#xff1a;连接VPN后&#xff0c; 开启Remmina直接连接远程 Windows 桌面 新方案优点&#xff1a…

分布式锁之-mysql

使用mysql实现分布式锁的方式这里演示两种&#xff1a; 1:基于 MySQL 实现的乐观锁 2:基于 MySQL 实现的悲观锁 数据库脚本 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for product_stock -- -----------------------…

【Python】机器学习之Sklearn基础教程大纲

机器学习之Sklearn基础教程大纲 1. 引言 机器学习简介Scikit-learn&#xff08;Sklearn&#xff09;库介绍安装和配置Sklearn 2. 数据预处理 2.1 数据加载与查看 - 加载CSV、Excel等格式的数据- 查看数据的基本信息&#xff08;如形状、数据类型等&#xff09;2.2 数据清洗…

Vue 组件间的数据绑定

在Vue组件中&#xff0c;v-model指令可以用来实现双向数据绑定。它用于将组件的属性和父组件中的数据进行双向绑定&#xff0c;使得当属性的值改变时&#xff0c;父组件中的数据也会相应地改变&#xff0c;并且当父组件中的数据改变时&#xff0c;属性的值也会相应地改变。 目…

【软考高项】三十一、成本管理4个过程

一、规划成本管理 1、定义、作用 定义&#xff1a;确定如何估算、预算、管理、监督和控制项目成本的过程作用&#xff1a;在整个项目期间为如何管理项目成本提供指南和方向 应该在项目规划阶段的早期就对成本管理工作进行规划&#xff0c;建立各成本管理过程的基本框架&…

使用docker-compose编排lnmp(dockerfile) 完成Wordpress

实验环境&#xff1a; 在已有docker环境和nginx镜像的基础上进行编排。 1、准备mysql容器目录及文件 2、dockerfile文件内容 3、my.cnf文件内容 4、准备php容器目录及文件 5、dockerfile文件内容 6、准备其他文件 7、编写docker-compose.yml文件 8、Docker Compose环境的实现…

Redisson 分布式锁和同步器

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列…

AI学习指南-人工智能概述

欢迎来到人工智能的奇妙世界&#xff01;如果你是初学者&#xff0c;那么你来对地方了。今天&#xff0c;我们将一起探索人工智能&#xff08;AI&#xff09;的基本概念&#xff0c;看看它是如何分类的&#xff0c;它的应用有哪些&#xff0c;以及未来可能的发展方向。准备好了…