仿射变换是一种二维变换,它可以将一个二维图形映射到另一个二维图形上,保持了图形的"形状"和"大小"不变,但可能会改变图形的方向和位置。仿射变换可以用一个线性变换矩阵来表示,该矩阵包含了六个参数,可以进行平移、缩放、旋转等操作。通过原理、函数和示例进行解析,帮助大家理解和使用。
下面我们将依次实现平移、旋转、缩放和仿射变换等功能,使用C++语言和OpenCV库。
目录
原理和函数
原理
由于矩阵A的最后一行为(0,0,1),所以认为A是仿射变换矩阵,变换类型主要包括平移、缩放和旋转。
warpAffine()函数详解
warpAffine() 是 OpenCV 库中的一个函数,用于进行二维仿射变换。该函数将输入图像映射到输出图像,应用仿射变换。
函数原型如下:
bash
void cv::warpAffine(InputArray src, OutputArray dst, InputArray mat, Size dsize = Size(), int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, Scalar borderValue = Scalar());
参数详解:
src:输入图像,必须是单通道或三通道的8位或32位浮点型图像。
dst:输出图像,其大小和类型与输入图像相同。
mat:2x3的变换矩阵。
dsize:输出图像的大小,如果这个参数为 Size() ,则输出图像的大小将与输入图像相同。
flags:用于指定插值的方法,默认为线性插值。可用的选项有 INTER_NEAREST, INTER_LINEAR, INTER_CUBIC 等。
borderMode:用于指定如何处理输出图像边缘的像素,默认为常量填充模式。可用的选项有 BORDER_CONSTANT, BORDER_REPLICATE, BORDER_WRAP 等。
borderValue:用于指定填充的边界值,默认为0。
这个函数使用仿射变换来将输入图像映射到输出图像。仿射变换包括旋转、缩放、平移等操作,但不包括扭曲和剪切。这个函数非常有用,特别是在需要将图像映射到另一个大小或以特定方式旋转或倾斜图像时。
示例
平移
原理
平移变换可以用一个3x3的变换矩阵来表示,其中第一行和第二行表示原始图像的行向量和列向量,第三行表示变换后的行向量和列向量,和原理部分类似,但有些版本只需要设置一个2x3的变换矩阵即可,可以省略第三行。在本例中,即定义的是2x3的变换矩阵,我们将原始图像向右移动100个像素,向下移动300个像素。其中,数值为正,则向正方向移,数值为负,则向反向相移。
如上述矩阵转换矩阵,只需要设置第一行的第三个元素tx和第二行第三列的ty即可,体现在2行3列的矩阵中如下面运行示例中的100和300所示。
运行示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("ceshi.jpg");
if (src.empty()) {
cout << "Could not read the source image" << endl;
return -1;
}
Mat trans_mat = (Mat_<double>(2, 3) << 1, 0, 100, 0, 1, 300);
Mat dst;
warpAffine(src, dst, trans_mat, src.size());
imshow("Source Image", src);
imshow("Affine Transformed Image", dst);
imwrite("pingyi.jpg", dst);
waitKey(0);
return 0;
}
在代码中,Mat trans_mat = (Mat_<double>(2, 3) << 1, 0, 100, 0, 1, 300);
是定义的一个2行3列的转换矩阵,第一行是右移100,第二行是下移300。最后,我们使用warpAffine()函数进行仿射变换,将原始图像映射到输出图像中,并显示原始图像和变换后的图像。运行可以看到相应的效果,如下图所示。
上面为原图,下面为平移后的图像。
缩放
原理
仿射变换中的缩放指的是对图像进行等比例的放大或缩小。在仿射变换矩阵中,可以通过设置第一行和第二行的元素来控制缩放。
由上面的平移可知,第一行第三列和第二行第三列的数值是控制平移的,在此处可知,第一行第一列和第二行第二列是用于控制x轴和y轴的缩放比例的,在下面的运行示例中也可以看出缩放因子在的位置与此相对应。
缩小示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("ceshi.jpg");
if (src.empty()) {
cout << "Could not read the source image" << endl;
return -1;
}
// 设置缩放比例
double scale = 0.5;
// 计算缩放矩阵
Mat scale_mat = (Mat_<double>(2, 3) << scale, 0, 0, 0, scale, 0);
// 进行仿射变换
Mat dst;
warpAffine(src, dst, scale_mat, src.size());
// 显示原始图像和变换后的图像
imshow("Source Image", src);
imshow("Affine Transformed Image", dst);
imwrite("suoxiao.jpg", dst);
waitKey(0);
return 0;
}
在上面的代码中,我们首先读取一张名为"ceshi.jpg"的图像,然后设置缩放比例为0.5。接着,我们计算缩放矩阵,其中第一行和第二行的前两个元素分别表示水平方向和垂直方向的缩放比例,第三行和第四行的前两个元素为0,表示不进行平移操作。最后,我们使用warpAffine()函数进行仿射变换,将原始图像映射到输出图像中,并显示原始图像和变换后的图像。变换后的效果如下图所示。
放大示例
此处,我们把缩放因子设置为1.5,即放大,
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("ceshi.jpg");
if (src.empty()) {
cout << "Could not read the source image" << endl;
return -1;
}
// 设置缩放比例
double scale = 1.5;
// 计算缩放矩阵
Mat scale_mat = (Mat_<double>(2, 3) << scale, 0, 0, 0, scale, 0);
// 进行仿射变换
Mat dst;
warpAffine(src, dst, scale_mat, src.size());
// 显示原始图像和变换后的图像
imshow("Source Image", src);
imshow("Affine Transformed Image", dst);
imwrite("fangda.jpg", dst);
waitKey(0);
return 0;
}
放大的效果如下图所示。
相当于把图像一部分放大,但显示的内容比原图就少了很多,因为图像的总体大小没有改变。
旋转
原理
此处,以原点(0,0)坐标为中心进行,α 是旋转角度。
旋转矩阵的元素是由旋转角度和旋转中心计算得出的。假设我们有一个二维坐标系,其中原点是旋转中心,x轴正方向是右方,y轴正方向是上方。现在我们要将点 (x, y) 绕原点逆时针旋转θ角度,那么旋转矩阵可以表示为:
cpp
[ cosα, -sinα, 0 ]
[ sinα, cosα, 0 ]
其中,cosθ 和 sinθ 是使用弧度值计算得出的。
也可以是如下公式:
若为上述A 变换矩阵,则当α 为正时,旋转方向为顺时针;当 α 为负时,旋转方向为逆时针。
同时,另外一种情况与此相反,如变换矩阵为:
bash
[ cosα, sinα, 0 ]
[ -sinα, cosα, 0 ]
则当α 为正时,旋转方向为逆时针;当 α 为负时,旋转方向为顺时针。
此外,也可以控制α 的值,如α 为45和为-45逆顺情况是相反的。
下面进行两个示例,分别进行顺时针和逆时针旋转。
顺时针示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat src = imread("ceshi.jpg");
if (src.empty()) {
cout << "Could not read the source image" << endl;
return -1;
}
// 设置旋转角度
double angle = 45;
// 计算旋转矩阵
Mat rot_mat = (Mat_<double>(2, 3) << cos(angle), -sin(angle), sin(angle), cos(angle), 1, 0);
cout<< cos(angle) <<endl;
cout << -sin(angle) << endl;
cout << sin(angle) << endl;
//rot_mat = rot_mat * 100; // 设置旋转中心点,这里将图像中心设置为旋转中心
// 进行仿射变换
Mat dst;
warpAffine(src, dst, rot_mat, src.size());
// 显示原始图像和变换后的图像
imshow("Source Image", src);
imshow("Affine Transformed Image", dst);
imwrite("shunshizhen.jpg", dst);
waitKey(0);
return 0;
}
上述代码中设置的double angle = 45;
为旋转角度,45为正数,所以是逆时针旋转,同时,(Mat_<double>(2, 3) << cos(angle), -sin(angle), sin(angle), cos(angle), 1, 0);
转换矩阵表示当α 为正时,旋转方向为顺时针;当 α 为负时,旋转方向为逆时针。
旋转结果为:
逆时针示例
(1)通过改变 α的值,如将上面顺时针的45变为-45.
(2)改变变换矩阵的顺序,如使用Mat rot_mat = (Mat_<double>(2, 3) << cos(angle), sin(angle), -sin(angle), cos(angle), 1, 0);
替换上面代码中的转换矩阵。
以原点(0,0)为中心,逆时针旋转45度效果图如下:
总结
我们可以看出,在使用warpAffine()
函数是比较方便的,通过使用定义2行3列和通过上面的例子,可以快速高效的实现平移、缩放和旋转等功能。