【OpenCV C++20 学习笔记】仿射变换-warpAffine, getRotationMatrix2D

news2024/9/24 21:24:47

仿射变换

  • 原理
    • 概述
    • 得到仿射变换的方法
  • API
    • getAffineTransform()函数
    • warpAffine()函数
    • getRotationMatrix2D()函数
  • 示例

原理

概述

仿射变换是矩阵乘法(线性变换)和向量加法的结合。它包含了:

  • 旋转(线性变换)
  • 转换(向量加法)
  • 缩放(线性变换)

本质上,仿射变换就是两个图像矩阵之间的运算。

通常用一个 2 × 3 2 \times 3 2×3的矩阵来展示仿射变换(向量加法):
A = [ a 00 a 01 a 10 a 11 ] 2 × 2 B = [ b 00 b 10 ] 2 × 1 A= \begin{bmatrix} a_{00} & a_{01} \\ a_{10} & a_{11} \end{bmatrix}_{2\times 2} B= \begin{bmatrix} b_{00} \\ b_{10} \end{bmatrix}_{2 \times 1} A=[a00a10a01a11]2×2B=[b00b10]2×1
M = [ A B ] = [ a 00 a 01 b 00 a 10 a 11 b 10 ] 2 × 3 M = \begin{bmatrix} A & B \end{bmatrix} = \begin{bmatrix} a_{00} & a_{01} & b_{00} \\ a_{10} & a_{11} & b_{10} \end{bmatrix}_{2 \times 3} M=[AB]=[a00a10a01a11b00b10]2×3
M M M就是要进行仿射变换的矩阵,它可以由 A A A B B B相加得到。下面用一个2D向量 X = [ x y ] X=\begin{bmatrix} x \\ y \end{bmatrix} X=[xy]来对其进行变换。可以对其中的 A A A B B B进行计算:
T = A ⋅ [ x y ] + B T=A \cdot \begin{bmatrix} x \\ y \end{bmatrix} + B T=A[xy]+B
也可以直接对 M M M进行计算。
T = M ⋅ [ x , y , 1 ] T T = M \cdot [x, y, 1]^T T=M[x,y,1]T
得到仿射变换后的结果 T T T
T = [ a 00 x a 01 y b 00 a 10 x a 11 y b 10 ] T= \begin{bmatrix} a_{00}x & a_{01}y & b_{00} \\ a_{10}x & a_{11}y & b_{10} \end{bmatrix} T=[a00xa10xa01ya11yb00b10]
总结:变换矩阵 M M M,将原矩阵 X X X和结果矩阵 T T T联系起来了, X X X通过 M M M的变换,得到 T T T

得到仿射变换的方法

在实际操作中通常有两种情况:

  1. X X X T T T已知,需要找到变换矩阵 M M M
  2. M M M X X X已知,需要计算结果矩阵 T T T。这种情况只需要将 M ⋅ X M \cdot X MX就能得到 T T T

也可以从几何学的角度来考虑第2种情况。如下图,图1中3个点组成的三角形经过仿射变换成了图2中的三角形:
仿射变换
因为三点可以确定一个平面,所以这个方法可以用在图片上;即确定图片上3个点的仿射变换,就相当于确定了整张图片的仿射变换。

API

getAffineTransform()函数

如上所诉,要像对整张图片进行放射变换,首先要确定图片上3个点的仿射变换,这个操作在OpenCV中通过getAffineTransform函数实现,其原型如下:

Mat cv::getAffineTransform(	InputArray	src,
							OutputArray	dst)
  • src是包含确定仿射变换的3个点的坐标的数组
  • dst是仿射变换之后3个点的坐标结果的数组
  • 返回的矩阵储存了从src变换到dst的变换方式,即第一章中讲的 2 × 3 2 \times 3 2×3 M M M变换矩阵

