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

news2024/11/17 1:42:07

文章目录

  • 一、仿射变换
    • 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;

    // 定义仿射旋转矩阵rM
    cv::Point2f center(h / 2, w / 2);  // 以图像中心为坐标
    double angle = 30;  // 设定30的逆时针旋转角度
    double scale = 1.0;   // 不进行缩放
    // double scale = 0.5;   // 缩放0.5
    cv::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.coloradmin.cn/o/1074033.html

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

相关文章

Windows10打开应用总是会弹出提示窗口的解决方法

用户们在Windows10电脑中打开应用程序&#xff0c;遇到了总是会弹出提示窗口的烦人问题。这样的情况会干扰到用户的正常操作&#xff0c;给用户带来不好的操作体验&#xff0c;接下来小编给大家详细介绍关闭这个提示窗口的方法&#xff0c;让大家可以在Windows10电脑中舒心操作…

Java Agent初探

1&#xff1a;Java Agent简介 Java Agent 这个技术出现在 JDK1.5 之后&#xff0c;对于大多数人来说都比较陌生&#xff0c;但是多多少少又接触过&#xff0c;实际上&#xff0c;我们平时用的很多工具&#xff0c;都是基于 Java Agent 实现的&#xff0c;例如常见的热部署 JRe…

电脑中的opencl.dll丢失怎么办,三步解决opencl.dll丢失

最近有不少用户都遇到了opencl.dll丢失的情况&#xff0c;其实解决opencl.dll丢失的办法很简单&#xff0c;今天就来教大家如何用三步解决opencl.dll丢失的问题。 一.了解opencl.dll opencl.dll是OpenCL的动态链接库文件。OpenCL&#xff08;Open Computing Language&#xff…

上班第一天同事让我下载个小乌龟,我就去百度小乌龟。。。。

记得那会儿是刚毕业&#xff0c;去上班第一天&#xff0c;管我的那个上级说让我下载个小乌龟&#xff0c;等下把代码拉一下&#xff0c;我那是一脸懵逼啊&#xff0c;我在学校只学过git啊&#xff0c;然后开始磨磨蹭蹭吭吭哧哧的不知所措&#xff0c;之后我想也许百度能救我&am…

华为云云耀云服务器L实例评测 | 实例使用教学之高级使用:配置 Git SSH Key 进行自动识别拉代码

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之高级使用&#xff1a;配置 Git SSH Key 进行自动识别拉代码 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云…

05-进程控制

1. 学习目标 了解进程相关的概念掌握fork/getpid/getppid函数的使用熟练掌握ps/kill命令的使用熟练掌握execl/execlp函数的使用说出什么是孤儿进程什么是僵尸进程熟练掌握wait函数的使用熟练掌握waitpid函数的使用 2 进程相关概念 2.1 程序和进程 程序&#xff0c;是指编译好…

mysql数据库root密码忘记了,这里有一个简单的方法可以解决

mysql安装久了&#xff0c;就容易忘记root密码&#xff0c;那么下面这个找回密码的方法将解决你的问题&#xff1a; 特别注意事项&#xff1a; 本方法只适合mysql数据库密码遗忘&#xff08;忘记了&#xff09; 这个解决方案的前提是你的电脑里安装了navicat&#xff08;其他…

阿桂天山的技术小结:Sqlalchemy+pyodbc连接MSSQL server测试

话不多说,有图有源码 1)确保本机安装了sql server对应的odbc驱动 在控制面板的管理工具中可以查&#xff1a;数据源(ODBC) 我这里已经安装了,如果没有安装可以自行下载安装 2)连接MsSql Server代码 # -*- coding: utf-8 -*- __author__ "阿桂天山"#----------判…

【位图+布隆过滤器】

目录 一、位图1.1位图的概念1.2位图的实现 二、布隆过滤器2.1布隆过滤器的概念2.2布隆过滤器的实现 三、位图的扩展--找只出现一次的数 一、位图 1.1位图的概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常…

IDEA 配置 云服务器远程部署

