OpenCV4(C++)—— 仿射变换、透射变换和极坐标变换

文章目录

  • 一、仿射变换
    • 1. getRotationMatrix2D()
    • 2. warpAffine()
  • 二、透射变换
  • 三、极坐标变换


一、仿射变换

在OpenCV中没有专门用于图像旋转的函数,而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心,之后确定旋转矩阵,最终通过仿射变换实现图像旋转。OpenCV 4提供了getRotationMatrix2D()函数用于计算旋转矩阵和warpAffine()函数用于实现图像的仿射变换

1. getRotationMatrix2D()

cv::Mat cv::getRotationMatrix2D(cv::Point2f center,      // 旋转中心点坐标。 通常以图像的中心为旋转中心double angle,            // 旋转角度(正值表示逆时针旋转)double scale             // 缩放因子。 默认值为1.0,表示不进行缩放
);返回值是一个 2x3 的仿射变换矩阵

为什么仿射变换矩阵是一个2*3的形式?
  先看仿射变换的概率:仿射变换其实就是图像的旋转、平移和缩放操作的统称,可以表示为线性变换和平移变换的叠加。
  仿射变换的数学表示是先乘以一个线形变换矩阵再加上一个平移向量,即:T = aX + b。但一个二维平面的像素坐标为(x,y),是一个1×2的向量矩阵,那公式就变成如下:
在这里插入图片描述
根据矩阵相乘的规则,其中线性变换矩阵A为2×2的矩阵,平移向量B为2×1的向量,两者结合就是一个2×3的变换矩阵。

在这里插入图片描述

2. warpAffine()

(1)getRotationMatrix2D()是为了得到一个仿射变换矩阵,然后通过 cv::warpAffine 函数应用到图像上进行旋转操作(如果我们已知图像旋转矩阵,可以自己生成旋转矩阵而不调用该函数)。

void cv::warpAffine(cv::InputArray src,          // 输入图像cv::OutputArray dst,         // 输出图像cv::InputArray M,            // 2x3 的仿射变换矩阵cv::Size dsize,              // 输出图像的尺寸int flags = cv::INTER_LINEAR,// 插值方法,默认为线性插值int borderMode = cv::BORDER_CONSTANT,  // 边界填充模式,默认为常数填充const cv::Scalar& borderValue = cv::Scalar()  // 边界填充颜色,默认为黑色(0,0,0)
);注:
常用的边界填充模式有 cv::BORDER_CONSTANT(常数填充)和 cv::BORDER_REPLICATE(复制边界像素)
边界填充颜色,仅当 borderMode 设置为 cv::BORDER_CONSTANT 时有效

(2)在使用 warpAffine() 函数进行旋转时,通常保留的是原图大小,这会导致目标保存的不完整。可以改变缩放因子来获得完整目标,但这会导致目标尺寸变小,如下:

