当前位置: 首页 > news >正文

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;
}    

在这里插入图片描述

相关文章:

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

文章目录 一、仿射变换1. getRotationMatrix2D()2. warpAffine() 二、透射变换三、极坐标变换 一、仿射变换 在OpenCV中没有专门用于图像旋转的函数&#xff0c;而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心&#xff0c;之后确定旋转…...

http.header.Set()与Add()区别;

在Go语言中进行HTTP请求时&#xff0c;http.Header对象表示HTTP请求或响应的头部信息。http.Header是一个map[string][]string类型的结构&#xff0c;用于存储键值对&#xff0c;其中键表示HTTP头字段的名称&#xff0c;值是一个字符串切片&#xff0c;可以存储多个相同名称的头…...

vue-7-vuex

一、Vuex 概述 目标&#xff1a;明确Vuex是什么&#xff0c;应用场景以及优势 1.是什么 Vuex 是一个 Vue 的 状态管理工具&#xff0c;状态就是数据。 大白话&#xff1a;Vuex 是一个插件&#xff0c;可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。例如&#xff1a;购…...

SSO单点登录和OAuth2.0区别

一、概述 SSO是Single Sign On的缩写&#xff0c;OAuth是Open Authority的缩写&#xff0c;这两者都是使用令牌的方式来代替用户密码访问应用。流程上来说他们非常相似&#xff0c;但概念上又十分不同。SSO大家应该比较熟悉&#xff0c;它将登录认证和业务系统分离&#xff0c…...

【轻松玩转MacOS】基本操作篇

引言 本文是系列的开篇&#xff0c;我将为大家介绍MacOS的基本操作。对于初次接触MacOS的用户来说&#xff0c;掌握这些基本操作是必不可少的。无论是启动和关机&#xff0c;还是使用键盘和鼠标&#xff0c;或者是快捷键的使用&#xff0c;这些基本操作都是你开始使用MacOS的第…...

华为ICT——第三章图像处理基本任务

目录 1&#xff1a;数字图像处理的层次&#xff1a;&#xff08;处理-分析-理解&#xff09;顺序不能错&#xff1a; 2&#xff1a;图像处理&#xff08;图像处理过程&#xff09;&#xff1a; 3&#xff1a;图像分析&#xff08;特征提取&#xff09;&#xff1a; 4&#x…...

(C++)引用的用法总结

引用&#xff08;reference&#xff09;是C极为重要的一部分&#xff0c;本文对其用法进行简单总结。 1. 引用的基本用法 引用的关键字为&&#xff0c;表示取地址的意思&#xff0c;引用变量定义如下&#xff1a; int m 1; int &n m; //定义 cout<<"n:…...

Charles:移动端抓包 / windows客户端 iOS手机 / 手机访问PC本地项目做调试

一、背景描述 1.1、本文需求&#xff1a;移动端进行抓包调试 1.2、理解Charles可以做什么 Charles是一款跨平台的网络代理软件&#xff0c;可以用于捕获和分析网络流量&#xff0c;对HTTP、HTTPS、HTTP/2等协议进行调试和监控。使用Charles可以帮助开发人员进行Web开发、调试…...

【AI】深度学习——人工智能、深度学习与神经网络

文章目录 0.1 如何开发一个AI系统0.2 表示学习(特征处理)0.2.1 传统特征学习特征选择过滤式包裹式 L 1 L_1 L1​ 正则化 特征抽取监督的特征学习无监督的特征学习 特征工程作用 0.2.2 语义鸿沟0.2.3 表示方式关联 0.2.4 表示学习对比 0.3 深度学习0.3.1 表示学习与深度学习0.3.…...

RK3288:BT656 RN6752调试

这篇文章主要想介绍一下再RK3288平台上面调试BT656 video in的注意事项。以RN6752转接芯片&#xff0c;android10平台为例进行介绍。 目录 1. RK3288 VIDEO INPUT 并口 2. 驱动调试 2.1 RN6752 驱动实现 ①rn6752_g_mbus_config总线相关配置 ②rn6752_querystd配置制式 …...