目录 参考资料远程部署与远程开发远程连接配置配置成功&#xff1a;同步文件自动更新文件配置自动更新文件参数调整正确运行问题1&#xff1a;运行mvn spring-boot:run之后一直卡在第一条下载问题2&#xff1a;运行成功后访问不到问题3&#xff1a;无法配置远程开发 参考资料 …

Vmware下载安装以及创建虚拟机

虚拟机有很多种&#xff0c;常见的有VMware Workstation、VirtualBox等。这里以VMware Workstation为例&#xff0c;可在官网下载并安装。 目录 一、下载 二、安装 三、创建虚拟机 四、Ubuntu安装 下载ISO镜像 Ubuntu 使用ISO镜像 一、下载 第一步那就是要下载一个工具&…

基于Java的在线问卷调查系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技…

PTA 7-2 彩虹瓶(单调栈)

题目 彩虹瓶的制作过程&#xff08;并不&#xff09;是这样的&#xff1a;先把一大批空瓶铺放在装填场地上&#xff0c;然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。 假设彩虹瓶里要按顺序装 N 种颜色的小球&#xff08;不妨将顺序就编号为 1 到 N&#xff09;。…

一文3000字从0到1使用pytest-xdist实现分布式APP自动化测试

目录 01、分布式测试的原理 02、测试项目 03、环境准备 04、搭建步骤 05、分布式执行 06、测试报告 不知道大家有没有遇到这样一种情况&#xff0c;实际工作中&#xff0c;app自动化测试的用例可能是成百上千条的&#xff0c;如果放在一台机器上跑&#xff0c;消耗的时间…

数据结构--算法、数据结构的基本概念

&#x1f4d5;参考&#xff1a;王道 一、算法的基本概念 1.程序数据结构算法 2.算法的特性 &#xff08;1&#xff09;有穷性 执行有穷步之后结束&#xff0c;且每一步都可在有穷时间内完成。 &#xff08;2&#xff09;确定性 &#xff08;3&#xff09;可行性 可通过已经实…

深度学习基础知识 register_buffer 与 register_parameter用法分析

深度学习基础知识 register_buffer 与 register_parameter用法分析 1、问题引入2、register_parameter()2.1 作用2.2 用法 3、register_buffer()3.1 作用3.2 用法 1、问题引入 思考问题&#xff1a;定义的weight与bias是否会被保存到网络的参数中&#xff0c;可否在优化器的作用…

解决PlatformIO下载速度慢以及容易出错(解决vscode下载缓慢问题)

Content 问题描述&#xff1a;依赖下载缓慢问题解决&#xff1a;为vscode配置代理端口 问题描述&#xff1a;依赖下载缓慢 Arduino对于ESP32的开发提供了众多的库&#xff0c;但是Arduino IDE编译速度过于缓慢的问题属实让人难受。 为此我们使用vscode中的platformIO插件&…

斑馬打印機打印中文

创建项目 首先說一下&#xff0c;本文章是借鉴了其他大佬的文章&#xff0c;然后我记录一下的文章。 首先创建好一个.net framework的winform项目。 这里面主要用到两个库文件&#xff1a; Fnthex32.dll、LabelPrint.dll。 Fnthex32这个有8位参数和9位参数的&#xff0c;我这…

数据结构--》解锁数据结构中树与二叉树的奥秘(一)

数据结构中的树与二叉树&#xff0c;是在建立非线性数据结构方面极为重要的两个概念。它们不仅能够模拟出生活中各种实际问题的复杂关系&#xff0c;还常被用于实现搜索、排序、查找等算法&#xff0c;甚至成为一些大型软件和系统中的基础设施。 无论你是初学者还是进阶者&…

三角函数和角公式

该文章对三角函数和角公式做了多种证明&#xff1a;https://mp.weixin.qq.com/s?__bizMzI4ODYwNTM3Ng&mid2247484178&idx1&sn1f6e04c50ae30b63198201db3d9a4f05&chksmec3a96bddb4d1fab4baf8188ca6ba60d8d4364be4f08dc53e13b2e4cdfcec4fdb43928108001&scen…