VC++中使用OpenCV对原图像中的四边形区域做透视变换

news2024/10/6 4:03:50

VC++中使用OpenCV对原图像中的四边形区域做透视变换

最近闲着跟着油管博主murtazahassan,学习了一下LEARN OPENCV C++ in 4 HOURS | Including 3x Projects | Computer Vision,对应的Github源代码地址为:Learn-OpenCV-cpp-in-4-Hours

视频里面讲到到原图中的扑克牌四个顶点标记画圆,并且将扑克牌K做透视变换后摆正重新显示,资源图像文件cards.png下载地址为:https://github.com/murtazahassan/Learn-OpenCV-cpp-in-4-Hours/tree/main/Resources
cards.png
cards.png

什么是透视变换

从名称中可以清楚地看出,透视变换与视点的变化相关。这种类型的转换不保留平行度、长度和角度。但它们确实保留了共线性和关联性。这意味着即使在变换之后直线仍将保持直线。

一般来说,透视变换可以表示为:
透视变换的数学形式
上面是透视变换的数学形式,说白了就是对图像中的某个区域做处理。
这里,(x’,y’)是变换点,而(x,y)是输入点。变换矩阵 (M) 可以看作是以下的组合:
透视变换点
对于仿射变换,投影向量等于0。因此,仿射变换可以被认为是透视变换的特例。

由于变换矩阵(M)由8个常数(自由度)定义,因此为了找到这个矩阵,我们首先在输入图像中选择4个点,然后根据用途将这4个点映射到未知输出图像中的所需位置-case(这样我们将有 8 个方程和 8 个未知数,并且可以很容易地求解)。

一旦计算出变换矩阵,我们就将透视变换应用于整个输入图像以获得最终的变换图像。让我们看看如何使用 OpenCV 来做到这一点。
对图形做透视变换

对扑克牌K做透视变换

OpenCV中的透视变换相关函数getPerspectiveTransformwarpPerspective

透视变换(Perspective Transformation)是将成像投影到一个新的视平面(Viewing Plane),也称作投影映射(Projective Mapping)。如图1,通过透视变换ABC变换到A’B’C’。透视变换是计算图像学和线性代数中的一个常用概念。
在视角转换中,我们可以改变给定图像或视频的视角,以便更好地洞察所需信息。在透视变换中,我们需要提供图像上想要通过改变透视来收集信息的点。我们还需要提供要在其中显示图像的点。然后,我们从给定的两组点获得透视变换并将其与原始图像包裹起来。

我们使用 getPerspectiveTransform, 然后使用 warpPerspective 函数,其中 getPerspectiveTransform它将 4 对对应点作为输入并输出变换矩阵,计算出变换矩阵 (M) 后,将其传递给 warpPerspective() 函数,该函数将透视变换应用于图像。

getPerspectiveTransform的函数有两种重载形式,其中一个函数原型如下:
getPerspectiveTransform函数原型1
getPerspectiveTransform重载函数原型2为:
getPerspectiveTransform函数原型2
warpPerspective 函数原型为:
warpPerspective函数原型

首先使用Windows电脑自带默认的画图工具打开cards.png原图,通过移动鼠标到扑克牌K的左上、右上、左下、右下角,在左下角即可查看图像某点的像素坐标,如下图所示:

卡片K的左上角坐标
可以看到K的左上角坐标为:{529, 144}
用同样的方法,依次获取K的右上、左下、右下角坐标,分别为:{771,190}、{405,395}、{674,457}

实现代码

1、根据原图,以及卡片K的位置,获取对应的透视变换矩阵
2、 对原图中的卡片K根据透视变化矩阵进行转换,得到目标图像imgWarp
3、在原图K的四个顶点位置处画一个圆,半径为10像素,颜色为红色
4、显示原图和目标图像K
我们要将扑克牌K进行透视变换摆正,类似下图的转换,以获得图像的自上而下的“鸟瞰图”。:
将某个四边形摆正,做透视变换

实现代码如下:

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

///  Warp Images  //