这个函数的算法如下:
[ x i ′ y i ′ ] = M ⋅ [ x i y i 1 ] \begin{bmatrix} x'_i \\ y'_i \end{bmatrix} = M \cdot \begin{bmatrix} x_i \\ y_i \\ 1 \end{bmatrix} [xiyi]=M xiyi1

  • i = 0 , 1 , 2 i=0, 1, 2 i=0,1,2,即代表3个点中的每一个
  • 等号左边的向量为变换后的点坐标,即 d s t ( i ) = ( x i ′ , y i ′ ) dst(i)=(x'_i, y'_i) dst(i)=(xi,yi)
  • M M M为函数返回的矩阵,即储存变换方式的矩阵
  • x i x_i xi y i y_i yi为原来的点坐标,即 s r c ( i ) = ( x i , y i ) src(i)=(x_i, y_i) src(i)=(xi,yi)

warpAffine()函数

确定了图片的变换方式之后,就可以将变换方式应用到图片上了。这时需要用到warpAffine()函数,其原型如下:

void cv::warpAffine(InputArray		src,
					OutputArray		dst,
					InputArray		M,
					Size			dsize,
					int				flags = INTER_LINEAR,
					int				borderMode = BORDER_CONSTANT,
					const Scalar&	borderValue = Scalar())
  • M为储存转换方式的矩阵,即getAffineTransform函数的输出结果
  • dsize为输出图片dst的尺寸
  • flags指定插值计算方法,默认为INTER_LINEAR,即双线性插值;特别地,当该参数的值为WARP_INVERSE_MAP的时候,执行与转换矩阵M相反的转换,即 d s t → s r c dst \rightarrow src dstsrc
  • borderMode指定外推计算方法,默认为BORDER_CONSTANT,即用单色进行外推扩充;特别的,当该参数的值为BORDER_TRANSPARENT时,超出原图范围的像素点将不被改函数修改
  • borderValue参数只有在borderMode = BORDER_CONSTANT时,才需要提供,用来指定扩充的像素颜色

这个函数的算法如下:
d s t ( x , y ) = s r c ( M 11 x + M 12 y + M 13 , M 21 x + M 22 y + M 23 ) dst(x,y)=src(M_{11}x+M_{12}y+M_{13}, M_{21}x+M_{22}y+M_{23}) dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)
即第一章中讲的 T = M ⋅ [ x , y , 1 ] T T = M \cdot [x, y, 1]^T T=M[x,y,1]T

getRotationMatrix2D()函数

上述的变换还只能像第一章的图中所展示的那样将图片进行变形。如果想要图中的三角形旋转一定的角度,则需要用到getRotationMatrix2D()方法,其原型如下:

Mat cv::getRotationMatrix2D(Point2f	center,
							double angle,
							double scale)

该函数与getAffineTransform函数类似,返回一个转换矩阵

  • center为旋转中心在原图中的位置坐标
  • angle为旋转角度,正值为逆时针旋转(坐标原点在左上角)
  • scale为各向同性缩放因子

该函数返回的变换矩阵如下:
[ α β ( 1 − α ) ⋅ c e n t e r . x − β ⋅ c e n t e r . y − β α β ⋅ c e n t e r . x + ( 1 − α ) ⋅ c e n t e r . y ] \begin{bmatrix} \alpha & \beta & (1-\alpha) \cdot center.x-\beta \cdot center.y \\ -\beta & \alpha & \beta \cdot center.x+(1-\alpha) \cdot center.y \end{bmatrix} [αββα(1α)center.xβcenter.yβcenter.x+(1α)center.y]

  • α = s c a l e ⋅ cos ⁡ a n g l e \alpha=scale \cdot \cos angle α=scalecosangle
  • β = s c a l e ⋅ sin ⁡ a n g l e \beta = scale \cdot \sin angle β=scalesinangle

示例

本示例先将图片进行仿射变换,再将其顺时针旋转50度,并缩小到0.6倍。完整代码如下:

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

using namespace cv;
using namespace std;

