图像处理之仿射变换

491 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天

1 仿射变换

本节中,我们将一起了解仿射变换的概念,以及OpenCV中相关的实现函数warpAffine和getRotationMatrix2D。

2 认识仿射变换

仿射变换(Affine Transformation 或 Affine Map),又称仿射映射,是指在几 何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间的过程。它保持了二维图形的“平直性”(直线经过变换之后依然是直线)和“平行性”(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。

一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式。

那么,我们能够用仿射变换来表示如下三种常见的变换形式:

  • 旋转,rotation(线性变换)
  • 平移,translation(向量加)
  • 缩放,scale(线性变换)

进行更深层次的理解,仿射变换代表的是两幅图之间的一种映射关系。而我们通常使用2x3的矩阵来表示仿射变换。

2.png

考虑到我们要使用矩阵A和B对二维向量

10.PNG

做变换,所以也能表示K文为下列形式。

3.png

或者

4.png

即:

5.png

3 仿射变换的求法

我们知道,仿射变换表示的就是两幅图片之间的一种联系,关于这种联系的信息大致可从以下两种场景获得。

  • 已知X和T,而且已知它们是有联系的。接下来的工作就是求出矩阵M。
  • 已知M和X,想求得T。只要应用算式T=M·X即可。对于这种联系的信息可以用矩阵M清晰地表达(即给出明确的2x3矩阵),也可以用两幅图片点之间几何关系来表达。

形象地说明一下,因为矩阵M联系着两幅图片,就以其表示两图中各三点直接的联系为例。

6.png

图中,点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(线性插值),可选的插值方式如表所示。

7.png

  • 第六个参数,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,缩放系数。此函数计算以下矩阵:

8.png

其中:

9.png

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


效果图: 原图

11.PNG

效果图1

12.PNG

效果图2

13.PNG