int main()
{

	string path = "Resources/cards.jpg";
	Mat img = imread(path);	// 读取原图
	Mat matrix, imgWarp;
	float w = 250, h = 350;	// 目标图像的宽度和高度

	Point2f src[4] = { {529,144},{771,190},{405,395},{674,457} };	// 扑克牌K的四个顶点坐标,分别为左上、右上、左下、右下角坐标
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };		// 目标输出图像imgWarp的四个顶点坐标

	matrix = getPerspectiveTransform(src, dst);	// 根据原图和目标图,获取对应透视变换的转换矩阵
	warpPerspective(img, imgWarp, matrix, Point(w, h));	// 对原图中的卡片K根据透视变化矩阵进行转换,得到目标图像imgWarp

	// 在原图K的四个顶点位置处画一个圆,半径为10像素,颜色为红色
	for (int i = 0; i < 4; i++)
	{
		circle(img, src[i], 10, Scalar(0, 0, 255), FILLED);
	}

	imshow("Image", img);			// 显示原图
	imshow("Image Warp", imgWarp);	// 显示目标图像K
	waitKey(0); // 永久等待直到用户按下键盘中的键,则退出程序

	return 0;
}

运行结果

在VS2017中运行结果如下图所示:
显示卡片K

对原图中的扑克片K、J、9、Q依次做透视变化并输出

接下来,我们参照上面扑克牌K的处理方法,可以依次对原图中的扑克牌J、9、Q做类似的处理,代码如下图所示:

#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

using namespace cv;
using namespace std;

///  Warp Images  //

int main()
{

	string path = "Resources/cards.jpg";
	Mat img = imread(path);
	Mat matrix, imgWarpK;
	Mat matrixJ, imgWarpJ;
	Mat matrix9, imgWarp9;
	Mat matrixQ, imgWarpQ;
	float w = 250, h = 350;	// 目标卡片显示的宽度和高度

	// 1.处理卡片K
	// 分别对应扑克牌K的左上、右上、左下、右下角的坐标
	Point2f src[4] = { {529,144},{771,190},{405,395},{674,457} };	// 源图像中K卡片对应的四边形顶点的坐标。
	Point2f dst[4] = { {0.0f,0.0f},{w,0.0f},{0.0f,h},{w,h} };		// 目标图像中K卡片对应的四边形顶点的坐标。

	// 获取透视变换矩阵
	matrix = getPerspectiveTransform(src, dst);
	warpPerspective(img, imgWarpK, matrix, Point(w, h));

	// 在原图K的四个顶点处画圆
	for (int i = 0; i < 4; i++)
	{
		circle(img, src[i], 10, Scalar(0, 0, 255), FILLED);
	}

	// 2.处理卡片J
	// 分别对应扑克牌J的左上、右上、左下、右下角的坐标
	Point2f srcOfJCard[4] = { {776, 108}, {1018, 85}, {849, 358}, {1116, 331} };
	Point2f destOfJCard[4] = { {0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h} };

	// 获取卡片J的透视变化矩阵
	matrixJ = getPerspectiveTransform(srcOfJCard, destOfJCard);
	warpPerspective(img, imgWarpJ, matrixJ, Point(w, h));

	// 在原图J的四个顶点画圆
	for (int i = 0; i < 4; i++) {
		circle(img, srcOfJCard[i], 10, Scalar(255, 0, 0), FILLED);
	}

	// 3.处理卡片9
	// 分别对应扑克牌9的左上、右上、左下、右下角的坐标
	Point2f srcOf9Card[4] = { {743, 383}, {1023, 438}, {646, 710}, {962, 781} };
	Point2f destOf9Card[4] = { {0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h} };

	// 获取卡片9的透视变化矩阵
	matrix9 = getPerspectiveTransform(srcOf9Card, destOf9Card);
	warpPerspective(img, imgWarp9, matrix9, Point(w, h));

	// 在原图9的四个顶点画圆
	for (int i = 0; i < 4; i++) {
		circle(img, srcOf9Card[i], 10, Scalar(0, 255, 0), FILLED);
	}

	// 4.处理卡片Q
	// 分别对应扑克牌Q的左上、右上、左下、右下角的坐标
	Point2f srcOfQCard[4] = { {64, 326}, {339, 279}, {91, 636}, {401, 573} };
	Point2f destOfQCard[4] = { {0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h} };

	// 获取卡片Q的透视变化矩阵
	matrixQ = getPerspectiveTransform(srcOfQCard, destOfQCard);
	warpPerspective(img, imgWarpQ, matrixQ, Point(w, h));

	// 在原图Q的四个顶点画圆
	for (int i = 0; i < 4; i++) {
		circle(img, srcOfQCard[i], 10, Scalar(0, 255, 0), FILLED);
	}


	imshow("Image", img);			// 显示原图
	imshow("Warp K", imgWarpK);		// 显示经透视变化后的卡片K,宽度为250,高度为350
	imshow("Warp J", imgWarpJ);		// 显示经透视变化后的卡片J,宽度为250,高度为350
	imshow("Warp 9", imgWarp9);		// 显示经透视变化后的卡片9,宽度为250,高度为350
	imshow("Warp Q", imgWarpQ);     // 显示经透视变化后的卡片Q,宽度为250,高度为350

	waitKey(0);	// 无限期的等待键盘输入


	return 0;
}