int main() {
	Mat src{ imread("lena.jpg") };

	//原图中的3个点
	Point2f srcTri[3];
	srcTri[0] = Point2f(0.f, 0.f);
	srcTri[1] = Point2f(src.cols - 1.f, 0.f);
	srcTri[2] = Point2f(0.f, src.rows - 1.f);

	//变换后3个点的坐标
	Point2f dstTri[3];
	dstTri[0] = Point2f(0.f, src.rows * 0.33f);
	dstTri[1] = Point2f(src.cols*0.85f, src.rows * 0.25f);
	dstTri[2] = Point2f(src.cols*0.15f, src.rows * 0.7f);

	//获取变换矩阵
	Mat warp_mat = getAffineTransform(srcTri, dstTri);

	//用于储存变换结果的矩阵(和原图有相同的尺寸和数据类型)
	Mat warp_dst{ Mat::zeros(src.rows, src.cols, src.type()) };
	//仿射变换
	warpAffine(src, warp_dst, warp_mat, warp_dst.size());

	Point center{ Point(warp_dst.cols / 2, warp_dst.rows / 2) };
	double angle{ -50.0 };
	double scale{ 0.6 };

	//获取旋转的变换矩阵
	Mat rot_mat{ getRotationMatrix2D(center, angle, scale) };

	//用于储存旋转结果的矩阵
	Mat warp_rotate_dst;
	//旋转变换
	warpAffine(warp_dst,warp_rotate_dst, rot_mat, warp_dst.size());

	imshow("原图", src);
	imshow("仿射变换", warp_dst);
	imshow("仿射变换+旋转", warp_rotate_dst);
	waitKey(0);
}

运行结果如下:
仿射变换+旋转

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

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

相关文章

【递归 + 记忆化搜索优化】力扣494. 目标和

给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 ‘’ &#xff0c;在 1 之前添加 ‘-…

立体连接模式下的传播与沟通:AI智能名片小程序的创新应用与深度剖析

摘要&#xff1a;在数字化浪潮的推动下&#xff0c;信息传播与沟通方式正经历着前所未有的变革。立体连接模式&#xff0c;作为这一变革的重要产物&#xff0c;通过整合物理空间、虚拟网络空间与社群心理空间的三维联动&#xff0c;实现了信息的深度传播与高效互动。AI智能名片…

Java新手指南:从菜鸟到编程大师的趣味之路-类和对象

这里是Themberfue 本章主要介绍的是Java最重要的面向对象&#xff08;OOP&#xff09;的基础 面向对象 Java是一门纯面向对象的语言&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用…

MySQL --- 复合查询

目录 一、多表查询 二、自连接 三、子查询 1、单行子查询 2、多行子查询 3、多列子查询 4、在from后面使用子查询 四、合并查询 1、union 2、union all 五、内连接 六、外连接 1、左外连接 2、右外连接 一、多表查询 我们需要的数据往往会来自不同的表&#xf…

redis面试(十)锁释放

自动释放 首先锁的释放分为两种&#xff0c;一种是自动释放&#xff0c;加入说加锁的线程宕机了不在了&#xff0c;我们之前说过这个。 那这个线程中的对redis这个锁不断刷新过期时间的看门狗逻辑就没有了&#xff0c;所以这个锁最多等待30s的时间就会自动过期删除&#xff0c…

文件I/O第一天作业 2024.8.8