#include <opencv2/opencv.hpp>
#include<iostream>  using namespace std;int main()
{cv::Mat img = cv::imread("C:/Users/Opencv/temp/lena.png", 0);int h = img.rows;int w = img.cols;// 定义仿射旋转矩阵rMcv::Point2f center(h / 2, w / 2);  // 以图像中心为坐标double angle = 30;  // 设定30的逆时针旋转角度double scale = 1.0;   // 不进行缩放// double scale = 0.5;   // 缩放0.5cv::Mat rM= cv::getRotationMatrix2D(center, angle, scale);// 进行普通旋转操作(保存原图大小)cv::Mat rotationImg1, rotationImg2;cv::warpAffine(img, rotationImg1, rM, img.size());

(3)如果想要保持目标大小不变,就只能加大输出图片。

在这里插入图片描述
  红色框是源图像 ,宽和高分别为 h 和 w,黑色框是逆时针旋转 θ 后的图像 。可以看到,如果旋转后图像的宽和高保持不变,那么肯定会有一部分图片会被裁掉。而如果想要保证旋转后图片的所有目标都保留下来,那么新图像就必须至少为浅蓝色框这么大。从图看出,浅蓝色框的尺寸为:
w 1 = w ∗ c o s θ + h ∗ s i n θ , h 1 = w ∗ s i n θ + h ∗ c o s θ w 1=w∗cosθ+h∗sinθ , h 1=w∗sinθ+h∗cosθ w1=wcosθ+hsinθ,h1=wsinθ+hcosθ
  现在我们知道了在warpAffine() 中要设置的输出尺寸应为cv::Size(w1,h1)。但是还有一点:上图的红色框和蓝色框看起来中心点是一样的,因为红色框位于蓝色框中心位置。但实际上红色框应该是在左上角的,所以两个框的中心点存在偏移,而我们就需要计算出偏移量,根据偏移量来移动旋转后的图片,使其位于中心位置。

那怎么计算偏移量,并对旋转图片进行移动呢?
   (1)偏移量就是蓝色框中心(newH, newW)和红色框中心(H,W)的差距,即 [ ( n e w H − H ) / 2 , ( n e w W − W ) / 2 ) ] [(newH-H)/ 2,(newW - W) / 2) ] [newHH/2(newWW)/2)]
   ((2)前面说到,仿射变换矩阵是一个2×3的形式,而它的第三列就是平移向量b0,b1。所以在平移向量上加上偏移量就能对旋转图片进行移动到中心位置。

代码示例如下:

#include <opencv2/opencv.hpp>
#include <cmath>
#include<iostream>  using namespace std;int main()
{cv::Mat img = cv::imread("C:/Opencv/temp/lena.png", 0);int h = img.rows;int w = img.cols;// 定义仿射旋转矩阵cv::Point2f center(h / 2, w / 2);double angle = 30;double scale = 1;cv::Mat rM= cv::getRotationMatrix2D(center, angle, scale);// 进行普通旋转操作(保存原图大小)cv::Mat rotationImg1, rotationImg2, rotationImg3;cv::warpAffine(img, rotationImg1, rM, img.size());//cv::imshow("旋转1,缩放0.5", rotationImg1);// 进行特殊旋转操作(保存完整目标)angle = angle / 180 * CV_PI;  // 转为弧度制// 新图的尺寸int h1 = static_cast<int>(w * fabs(sin(angle)) + h * fabs(cos(angle))); int w1 = static_cast<int>(w * fabs(cos(angle)) + h * fabs(sin(angle)));cv::warpAffine(img, rotationImg2, rM, cv::Size(w1,h1));cv::imshow("旋转2", rotationImg2);// 加上偏移量rM.at<double>(0, 2) += (w1 - w) / 2;rM.at<double>(1, 2) += (h1 - h) / 2;cv::warpAffine(img, rotationImg3, rM, cv::Size(w1, h1));cv::imshow("旋转3", rotationImg3);cv::waitKey(0);cv::destroyAllWindows();return 0;
}

在这里插入图片描述
注:
1)在实际使用中,往往是把存在偏移角度的目标进行旋转,让目标保持水平。为此旋转角度通常为逆时针或超过180度,所以计算新尺寸时最好加上绝对值(C++中用库的fabs,python中用Numpy库的np.fbs)

(2)上述例子是自己设定旋转角度和旋转中心。在实际使用时,要旋转多少角度比较好呢? 其中一种方式就是使用minAreaRect()函数,它是获得目标最小外接矩阵的一种函数,返回值就有中心位置和与水平线的角度。将其作为getRotationMatrix2D()的输入参数,就能旋转图片,使目标呈水平线放置。还有一种方式,当你有一张原图和要旋转的结果图时,可以通过这两张图计算它们之间存在的仿射变换,从而得到一个仿射变换矩阵,关键函数getAffineTransform(),使用方法跟下面的透射变换的getPerspectiveTransform 函数相似。

二、透射变换