对应的运行结果如下图所示:
对4个卡片做透视变换

参考资料

  • Perspective Transformation – Python OpenCV
  • TAG ARCHIVES: CV2.GETPERSPECTIVETRANSFORM()
  • LEARN OPENCV C++ in 4 HOURS | Including 3x Projects | Computer Vision
  • murtazahassan/Learn-OpenCV-cpp-in-4-Hours
  • OpenCV官网
  • OpenCV-Get Started
  • OpenCV Github仓库源代码
  • OpenCV tutorial
  • Warp Images
  • https://docs.opencv.org/4.x/da/d54/group__imgproc__transform.html

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

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

相关文章

【计算机网络】第七,八,九章摘要重点

第七章网络管理 1.计算机网络面临的两大威胁&#xff1f; 恶意程序有&#xff1a;计算机病毒&#xff0c;计算机蠕虫&#xff0c;特洛伊木马&#xff0c;逻辑炸弹&#xff0c;后门入侵和流氓软件。 2.安全的计算机网络四个目标&#xff1a; 机密性&#xff0c;端点鉴别&…

VMware虚拟机自定义网段及物理机ping不通虚拟机问题解决

Vmware网络介绍&#x1f6dc; VMware虚拟机提供了几种网络模式&#xff0c;其中包括桥接模式&#xff08;Bridged Mode&#xff09;、NAT模式&#xff08;Network Address Translation Mode&#xff09;和仅主机模式&#xff08;Host-Only Mode&#xff09;。这些模式允许虚拟…

如何在亚马逊 SageMaker 进行 Stable Diffusion 模型在线服务部署

文章目录 前言 - 浅谈 AIGCAIGC - 引领人工智能走向春天春天里盛开的 AI 绘画AI 绘画之Stable Diffusion 2.0 登场人人都有机会成为前沿的技术探索者 基于Amazon SageMaker进行Stable Diffusion 模型部署认识 Amazon SageMaker借助 Amazon SageMaker 进行环境搭建和模型推理1. …

go语言初探(一)

package mainimport ("fmt""time" )func main() {fmt.Print("hello go!")time.Sleep(1 * time.Second)}运行后&#xff0c;结果如下&#xff1a; 1、golang表达式中&#xff0c;加&#xff1b;和不加&#xff1b;都可以 2、函数的{和函数名一…

【前端架构】前端通用架构

一个强大的前端通用架构应该具备多种能力&#xff0c;以支持现代化的应用程序开发和提高开发效率。以下是一些前端通用架构应该具备的关键能力&#xff1a; 模块化和组件化&#xff1a;支持模块化开发和组件化架构&#xff0c;能够将应用拆分为独立的模块和组件&#xff0c;以便…

逻辑回归(解决分类问题)

定义&#xff1a;逻辑回归是一种用于解决分类问题的统计学习方法。它通过对数据进行建模&#xff0c;预测一个事件发生的概率。逻辑回归通常用于二元分类问题&#xff0c;即将数据分为两个类别。它基于线性回归模型&#xff0c;但使用了逻辑函数&#xff08;也称为S形函数&…

【动态规划】【C++算法】639 解码方法 II

作者推荐 【矩阵快速幂】封装类及测试用例及样例 涉及知识点 动态规划 字符串 滚动向量 LeetCode 639. 解码方法 II 一条包含字母 A-Z 的消息通过以下的方式进行了 编码 &#xff1a; ‘A’ -> “1” ‘B’ -> “2” … ‘Z’ -> “26” 要 解码 一条已编码的消息…

【VTKExamples::PolyData】第四期 DijkstraGraphGeodesicPath

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例DijkstraGraphGeodesicPath,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. DijkstraGraphGeodesicPath /…

