【OpenCV C++20 学习笔记】仿射变换-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 M⋅X就能得到 T T T了

也可以从几何学的角度来考虑第2种情况。如下图,图1中3个点组成的三角形经过仿射变换成了图2中的三角形:

因为三点可以确定一个平面,所以这个方法可以用在图片上;即确定图片上3个点的仿射变换,就相当于确定了整张图片的仿射变换。

API

getAffineTransform()函数

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

cpp 复制代码
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} \[xi′yi′\]=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()`函数,其原型如下: ```cpp 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 dst→src > * `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()`方法,其原型如下: ```cpp 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 α=scale⋅cosangle
  • β = s c a l e ⋅ sin ⁡ a n g l e \beta = scale \cdot \sin angle β=scale⋅sinangle

示例

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

cpp 复制代码
#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);
}

运行结果如下:

相关推荐
liang89999 分钟前
Shiro学习(七):总结Shiro 与Redis 整合过程中的2个问题及解决方案
redis·学习·bootstrap
weixin_4862814516 分钟前
FFmpeg源码学习---ffmpeg
学习·ffmpeg
CodeCipher2 小时前
Java后端程序员学习前端之html
学习·html5
Dr_Zobot2 小时前
SLAM学习系列——ORB-SLAM3安装(Ubuntu20-ROS/Noetic)
学习·ubuntu·软件安装
枫叶20003 小时前
OceanBase数据库-学习笔记5-用户
数据库·笔记·学习·oceanbase
Nuyoah.4 小时前
《Vue3学习手记7》
javascript·vue.js·学习
冰茶_4 小时前
WPF之Button控件详解
大数据·学习·microsoft·c#·wpf
MrZWCui5 小时前
iOS—仿tableView自定义闹钟列表
学习·macos·ios·objective-c
jndingxin5 小时前
OpenCV 图形API(64)图像结构分析和形状描述符------在图像中查找轮廓函数findContours()
人工智能·opencv
IT技术员5 小时前
【Java学习】Java的CGLIB动态代理:通俗解释与使用指南
java·开发语言·学习