OpenCV:图像几何变换(镜像,错切,缩放,旋转)

        图像的几何变换是各种图像处理的基础,一般来说图像的几何变换是在不改变原有图像像素的基础上进行的,几何变换相当于是原图像到新图像的一种映射。图像的几何变换基本有镜像,平移、错切,缩放,旋转。常用的几何变换方法有“向前映射”和”向后映射“。对于图像大小不改变的几何变换,如镜像、平移等,一般采用“向前映射”,对于图像大小改变的几何变形,如错切、缩放、旋转,一般采用“向后映射”。

       “向前映射”是指将输入图像像素一个个的映射到输出图像。如果输入像素被映射到四个输出像素之间的位置,则其像素值按插值算法在四个输出像素之间分配。

        “向后映射”是指输出像素一个个地映射回输入图像中,以确定其像素值。如果输出像素被映射到邻近的四个输入像素之间,则其像素值由插值决定。

常用的插值方法:双线性插值

一、镜像变换

镜像变换分为水平镜像和垂直镜像。水平镜像以图像垂直中轴线为对称轴,垂直镜像以图像水平中轴线为对称轴。设点P_{0}(x_{0},y_{0})进行镜像后的对应点为P(x,y),图像高度为H,宽度为W。

经过水平镜像后,原图像P_{0}(x_{0},y_{0})坐标变为P(W-x_{0},y_{0})

矩阵表达式为:\begin{bmatrix} x \\ y \\1 \end{bmatrix}=\begin{bmatrix} -1 & 0 & W\\ 0& 1 & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x_{0}\\ y_{0} \\ 1 \end{bmatrix}

逆运算为:\begin{bmatrix} x_{0} \\ y_{0} \\1 \end{bmatrix}=\begin{bmatrix} -1 & 0 & W\\ 0& 1 & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

经过垂直镜像后,原图像P_{0}(x_{0},y_{0})坐标变为P(x_{0},H-y_{0})

矩阵表达式为:\begin{bmatrix} x \\ y \\1 \end{bmatrix}=\begin{bmatrix} 1 & 0 & 0\\ 0& -1 & H\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x_{0}\\ y_{0} \\ 1 \end{bmatrix}

逆运算为:\begin{bmatrix} x_{0} \\ y_{0} \\1 \end{bmatrix}=\begin{bmatrix} 1 & 0 & 0\\ 0& -1 & H\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

具体程序如下:

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include "opencv2/opencv.hpp"