element plus 可选择树形组件(el-tree) 怎样一键展开/收起?实现方法详解

实现代码&#xff1a; 按钮&#xff1a; <el-button click"takeall" style"height: 24px">{{zhanstatus % 2 ! 0 ? "收起所有" : "展开所有"}} </el-button> 组件&#xff1a; <el-form-item label"可选择菜单…

Kafka系列(四)

本文接kafka三&#xff0c;代码实践kafkaStream的应用&#xff0c;用来完成流式计算。 kafkastream 关于流式计算也就是实时处理&#xff0c;无时间概念边界的处理一些数据。想要更有性价比地和java程序进行结合&#xff0c;因此了解了kafka。但是本人阅读了kafka地官网&#…

32单片机RTC时间接续,掉电时间保存

1、实现思路 前提&#xff1a;首先要实现RTC掉电之后时间还能继续走&#xff0c;RTC电池是必要的 说明&#xff1a;设备第一次启动需要初始化配置RTC&#xff0c;但当二次启动再重新配置RTC会导致RTC计数器置零&#xff0c;所以传统的程序流程是不行的&#xff0c;我们需要知…

苹果最新系统iOS 17的调试和适配方法 - Xcode 14.3.1 真机调试指南

最近苹果发布了iOS 17作为其最新操作系统版本&#xff0c;作为开发者&#xff0c;你可能需要了解如何在Xcode 14.3.1中进行真机调试和适配。本文将为你详细介绍步骤和注意事项。 I. 检查Xcode版本 在开始之前&#xff0c;确保你已经安装了Xcode 14.3.1或更高版本。你可以在Xco…

计算机网络(超详解!) 第二节 数据链路层(上)

1.数据链路层使用的信道 数据链路层使用的信道主要有以下两种类型&#xff1a; 1.点对点信道&#xff1a;这种信道使用一对一的点对点通信方式。 2.广播信道&#xff1a;这种信道使用一对多的广播通信方式&#xff0c;因此过程比较复杂。广播信道上连接的主机很多&#xff0…

学习记录-自动驾驶与机器人中的SLAM技术

以下所有内容均为高翔大神所注的《自动驾驶与机器人中的SLAM技术》中的内容 融合导航 1. EKF和优化的关系 2. 组合导航eskf中的预测部分&#xff0c;主要是F矩阵的构建 template <typename S> bool ESKF<S>::Predict(const IMU& imu) {assert(imu.timestamp…

HCIA——10实验:跨路由转发。静态路由、负载均衡、缺省路由、手工汇总、环回接口。空接口与路由黑洞、浮动静态。

学习目标&#xff1a; 跨路由转发、负载均衡、环回接口、手工汇总、缺省路由、空接口与路由黑洞、浮动静态 学习内容&#xff1a; 跨路由转发静态路由、负载均衡、缺省路由、手工汇总。环回接口空接口与路由黑洞、浮动静态 目录 学习目标&#xff1a; 学习内容&#xff1a…

imgaug库指南(27):从入门到精通的【图像增强】之旅

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

23/76-LeNet

LeNet 早期成功的神经网络。 先使用卷积层来学习图片空间信息。 然后使用全连接层转换到类别空间。 #In[]LeNet,上世纪80年代的产物,最初为了手写识别设计from d2l import torch as d2l import torch from torch import nn from torch.nn.modules.loss import CrossEntropyLos…

Git中,版本库和远程库有什么区别

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;每天一个知识点 ✨特色专栏&#xff1a…

【jupyter添加虚拟环境内核(pytorch、tensorflow)- 实操可行】

jupyter添加虚拟环境内核&#xff08;pytorch、tensorflow&#xff09;- 实操可行 1、查看当前状态(winR&#xff0c;cmd进入之后)2、激活虚拟环境并进入3、安装ipykernel5、完整步骤代码总结6、进入jupyter 添加pytorch、tensorflow内核操作相同&#xff0c;以下内容默认已经安…

IP定位技术在网络安全行业的探索

随着互联网的普及和深入生活&#xff0c;网络安全问题日益受到人们的关注。作为网络安全领域的重要技术&#xff0c;IP定位技术正逐渐成为行业研究的热点。本文将深入探讨IP定位技术在网络安全行业的应用和探索。 一、IP定位技术的概述 IP定位技术是通过IP地址来确定设备地理位…