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

运行结果如下:

相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习
im_AMBER4 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J4 天前
从“Hello World“ 开始 C++
c语言·c++·学习