开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天
1 仿射变换
本节中,我们将一起了解仿射变换的概念,以及OpenCV中相关的实现函数warpAffine和getRotationMatrix2D。
2 认识仿射变换
仿射变换(Affine Transformation 或 Affine Map),又称仿射映射,是指在几 何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程。它保持了二维图形的“平直性”(直线经过变换之后依然是直线)和“平行性”(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。
一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式。
那么,我们能够用仿射变换来表示如下三种常见的变换形式:
- 旋转,rotation(线性变换)
- 平移,translation(向量加)
- 缩放,scale(线性变换)
进行更深层次的理解,仿射变换代表的是两幅图之间的一种映射关系。而我们通常使用2x3的矩阵来表示仿射变换。
考虑到我们要使用矩阵A和B对二维向量
做变换,所以也能表示K文为下列形式。
或者
即:
3 仿射变换的求法
我们知道,仿射变换表示的就是两幅图片之间的一种联系,关于这种联系的信息大致可从以下两种场景获得。
- 已知X和T,而且已知它们是有联系的。接下来的工作就是求出矩阵M。
- 已知M和X,想求得T。只要应用算式T=M·X即可。对于这种联系的信息可以用矩阵M清晰地表达(即给出明确的2x3矩阵),也可以用两幅图片点之间几何关系来表达。
形象地说明一下,因为矩阵M联系着两幅图片,就以其表示两图中各三点直接的联系为例。
图中,点1、2和3(在Imagel中形成一个三角形)与Image2中的三个点是一一映射的关系,且它们仍然形成三角形,但形状已经和之前不一样了。我们能通过这样两组三点求出仿射变换(可以选择自己喜欢的点),接着就可以把仿射变换应用到图像中去。
OpenCV 仿射变换相关的函数一般涉及到 warpAffine和 getRotationMatrix2D 这两个函数:
- 使用OpenCV函数warpAffine 来实现一些简单的重映射。
- 使用OpenCV函数 getRotationMatrix2D 来获得旋转矩阵。 下面分别对其进行讲解。
4 进行仿射变换:warpAffine()函数
warpAffine函数的作用是依据以下公式子,对图像做仿射变换。
dst(x,y)=src(M11x+M12y+M13,M21x+M22y+ M23)
函数原型如下。
C++:
void warpAffine(InputArray src,OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, intborderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
- 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。
- 第二个参数,OutputArray 类型的dst,函数调用后的运算结果存在这里,需和源图片有一样的尺寸和类型。
- 第三个参数,InputArray类型的M,2x3的变换矩阵。
- 第四个参数,Size类型的dsize,表示输出图像的尺寸。
- 第五个参数,int 类型的 flags,插值方法的标识符。此参数有默认值INTER_LINEAR(线性插值),可选的插值方式如表所示。
- 第六个参数,int类型的 borderMode,边界像素模式,默认值为BORDER_CONSTANT。
- 第七个参数,const Scalar&类型的 borderValue,在恒定的边界情况下取的 值,默认值为Scalar(),即0。
另外提一点,WarpAffine函数与一个叫做 cvGetQuadrangleSubPix()的函数类 似,但是不完全相同。WarpAffine要求输入和输出图像具有同样的数据类型,有更大的资源开销(因此对小图像不太合适)而且输出图像的部分可以保留不变。而 cvGetQuadrangleSubPix 可以精确地从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,而且总是全部改变输出图像的内容。
5 计算二维旋转变换矩阵:getRotationMatrix2DO函数
getRotationMatrix2DO函数用于计算二维旋转变换矩阵。变换会将旋转中心映射到它自身。
C++:
Mat getRotationMatrix2D(Point2fcenter, double angle, double scale)
- 第一个参数,Point2f类型的center,表示源图像的旋转中心。
- 第二个参数,double类型的angle,旋转角度。角度为正值表示向逆时针旋转(坐标原点是左上角)。
- 第三个参数,double类型的scale,缩放系数。此函数计算以下矩阵:
其中:
6 示例
代码:
//---------------------------------【头文件、命名空间包含部分】----------------------------
// 描述:包含程序所使用的头文件和命名空间
//------------------------------------------------------------------------------------------------
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//--------------------------------------------------------------------------------------------
#define WINDOW_NAME1 "【原始图窗口】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【经过Warp后的图像】" //为窗口标题定义的宏
#define WINDOW_NAME3 "【经过Warp和Rotate后的图像】" //为窗口标题定义的宏
//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数的声明
//-------------------------------------------------------------------------------------------
static void ShowHelpText( );
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始执行
//---------------------------------------------------------------------------------------------
int main( )
{
//【0】改变console字体颜色
system("color 1F");
//【0】显示欢迎和帮助文字
ShowHelpText( );
//【1】参数准备
//定义两组点,代表两个三角形
Point2f srcTriangle[3];
Point2f dstTriangle[3];
//定义一些Mat变量
Mat rotMat( 2, 3, CV_32FC1 );
Mat warpMat( 2, 3, CV_32FC1 );
Mat srcImage, dstImage_warp, dstImage_warp_rotate;
//【2】加载源图像并作一些初始化
srcImage = imread( "1.jpg", 1 );
if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; }
// 设置目标图像的大小和类型与源图像一致
dstImage_warp = Mat::zeros( srcImage.rows, srcImage.cols, srcImage.type() );
//【3】设置源图像和目标图像上的三组点以计算仿射变换
srcTriangle[0] = Point2f( 0,0 );
srcTriangle[1] = Point2f( static_cast<float>(srcImage.cols - 1), 0 );
srcTriangle[2] = Point2f( 0, static_cast<float>(srcImage.rows - 1 ));
dstTriangle[0] = Point2f( static_cast<float>(srcImage.cols*0.0), static_cast<float>(srcImage.rows*0.33));
dstTriangle[1] = Point2f( static_cast<float>(srcImage.cols*0.65), static_cast<float>(srcImage.rows*0.35));
dstTriangle[2] = Point2f( static_cast<float>(srcImage.cols*0.15), static_cast<float>(srcImage.rows*0.6));
//【4】求得仿射变换
warpMat = getAffineTransform( srcTriangle, dstTriangle );
//【5】对源图像应用刚刚求得的仿射变换
warpAffine( srcImage, dstImage_warp, warpMat, dstImage_warp.size() );
//【6】对图像进行缩放后再旋转
// 计算绕图像中点顺时针旋转50度缩放因子为0.6的旋转矩阵
Point center = Point( dstImage_warp.cols/2, dstImage_warp.rows/2 );
double angle = -50.0;
double scale = 0.6;
// 通过上面的旋转细节信息求得旋转矩阵
rotMat = getRotationMatrix2D( center, angle, scale );
// 旋转已缩放后的图像
warpAffine( dstImage_warp, dstImage_warp_rotate, rotMat, dstImage_warp.size() );
//【7】显示结果
imshow( WINDOW_NAME1, srcImage );
imshow( WINDOW_NAME2, dstImage_warp );
imshow( WINDOW_NAME3, dstImage_warp_rotate );
// 等待用户按任意按键退出程序
waitKey(0);
return 0;
}
//-----------------------------------【ShowHelpText( )函数】----------------------------------
// 描述:输出一些帮助信息
//---------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//输出欢迎信息和OpenCV版本
printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
printf("\n\n\t\t\t此为本书OpenCV2版的第67个配套示例程序\n");
printf("\n\n\t\t\t 当前使用的OpenCV版本为:" CV_VERSION );
printf("\n\n ----------------------------------------------------------------------------\n");
//输出一些帮助信息
printf( "\n\n\t\t欢迎来到仿射变换综合示例程序.\n\n");
printf( "\t\t键盘按键【ESC】- 退出程序\n" );
}
效果图: 原图
效果图1
效果图2