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

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);	// 显示目标图像KwaitKey(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,高度为350imshow("Warp J", imgWarpJ);		// 显示经透视变化后的卡片J,宽度为250,高度为350imshow("Warp 9", imgWarp9);		// 显示经透视变化后的卡片9,宽度为250,高度为350imshow("Warp Q", imgWarpQ);     // 显示经透视变化后的卡片Q,宽度为250,高度为350waitKey(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.mzph.cn/news/628843.shtml

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

相关文章

ChatGPT新出Team号 年付费

之前一直传的团队版ChatGPT终于来了&#xff0c;这个对拼单的比较合算。每人每月25美元&#xff0c;只能按年支付。 团队版比普通版多的权益有&#xff1a; ◈更多的GPT-4消息上限&#xff0c;三小时100次。 ◈可以创建与团队内部共享的GPTs。 ◈用于工作空间管理的管理员控…

圈小猫游戏HTML源码

源码介绍 圈小猫游戏html源码&#xff0c;HTMLCSSJS,记事本可以打开修改内容&#xff0c;电脑本地双击index.html即可运行&#xff0c;也可以上传到服务器上面运行&#xff0c;喜欢的同学可以拿去使用 下载地址 蓝奏云&#xff1a;https://wfr.lanzout.com/iFkVc1lb5akj CS…

Spring高手之路-Spring事务失效的场景详解

目录 前言 Transactional 应用在非 public 修饰的方法上 同一个类中方法调用&#xff0c;导致Transactional失效 final、static方法 Transactional的用法不对 Transactional 注解属性 propagation 设置不当 Transactional注解属性 rollbackFor 设置错误 用错注解 异常…

QT quick基础:组件gridview

组件gridview与android中gridview布局效果相同。下面记录qt quick该组件的使用方法。 方法一&#xff1a; // ContactModel.qml import QtQuick 2.0ListModel {ListElement {name: "1"portrait: "icons/ic_find.png"}ListElement {name: "2"por…

MYSQL第四次作业--多表查询

二、多表查询 1.创建student和score表 创建student表 创建score表。 CREATE TABLE score ( id INT(10) NOT NULL UNIQUE PRIMARY KEY AUTO_INCREMENT , stu_id INT(10) NOT NULL , c_name VARCHAR(20) , grade INT(10) ); 2.为student表和score表增加记录 向student表插入记录的…

决战排序之巅(二)

决战排序之巅&#xff08;二&#xff09; 排序测试函数 void verify(int* arr, int n) 归并排序递归方案代码可行性测试 非递归方案代码可行性测试 特点分析 计数排序代码实现代码可行性测试 特点分析 归并排序 VS 计数排序&#xff08;Release版本&#xff09;说明1w rand( ) …

MATLAB - 加载预定义的机器人模型

系列文章目录 前言 一、 要快速访问常见的机器人模型&#xff0c;可使用 loadrobot 功能&#xff0c;该功能可加载市售的机器人模型&#xff0c;如 Universal Robots™ UR10 cobot、Boston Dynamics™ Atlas 人形机器人和 KINOVA™ Gen 3 机械手。探索如何生成关节配置并与机器…

小程序基础库与Android之间通信优化的可能

最近在学习graalvm&#xff0c;发现有一个graaljs项目&#xff0c;项目中介绍可以让java与JavaScript做数据转换&#xff0c;比如JavaScript中可以使用java的数据类型与结构。突然想到之前遇到的一个问题&#xff0c;小程序中开发的代码和基础库的部分代码都是j2v8来执行的&…

深入理解 Spark(二)SparkApplication 提交和运行源码分析

spark 核心流程 yarn-client yarn-cluster spark 任务调度 spark stage 级别调度 spark task 级别调度 失败重试和白名单 对于运行失败的 Task&#xff0c;TaskSetManager 会记录它失败的次数&#xff0c;如果失败次数还没有超过最大重试次数&#xff0c;那么就把它放回待调…

【Docker构建MySQL8.0镜像】

