您的位置:首页 > 其它

(转)任意角度的高质量的快速的图…

2016-12-25 12:21 148 查看
图形图像处理-之-任意角度的高质量的快速的图像旋转 上篇 纯软件的任意角度的快速旋转 收藏

         
图形图像处理-之-任意角度的高质量的快速的图像旋转 上篇 纯软件的任意角度的快速旋转

                               HouSisong@GMail.com  
2007.04.26

(2009.03.09 
可以到这里下载旋转算法的完整的可以编译的项目源代码: http://blog.csdn.net/housisong/archive/2009/03/09/3970925.aspx 
)

(2007.06.22 优化PicRotary3加快13.6%,优化PicRotarySSE加快6.1%,

           
尝试了一下使用SSE2的写缓冲优化MOVNTI)

(2007.04.29 修正一个TRotaryClipData.find_begin的bug)

(2007.05.16 更换测试用电脑和编译器,为了保持测试数据一致和可对比性,更新了测试数据)

 

tag:图像旋转,任意角度,图像缩放,速度优化,定点数优化,近邻取样插值,二次线性插值,

  
三次卷积插值,MipMap链,三次线性插值,MMX\SSE优化,CPU缓存优化,AlphaBlend,颜色混合,并行

摘要:首先给出一个基本的图像旋转算法,然后一步一步的优化其速度和旋转质量,打破不能软件旋转的神话!

任意角度的高质量的快速的图像旋转 全文 分为:

    
上篇 纯软件的任意角度的快速旋转

    
中篇 高质量的旋转

    
下篇 补充话题(完整AlphaBlend旋转、旋转函数并行化、针对大图片的预读缓冲区优化)

正文:

  为了便于讨论,这里只处理32bit的ARGB颜色;

  代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;

  为了代码的可读性,没有加入异常处理代码;

  
测试使用的CPU为赛扬2G(新的测试平台的CPU为AMD64x2 4200+(2.37G),测试时使用的单线程执行);

 
(一些基础代码和插值原理的详细说明参见作者的《图形图像处理-之-高质量的快速的图像缩放》系列文章)

速度测试说明:

  只测试内存数据到内存数据的缩放

  测试图片都是800*600旋转到1004*1004,测试成绩取各个旋转角度的平均速度值;
fps表示每秒钟的帧数,值越大表示函数越快

A:旋转原理和旋转公式:

  推导旋转公式:



             

                      
旋转示意图

   有: 
tg(b)=y/x                            
----(1)

        
tg(a+b)=y'/x'                        
----(2)

        
x*x + y*y = x'*x' +
y'*y'            
----(3)

   有公式:tg(a+b) = ( tg(a)+tg(b) )
/ ( 1-tg(a)*tg(b) )  ----(4)

    
把(1)代入(4)从而消除参数b;

    
tg(a)+y/x = y'/x'*( 1-tg(a)*y/x
)               
----(5)

    
由(5)可以得x'=y'*(x-y*tg(a))/( x*tg(a)+y
)      
----(6)

   把(6)代入(3)从而消除参数x',化简后求得:

    
y'=x*sin(a)+y*cos(a);                    
----(7)

   把(7)代入(6),有:

    
x'=x*cos(a)-y*sin(a);                    
----(8)

  OK,旋转公式有了,那么来看看在图片旋转中的应用;

 
假设对图片上任意点(x,y),绕一个坐标点(rx0,ry0)逆时针旋转RotaryAngle角度后的新的坐标设为(x',
y'),有公式:

  (x平移rx0,y平移ry0,角度a对应-RotaryAngle ,
带入方程(7)、(8)后有: ) 

  x'= (x - rx0)*cos(RotaryAngle) + (y -
ry0)*sin(RotaryAngle) + rx0 ;

  y'=-(x - rx0)*sin(RotaryAngle) + (y -
ry0)*cos(RotaryAngle) + ry0 ;

那么,根据新的坐标点求源坐标点的公式为:

  x=(x'- rx0)*cos(RotaryAngle) - (y'-
ry0)*sin(RotaryAngle) + rx0 ;

  y=(x'- rx0)*sin(RotaryAngle) + (y'-
ry0)*cos(RotaryAngle) + ry0 ;