使用系统I/O实现Linux的cat命令 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h>int main(int argc, const char *argv[]) {if(argc < 2){printf("请输…

【笔试题面试题】IO类2 知识点汇总(笔试面试题)

书接上文&#xff0c;配上前文一起实用更加&#xff0c;持续更新&#xff0c;督促自己学习 目录 1、详细描述一下什么是IO以及标准IO和文件IO的区别&#xff08;补充&#xff09; 2、什么是死锁&#xff0c;如何避免死锁&#xff08;补充&#xff09; 3、为什么引入同步互斥…

NVIDIA Triton系列09-为服务器添加模型

NVIDIA Triton系列09-为服务器添加模型 B站&#xff1a;肆十二-的个人空间-肆十二-个人主页-哔哩哔哩视频 (bilibili.com) 博客&#xff1a;肆十二-CSDN博客 问答&#xff1a;(10 封私信 / 72 条消息) 肆十二 - 知乎 (zhihu.com) 前面已经用 https://github.com/triton-inferen…

基于python的百度迁徙迁入、迁出数据分析(七)

参考&#xff1a;【Python】基于Python的百度迁徙2——迁徙规模指数&#xff08;附代码&#xff09;-CSDN博客 记录于2024年8月&#xff0c;这篇是获取百度迁徙指数&#xff0c;之前我们都在讨论不同城市的迁徙比例关系&#xff0c;这篇我们来获取百度迁徙指数这个数据&#x…

大学新生入门编程的最佳路径:嵌入式领域的深入探索

对于大学新生来说&#xff0c;编程已成为一项必不可少的技能。面对众多编程语言和学习资源&#xff0c;新生们常常感到迷茫。如何选择适合自己的编程语言&#xff1f;如何制定有效的学习计划&#xff1f;如何避免常见的学习陷阱&#xff1f;本文将为你提供嵌入式领域的编程入门…

测试工具之JMeter

JMeter Apache JMeter应用程序是开源软件,是一个100%纯Java应用程序,旨在负载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的,但后来扩展到其他测试功能。 JMeter是一个免费、开源、跨平台的性能测试工具,于20世纪90年代后期面世。这是一个成熟、健全且具有…

开源一套金融大模型插件(ChatGPT)

shares vscode 插件A 股量化交易系统自研金融大模型&#xff0c;复利Chat 源码地址&#xff1a; https://github.com/xxjwxc/shares

NPDP考前怎么复习?NPDP200问PDF版来啦~

距离NPDP下半年考试还有4个月的时间&#xff0c;现在正是备考的黄金期。 以下复习建议~ 01.制定详细计划 首先&#xff0c;根据考试大纲&#xff0c;可以将内容划分为几个模块&#xff0c;如新产品开发流程、市场研究、产品规划等&#xff0c;并为每个模块设定学习目标和时间…

C语言 | Leetcode C语言题解之第328题奇偶链表

题目&#xff1a; 题解&#xff1a; struct ListNode* oddEvenList(struct ListNode* head) {if (head NULL) {return head;}struct ListNode* evenHead head->next;struct ListNode* odd head;struct ListNode* even evenHead;while (even ! NULL && even->…

`【《Spark SQL 深度探索:内置函数、数据源处理与自定义函数实践》】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索大数据技术Spark—SparkSQL&#xff0c;本篇文章主要讲述了&#xff1a;Spark SQL 深度探索&#xff1a;内置函数、数据源处理与自定义函数实践等等。欢迎大家…

Netty技术全解析:MessageToMessageCodec类深度解析

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

有序数组的平方(LeetCode)

题目 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 解题 以下算法时间复杂度为 def sortedSquares(nums):n len(nums)result [0] * n # 创建一个结果数组&#xff0c;长度与 nums 相同le…

【wiki知识库】08.添加用户登录功能--前端Vue部分修改

&#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 目录 &#x1f34a; 编程有易不绕弯&#xff0c;成长之路不孤单&#xff01; 一、今日目标 二、前端Vue模块的修改 2.1 the-header组件 2.2 store工具 2.3 router路由配置修改 一、今日目标 上篇文章…

河南萌新联赛2024第(四)场:河南理工大学

河南萌新联赛2024第&#xff08;四&#xff09;场&#xff1a;河南理工大学 2024.8.7 13:00————17:00 过题数5/12 补题数8/12 该出奇兵了 小雷的神奇电脑 岗位分配 简单的素数 AND 小雷的算式 循环字符串 聪明且狡猾的恶魔 马拉松 尖塔第四强的高手 比赛 抓字符 B - 小雷…

企业如何选择靠谱的第三方软件测试机构?

第三方软件测试机构是专门提供软件测试服务的第三方检测机构&#xff0c;旨在对软件的功能、性能、安全性等方面进行系统评估&#xff0c;确保其满足设定的标准和需求。这些机构通常拥有丰富的行业经验和专业资质&#xff0c;可以为企业提供包括项目验收测试、软件确认测试、安…