int main()
{
	const char *filename = "island.bmp";
	IplImage *inputimage = cvLoadImage(filename, -1);
	// x_mirror(垂直镜像),y_mirror(水平镜像),xy_mirror(中心对称)
	IplImage *xmirrorimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
	IplImage *ymirrorimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
	IplImage *xymirrorimage = cvCreateImage(cvSize(inputimage->width, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);

	for (int i = 0; i < xmirrorimage->height; i++)
	{
		for (int j = 0; j < xmirrorimage->width; j++)
		{
			for (int k = 0; k < xmirrorimage->nChannels; k++)
			{
				ymirrorimage->imageData[i*ymirrorimage->widthStep + j * ymirrorimage->nChannels + k]
					= inputimage->imageData[i*inputimage->widthStep + (inputimage->width - 1 - j)*inputimage->nChannels + k];

				xmirrorimage->imageData[i*xmirrorimage->widthStep + j * xmirrorimage->nChannels + k]
					= inputimage->imageData[(inputimage->height-1-i)*inputimage->widthStep + j*inputimage->nChannels + k];

				xymirrorimage->imageData[i*xymirrorimage->widthStep + j * xymirrorimage->nChannels + k]
					= inputimage->imageData[(inputimage->height - 1 - i)*inputimage->widthStep + (inputimage->width-1-j) * inputimage->nChannels + k];
			}
		}
	}

	cvNamedWindow("inputimage", 1);
	cvNamedWindow("ymirrorimage", 1);
	cvNamedWindow("xmirrorimage", 1);
	cvNamedWindow("xymirrorimage", 1);

	cvShowImage("inputimage", inputimage);
	cvShowImage("ymirrorimage", ymirrorimage);
	cvShowImage("xmirrorimage", xmirrorimage);
	cvShowImage("xymirrorimage", xymirrorimage);
	cvWaitKey(0);

	cv::Mat amplification = cv::cvarrToMat(ymirrorimage);
	cv::Mat amplification1 = cv::cvarrToMat(xmirrorimage);
	cv::Mat amplification2 = cv::cvarrToMat(xymirrorimage);
	cv::imwrite("ymirrorimage.bmp", amplification);
	cv::imwrite("xmirrorimage.bmp", amplification1);
	cv::imwrite("xymirrorimage.bmp", amplification2);

	cvDestroyWindow("inputimage");
	cvDestroyWindow("ymirrorimage");
	cvDestroyWindow("xmirrorimage");
	cvDestroyWindow("xymirrorimage");

	cvReleaseImage(&inputimage);
	cvReleaseImage(&ymirrorimage);
	cvReleaseImage(&xmirrorimage);
	cvReleaseImage(&xymirrorimage);
}

二、错切变换


#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include<math.h>
#include "opencv2/opencv.hpp"
//#define pi 3.141592653;   //定义pi的值
//#define a pi/4;

int main()
{
	float pi = 3.141592653;
	float a = pi / 3;//错切角度
	const char *filename = "island.bmp";
	IplImage *inputimage = cvLoadImage(filename, -1);

	IplImage *x_shear = cvCreateImage(cvSize((inputimage->width+inputimage->height/tan(a)), inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
	IplImage *y_shear = cvCreateImage(cvSize(inputimage->width, (inputimage->height + inputimage->width / tan(a))), IPL_DEPTH_8U, inputimage->nChannels);

	for (int i = 0; i < x_shear->height; i++)
	{
		for (int j = 0; j < x_shear->width; j++)
		{
			for (int k = 0; k < x_shear->nChannels; k++)
				if (j < x_shear->height / tan(a) - i / tan(a) || j > x_shear->height / tan(a) - i / tan(a) + inputimage->width)
					x_shear->imageData[i*x_shear->widthStep + j*x_shear->nChannels + k]
					= 0;
				else
					x_shear->imageData[i*x_shear->widthStep+ j*x_shear->nChannels+k]
						= inputimage->imageData[i*inputimage->widthStep+ (j - int(x_shear->height / tan(a) - i / tan(a)))*inputimage->nChannels+k];
		}
	}

	for (int i = 0; i < y_shear->height; i++)
	{
		for (int j = 0; j < y_shear->width; j++)
		{
			for (int k = 0; k < y_shear->nChannels; k++)
				if(i < y_shear->width / tan(a) - j / tan(a) || i > y_shear->width / tan(a) - j / tan(a) + inputimage->height)
					y_shear->imageData[i*y_shear->widthStep + j * y_shear->nChannels + k]
					= 0;
				else
					y_shear->imageData[i*y_shear->widthStep + j *y_shear->nChannels + k]
						= inputimage->imageData[(i- int(y_shear->width / tan(a) - j / tan(a)))*inputimage->widthStep + j * inputimage->nChannels + k];
		}
	}

	cvNamedWindow("inputimage", 1);
	cvNamedWindow("x_shear", 1);
	cvNamedWindow("y_shear", 1);
	//显示图像
	cvShowImage("inputimage", inputimage);
	cvShowImage("x_shear", x_shear);
	cvShowImage("y_shear", y_shear);
	cvWaitKey(0);
	//保存图像
	cv::Mat amplification1 = cv::cvarrToMat(x_shear);
	cv::Mat amplification2 = cv::cvarrToMat(y_shear);
	cv::imwrite("x_island.bmp", amplification1);
	cv::imwrite("y_island.bmp", amplification2);

	cvDestroyWindow("inputimage");
	cvDestroyWindow("x_shear");
	cvDestroyWindow("y_shear");

	cvReleaseImage(&inputimage);
	cvReleaseImage(&x_shear);
	cvReleaseImage(&y_shear);
}

以下为原图及延水平方向和垂直方向不同角度的错切

三、图像缩放

        图像比例缩放是将给定图像在x轴方向按比例缩放f_{x}倍,在y轴方向缩放f_{y}倍,得到新图像。如果f_{x}=f_{y},即x轴与y轴缩放比例相同,称为全比例缩放。比例缩放前后,两点P_{0}(x_{0},y_{0})P(x,y)之间的关系矩阵表达式如下,

矩阵表达式为:\begin{bmatrix} x \\ y \\1 \end{bmatrix}=\begin{bmatrix} f_{x} & 0 & 0\\ 0& f_{y} & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x_{0}\\ y_{0} \\ 1 \end{bmatrix}

逆运算为:\begin{bmatrix} x_{0} \\ y_{0} \\1 \end{bmatrix}=\begin{bmatrix} 1/f_{x} & 0 & 0\\ 0& 1/f_{y} & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x\\ y \\ 1 \end{bmatrix},即 \left\{\begin{matrix} x_{0}=x/f_{x}\\ y_{0}=y/f_{y} \end{matrix}\right.

       图像的比例缩小,因为缩小后信息量变少,只需在原图像中按缩小比例隔行隔列选取像素点即可,构成新图像。

       图像的比例放大,放大后的图像像素点变多,有些放大后的图像像素在原图像中找不到对应像素,需要进行近似处理。常用方法有两种(1)直接赋值为和他最相近的像素点(2)通过插值计算相应像素值。第一种计算方法虽然简单,但是会造成图像马赛克现象;第二种方法处理效果好些,但是运算量加大。

我们采用“向后映射”的方法,用双线性插值计算放大后图像的像素值。

 

放大后灰度值计算如下(彩色图像对RGB三通道分别按如下公式计算):

g(x,y)=(1-q)*\left ( (1-p)*f([x],[y])+p*f([x]+1,[y]) \right )+ q*\left ( (1-p)*f([x],[y]+1)+p*f([x]+1,[y]+1) \right )

式中:g(x,y)为插值后新图像坐标(x,y)的灰度值;f(x,y)为插值前坐标(x,y)的灰度值;[x][y]分别为不大于x,y的整数。

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include<math.h>
#include "opencv2/opencv.hpp"

int main()
{
	 //amplification
	const char *filename = "island.bmp";
	IplImage *inputimage = cvLoadImage(filename, -1);
	float multiple = 2.5;//放大倍数
	IplImage * ScaleAmplifying = cvCreateImage(cvSize(inputimage->width * multiple, inputimage->height * multiple), IPL_DEPTH_8U, inputimage->nChannels);
	IplImage * X_Amplifying = cvCreateImage(cvSize(inputimage->width * multiple, inputimage->height), IPL_DEPTH_8U, inputimage->nChannels);
	IplImage * Y_Amplifying = cvCreateImage(cvSize(inputimage->width , inputimage->height * multiple), IPL_DEPTH_8U, inputimage->nChannels);
	//比例缩放
	for (int i = 0; i < ScaleAmplifying->height; i++)
	{
		for (int j = 0; j < ScaleAmplifying->width; j++)
		{
			for (int k = 0; k < ScaleAmplifying->nChannels; k++)
			{
				float q = i / multiple - floor(i / multiple);
				float p = j / multiple - floor(j / multiple);
				int x = floor(i / multiple);
				int y = floor(j / multiple);
				if (q < 0.001 && p < 0.001)
				{
					ScaleAmplifying->imageData[i*ScaleAmplifying->widthStep + j * ScaleAmplifying->nChannels + k]
						= inputimage->imageData[x * inputimage->widthStep + y * inputimage->nChannels + k];
				}
				else
				{
					float temp1 = 0,temp2 = 0;
					temp1 = (1-p)*(unsigned char)inputimage->imageData[x * inputimage->widthStep + y * inputimage->nChannels + k]
						+p* (unsigned char)inputimage->imageData[x * inputimage->widthStep + (y + 1) * inputimage->nChannels + k];
					temp2=(1-p)*(unsigned char)inputimage->imageData[(x + 1) * inputimage->widthStep + y * inputimage->nChannels + k]
						+ p*(unsigned char)inputimage->imageData[(x + 1)* inputimage->widthStep + (y + 1) * inputimage->nChannels + k];
					
					ScaleAmplifying->imageData[i*ScaleAmplifying->widthStep + j * ScaleAmplifying->nChannels + k]
						= (1 - q)*temp1 + q * temp2;
				}
			}
		}
	}
	//水平缩放
	for (int i = 0; i < X_Amplifying->height; i++)
	{
		for (int j = 0; j < X_Amplifying->width; j++)
		{
			for (int k = 0; k < X_Amplifying->nChannels; k++)
			{
				float p = j / multiple - floor(j / multiple);
				int x = floor(i / multiple);
				int y = floor(j / multiple);
				if ( p < 0.001)
				{
					X_Amplifying->imageData[i*X_Amplifying->widthStep + j * X_Amplifying->nChannels + k]
						= inputimage->imageData[i * inputimage->widthStep + y * inputimage->nChannels + k];
				}
				else
				{
					float temp = 0;
					temp = (1 - p)*(unsigned char)inputimage->imageData[i * inputimage->widthStep + y * inputimage->nChannels + k]
						+ p * (unsigned char)inputimage->imageData[i * inputimage->widthStep + (y + 1) * inputimage->nChannels + k];

					X_Amplifying->imageData[i*X_Amplifying->widthStep + j * X_Amplifying->nChannels + k]
						= temp ;
				}
			}
		}
	}
	//垂直缩放
	for (int i = 0; i < Y_Amplifying->height; i++)
	{
		for (int j = 0; j < Y_Amplifying->width; j++)
		{
			for (int k = 0; k < Y_Amplifying->nChannels; k++)
			{
				float p = j / multiple - floor(j / multiple);
				float q = i / multiple - floor(i / multiple);
				int x = floor(i / multiple);
				int y = floor(j / multiple);
				if (q < 0.001)
				{
					Y_Amplifying->imageData[i*Y_Amplifying->widthStep + j * Y_Amplifying->nChannels + k]
						= inputimage->imageData[x * inputimage->widthStep + j * inputimage->nChannels + k];
				}
				else
				{
					float temp = 0;
		
					temp = (1 - q)*(unsigned char)inputimage->imageData[(x + 1) * inputimage->widthStep + j * inputimage->nChannels + k]
						+ q * (unsigned char)inputimage->imageData[x* inputimage->widthStep + j * inputimage->nChannels + k];

					Y_Amplifying->imageData[i*Y_Amplifying->widthStep + j * Y_Amplifying->nChannels + k]
						= temp;
				}
			}
		}
	}

	cvNamedWindow("inputimage", 1);
	cvNamedWindow("ScaleAmplifying", 1);
	cvNamedWindow("X_Amplifying", 1);
	cvNamedWindow("Y_Amplifying", 1);
	//显示图像
	cvShowImage("inputimage", inputimage);
	cvShowImage("ScaleAmplifying", ScaleAmplifying);
	cvShowImage("X_Amplifying", X_Amplifying);
	cvShowImage("Y_Amplifying", Y_Amplifying);
	cvWaitKey(0);
	//保存图像
	cv::Mat amplification = cv::cvarrToMat(ScaleAmplifying);
	cv::Mat amplification1 = cv::cvarrToMat(X_Amplifying);
	cv::Mat amplification2 = cv::cvarrToMat(Y_Amplifying);
	cv::imwrite("ScaleAmplifying.bmp", amplification);
	cv::imwrite("X_Amplifying.bmp", amplification1);
	cv::imwrite("Y_Amplifying.bmp", amplification2);


	cvDestroyWindow("inputimage");
	cvDestroyWindow("ScaleAmplifying");
	cvDestroyWindow("X_Amplifying");
	cvDestroyWindow("Y_Amplifying");

	cvReleaseImage(&inputimage);
	cvReleaseImage(&ScaleAmplifying);
	cvReleaseImage(&X_Amplifying);
	cvReleaseImage(&Y_Amplifying);
}

图像依次为原图、延x方向放大、延y方向放大、比例放大。

四、旋转变换

        图像旋转变换会改变图像大小,如果采用“前向映射”生成图像会有一些空洞点,空洞点需要差值的方法补全,过程比较麻烦,所以采用“后向映射”效果较好,不会产生空洞点。

        设P_{0}(x_{0},y_{0})旋转\theta角后对应的点为P(x,y)(逆时针旋转)

旋转前后点P_{0}(x_{0},y_{0})P(x,y)的坐标分别是

\left\{\begin{matrix} x_{0}=r*\cos \alpha \\ y_{0}=r*\sin \alpha \end{matrix}\right.

\left\{\begin{matrix} x=r*\cos(\alpha -\theta ) =r*\cos \alpha \cos \theta +r*\sin \alpha \sin \theta =x_{0}*\cos \theta +y_{0}*\sin \theta \\ y=r*\sin(\alpha -\theta )=r*\sin \alpha \cos \theta -r*\cos \alpha \sin \theta =-x_{0}*\sin \theta +y_{0}*\cos \theta \end{matrix}\right.

矩阵表达式为:\begin{bmatrix} x \\ y \\1 \end{bmatrix}=\begin{bmatrix} \cos \theta & \sin \theta & 0\\ -\sin \theta & \cos \theta & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x_{0}\\ y_{0} \\ 1 \end{bmatrix}

逆运算为:\begin{bmatrix} x_{0} \\ y_{0} \\1 \end{bmatrix}=\begin{bmatrix} \cos \theta & \--sin \theta & 0\\ \sin \theta & \cos \theta & 0\\ 0& 0 & 1 \end{bmatrix}*\begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

上述算法是以(0,0)点为中心进行旋转,如果需要以(a,b)点为中心进行旋转,需要先将图像平移至该点,然后进行旋转,旋转后再平移回原点。

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include<math.h>
#include "opencv2/opencv.hpp"

int main()
{
	float pi = 3.141592653;
	float a = pi / 4.0;//控制旋转角度
	float b = pi / 3.0;
	// Rotation
	const char *filename = "football.jpg";
	IplImage *inputimage = cvLoadImage(filename, -1);
	float diagonal = sqrt(inputimage->width * inputimage->width + inputimage->height*inputimage->height);
	float angle = atan((float)inputimage->width / (float)inputimage->height);
	float a_x = pi / 2.0 - angle - a;
	float a_y = angle - a;
	int width = diagonal * cos(a_x);
	int height = diagonal * cos(a_y);
	float d_x = (width - inputimage -> width) / 2.0;
	float d_y = (height - inputimage->height) / 2.0;
	//printf("%f", angle);
	//printf("%d %d %f %f %f %d %d %f", width,height,a_x,a_y,angle,inputimage->width,inputimage->height, (float)inputimage->width/(float)inputimage->height);
	IplImage *Rotateimage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, inputimage->nChannels);
	
	for (int i = 0; i < Rotateimage->height; i++)
	{
		for (int j = 0; j < Rotateimage->width; j++)
		{
			for (int k = 0; k < Rotateimage->nChannels; k++)
			{
				float x = j * cos(a) + i * sin(a);
				float y = -j * sin(a) + i * cos(a);
				float x0 = ((j- Rotateimage->width/2) * cos(a) - (i - Rotateimage->height/2)* sin(a))+ Rotateimage->width/2;
				float y0 = ((j - Rotateimage->width/2)* sin(a) + (i - Rotateimage->height/2) * cos(a))+ Rotateimage->height/2;
				if (x0 >(int)d_x && x0 < Rotateimage->width-d_x && y0>d_y && y0 < Rotateimage->height-d_y)
				{
					int x_coordinate = x0;
					int y_coordinate = y0;
					Rotateimage->imageData[i*Rotateimage->widthStep + j * Rotateimage->nChannels + k]
						= inputimage->imageData[int(y_coordinate-d_y)*inputimage->widthStep + (int)(x_coordinate-d_x) * inputimage->nChannels + k];
				}
				else
					Rotateimage->imageData[i*Rotateimage->widthStep + j * Rotateimage->nChannels + k]
					= 0;
			}
		}
	}

	cvNamedWindow("inputimage", 1);
	cvNamedWindow("Rotateimage", 1);
	//显示图像
	cvShowImage("inputimage", inputimage);
	cvShowImage("Rotateimage", Rotateimage);
	
	cvWaitKey(0);
	//储存图像
	cv::Mat rotate = cv::cvarrToMat(Rotateimage);
	cv::imwrite("Rotation.bmp", rotate);

	cvDestroyWindow("inputimage");
	cvDestroyWindow("Rotateimage");

	cvReleaseImage(&inputimage);
	cvReleaseImage(&Rotateimage);
}

图像依次为原图、逆时针旋15转度、逆时针旋转30度、逆时针旋转45度、逆时针旋转60度、逆时针旋转90度

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值