opencv实践项目-图像拼接

news2025/1/12 7:38:39

目录

  • 1.简介
  • 2. 步骤
    • 2.1 特征检测与提取
    • 2.2 关键点检测
    • 2.3 关键点和描述符
    • 2.4 特征匹配
    • 2.5 比率测试
    • 2.6 估计单应性
  • 3. 完整代码

1.简介

图像拼接是计算机视觉中最成功的应用之一。如今,很难找到不包含此功能的手机或图像处理API。在本
文中,我们将讨论如何使用OpenCV进行图像拼接。也就是,给定两张共享某些公共区域的图
像,目标是“缝合”它们并创建一个全景图像场景。当然也可以是给定多张图像,但是总会转换成两张共享
某些公共区域图像拼接的问题。

2. 步骤

拼接的两幅图
在这里插入图片描述
在这里插入图片描述

2.1 特征检测与提取

给定上述一对图像,我们希望将它们缝合以创建全景场景。重要的是要注意,两个图像都需要
有一些公共区域。当然,我们上面给出的两张图像时比较理想的,有时候两个图像虽然具有公
共区域,但是同样还可能存在缩放、旋转、来自不同相机等因素的影响。但是无论哪种情况,
我们都需要检测图像中的特征点。

2.2 关键点检测

最初的并且可能是幼稚的方法是使用诸如Harris Corners之类的算法来提取关键点。然后,我
们可以尝试基于某种相似性度量(例如欧几里得距离)来匹配相应的关键点。众所周知,角点
具有一个不错的特性:角点不变。这意味着,一旦检测到角点,即使旋转图像,该角点仍将存
在。
但是,如果我们旋转然后缩放图像怎么办?在这种情况下,我们会很困难,因为角点的大小不
变。也就是说,如果我们放大图像,先前检测到的角可能会变成一条线!
总而言之,我们需要旋转和缩放不变的特征。那就是更强大的方法(如SIFT,SURF和
ORB)。

2.3 关键点和描述符

诸如SIFT和SURF之类的方法试图解决角点检测算法的局限性。通常,角点检测器算法使用固
定大小的内核来检测图像上的感兴趣区域(角)。不难看出,当我们缩放图像时,该内核可能
变得太小或太大。为了解决此限制,诸如SIFT之类的方法使用高斯差分(DoD)。想法是将
DoD应用于同一图像的不同缩放版本。它还使用相邻像素信息来查找和完善关键点和相应的描
述符。
首先,我们需要加载2个图像,一个查询图像和一个训练图像。最初,我们首先从两者中提取
关键点和描述符。通过使用OpenCV detectAndCompute()函数,我们可以一步完成它。请注
意,为了使用detectAndCompute(),我们需要一个关键点检测器和描述符对象的实例。它可
以是ORB,SIFT或SURF等。此外,在将图像输入给detectAndCompute()之前,我们将其转
换为灰度。
代码:

void detectAndDescribe(const cv::Mat &image, Extract_Features_Method method, std::vector<KeyPoint> &keypoints, cv::Mat &descriptor)
{
	switch (method)
	{
	case Extract_Features_Method::METHOD_SIFT:
	{
		Ptr<cv::SIFT> detector = cv::SIFT::create(800);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_SURF:
	{
		int minHessian = 400;
		Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_BRISK:
	{
		int minHessian = 400;
		
		Ptr<BRISK> detector = BRISK::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_ORB:
	{
		int minHessian = 400;

		Ptr<ORB> detector = ORB::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	default:
		break;
	}
}

我们为两个图像都设置了一组关键点和描述符。如果我们使用SIFT作为特征提取器,它将为
每个关键点返回一个128维特征向量。如果选择SURF,我们将获得64维特征向量。

2.4 特征匹配

现在,我们想比较两组特征,并尽可能显示更多相似性的特征点
对。使 用 OpenCV , 特 征 点 匹 配 需 要 Matcher 对 象 。 在 这 里 , 我 们 探 索 两 种 方 式 : 暴 力 匹 配 器(BruteForce)和KNN(k最近邻)。
BruteForce(BF)Matcher的作用恰如其名。给定2组特征(来自图像A和图像B),将A组的每个特征与B组的所有特征进行比较。默认情况下,BF Matcher计算两点之间的欧式距离。因此,对于集合A中的每个特征,它都会返回集合B中最接近的特征。对于SIFT和SURF,OpenCV建议使用欧几里得距离。对于ORB和BRISK等其他特征提取器,建议使用汉明距离。我们要使用OpenCV创建BruteForce Matcher,一般情况下,我们只需要指定2个参数即可。第一个是距离度量。第二个是是否进行交叉检测的布尔参数。
具体代码如下:

auto createMatcher(Extract_Features_Method method, bool crossCheck)
{

	if (method == Extract_Features_Method::METHOD_SIFT || method == Extract_Features_Method::METHOD_SURF)
	{
		return cv::BFMatcher(cv::NORM_L2, crossCheck);
	}

	return cv::BFMatcher(cv::NORM_HAMMING, crossCheck);
}

交叉检查布尔参数表示这两个特征是否具有相互匹配才视为有效。换句话说,对于被认为有效的一对特征(f1,f2),f1需要匹配f2,f2也必须匹配f1作为最接近的匹配。此过程可确保提供更强大的匹配功能集,这在原始SIFT论文中进行了描述。
但是,对于要考虑多个候选匹配的情况,可以使用基于KNN的匹配过程。KNN不会返回给定特征的单个
最佳匹配,而是返回k个最佳匹配。需要注意的是,k的值必须由用户预先定义。如我们所料,KNN提供
了更多的候选功能。但是,在进一步操作之前,我们需要确保所有这些匹配对都具有鲁棒性。

2.5 比率测试

为了确保KNN返回的特征具有很好的可比性,SIFT论文的作者提出了一种称为比率测试的技术。一般情
况下,我们遍历KNN得到匹配对,之后再执行距离测试。对于每对特征(f1,f2),如果f1和f2之间的距
离在一定比例之内,则将其保留,否则将其丢弃。同样,必须手动选择比率值。
本质上,比率测试与BruteForce Matcher的交叉检查选项具有相同的作用。两者都确保一对检测到的特征确实足够接近以至于被认为是相似的。下面2个图显示了BF和KNN Matcher在SIFT特征上的匹配结果。我们选择仅显示100个匹配点以清晰显示。
在这里插入图片描述

需要注意的是,即使做了多种筛选来保证匹配的正确性,也无法完全保证特征点完全正确匹配。尽管如
此,Matcher算法仍将为我们提供两幅图像中最佳(更相似)的特征集。接下来,我们利用这些点来计算
将两个图像的匹配点拼接在一起的变换矩阵。
这种变换称为单应矩阵。简而言之,单应性是一个3x3矩阵,可用于许多应用中,例如相机姿态估计,透
视校正和图像拼接。它将点从一个平面(图像)映射到另一平面。

2.6 估计单应性

随机采样一致性(RANSAC)是用于拟合线性模型的迭代算法。与其他线性回归器不同,RANSAC被设计为对异常值具有鲁棒性。
像线性回归这样的模型使用最小二乘估计将最佳模型拟合到数据。但是,普通最小二乘法对异常值非常敏感。如果异常值数量很大,则可能会失败。RANSAC通过仅使用数据中的一组数据估计参数来解决此问题。下图显示了线性回归和RANSAC之间的比较。需要注意数据集包含相当多的离群值。
在这里插入图片描述
我们可以看到线性回归模型很容易受到异常值的影响。那是因为它试图减少平均误差。因此,它倾向于支持使所有数据点到模型本身的总距离最小的模型。包括异常值。相反,RANSAC仅将模型拟合为被识别为点的点的子集。这个特性对我们的用例非常重要。在这里,我们将使用RANSAC来估计单应矩阵。事实证明,单应矩阵对我们传递给它的数据质量非常敏感。因此,重要的是要有一种算法(RANSAC),该算法可以从不属于数据分布的点中筛选出明显属于数据分布的点。
估计了单应矩阵后,我们需要将其中一张图像变换到一个公共平面上。在这里,我们将对其中一张图像应用透视变换。透视变换可以组合一个或多个操作,例如旋转,缩放,平移或剪切。我们可以使用
OpenCV warpPerspective()函数。它以图像和单应矩阵作为输入。

3. 完整代码

typedef enum
{
	METHOD_SIFT,
	METHOD_SURF,
	METHOD_BRISK,
	METHOD_ORB
}Extract_Features_Method;

void detectAndDescribe(const cv::Mat &image, Extract_Features_Method method, std::vector<KeyPoint> &keypoints, cv::Mat &descriptor)
{
	switch (method)
	{
	case Extract_Features_Method::METHOD_SIFT:
	{
		Ptr<cv::SIFT> detector = cv::SIFT::create(800);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_SURF:
	{
		int minHessian = 400;
		Ptr<cv::xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_BRISK:
	{
		int minHessian = 400;
		
		Ptr<BRISK> detector = BRISK::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	case Extract_Features_Method::METHOD_ORB:
	{
		int minHessian = 400;

		Ptr<ORB> detector = ORB::create(minHessian);
		detector->detectAndCompute(image, cv::Mat(), keypoints, descriptor);
		break;
	}
	default:
		break;
	}
}

auto createMatcher(Extract_Features_Method method, bool crossCheck)
{

	if (method == Extract_Features_Method::METHOD_SIFT || method == Extract_Features_Method::METHOD_SURF)
	{
		return cv::BFMatcher(cv::NORM_L2, crossCheck);
	}

	return cv::BFMatcher(cv::NORM_HAMMING, crossCheck);
}

int main()//stich_demo()
{
	string imgPath1 = "E:\\code\\Yolov5_Tensorrt_Win10-master\\pictures\\stich1.jpg";
	string imgPath2 = "E:\\code\\Yolov5_Tensorrt_Win10-master\\pictures\\stich2.jpg";
	Mat img1 = imread(imgPath1, IMREAD_GRAYSCALE);
	Mat img2 = imread(imgPath2, IMREAD_GRAYSCALE);

	std::vector<cv::KeyPoint> keypoint1;
	cv::Mat describe1;
	detectAndDescribe(img1, Extract_Features_Method::METHOD_SIFT, keypoint1, describe1);

	std::vector<cv::KeyPoint> keypoint2;
	cv::Mat describe2;
	detectAndDescribe(img2, Extract_Features_Method::METHOD_SIFT, keypoint2, describe2);

	auto matcher = createMatcher(Extract_Features_Method::METHOD_SIFT, false);

	vector<DMatch> firstMatches;
	matcher.match(describe1, describe2, firstMatches);
	
	vector<cv::Point2f> points1, points2;
	for (vector<DMatch>::const_iterator it = firstMatches.begin(); it != firstMatches.end(); ++it)
	{
		points1.push_back(keypoint1.at(it->queryIdx).pt);
		points2.push_back(keypoint2.at(it->trainIdx).pt);
	}

	auto inliers = vector<uchar>(keypoint1.size(), 0);
	cv::Mat h12 = cv::findHomography(points1, points2, inliers, RANSAC, 1.0);
	Mat h21;
	invert(h12, h21, DECOMP_LU);
	
	Mat canvas;
	Mat img1_color = imread(imgPath1);
	Mat img2_color = imread(imgPath2);

	warpPerspective(img2_color, canvas, h21, Size(img1.cols * 2, img1.rows));
	imshow("warp", canvas);
	img1_color.copyTo(canvas(Range::all(), Range(0, img1.cols)));

	imshow("canvas", canvas);

	waitKey(0);

	return 0;
}

生成的全景图像如下所示。如我们所见,结果中包含了两个图像中的内容。另外,我们可以看
到一些与照明条件和图像边界边缘效应有关的问题。理想情况下,我们可以执行一些处理技术
来标准化亮度,例如直方图匹配,这会使结果看起来更真实和自然一些。在这里插入图片描述

本文参考Python视觉实战项目71讲,将其中的python代码移植到c++

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

虹科分享 | 专为创意专业人士设计的Thunderbolt适配器

一、方案介绍 虹科HK-ATTO ThunderLink雷电™ 适配器可以适用于任何地方。 1.小。 2.便携式。 3.强大。 我们的10GBE Thunderbolt适配器的性能至少比内置或附加NIC&#xff08;包括Mac&#xff09;高20% , ATTO 360只需点击一个按钮即可优化以太网SAN&#xff0c;并可与Thunder…

xxl-job 使用示例

目录 介绍 下载源码地址 文档网站 源码导入就是这样目录 数据库建表sql 就这么几个表出来了 修改xxl-job-admin项目下的application.properties文件 完事启动就行了 页面 页面访问地址 账号密码 增加自己的定时任务 介绍 这篇写的是接入使用xxl-job 的一个简单流程…

win系统使用macOS系统

最近 win 系统和 ubuntu 系统用的久了&#xff0c;想用一下 MacOS 系统&#xff0c;于是去网上查了相关资料&#xff0c;发现用一款叫 NEXUS 的软件可以实现在 windows 系统体验效果&#xff0c;现把教程记录下来&#xff0c;供大家使用。 目录 一、下载NEXUS 二、 安装NEXU…

IMX6ULL裸机篇之串口实验说明一

一. 串口 本章我们就来学习如何驱动 I.MX6U 上的串口&#xff0c;并使用串口和电脑进行通信。 串行接口指的是数据一个一个的顺序传输&#xff0c;通信线路简单。 UART 作为串口的一种&#xff0c;其工作原理也是将数据一位一位的进行传输&#xff0c;发送和接收各用一 条…

win11本地安全机构保护已关闭怎么办?如何修复windows11本地安全机构保护已关闭?

win11本地安全机构保护已关闭怎么办&#xff1f; 如何修复windows11本地安全机构保护已关闭&#xff1f; 近日有windows11系统用户反映说遇到了这样一个问题&#xff1a;启动电脑后&#xff0c;发现windows右下角的安全中心图标上会显示一个黄色叹号&#xff0c;打开windows安…

操作系统笔记--进程与线程

1--进程 1-1--进程的定义 进程表示一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程&#xff1b; 1-2--进程的组成 一个进程由以下部分组成&#xff1a;① 程序的代码&#xff1b; ② 程序处理的数据&#xff1b;③ 程序计数器中的值&#xff0c;其指示下一条将…

PHP流行框架的报错页面,你见过那些?

在PHP开发过程中&#xff0c;使用框架能够帮助我们更快速、高效地完成项目开发。但是&#xff0c;即使使用了框架&#xff0c;我们还是难免会遇到各种报错。而当我们在开发阶段或调试过程中遇到报错时&#xff0c;框架提供的错误页面可以给我们带来很大的帮助。PHP常用的流行框…

网易一面:如何设计线程池?请手写一个简单线程池?

说在前面 在40岁老架构师 尼恩的读者社区(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如极兔、有赞、希音、百度、网易的面试资格&#xff0c;遇到了几个很重要的面试题&#xff1a; 如何设计线程池&#xff1f; 与之类似的、其他小伙伴遇到过的问题还有&#xff1a; …

Reinhart FoodService的EDI需求详解

Reinhart FoodService是一家成立于1972年的美国食品服务公司&#xff0c;隶属于上市公司Performance Food Group。Reinhart FoodService为餐馆、酒店、医院、学校等各类机构提供广泛的食品选择和相关服务&#xff0c;产品包括新鲜的肉类、禽类、海鲜、奶制品、烘焙用品、蔬菜和…

迪赛智慧数——柱状图(象形动态图):不同性别消费者点外卖频率

效果图 我国超4亿人叫外卖&#xff0c;你多久点一次外卖? 据数据显示&#xff0c;30.7%男性消费者每周点3-4次外卖&#xff0c;34.3%的女性每周点3-4次&#xff0c;明显女性比男性点外卖频率多。而每周点1-2次外卖中均超过80%。 数据源&#xff1a;静态数据 { "column&…

MIT开源协议,多端适用的租房小程序,带完整的管理员后台

一、开源项目简介 多端适用的租房小程序&#xff0c;带管理员后台。是一个完整的项目&#xff0c;可以直接使用。 二、开源协议 使用MIT开源协议 三、界面展示 部分截图 1. 前台截图 2. 后台截图 四、功能概述 1、使用Uniapp开发的前台&#xff0c;基于 Vue.js 开发所有…

2 种方式在流水线中集成 DAST,动态保护应用程序安全

&#x1f4a1; 如何在流水线中集成与应用 DAST &#xff1f; 近日&#xff0c;在「DevSecOps软件安全开发实践」课程上&#xff0c;极狐(GitLab) 前端工程师钱堃、极狐(GitLab) 高级后端工程师张林杰&#xff0c;展开了关于 DAST 的概念、必要性、优缺点的内容分享&#xff0c;…

如何完美实现数据可视化?

为什么要可视化数据? 在工作中&#xff0c;无论你在哪个场景&#xff0c;你都会接触到数据&#xff0c;需要表达出来。数据可视化的作用是通过结合图表和数据来更好地传达业务信息。目前&#xff0c;大多数公司正在逐步从传统的流程管理过渡到基于数据的管理。数据可视化可以…

明明花钱上了ERP,为什么还要我装个MES系统

目前&#xff0c; ERP系统依旧是很多制造企业的选择。据统计&#xff0c;ERP系统的应用已经达到70&#xff05;以上&#xff0c;但是在车间的应用&#xff0c; MES系统的应用比例并不高。那么&#xff0c;为什么现在很多企业又都选择再上个MES呢&#xff1f; MES系统是一个面向…

高性能HMI 走向扁平化

个人计算机作为图形用户界面&#xff08;GUI&#xff09;在自动化中已经使用了30多年。在那段时间里&#xff0c;从技术、术语、功能到用于创建接口的标准和指南&#xff0c;发生了许多变化。 PC 技术的飞速发展&#xff0c;特别是图形显示&#xff0c;用户界面的技术发展导致了…

分享8款开源的自动化测试框架

在如今开源的时代&#xff0c;我们就不要再闭门造车了&#xff0c;热烈的拥抱开源吧&#xff01;本文针对性能测试、Web UI 测试、API 测试、数据库测试、接口测试、单元测试等方面&#xff0c;为大家整理了github或码云上优秀的自动化测试开源项目&#xff0c;希望能给大家带来…

PyQt5零基础入门(二)——主窗口的显示与退出

系列文章目录 PyQt5与QtDesigner的安装及测试 创建主窗口和状态栏 系列文章目录PyQt5与QtDesigner的安装及测试 前言主窗口代码运行结果解释 窗口居中代码解释 退出窗口代码运行结果解释 前言 本文主要介绍了如何使用PyQt5创建第一个主窗口&#xff0c;并向其中添加状态栏和消…

Contest3137 - 2022-2023-2 ACM集训队每月程序设计竞赛(1)五月月赛

A 1! 5! 46 169 有一种数字&#xff0c;我们称它为 纯真数。 它等于自身每一个数位的阶乘之和。请你求出不超过n的所有 纯真数。(注&#xff1a;纯真数不含有前导0&#xff09;数据范围1e18 纯真数只有四个&#xff0c;注意0!1 1,2,145,40585 int n;cin>>n;int res[]{…

【Java 并发编程】一文详解 Java volatile关键字

一文详解 Java volatile关键字 1. JMM&#xff08;Java Memory Model&#xff09;1.1 现代计算机的内存模型1.2 JMM 简介1.3 JMM 的三大特性1.4 指令重排1.5 happens-before1.5.1 happens-before 规则1.5.2 总结 1.6 as-if-serial 2. volatile 关键字2.1 volatile 的内存语义2.…

mssql 中msdb 权限说明

msdb MSDB是Microsoft SQL Server中一个系统数据库&#xff0c;它存储了SQL Server代理作业&#xff0c;备份和还原&#xff0c;数据库维护计划&#xff0c;邮件&#xff0c;日志和数据库相关的其他信息。MSDB数据库包含了许多系统表和视图&#xff0c;例如sysjobs、sysjobhis…