旋转的时候还可以顺便加入x轴和y轴的缩放和平移,而不影响速度,那么完整的公式为:           

  x=(x'- move_x-rx0)/ZoomX*cos(RotaryAngle) - (y'-
move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0 ;

  y=(x'- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y'-
move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0 ;

  其中: RotaryAngle为逆时针旋转的角度;

        
ZoomX,ZoomY为x轴y轴的缩放系数(支持负的系数,相当于图像翻转);

        
move_x,move_y为x轴y轴的平移量;

 一些颜色和图片的数据定义:

#define asm __asm

typedef unsigned char TUInt8; // [0..255]

struct
TARGB32     
//32 bit color

{

   
TUInt8 
b,g,r,a;         
//a is alpha

};

struct TPicRegion  //一块颜色数据区的描述,便于参数传递

{

   
TARGB32*   
pdata;        
//颜色数据首地址

   
long       
byte_width;   
//一行数据的物理宽度(字节宽度);

               
//abs(byte_width)有可能大于等于width*sizeof(TARGB32);

   
long       
width;        
//像素宽度

   
long       
height;       
//像素高度

};

//那么访问一个点的函数可以写为:

inline TARGB32& Pixels(const
TPicRegion& pic,const long x,const long y)

{

    return (
(TARGB32*)((TUInt8*)pic.pdata+pic.byte_width*y) )[x];

}

//判断一个点是否在图片中

inline bool PixelsIsInPic(const TPicRegion&
pic,const long x,const long y)

{

    return (
(x>=0)&&(x<pic.width)
&&
(y>=0)&&(y<pic.height)
);

}

 

 B:一个简单的浮点实现版本

//////////////////////////////////////////////////////////////////////////////////////////////////////

//函数假设以原图片的中心点坐标为旋转和缩放的中心

void PicRotary0(const TPicRegion& Dst,const
TPicRegion& Src,double RotaryAngle,double
ZoomX,double ZoomY,double move_x,double move_y)

{

    if (
(fabs(ZoomX*Src.width)<1.0e-4) ||
(fabs(ZoomY*Src.height)<1.0e-4) ) return;
//太小的缩放比例认为已经不可见

    double
rx0=Src.width*0.5;  //(rx0,ry0)为旋转中心

    double
ry0=Src.height*0.5;

    for (long
y=0;y<Dst.height;++y)

    {

       
for (long x=0;x<Dst.width;++x)

       
{

           
long srcx=(long)((x- move_x-rx0)/ZoomX*cos(RotaryAngle) - (y-
move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0) ;

           
long srcy=(long)((x- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y-
move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0) ;

           
if (PixelsIsInPic(Src,srcx,srcy))

               
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);

       
}

    }

}

(调用方法比如:

  
PicRotary0(ppicDst,ppicSrc,PI/6,0.9,0.9,(dst_wh-ppicSrc.width)*0.5,(dst_wh-ppicSrc.height)*0.5);

  
//作用:将图片ppicSrc按0.9的缩放比例旋转PI/6幅度后绘制到图片ppicDst的中心

)

//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值

////////////////////////////////////////////////////////////////////////////////

//速度测试:                 

//==============================================================================

//
PicRotary0             
34.9 fps

//////////////////////////////////////////////////////////////////////////////// 

旋转结果图示(小图):

  

  
 



                
30度                           
  
60度                     

  

     


                
90度                                  
120度

 

     



               
150度                                 
180度

 

     



               
210度                                 
240度

 

     



               
270度                                 
300度

 

     



             
330度                                  
360度

C:优化循环内部,化简系数

 
1.sin和cos函数是很慢的计算函数,可以在循环前预先计算好sin(RotaryAngle)和cos(RotaryAngle)的值:

    double
sinA=sin(RotaryAngle);

    double
cosA=cos(RotaryAngle);

  2.可以将除以ZoomX、ZoomY改成乘法,预先计算出倒数:

    double
rZoomX=1.0/ZoomX;

    double
rZoomY=1.0/ZoomY;

  3.优化内部的旋转公式,将能够预先计算的部分提到循环外(即:拆解公式):

    
原:  long srcx=(long)((x-
move_x-rx0)/ZoomX*cos(RotaryAngle) - (y-
move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0) ;

          
long srcy=(long)((x- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y-
move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0) ;

    
变形为:

          
long srcx=(long)( Ax*x + Bx*y +Cx ) ;

          
long srcy=(long)( Ay*x + By*y +Cy ) ;

     
其中: Ax=(rZoomX*cosA); Bx=(-rZoomY*sinA);
Cx=(-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0);

           
Ay=(rZoomX*sinA); By=(rZoomY*cosA); 
Cy=(-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0);

     
(提示: Ax,Bx,Cx,Ay,By,Cy都可以在旋转之前预先计算出来)

  改进后的函数为:

void PicRotary1(const TPicRegion& Dst,const
TPicRegion& Src,double RotaryAngle,double
ZoomX,double ZoomY,double move_x,double move_y)

{

    if (
(fabs(ZoomX*Src.width)<1.0e-4) ||
(fabs(ZoomY*Src.height)<1.0e-4) ) return;
//太小的缩放比例认为已经不可见

    double
rZoomX=1.0/ZoomX;

    double
rZoomY=1.0/ZoomY;

    double
sinA=sin(RotaryAngle);

    double
cosA=cos(RotaryAngle);

    double
Ax=(rZoomX*cosA);

    double
Ay=(rZoomX*sinA);

    double
Bx=(-rZoomY*sinA);

    double
By=(rZoomY*cosA);

    double
rx0=Src.width*0.5;  //(rx0,ry0)为旋转中心

    double
ry0=Src.height*0.5;

    double
Cx=(-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0);

    double
Cy=(-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0);

    TARGB32*
pDstLine=Dst.pdata;

    double
srcx0_f=(Cx);

    double
srcy0_f=(Cy);

    for (long
y=0;y<Dst.height;++y)

    {

       
double srcx_f=srcx0_f;

       
double srcy_f=srcy0_f;

       
for (long x=0;x<Dst.width;++x)

       
{

           
long srcx=(long)(srcx_f);

           
long srcy=(long)(srcy_f);

           
if (PixelsIsInPic(Src,srcx,srcy))

               
pDstLine[x]=Pixels(Src,srcx,srcy);

           
srcx_f+=Ax;

           
srcy_f+=Ay;

       
}

       
srcx0_f+=Bx;

       
srcy0_f+=By;

       
((TUInt8*&)pDstLine)+=Dst.byte_width;

    }

}

////////////////////////////////////////////////////////////////////////////////

//速度测试:                 

//==============================================================================

//
PicRotary1              
62.0 fps

////////////////////////////////////////////////////////////////////////////////

( 在AMD64x2
4200+和VC2005编译下PicRotary1(51.8fps)比PicRotary0(27.1fps)快90%;

  在AMD64x2
4200+和VC6编译下PicRotary1(20.3fps)比PicRotary0(16.1fps)快26%;

 
以前在赛扬2G和VC6编译下PicRotary1(8.4fps)反而比PicRotary0(12.7fps)慢50%! :(
)

D:更深入的优化、定点数优化

  (浮点数到整数的转化也是应该优化的一个地方,这里不再处理,可以参见

   
<图形图像处理-之-高质量的快速的图像缩放 上篇
近邻取样插值和其速度优化>中的PicZoom3_float函数)

  1.优化除法:

    原: double
rZoomX=1.0/ZoomX;

        
double rZoomY=1.0/ZoomY;

   
改写为(优化掉了一次除法):

        
double tmprZoomXY=1.0/(ZoomX*ZoomY); 

        
double rZoomX=tmprZoomXY*ZoomY;

        
double rZoomY=tmprZoomXY*ZoomX;

 
2.x86的浮点计算单元(FPU)有一条指令"fsincos"可以同时计算出sin和cos值

   
//定义SinCos函数: 同时计算sin(Angle)和cos(Angle)的内嵌x86汇编函数

    void
__declspec(naked) __stdcall SinCos(const double
Angle,double& sina,double&
cosa)

    {

       
asm

       
{

           
fld  qword ptr
[esp+4]//Angle  

           
mov  eax,[esp+12]//&sina

           
mov  edx,[esp+16]//&cosa

           
fsincos  

           
fstp qword ptr [edx]  

           
fstp qword ptr [eax] 

           
ret 16

       
}

    }

  3.用定点数代替浮点计算

void PicRotary2(const TPicRegion& Dst,const
TPicRegion& Src,double RotaryAngle,double
ZoomX,double ZoomY,double move_x,double move_y)

{

    if (
(fabs(ZoomX*Src.width)<1.0e-4) ||
(fabs(ZoomY*Src.height)<1.0e-4) ) return;
//太小的缩放比例认为已经不可见

    double
tmprZoomXY=1.0/(ZoomX*ZoomY); 

    double
rZoomX=tmprZoomXY*ZoomY;

    double
rZoomY=tmprZoomXY*ZoomX;

    double
sinA,cosA;

   
SinCos(RotaryAngle,sinA,cosA);

    long
Ax_16=(long)(rZoomX*cosA*(1<<16));

    long
Ay_16=(long)(rZoomX*sinA*(1<<16));

    long
Bx_16=(long)(-rZoomY*sinA*(1<<16));

    long
By_16=(long)(rZoomY*cosA*(1<<16));

    double
rx0=Src.width*0.5;  //(rx0,ry0)为旋转中心

    double
ry0=Src.height*0.5;

    long
Cx_16=(long)((-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0)*(1<<16));

    long
Cy_16=(long)((-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0)*(1<<16));

    TARGB32*
pDstLine=Dst.pdata;

    long
srcx0_16=(Cx_16);

    long
srcy0_16=(Cy_16);

    for (long
y=0;y<Dst.height;++y)

    {

       
long srcx_16=srcx0_16;

       
long srcy_16=srcy0_16;

       
for (long x=0;x<Dst.width;++x)

       
{

           
long srcx=(srcx_16>>16);

           
long srcy=(srcy_16>>16);

           
if (PixelsIsInPic(Src,srcx,srcy))

               
pDstLine[x]=Pixels(Src,srcx,srcy);

           
srcx_16+=Ax_16;

           
srcy_16+=Ay_16;

       
}

       
srcx0_16+=Bx_16;

       
srcy0_16+=By_16;

       
((TUInt8*&)pDstLine)+=Dst.byte_width;

    }

}

////////////////////////////////////////////////////////////////////////////////

//速度测试:

//==============================================================================

//
PicRotary2            
134.2 fps

//////////////////////////////////////////////////////////////////////////////// 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