图像的几何变换是各种图像处理的基础,一般来说图像的几何变换是在不改变原有图像像素的基础上进行的,几何变换相当于是原图像到新图像的一种映射。图像的几何变换基本有镜像,平移、错切,缩放,旋转。常用的几何变换方法有“向前映射”和”向后映射“。对于图像大小不改变的几何变换,如镜像、平移等,一般采用“向前映射”,对于图像大小改变的几何变形,如错切、缩放、旋转,一般采用“向后映射”。
“向前映射”是指将输入图像像素一个个的映射到输出图像。如果输入像素被映射到四个输出像素之间的位置,则其像素值按插值算法在四个输出像素之间分配。
“向后映射”是指输出像素一个个地映射回输入图像中,以确定其像素值。如果输出像素被映射到邻近的四个输入像素之间,则其像素值由插值决定。
常用的插值方法:双线性插值
一、镜像变换
镜像变换分为水平镜像和垂直镜像。水平镜像以图像垂直中轴线为对称轴,垂直镜像以图像水平中轴线为对称轴。设点进行镜像后的对应点为
,图像高度为H,宽度为W。
经过水平镜像后,原图像坐标变为
。
矩阵表达式为:
逆运算为:
经过垂直镜像后,原图像坐标变为
矩阵表达式为:
逆运算为:
具体程序如下:
#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轴方向按比例缩放倍,在y轴方向缩放
倍,得到新图像。如果
,即x轴与y轴缩放比例相同,称为全比例缩放。比例缩放前后,两点
,
之间的关系矩阵表达式如下,
矩阵表达式为:
逆运算为:,即
图像的比例缩小,因为缩小后信息量变少,只需在原图像中按缩小比例隔行隔列选取像素点即可,构成新图像。
图像的比例放大,放大后的图像像素点变多,有些放大后的图像像素在原图像中找不到对应像素,需要进行近似处理。常用方法有两种(1)直接赋值为和他最相近的像素点(2)通过插值计算相应像素值。第一种计算方法虽然简单,但是会造成图像马赛克现象;第二种方法处理效果好些,但是运算量加大。
我们采用“向后映射”的方法,用双线性插值计算放大后图像的像素值。
放大后灰度值计算如下(彩色图像对RGB三通道分别按如下公式计算):
式中:为插值后新图像坐标
的灰度值;
为插值前坐标
的灰度值;
,
分别为不大于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方向放大、比例放大。
四、旋转变换
图像旋转变换会改变图像大小,如果采用“前向映射”生成图像会有一些空洞点,空洞点需要差值的方法补全,过程比较麻烦,所以采用“后向映射”效果较好,不会产生空洞点。
设旋转
角后对应的点为
(逆时针旋转)
旋转前后点、
的坐标分别是
矩阵表达式为:
逆运算为:
上述算法是以(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度