仿射变换是一种在平面上的操作,而透射变换可以看成一种空间上的操作,将图像从一个视角映射到另一个视角(OCR中常用)。OpenCV提供了cv::warpPerspective函数来进行透射变换。和仿射变换一样,需要一个透射变换矩阵,常用的方式就是使用 cv::getPerspectiveTransform 函数计算得到透视变换矩阵

getPerspectiveTransform 函数和上面提到的仿射变换的getAffineTransform()相似,只不过仿射变换只需要三个像素坐标,而透射变换中需要四个像素坐标。

注:源图像srcPoints的四个坐标要与目标图像dstPoints的四个坐标一一对应
cv::Mat perspectiveMatrix = cv::getPerspectiveTransform(srcPoints, dstPoints);返回值是一个 3x3 的透射变换矩阵

warpPerspective函数和仿射变换的warpAffine函数使用方式一样。透射变换的关键问题是在于如何获取源图像的4个像素坐标和目标图像的4个像素坐标。常用的一种方式是使用角点检测,来获取目标的边界角点。

    cv::Mat img = imread("111.png");Point2f src_points[4];Point2f dst_points[4];//设定源图像4个点的像素坐标src_points[0] = cv::Point2f(94.0, 374.0);src_points[1] = cv::Point2f(507.0, 380.0);src_points[2] = cv::Point2f(1.0, 623.0);src_points[3] = cv::Point2f(627.0, 627.0);//设置期望透视变换后这四个点的像素坐标dst_points[0] = cv::Point2f(0.0, 0.0);dst_points[1] = cv::Point2f(627.0, 0.0);dst_points[2] = cv::Point2f(0.0, 627.0);dst_points[3] = cv::Point2f(627.0, 627.0);cv::Mat rotation, img_warp;rotation = cv::getPerspectiveTransform(src_points, dst_points); //计算透视变换矩阵cv::warpPerspective(img, img_warp, rotation, img.size()); //透视变换投影

三、极坐标变换

极坐标变换就是将图像在直角坐标系与极坐标系中互相变换,它可以将一圆形图像变换成一个矩形图像,常用于处理钟表、圆盘等图像。圆形图案边缘上的文字经过及坐标变换后可以垂直的排列在新图像的边缘,便于对文字的识别和检测。OpenCV中提供了cv::warpPolar()函数用于实现图像的极坐标变换