LLMs 蒸馏, 量化精度, 剪枝 模型优化以用于部署 Model optimizations for deployment

现在&#xff0c;您已经了解了如何调整和对齐大型语言模型以适应您的任务&#xff0c;让我们讨论一下将模型集成到应用程序中需要考虑的事项。 在这个阶段有许多重要的问题需要问。第一组问题与您的LLM在部署中的功能有关。您需要模型生成完成的速度有多快&#xff1f;您有多…...

Milvus踩坑笔记

本文用于记录在学习 Milvus文档时所遇到的一些Bug或报错及解决方法 参考文章&#xff1a; 官方demo&#xff1a;在Dynamic Schema的集合中插入数据 报错1&#xff1a;auto id enabled, id shouldnt in entities[0] 问题描述 此报错出现在Milvus官方在介绍 Dynamic Schema …...

什么是轴电流?轴电流对轴承有什么危害?

根据同步发电机结构及工作原理&#xff0c;由于定子铁芯组合缝、定子硅钢片接缝&#xff0c;定子与转子空气间隙不均匀&#xff0c;轴中心与磁场中心不一致等&#xff0c;机组的主轴不可避免地要在一个不完全对称的磁场中旋转。这样&#xff0c;在轴两端就会产生一个交流电压。…...

react create-react-app v5配置 px2rem (不暴露 eject方式)

环境信息&#xff1a; create-react-app v5 “react”: “^18.2.0” “postcss-plugin-px2rem”: “^0.8.1” 配置步骤&#xff1a; 不暴露 eject 配置自己的webpack&#xff1a; 1.下载react-app-rewired 和 customize-cra-5 npm install react-app-rewired customize-cra…...

.net中用标志位解决socket粘包问题

以下为wpf中, 用标志位"q" 解决粘包问题 using MyFrameWorkWpf.Entities; using System.Collections.ObjectModel; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.…...

【Ubuntu】Systemctl 管理 MinIO 服务器的启动和停止

要使用 systemctl 来管理 MinIO 服务器的启动和停止&#xff0c;您需要创建一个 systemd 服务单元文件&#xff0c;以便 systemd 能够启动和停止 MinIO 服务器。下面是一般的步骤&#xff1a; 创建 systemd 服务单元文件&#xff1a; 打开终端并使用文本编辑器创建一个新的 sys…...

《golang设计模式》第二部分·结构型模式-07-代理模式(Proxy)

文章目录 1. 概述1.1 角色1.2 模式类图 2. 代码示例2.1 设计2.2 代码2.3 示例类图 1. 概述 代理&#xff08;Proxy&#xff09;是用于控制客户端访问目标对象的占位对象。 需求&#xff1a;在调用接口实现真是主题之前需要一些提前处理。 解决&#xff1a;写一个代理&#xff…...

Jmeter常用线程组设置策略

一、前言 在JMeter压力测试中&#xff0c;我们时常见到的几个场景有&#xff1a;单场景基准测试、单场景并发测试、单场景容量测试、混合场景容量测试、混合场景并发测试以及混合场景稳定性测试 在本篇文章中&#xff0c;我们会用到一些插件&#xff0c;在这边先给大家列出&…...

【Spring】Spring MVC 程序开发

Spring MVC 程序开发 一. 什么是 Spring MVC1. MVC2. Spring、Spring Boot 与 Spring MVC 二. 创建 Spring MVC 项目1. 创建项目2. 用户和程序的映射3. 获取用户请求参数①. 获取单个参数②. 获取多个参数③. 传递对象④. 后端参数重命名&#xff08;后端参数映射&#xff09;R…...

如何在企业网站里做好网络安全

在当今数字时代&#xff0c;网站不仅仅是企业宣传和产品展示的平台&#xff0c;更是日常生活和商业活动中不可或缺的一部分。然而&#xff0c;随着网络技术不断发展&#xff0c;网站的安全问题日益凸显。保护网站和用户数据的安全已经成为至关重要的任务&#xff0c;以下是一些…...

