图像旋转变换

  参考原文http://vipbase.net/ipbook/chap02.htm   

   这里主要讨论以图象的中心为圆心旋转。旋转之后若要保持目标区域大小不变,则整幅图像变大;若要保持整幅图像的大小不变,则旋转出去的部分需要裁剪掉。

 

 

         旋转前的图

    旋转后的图

 

旋转后保持原图大小,转出的部分被裁掉

    以顺时针旋转为例来堆到旋转变换公式。如下图所示。

 

旋转前:

x0=rcosby0=rsinb

旋转a角度后:

x1=rcos(b-a)=rcosbcosa+rsinbsina=x0cosa+y0sina

y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa

矩阵形式为

逆变换为

 

    上面的公式是以图像的左下角为原点旋转的。现我们要以图像的中心为原点旋转。因此需要先将坐标平移到图像中心,如下所示

设图象的宽为w,高为h,容易得到:

逆变换为

     现在可以分三步来完成旋转变换:

     1. 将坐标系x'o'y'平移到xoy ;  2. 在xoy坐标系下作旋转变换;  3.变换后将坐标系平移回原来位置。

     用矩阵表示就是        

   其中R表示旋转变换矩阵。当旋转不改变图像大小时,T 与 T' 互为逆矩阵;当旋转后图像变大时,T 与 T'不是逆矩阵关系,因为图像变大了,第一次平移和第二次平移坐标系的距离不一样。因此当图像变大时,公式应该如下:

    由于算法实现过程中我们需要的是逆变换的公式,因此我们只写出逆变换的表达式,如下:

       其中wn ,hn 表示新图像的宽和高,wo, ho 表示原图像的宽和高。

     这样,对于新图中的每一点,我们就可以根据上面逆变换公式求出对应原图中的点,得到它的灰度。如果超出原图范围,则设置为背景色。要注意的是,由于有浮点运算,计算出来点的坐标可能不是整数,采用取整处理,即找最接近的点,这样会带来一些误差(图象可能会出现锯齿)。更精确的方法是采用插值,这里暂不讨论。

    C++代码如下:

/*
* rotate.cpp
* 图像旋转变换(顺时针)
* Created on: 2011-10-10
* Author: LiChanghai
*/
// 以图像中心为坐标原点,旋转后不改变图像大小
// 函数返回值为指针,指向新申请的内存区域
// 因为新图像大小改变了,需要返回新图像的尺寸
// 因此形参的高度和宽度都采用指针变量
#include<string.h>
#include<stdlib.h>
#include<cmath>
#define pi 3.1415926535
unsigned char * rotate(unsigned char *pImage, int *width, int *height, int biBitCount, float angle)
{
//定义以图像中心为原点的坐标系下原图像和新图像的四个角点坐标
float src_x1, src_y1, src_x2, src_y2, src_x3, src_y3, src_x4, src_y4;
float dst_x1, dst_y1, dst_x2, dst_y2, dst_x3, dst_y3, dst_x4, dst_y4;

//定义新图像的高度和宽度
int wnew, hnew;

//定义计算过程中需要的相关变量
float sina, cosa, temp1, temp2, alpha;

//角度转化为弧度
alpha=pi*angle/180;

cosa = float(cos(double(alpha)));
sina = float(sin(double(alpha)));

//原图像的四个角点的坐标
src_x1 = float(-0.5*(*width)); src_y1 = float(0.5*(*height));
src_x2 = float(0.5*(*width)); src_y2 = src_y1;
src_x3 = src_x1; src_y3 = float(-0.5*(*height));
src_x4 = src_x2; src_y4 = src_y3;

//计算新图像的四个角点坐标
dst_x1 = cosa*src_x1+sina*src_y1;
dst_y1 = -sina*src_x1+cosa*src_y1;

dst_x2 = cosa*src_x2+sina*src_y2;
dst_y2 = -sina*src_x2+cosa*src_y2;

dst_x3 = cosa*src_x3+sina*src_y3;
dst_y3 = -sina*src_x3+cosa*src_y3;

dst_x4 = cosa*src_x4+sina*src_y4;
dst_y4 = -sina*src_x4+cosa*src_y4;

//计算新图像的高度和宽度
float t1 = fabs(dst_x4-dst_x1), t2 = fabs(dst_x3-dst_x2);
wnew = int(t1>t2 ? t1:t2);
t1 = fabs(dst_y4-dst_y1), t2 = fabs(dst_y3-dst_y2);
hnew = int(t1>t2 ? t1:t2);

// 计算旋转变换中的两个中间变量,便于以后计算
temp1=float( -0.5*wnew*cosa+0.5*hnew*sina+0.5*(*width));
temp2=float(-0.5*wnew*sina-0.5*hnew*cosa+0.5*(*height));
//计算原图像和新图像每行像素所占的字节数(必须是4的倍数)
int lineByte = ((*width) * biBitCount/8+3)/4*4;
int lineByte2=(wnew * biBitCount/8+3)/4*4;

//申请新的位图数据存储空间
unsigned char *pImage2;
pImage2=new unsigned char[lineByte2*hnew];

//将新图像设置为背景色
memset(pImage2, 0, lineByte2*hnew);

//遍历新图像的每一个像素进行判断
int x, y, x0, y0; // x0, y0为原图像中对应坐标
for(y=0; y<hnew; y++)
for(x=0; x<wnew; x++)
{
x0= int(x*cosa-y*sina+temp1);
y0= int(x*sina+y*cosa+temp2);
//如果在原图像范围内则复制像素值
if( (x0>=0) && (x0<(*width)) && (y0>=0) && (y0<(*height)))
{
*(pImage2+lineByte2*y+x) = *(pImage+lineByte*y0+x0);
}
}

//修改原图像的高度和宽度
*width = wnew;
*height = hnew;
delete [ ] pImage; //释放原内存空间
return pImage2;
}

    该程序在Eclipse上调试通过,结果正确。






原文地址:https://www.cnblogs.com/haigege/p/2205718.html