【从零学习OpenCV 4】图像仿射变换
重磅干货,第一时间送达
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《从零学习OpenCV 4》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。
介绍完图像的缩放和翻转后,接下来将要介绍图像的旋转,但是在OpenCV 4中并没有专门用于图像旋转的函数,而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心,之后确定旋转矩阵,最终通过仿射变换实现图像旋转。针对这个流程,OpenCV 4提供了getRotationMatrix2D()函数用于计算旋转矩阵和warpAffine()函数用于实现图像的仿射变换。首先介绍计算旋转矩阵getRotationMatrix2D()函数,该函数的函数原型在代码清单3-31中给出。
代码清单3-31 getRotationMatrix2D()函数原型
1. Mat cv::getRotationMatrix2D (Point2f center,
2. double angle,
3. double scale
4. )
center:图像旋转的中心位置。
angle:图像旋转的角度,单位为度,正值为逆时针旋转。
scale:两个轴的比例因子,可以实现旋转过程中的图像缩放,不缩放输入1。
该函数输入旋转角度和旋转中心,返回图像旋转矩阵,该返回值得数据类型为Mat类,是一个2×3的矩阵。如果我们已知图像旋转矩阵,可以自己生成旋转矩阵而不调用该函数。该函数生成的旋转矩阵与旋转角度和旋转中心的关系如式(3.11)所示。
(3.11)
其中:
(3.12) 确定旋转矩阵后,通过warpAffine()函数进行仿射变换,就可以实现图像的旋转,在代码清单3-32中给出了warpAffine()函数的函数原型。
代码清单3-32 warpAffine()函数原型
1. void cv::warpAffine(InputArray src,
2. OutputArray dst,
3. InputArray M,
4. Size dsize,
5. int flags = INTER_LINEAR,
6. int borderMode = BORDER_CONSTANT,
7. const Scalar& borderValue = Scalar()
8. )
src:输入图像。
dst:仿射变换后输出图像,与src数据类型相同,但是尺寸与dsize相同。
M:2×3的变换矩阵。
dsize:输出图像的尺寸。
flags:插值方法标志,可选参数及含义在表3-3和表3-4中给出。
borderMode:像素边界外推方法的标志。
borderValue:填充边界使用的数值,默认情况下为0。
该函数拥有多个参数,但是多数都与前面介绍的图像尺寸变换具有相同的含义。函数中第三个参数为前面求取的图像旋转矩阵,第四个参数是输出图像的尺寸。函数第五个参数是仿射变换插值方法的标志,这里相比于图像尺寸变换多增加了两个类型,可以与其他插值方法一起使用,这两种类型在表3-4中给出。函数第六个参数为像素边界外推方法的标志,其可以的标志和对应的方法在表3-5中给出。第七个参数是外推标志选择BORDER_CONSTANT时的定值,默认情况下为0。
表3-4 图像仿射变换中的补充插值方法
标志参数 |
简记 |
作用 |
WARP_FILL_OUTLIERS |
8 |
填充所有输出图像的像素,如果部分像素落在输入图像的边界外,那么他们的值设定为fillval |
WARP_INVERSE_MAP |
16 |
表示M为输出图像到输入图像的反变换。 |
表3-5 边界填充方法和对应标志
标志参数 |
简记 |
作用 |
BORDER_CONSTANT |
0 |
用特定值填充,如iiiiii|abcdefgh|iiiiiii |
BORDER_REPLICATE |
1 |
两端复制填充,如aaaaaa|abcdefgh|hhhhhhh |
BORDER_REFLECT |
2 |
倒叙填充,如fedcba|abcdefgh|hgfedcb |
BORDER_WRAP |
3 |
正序填充,如cdefgh|abcdefgh|abcdefg |
BORDER_REFLECT_101 |
4 |
不包含边界值倒叙填充,如gfedcb|abcdefgh|gfedcba |
BORDER_TRANSPARENT |
5 |
随机填充,uvwxyz|abcdefgh|ijklmno |
BORDER_REFLECT101 |
4 |
与BORDER_REFLECT_101相同 |
BORDER_DEFAULT |
4 |
与BORDER_REFLECT_101相同 |
BORDER_ISOLATED |
16 |
不关心感兴趣区域之外的部分 |
在了解函数每个参数的含义之后,为了更好的理解函数作用,需要介绍一下仿射变换的概念。仿射变换就是图像的旋转、平移和缩放操作的统称,可以表示为线性变换和平移变换的叠加。仿射变换的数学表示是先乘以一个线形变换矩阵再加上一个平移向量,其中线性变换矩阵为2×2的矩阵,平移向量为2×1的向量,至此你可能理解了为什么函数需要输入一个2×3的变换矩阵。假设我们存在一个线性变换矩阵和平移矩阵,两者与输入的矩阵之间的关系如式(3.13)中所示。
(3.13)
根据旋转矩阵和平移矩阵以及图像像素值,仿射变换的数学原理可以用式(3.14)来表示。
(3.14)
仿射变换又称为三点变换,如果知道变换前后两张图像中三个像素点坐标的对应关系,就可以求得仿射变换中的变换矩阵,OpenCV 4提供了利用三个对应像素点来确定矩阵的函数getAffineTransform(),该函数的函数原型在代码清单3-33中给出。
代码清单3-33 getAffineTransform()函数原型
1. Mat cv::getAffineTransform(const Point2f src[],
2. const Point2f dst[]
3. )
src[]:原图像中的三个像素坐标。
dst[]:目标图像中的三个像素坐标。
该函数两个输入量都是存放浮点坐标的数组,在生成数组的时候像素点的输入顺序无关,但是需要保证像素点的对应关系,函数的返回值是一个2×3的变换矩阵。
有了前面变换矩阵的求取,就可以利用warpAffine()函数实现矩阵的仿射变换,我们在代码清单3-34的例程中实现了图像的旋转以及图像三点映射的仿射变换,最终结果在图3-23中给出。
代码清单3-34 myWarpAffine.cpp图像旋转与仿射变换
1. #include <opencv2\opencv.hpp>
2. #include <iostream>
3. #include <vector>
4.
5. using namespace std;
6. using namespace cv;
7.
8. int main()
9. {
10. Mat img = imread("lena.png");
11. if (img.empty())
12. {
13. cout << "请确认图像文件名称是否正确" << endl;
14. return -1;
15. }
16.
17. Mat rotation0, rotation1, img_warp0, img_warp1;
18. double angle = 30; //设置图像旋转的角度
19. Size dst_size(img.rows, img.cols); //设置输出图像的尺寸
20. Point2f center(img.rows / 2.0, img.cols / 2.0); //设置图像的旋转中心
21. rotation0 = getRotationMatrix2D(center, angle, 1); //计算放射变换矩阵
22. warpAffine(img, img_warp0, rotation0, dst_size); //进行仿射变换
23. imshow("img_warp0", img_warp0);
24. //根据定义的三个点进行仿射变换
25. Point2f src_points[3];
26. Point2f dst_points[3];
27. src_points[0] = Point2f(0, 0); //原始图像中的三个点
28. src_points[1] = Point2f(0, (float)(img.cols - 1));
29. src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1));
30. //放射变换后图像中的三个点
31. dst_points[0] = Point2f((float)(img.rows)*0.11, (float)(img.cols)*0.20);
32. dst_points[1] = Point2f((float)(img.rows)*0.15, (float)(img.cols)*0.70);
33. dst_points[2] = Point2f((float)(img.rows)*0.81, (float)(img.cols)*0.85);
34. rotation1 = getAffineTransform(src_points, dst_points); //根据对应点求取仿射变换矩阵
35. warpAffine(img, img_warp1, rotation1, dst_size); //进行仿射变换
36. imshow("img_warp1", img_warp1);
37. waitKey(0);
38. return 0;
39. }
图3-23 mywarpAffine.cpp程序运行结果