void cv::warpPolar(InputArray src,OutputArray dst,Size dsize,  目标图像大小Point2f center,  // 极坐标变换时极坐标的原点坐标double  maxRadius,  // 变换时边界圆的半径,它也决定了逆变换时的比例参数int  flags // 插值方法与极坐标映射方法标志,两个方法之间通过“+”或者“|”号进行连接)
可以对图像进行极坐标正变换也可以进行逆变换,关键在于最后一个参数如何选择 :
WARP_POLAR_LINEAR  极坐标变换       
WARP_POLAR_LOG     半对数极坐标变换
WARP_INVERSE_MAP   逆变换           

示例代码如下:

#include <opencv2/opencv.hpp>
#include<iostream>  using namespace std;int main()
{cv::Mat img = cv::imread("C:/Opencv/temp/yuan.png");cv::Mat img1, img2;cv::Point2f center = cv::Point2f(img.cols / 2, img.rows / 2);  //极坐标在图像中的原点// 正极坐标变换cv::warpPolar(img, img1, cv::Size(400, 800), center, center.x, cv::INTER_LINEAR | cv::WARP_POLAR_LINEAR);// 逆极坐标变换cv::warpPolar(img1, img2, cv::Size(img.cols, img.rows), center, center.x, cv::INTER_LINEAR  | cv::WARP_INVERSE_MAP);cv::imshow("原图", img);cv::imshow("正极坐标变换", img1);cv::imshow("负极坐标变换", img2);cv::waitKey(0);cv::destroyAllWindows();return 0;
}    

在这里插入图片描述

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

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

相关文章

c#设计模式-行为型模式 之 状态模式

&#x1f680;简介 状态模式是一种行为设计模式&#xff0c;它允许对象在其内部状态改变时改变其行为&#xff0c;我们可以通过创建一个状态接口和一些实现了该接口的状态类来实现状态模式。然后&#xff0c;我们可以创建一个上下文类&#xff0c;它会根据其当前的状态对象来改…

CUDA+cuDNN+TensorRT 配置避坑指南

深度学习模型加速部署的环境配置&#xff0c;需要在本地安装NVIDIA的一些工具链和软件包&#xff0c;这是一个些许繁琐的过程&#xff0c;而且一步错&#xff0c;步步错。笔者将会根据自己的经验来提供建议&#xff0c;减少踩坑几率。当然可以完全按照官方教程操作&#xff0c;…

【Sentinel】Sentinel原码分析

本文内容来自【黑马】Sentinel从使用到源码解读笔记&#xff0c;做了部分修改和补充 目录 Sentinel 基本概念 基本流程 Node Entry 定义资源的两种方式 使用try-catch定义资源 使用注解标记资源 基于注解标记资源的实现原理 Context 什么是Context Context的初始化 …

SpringBoot青海省旅游系统

本系统采用基于JAVA语言实现、架构模式选择B/S架构&#xff0c;Tomcat7.0及以上作为运行服务器支持&#xff0c;基于JAVA、JSP等主要技术和框架设计&#xff0c;idea作为开发环境&#xff0c;数据库采用MYSQL5.7以上。 开发环境&#xff1a; JDK版本&#xff1a;JDK1.8 服务器…

查看本机Arp缓存,以及清除arp缓存

查看Arp缓存目录 Windows 系统使用 winR&#xff0c;输入cmd 在命令窗口输入 arp -a 删除Arp缓存目录 在命令窗口输入 arp -d * 查看主机路由表

深度学习纯小白如何从零开始写第一篇论文?看完这篇豁然开朗!

&#x1f4e2;前言 上个月小贾消失了一段时间&#xff0c;原因就是。。。 写论文去啦&#xff01;&#xff01;&#xff01; 先拿我导的认可镇个楼&#xff1a; 本篇文章将分享我个人从迷茫地找方向→苦苦做了48次实验才高效涨点→写论文到头秃等等一系列真实经历&#xff0c…

R语言通过接口获取网上数据平台的免费数据

大家好&#xff0c;我是带我去滑雪&#xff01; 作为一名统计学专业的学生&#xff0c;时常和数据打交道&#xff0c;我深知数据的重要性。数据是实证研究的重要基础&#xff0c;每当在完成一篇科研论文中的实证研究部分时&#xff0c;我都能深刻体会实证研究最复杂、最耗时的工…

【广州华锐互动】VR线上播控管理系统让虚拟现实教学效果更加显著

随着科技的不断发展&#xff0c;虚拟现实(VR)技术已经逐渐走进我们的生活&#xff0c;尤其在教育领域&#xff0c;VR技术的应用为学生提供了全新的学习体验。 广州华锐互动作为一家成立16年的老牌VR公司&#xff0c;开发了不少VR教学课件&#xff0c;包括物理、化学、农林、土木…

【Linux】 grep命令使用

grep (global regular expression) 命令用于查找文件里符合条件的字符串或正则表达式。 grep命令 -Linux手册页 语法 grep [选项] pattern [files] ls命令常用选项及作用 执行令 grep --help 执行命令结果 参数 -i&#xff1a;忽略大小写进行匹配。-v&#xff1a;反…

分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测

分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测 目录 分类预测 | MATLAB实现KOA-CNN-BiLSTM开普勒算法优化卷积双向长短期记忆神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现KOA-CNN-BiLST…

剑指offer——JZ68 二叉搜索树的最近公共祖先 解题思路与具体代码【C++】

一、题目描述与要求 二叉搜索树的最近公共祖先_牛客题霸_牛客网 (nowcoder.com) 题目描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q&#xff0c;最近公共祖先LCA(T,p,q)表示一个节点x&#…

你知道AI智能知识库最大的用处在哪里吗?

在当今信息爆炸的时代&#xff0c;获取准确、及时的知识变得至关重要&#xff0c;而AI智能知识库的出现正好可以很好地解决这些问题。AI智能知识库的最大用处是在于帮助人们快速、高效地获取所需的知识和信息。它不仅可以减少人们在查找和筛选信息上的时间和精力投入&#xff0…

Arm64体系架构-MPIDR_EL1寄存器

背景 在Arm64多核处理器中, 各核间的关系可能不同. 比如1个16 core的cpu, 每4个core划分为1个cluster,共享L2 cache. 当我们需要从core 0将任务调度出来时,如果优先选择core 1~3, 那么性能明显时优于其他core的. 那么操作系统怎么知道core之间这样的拓扑信息呢? Arm提供了MPID…

Selenium+Phantomjs动态获取CSDN下载资源信息和评论

源代码 # codingutf-8 from selenium import webdriver from selenium.webdriver.common.keys import Keys import selenium.webdriver.support.ui as ui from selenium.webdriver.common.action_chains import ActionChains import time import re…

FPGA设计时序约束三、设置时钟组set_clock_groups

目录 一、背景 二、时钟间关系 2.1 时钟关系分类 2.2 时钟关系查看 三、异步时钟组 3.1 优先级 3.2 使用格式 3.3 asynchronous和exclusive 3.4 结果示例 四、参考资料 一、背景 Vivado中时序分析工具默认会分析设计中所有时钟相关的时序路径&#xff0c;除非时序约束…

Games104现代游戏引擎笔记 基础ai

游戏AI navigation(导航系统) 地图的表达形式&#xff0c; 寻路&#xff0c;路径优化 Map representation&#xff1a; 1.可行走区域&#xff08;物理碰撞&#xff0c;跳跃距离&#xff0c;攀爬高度&#xff09; 2.表达形式&#xff1a;waypoint networks(路点网络图)&#…

交互式ICP

以下程序演示如何编写交互式ICP查看器。该程序将加载点云并对其进行刚性变换。之后&#xff0c;使用ICP算法将变换后的点云与原来的点云对齐。每次用户按下“空格”&#xff0c;进行ICP迭代&#xff0c;刷新可视化界面。 代码实现 资源准备 monkey.ply #include <string&…

I/O多路复用【Linux/网络】(C++实现select、poll和epoll服务器)

阅读前导&#xff1a; “I/O 多路复用”处于知识树中网络和操作系统的最后&#xff0c;因此本文默认读者有计算机网络和操作系统的基础。 1. 引入&#xff1a;C10K 问题 c10k 问题是指如何让一个服务器同时处理超过 10000 个客户端的连接&#xff0c;这是一个网络编程中的经…

STM32+USB3300复位枚举异常的问题

关键字&#xff1a;STM32F4&#xff0c;STM32H7&#xff0c;USB3300&#xff0c;USBHS&#xff0c;Reset复位 F4和H7用的都是DWC2的USBIP&#xff0c;我的板子上3300单片机工作的很好&#xff0c;插入枚举一切正常&#xff0c;但是设备收到上位机的复位命令后&#xff0c;单片…

Redis之主从复制,哨兵模式,集群

Redis之主从复制&#xff0c;哨兵模式&#xff0c;集群 1、主从复制1.1主从复制概述1.2Redis主从复制作用1.3Redis主从复制流程1.4部署Redis 主从复制 2、哨兵模式2.1哨兵模式原理2.2哨兵模式的作用2.3哨兵模式的结构2.4故障转移机制2.5搭建Redis 哨兵模式 3、Redis集群模式3.1…