windows server 2012 服务器打开系统远程功能

服务器上开启远程功能 进入服务器&#xff0c;选择“添加角色和功能” 需要选择安装的服务器类型&#xff0c;如图所示 然后在服务器池中选择你需要使用的服务器。 选择完成后&#xff0c;在图示列表下勾选“远程桌面服务” 再选择需要安装的功能和角色服务。 选择完成确认内容…...

智能工厂MES系统,终端设备支持手机、PDA、工业平板、PC

一、开源项目简介 源计划智能工厂MES系统(开源版) 功能包括销售管理&#xff0c;仓库管理&#xff0c;生产管理&#xff0c;质量管理&#xff0c;设备管理&#xff0c;条码追溯&#xff0c;财务管理&#xff0c;系统集成&#xff0c;移动端APP。 二、开源协议 使用GPL-3.0开…...

GPT的优势和GPT缺点

GPT&#xff0c;即Generative Pre-trained Transformer&#xff0c;是一种基于人工智能技术的自然语言处理模型。它采用了深度学习算法&#xff0c;可以通过大量的文本数据来学习自然语言的规律&#xff0c;并能够生成流畅、准确的语句。下面我们将探讨GPT技术的优势。 首先&a…...

微信小程序开发缺少中间证书问题(腾讯云、阿里云等做服务器)

项目使用nginx做负载均衡后&#xff0c;不再采用原来直接用jar包的方式直接开启对应端口&#xff0c;所以需要重新从云服务器上下载证书&#xff0c;写入到Nginx读取的证书路径上即可。...

动态代理初步了解

准备案例 需求 模拟某企业用户管理业务&#xff0c;需包含用户登录&#xff0c;用户删除&#xff0c;用户查询功能&#xff0c;并要统计每个功能的耗时。 分析与实现 定义一个UserService表示用户业务接口&#xff0c;规定必须完成用户登录&#xff0c;用户删除&#xff0c…...

QT国际化

引入 在代码里面写中文就很low&#xff0c;运行时多语言切换是客户端程序都应该具备的。 qt国际化其实就是qt中字符串的字符集编码的设置。当然这个设置不是简单的选择一下什么语言就好&#xff0c;这个需要编程人员来处理的。 通常对于非拉丁字符(主要指​​latin1​​​字符…...

微信小程序button按钮去除边框去除背景色

button边框 去除button边框 在button上添加plain“true”在css中添加button.avatar-wrapper {background: none}用于去除button背景色在css中添加button.avatar-wrapper[plain]{ border:0 }用于去除button边框...

Neo4j深度学习

Neo4j的简介 Neo4j是用Java实现的开源NoSQL图数据库。从2003年开始开发&#xff0c;2007年正式发布第一版&#xff0c;其源码托管于GitHtb。Neo4j作为图数据库中的代表产品&#xff0c;已经在众多的行业项目中进行了应用&#xff0c;如&#xff1a;网络管理、软件分析、组织和…...

【数据结构C/C++】链式存储与顺序存储结构栈

文章目录 链式存储结构顺序存储结构 下面这篇文章是我大二时候写的比较详细的实现过程&#xff0c;再这篇文章我也会再一次比较简单的再次简述一下链式与顺序存储结构的实现方式。 链式存储结构与顺序存储结构详解 这里我就不使用C再一次实现这两个栈了&#xff0c;有兴趣的也可…...

【数据库系统概论】数据定义之基本表的定义/创建、修改和删除

前言 &#x1f6a9;定义/创建基本表语法示例 修改基本表语法示例 删除基本表语法示例 感谢 &#x1f496; 前言 &#x1f6a9; SQL支持数据库系统的三级模式结构&#xff0c;其模式、外模式和内模式中的基本对象有表、视图和索引&#xff0c;因此&#xff0c;SQL的数据定义功能…...