Docker构建MySQL8.0镜像 部署流程1. 拉取docker镜像2. 创建数据卷&#xff0c;存放MySQL数据3. 启动MySQL镜像4. 初始化sql放入MySQL镜像5. 执行MySQL脚本6. MySQL镜像打包7. MySQL镜像迁移 部署流程 1. 拉取docker镜像 docker pull mysql:8.0.35拉取成功后就可以看到镜像了&…

NFS(Network File System 网络文件服务)

一&#xff0c;nfs 简介 1&#xff0c;nfs 性质 NFS&#xff08;Network File System 网络文件服务&#xff09; 文件系统&#xff08;软件&#xff09;文件的权限 NFS 是一种基于 TCP/IP 传输的网络文件系统协议 通过使用 NFS 协议&#xff0c;客户机可以像访问本地目录一样…

【NI国产替代】NI-9231,8通道,51.2 kS/s/ch,-5至5 V,C系列声音和振动输入模块

8通道&#xff0c;51.2 kS/s/ch&#xff0c;-5至5 V&#xff0c;C系列声音和振动输入模块 NI-9231是一款高密度声音和振动模块&#xff0c;能够测量来自速度计、转速计和接近式探针等集成电子压电(IEPE)和非IEPE传感器的信号。该模块可以执行现代麦克风和加速计所需的高动态范围…

verilog编程题

verilog编程题 文章目录 verilog编程题序列检测电路&#xff08;状态机实现&#xff09;分频电路计数器译码器选择器加减器触发器寄存器 序列检测电路&#xff08;状态机实现&#xff09; module Detect_101(input clk,input rst_n,input data,o…

力扣每日一练(24-1-15)

重复n次检查&#xff0c;几乎都用双指针。。。 固然双指针就是题解&#xff1a; if len(nums) < 3:return len(nums)p1 2 for p2 in range(2, len(nums)):if nums[p2] ! nums[p1 - 2]:nums[p1] nums[p2]p1 1return p1 可以重复两次&#xff0c;那么隔一个检查就行&#…

vtk9.3 配置 visual studio 2019 运行环境 和运行实例详解

&#xff08;1&#xff09;包含文件配置&#xff1a; 项目--属性--VC目录&#xff0c;在包含目录中把include文件夹的地址加进去&#xff0c;一直要到下一级 vtk-9.3目录下&#xff0c; 小知识&#xff1a; 在Visual Studio 2019中运行项目时&#xff0c;如果项目中使用了第三…

网络安全笔记-SQL注入

文章目录 前言一、数据库1、Information_schema2、相关函数 二、SQL注入分类1、联合查询注入&#xff08;UNION query SQL injection&#xff09;语法 2、报错注入&#xff08;Error-based SQL injection&#xff09;报错注入分类报错函数报错注入原理 3、盲注布尔型盲注&#…

【Java 设计模式】创建型之抽象工厂模式

文章目录 1. 定义2. 应用场景3. 代码实现4. 应用示例结语 在软件开发中&#xff0c;抽象工厂模式是一种常见的创建型设计模式&#xff0c;它提供了一种创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。抽象工厂模式的核心思想是将一组相关的产品组合成…

docker screen 常用基础命令

1.docker基础命令 1.1开启docker systemctl start docker #开启docker service docker restart #重启docker systemctl stop docker #关闭docker 1.2查看命令 docker images #查看docker镜像docker ps #查看正在运行的镜像或者容器docker ps -a #查看所有容器1.3运…

刘知远LLM入门到实战——自然语言基础

文章目录 自然语言处理基础词表示语言模型N-gram ModelNeural Language Model: 为什么NLP等领域的模型越来越大&#xff1f; 大模型会带来哪些新的范式和挑战&#xff1f; 自然语言处理基础 让计算机理解人类语言&#xff0c;图灵测试就是基于对话的方式。 研究历史&#xff…

FlinkAPI开发之处理函数

案例用到的测试数据请参考文章&#xff1a; Flink自定义Source模拟数据流 原文链接&#xff1a;https://blog.csdn.net/m0_52606060/article/details/135436048 概述 之前所介绍的流处理API&#xff0c;无论是基本的转换、聚合&#xff0c;还是更为复杂的窗口操作&#xff0c…