2 图象的几何变换

这一章我们将介绍图象的几何变换,包括图象的平移、旋转、镜象变换、转置、放缩等。如果你熟悉矩阵运算,你将发现,实现这些变换是非常容易的。

2.1 平移

平移(translation)变换大概是几何变换中最简单的一种了。

如图2.1所示,初始坐标为(x0,y0)的点经过平移(tx,ty)(以向右,向下为正方向)后,坐标变为(x1,y1)。这两点之间的关系是x1=x0+tx y1=y0+ty

2.1     平移的示意图

以矩阵的形式表示为

                 (2.1)

我们更关心的是它的逆变换:

(2.2)

这是因为:我们想知道的是平移后的图象中每个象素的颜色。例如我们想知道,新图中左上角点的RGB值是多少?很显然,该点是原图的某点经过平移后得到的,这两点的颜色肯定是一样的,所以只要知道了原图那点的RGB值即可。那么到底新图中的左上角点对应原图中的哪一点呢?将左上角点的坐标(0,0)入公式(2.2),得到x0=-tx y0=-ty;所以新图中的(0,0)点的颜色和原图中(-tx , -ty)的一样。

这样就存在一个问题:如果新图中有一点(x1,y1),按照公式(2.2)得到的(x0,y0)不在原图中该怎么办?通常的做法是,把该点的RGB值统一设成(0,0,0)或者(255,255,255)

另一个问题是:平移后的图象是否要放大?一种做法是不放大,移出的部分被截断。例如,图2.2为原图,图2.3为移动后的图。这种处理,文件大小不会改变。

2.2     移动前的图

2.3     移动后的图

还有一种做法是:将图象放大,使得能够显示下所有部分,如图2.4所示。

2.4    移动后图象被放大

这种处理,文件大小要改变。设原图的宽和高分别是w1,h1则新图的宽和高变为w1+|tx|h1+|ty|,加绝对值符号是因为tx, ty有可能为负(即向左,向上移动)

下面的函数Translation采用的是第一种做法,即移出的部分被截断。在给出源代码之前,先说明一个问题。

如果你用过Photoshop,Corel PhotoPaint等图象处理软件,可能听说过“灰度图”(grayscale)这个词。灰度图是指只含亮度信息,不含色彩信息的图象,就象我们平时看到的黑白照片:亮度由暗到明,变化是连续的。因此,要表示灰度图,就需要把亮度值进行量化。通常划分成0255256个级别,其中0最暗(全黑)255最亮(全白).bmp格式的文件中,并没有灰度图这个概念,但是,我们可以很容易在.bmp文件中表示灰度图。方法是用256色的调色板,只不过这个调色板有点特殊,每一项的RGB值都是相同的。也就是说RGB值从(000)(111)一直到(255255255)(000)是全黑色,(255255255)是全白色,中间的是灰色。这样,灰度图就可以用256色图来表示了。为什么会这样呢?难道是一种巧合?其实并不是。

在表示颜色的方法中,除了RGB外,还有一种叫YUV的表示方法,应用也很多。电视信号中用的就是一种类似于YUV的颜色表示方法。

在这种表示方法中,Y分量的物理含义就是亮度,UV分量代表了色差信号(你不必了解什么是色差,只要知道有这么一个概念就可以了)。使用这种表示方法有很多好处,最主要的有两点:

(1)    因为Y代表了亮度,所以Y分量包含了灰度图的所有信息,只用Y分量就能完全能够表示出一幅灰度图来。当同时考虑UV分量时,就能够表示出彩色信息来。这样,用同一种表示方法可以很方便的在灰度和彩色图之间切换,而RGB表示方法就做不到这一点了。

(2)    人眼对于亮度信号非常敏感,而对色差信号的敏感程度相对较弱。也就是说,图象的主要信息包含在Y分量中。这就提示我们:如果在对YUV信号进行量化时,可以“偏心”一点,让Y的量化级别多一些(谁让它重要呢?)而让UV的量化级别少一些,就可以实现图象信息的压缩。这一点将在第9章介绍图象压缩时仔细研究,这里就不深入讨论了。而RGB的表示方法就做不到这一点,因为RGB三个分量同等重要,缺了谁也不行。YUVRGB之间有着如下的对应关系

(2.3)

(2.4)

RGB三个分量的大小一样时,假设都是a,代入公式(2.3),得到Y=aU=0V=0 。你现在该明白我前面所说不是巧合的原因了吧。

使用灰度图有一个好处,那就是方便。首先RGB的值都一样;其次,图象数据即调色板索引值,也就是实际的RGB值,也就是亮度值;另外,因为是256色调色板,所以图象数据中一个字节代表一个象素,很整齐。如果是2色图或16色图,还要拼凑字节,很麻烦。如果是彩色的256色图,由于图象处理后有可能会产生不属于这256种颜色的新颜色,就更麻烦了;这一点,今后你就会有深刻体会的。所以,做图象处理时,一般采用灰度图。为了将重点放在算法本身上,今后给出的程序如不做特殊说明,都是针对256级灰度图的。其它颜色的情况,你可以自己想一想,把算法补全。

如果想得到一幅灰度图,可以使用Sea或者PhotoShop等软件提供的颜色转换功能将彩色图转换成灰度图。

好了,言归正传,下面给出Translation的源代码。算法的思想是先将所有区域填成白色,然后找平移后显示区域的左上角点(x0,y0) 和右下角点(x1,y1) ,分几种情况进行处理。

先看x方向(width指图象的宽度)

(1)    tx-width:很显然,图象完全移出了屏幕,不用做任何处理;

(2)    -width<tx0:如图2.5所示。容易看出,图象区域的x范围从0width-|tx|,对应原图的范围从|tx|width

2.5     tx0ty0的情况

(3)    0< tx <width:如图2.6所示。容易看出,图象区域的x范围从tx width,对应原图的范围从0width - tx

2.6     0< tx<width0<ty<height的情况

(4)    tx width:很显然,图象完全移出了屏幕,不用做任何处理。

y方向是对应的(height表示图象的高度)

(1)    ty-height,图象完全移出了屏幕,不用做任何处理;

(2)    -height<ty0,图象区域的y范围从0height-|ty|,对应原图的范围从|ty|height

(3)    0<ty<height ,图象区域的y范围从tyheight,对应原图的范围从0height-ty

(4)    tyheight,图象完全移出了屏幕,不用做任何处理。

这种做法利用了位图存储的连续性,即同一行的象素在内存中是相邻的。利用memcpy函数,从(x0,y0)点开始,一次可以拷贝一整行(宽度为x1x0),然后将内存指针移到(x0,y0+1)处,拷贝下一行。这样拷贝(y1-y0)行就完成了全部操作,避免了一个一个象素的计算,提高了效率。Translation的源代码如下:

int    xOffset=0,yOffset=0;

BOOL Translation(HWND hWnd)

{

DLGPROC                                dlgInputBox = NULL;

DWORD                                   OffBits,BufSize;

LPBITMAPINFOHEADER   lpImgData;

LPSTR                                      lpPtr;

HLOCAL                                   hTempImgData;

LPBITMAPINFOHEADER   lpTempImgData;

LPSTR                                      lpTempPtr;

int                                             SrcX0,SrcY0,SrcX1,SrcY1;

int                                             DstX0,DstY0,DstX1,DstY1;

int                                             RectWidth,RectHeight;

BOOL                                       xVisible,yVisible;

HDC                                         hDc;

HFILE                                       hf;

int                                             i;

//出现对话框,输入x偏移量xOffset,和y偏移量yOffset

dlgInputBox = (DLGPROC) MakeProcInstance ( (FARPROC)InputBox,ghInst );

DialogBox (ghInst, "INPUTBOX", hWnd, dlgInputBox);

FreeProcInstance ( (FARPROC) dlgInputBox );

//OffBitsBITMAPINFOHEADER结构长度加调色板的大小

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

BufSize=OffBits+bi.biHeight*LineBytes;//要开的缓冲区的大小

//为新产生的位图分配缓冲区内存

if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK|

MB_ICONEXCLAMATION);

return FALSE; //失败,返回

}

//lpImgData为指向原来位图数据的指针

lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);

//lpTempImgData为指向新产生位图数据的指针

lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

lpPtr=(char *)lpImgData;

lpTempPtr=(char *)lpTempImgData;

//将新的缓冲区中的每个字节都填成255,这样以后未处理的象素就是白色

memset(lpTempPtr,(BYTE)255,BufSize);

//两幅图之间的头信息,包括调色板都是相同的,所以直接拷贝头和调色板

memcpy(lpTempPtr,lpPtr,OffBits);

//xVisibleFALSE时,表示x方向已经移出了可显示的范围

xVisible=TRUE;

if( xOffset<= -bi.biWidth )

xVisible=FALSE;

else if( xOffset<=0){

DstX0=0;  //表示移动后,有图区域的左上角点的x坐标

DstX1=bi.biWidth+xOffset; //表示移动后,有图区域的右下角点的x坐标

}

else if ( xOffset<bi.biWidth){

DstX0=xOffset;

DstX1=bi.biWidth;

}

else

xVisible=FALSE;

SrcX0=DstX0-xOffset; //对应DstX0在原图中的x坐标

SrcX1=DstX1-xOffset; //对应DstX1在原图中的x坐标

RectWidth=DstX1-DstX0; //有图区域的宽度

//yVisibleFALSE时,表示y方向已经移出了可显示的范围

yVisible=TRUE;

if( yOffset<= -bi.biHeight )

yVisible=FALSE;

else if( yOffset<=0){

DstY0=0; //表示移动后,有图区域的左上角点的y坐标

DstY1=bi.biHeight+yOffset; //表示移动后,有图区域的右下角点的y坐标

}

else if ( yOffset<bi.biHeight){

DstY0=yOffset;

DstY1=bi.biHeight;

}

else

yVisible=FALSE;

SrcY0=DstY0-yOffset; //对应DstY0在原图中的y坐标

SrcY1=DstY1-yOffset; //对应DstY1在原图中的y坐标

RectHeight=DstY1-DstY0; //有图区域的高度

if( xVisible && yVisible){ //x,y方向都没有完全移出可显示的范围

for(i=0;i<RectHeight;i++){ //拷贝每一行

//lpPtr指向要拷贝的那一行的最左边的象素对应在原图中的位

//置。特别要注意的是,由于.bmp是上下颠倒的,偏移是

//(BufSize-LineBytes-(i+SrcY0)*LineBytes)+SrcX0而不是

//(i+SrcY0)*LineBytes)+SrcX0,你试着举个例子就明白了。

lpPtr=(char*)lpImgData+(BufSize-LineBytes-

(i+SrcY0)*LineBytes)+SrcX0;

//lpTempPtr指向要拷贝的那一行的最左边的象素对应在新图中//的位置。同样要注意上面//的问题。

lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-(i+DstY0)*LineBytes)+DstX0;

//拷贝一行(宽度为RectWidth)

              memcpy(lpTempPtr,lpPtr,RectWidth);

}

}

hDc=GetDC(hWnd);

if(hBitmap!=NULL)

DeleteObject(hBitmap); //释放原来的位图句柄

//产生新的位图

hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER) +

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData,

DIB_RGB_COLORS);

//将平移后的图象存成文件

hf=_lcreat("c:\\translation.bmp",0);

_lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));

_lwrite(hf,(LPSTR)lpTempImgData,BufSize);

_lclose(hf);

//释放资源和内存

ReleaseDC(hWnd,hDc);

LocalUnlock(hTempImgData);

LocalFree(hTempImgData);

GlobalUnlock(hImgData);

return TRUE;

}

2.2 旋转

旋转(rotation)有一个绕着什么转的问题,通常的做法是以图象的中心为圆心旋转,举个例子,图2.7旋转30(顺时针方向)后如图2.8所示:

2.7     旋转前的图

2.8     旋转后的图

可以看出,旋转后图象变大了。另一种做法是不让图象变大,转出的部分被裁剪掉。如图2.9所示。

我们采用第一种做法,首先给出变换矩阵。在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,如图2.10所示,r为该点到原点的距离,在旋转过程中,r保持不变;brx轴之间的夹角。

2.9 旋转后保持原图大小,

转出的部分被裁掉

2.10   旋转示意图

旋转前:x0=rcosby0=rsinb

旋转a角度后:

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

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

以矩阵的形式表示:

(2.5)

上面的公式中,坐标系xoy是以图象的中心为原点,向右为x轴正方向,向上为y轴正方向。它和以图象左上角点为原点o’,向右为x’轴正方向,向下为y’轴正方向的坐标系x’o’y’之间的转换关系如何呢?如图2.11所示。

2.11    两种坐标系间的转换关系

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

(2.6)

逆变换为:

(2.7)

有了上面的公式,我们可以把变换分成三步:

1.将坐标系o’变成o

2.将该点顺时针旋转a角;

3.将坐标系o变回o’,这样,我们就得到了变换矩阵,是上面三个矩阵的级联。

(2.8)

要注意的是,因为新图变大,所以上面公式中出现了woldholdwnewhnew,它们分别表示原图(old)和新图(new)的宽、高。我们从图2.8中容易看出:wnew=max(|x4-x1|,|x3-x2|) hnew=max(|y4-y1|,|y3-y2|)

(2.8)的逆变换为

(2.9)

这样,对于新图中的每一点,我们就可以根据公式(2.9)求出对应原图中的点,得到它的灰度。如果超出原图范围,则填成白色。要注意的是,由于有浮点运算,计算出来点的坐标可能不是整数,采用取整处理,即找最接近的点,这样会带来一些误差(图象可能会出现锯齿)。更精确的方法是采用插值,将在图象缩放时介绍。

源程序如下:

#define PI 3.1415926535

#define RADIAN(angle) ((angle)*PI/180.0) //角度到弧度转化的宏

BOOL Rotation(HWND hWnd)

{

DLGPROC                                dlgInputBox = NULL;

DWORD                                   OffBits,SrcBufSize,DstBufSize,DstLineBytes;

LPBITMAPINFOHEADER   lpImgData;

LPSTR                                      lpPtr;

HLOCAL                                   hTempImgData;

LPBITMAPINFOHEADER   lpTempImgData;

LPSTR                                      lpTempPtr;

float                                          SrcX1,SrcY1,SrcX2,SrcY2;

float                                          SrcX3,SrcY3,SrcX4,SrcY4;

float                                          DstX1,DstY1,DstX2,DstY2;

float                                          DstX3,DstY3,DstX4,DstY4;

DWORD                                   Wold,Hold,Wnew,Hnew;

HDC                                         hDc;

HFILE                                       hf;

DWORD                                   x0,y0,x1,y1;

float                                          cosa,sina; //cos(a),sin(a);

float                                          num1,num2;

BITMAPFILEHEADER        DstBf;

BITMAPINFOHEADER              DstBi;

//出现对话框,输入旋转角度(顺时针方向)

dlgInputBox = (DLGPROC) MakeProcInstance ( (FARPROC)InputBox,ghInst );

DialogBox (ghInst, "INPUTBOX", hWnd, dlgInputBox);

FreeProcInstance ( (FARPROC) dlgInputBox );

//角度到弧度的转化

RotateAngle=(float)RADIAN(RotateAngle);

cosa=(float)cos((double)RotateAngle);

sina=(float)sin((double)RotateAngle);

//原图的宽度和高度

Wold=bi.biWidth;

Hold=bi.biHeight;

//原图的四个角的坐标

SrcX1=(float)(-0.5*Wold);

SrcY1=(float)(0.5*Hold);

SrcX2=(float)(0.5*Wold);

SrcY2=(float)(0.5*Hold);

SrcX3=(float)(-0.5*Wold);

SrcY3=(float)(-0.5*Hold);

SrcX4=(float)(0.5*Wold);

SrcY4=(float)(-0.5*Hold);

//新图四个角的坐标

DstX1=cosa*SrcX1+sina*SrcY1;

DstY1=-sina*SrcX1+cosa*SrcY1;

DstX2=cosa*SrcX2+sina*SrcY2;

DstY2=-sina*SrcX2+cosa*SrcY2;

DstX3=cosa*SrcX3+sina*SrcY3;

DstY3=-sina*SrcX3+cosa*SrcY3;

DstX4=cosa*SrcX4+sina*SrcY4;

DstY4=-sina*SrcX4+cosa*SrcY4;

//计算新图的宽度,高度

Wnew = (DWORD)(max(fabs(DstX4-DstX1), fabs(DstX3-DstX2))+0.5);

Hnew = (DWORD)(max(fabs(DstY4-DstY1), fabs(DstY3-DstY2))+0.5);

//计算矩阵(2.9)中的两个常数,这样不用以后每次都计算了

num1=(float)( -0.5*Wnew*cosa-0.5*Hnew*sina+0.5*Wold);

num2=(float)(0.5*Wnew*sina-0.5*Hnew*cosa+0.5*Hold);

//OffBitsBITMAPINFOHEADER结构长度加调色板的大小

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

SrcBufSize=OffBits+bi.biHeight*LineBytes;

//显示时,采用新图的宽度和高度,

ImgWidth=Wnew;

ImgHeight=Hnew;

//新图每行占用的字节

DstLineBytes=(DWORD)WIDTHBYTES(Wnew*bi.biBitCount);

DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD)+

(DWORD)DstLineBytes*Hnew); //要开的缓冲区的大小

//为新产生的位图分配缓冲区内存

if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL)

{

MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE; //失败,返回

}

//lpImgData为指向原来位图数据的指针

lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);

//lpTempImgData为指向新产生位图数据的指针

lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

lpPtr=(char *)lpImgData;

lpTempPtr=(char *)lpTempImgData;

//将新的缓冲区中的每个字节都填成255,这样以后未处理的象素就是白色

memset(lpTempPtr,(BYTE)255,DstBufSize);

//拷贝头和调色板信息

memcpy(lpTempPtr,lpPtr,OffBits);

//得到新的BITMAPFILEDERBITMAPINFOHERDER

memcpy((char *)&DstBf,(char *)&bf,sizeof(BITMAPFILEHEADER));

memcpy((char *)&DstBi,(char *)&bi,sizeof(BITMAPINFOHEADER));

//做一些必要的改变,这一点特别要注意

DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER);

DstBi.biWidth=Wnew;

DstBi.biHeight=Hnew;

//用新的BITMAPINFOHERDER覆盖原来的那个

memcpy(lpTempPtr,(char *)&DstBi,sizeof(BITMAPINFOHEADER));

for(y1=0;y1<Hnew;y1++)

      for(x1=0;x1<Wnew;x1++){

             //x0,y0为对应的原图上的坐标

             x0= (DWORD)(x1*cosa+y1*sina+num1);

             y0= (DWORD)(-1.0f*x1*sina+y1*cosa+num2);

             if( (x0>=0) && (x0<Wold) && (y0>=0) && (y0<Hold))  

//在原图范围内

             {

                    lpPtr=(char*)lpImgData+

(SrcBufSize-LineBytes-y0*LineBytes)+x0;

                    lpTempPtr=(char*)lpTempImgData+

(DstBufSize-DstLineBytes-y1*DstLineBytes)+x1;

                    *lpTempPtr=*lpPtr; //进行象素的复制

             }

      }

hDc=GetDC(hWnd);

if(hBitmap!=NULL)

DeleteObject(hBitmap); //释放原来的位图句柄

hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER) +

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData,

DIB_RGB_COLORS);

//将旋转后的图象存成文件

hf=_lcreat("c:\\rotation.bmp",0);

_lwrite(hf,(LPSTR)&DstBf,sizeof(BITMAPFILEHEADER));

_lwrite(hf,(LPSTR)lpTempImgData,DstBufSize);

_lclose(hf);

//释放资源和内存

ReleaseDC(hWnd,hDc);

LocalUnlock(hTempImgData);

LocalFree(hTempImgData);

GlobalUnlock(hImgData);

return TRUE;

}

程序运行时的画面如图2.12所示

2.12   旋转

2.3 镜象

镜象(mirror)分水平镜象和垂直镜象两种。图2.2的水平镜象和垂直镜象分别如图2.13和图2.14所示

2.13   2.2的水平镜象

2.14   2.2的垂直镜象

镜象的变换矩阵很简单。设原图宽为w,高为h,变换后,图的宽和高不变。

水平镜象的变化矩阵为:

(2.10)

垂直镜象的变化矩阵为:

(2.11)

镜象变换的源代码如下,因为和平移的那段程序很类似,程序中的注释就简单一些。

BOOL Mirror(HWND hWnd,BOOL XDirection)

//XdirectionTRUE时表示水平镜象,为FALSE时表示垂直镜象变换

{

       DWORD                             OffBits,BufSize;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                                       lpPtr;

       HLOCAL                             hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                                       lpTempPtr;

       HDC                                          hDc;

       HFILE                                        hf;

       LONG                                        x0,y0,x1,y1;

       OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

       BufSize=OffBits+bi.biHeight*LineBytes;

if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK|

MB_ICONEXCLAMATION);

       return FALSE;

}

lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

lpPtr=(char *)lpImgData;

lpTempPtr=(char *)lpTempImgData;

memset(lpTempPtr,(BYTE)255,BufSize);

memcpy(lpTempPtr,lpPtr,OffBits);

       if( XDirection){ //水平镜象

              for(y1=0;y1<bi.biHeight;y1++)

                     for(x1=0;x1<bi.biWidth;x1++){

                            x0=bi.biWidth-1-x1; //因为x坐标是从0bi.biWidth-1

                            y0=y1;

                            lpPtr=(char *)lpImgData+(BufSize-LineBytes-y0*LineBytes)+x0;

                            lpTempPtr=(char *)lpTempImgData+

(BufSize-LineBytes-y1*LineBytes)+x1;

                            *lpTempPtr=*lpPtr;

                     }

}

       else{ //垂直镜象

              for(y1=0;y1<bi.biHeight;y1++)

                     for(x1=0;x1<bi.biWidth;x1++){

                            x0=x1;

                            y0=bi.biHeight-1-y1;

                            lpPtr=(char *)lpImgData+(BufSize-LineBytes-y0*LineBytes)+x0;

                            lpTempPtr=(char *)lpTempImgData+

(BufSize-LineBytes-y1*LineBytes)+x1;

                            *lpTempPtr=*lpPtr;

                     }

}

hDc=GetDC(hWnd);

if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);

if( XDirection)

              hf=_lcreat("c:\\mirrorx.bmp",0);

       else

              hf=_lcreat("c:\\mirrory.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

2.4 转置

转置(transpose)是指将xy坐标对换,图2.2的转置如图2.15所示。

2.15   2.2的转置

要注意的是,转置和旋转900是有区别的,不信你可以试试:怎么旋转,图2.2也转不出图2.15来。另外,转置后图的宽高对换了。转置的变换矩阵很简单:

(2.12)

镜象变换的源代码如下,因为和旋转的那段程序很类似,程序中的注释就简单一些:

BOOL Transpose(HWND hWnd)

{

DWORD                             OffBits,SrcBufSize,DstBufSize,DstLineBytes;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                                       lpPtr;

       HLOCAL                             hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                                       lpTempPtr;

       DWORD                             Wnew,Hnew;

       HDC                                          hDc;

       HFILE                                        hf;

       DWORD                             x0,y0,x1,y1;

       BITMAPFILEHEADER         DstBf;

       BITMAPINFOHEADER        DstBi;

       //新图的宽度和高度

       Wnew = (DWORD)bi.biHeight;

Hnew = (DWORD)bi.biWidth;

       OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

       SrcBufSize=OffBits+bi.biHeight*LineBytes;

//显示时,采用新图的宽度和高度,

       ImgWidth=Wnew;

       ImgHeight=Hnew;

       DstLineBytes=(DWORD)WIDTHBYTES(Wnew*bi.biBitCount);

       DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD)+

(DWORD)DstLineBytes*Hnew);

if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL)

    {

           MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK|

MB_ICONEXCLAMATION);

return FALSE;

}

lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);   

lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

lpPtr=(char *)lpImgData;

       lpTempPtr=(char *)lpTempImgData;

       memset(lpTempPtr,(BYTE)255,DstBufSize);

       memcpy(lpTempPtr,lpPtr,OffBits);

       //头信息中做一些必要的改变,这一点非常重要

       memcpy((char *)&DstBf,(char *)&bf,sizeof(BITMAPFILEHEADER));

       memcpy((char *)&DstBi,(char *)&bi,sizeof(BITMAPINFOHEADER));

       DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER);

       DstBi.biWidth=Wnew;

       DstBi.biHeight=Hnew;

       memcpy(lpTempPtr,(char *)&DstBi,sizeof(BITMAPINFOHEADER));

       for(y1=0;y1<Hnew;y1++)

              for(x1=0;x1<Wnew;x1++){

                     x0= y1;

                     y0= x1;

                     lpPtr=(char *)lpImgData+(SrcBufSize-LineBytes-y0*LineBytes)+x0;

                     lpTempPtr=(char *)lpTempImgData+

(DstBufSize-DstLineBytes-y1*DstLineBytes)+x1;

                     *lpTempPtr=*lpPtr;

}

hDc=GetDC(hWnd);

     if(hBitmap!=NULL)

           DeleteObject(hBitmap);

hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);

hf=_lcreat("c:\\transpose.bmp",0);

       _lwrite(hf,(LPSTR)&DstBf,sizeof(BITMAPFILEHEADER));

       _lwrite(hf,(LPSTR)lpTempImgData,DstBufSize);

       _lclose(hf);

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

2.5 缩放

假设放大因子为ratio(为了避免新图过大或过小,我们在程序中限制0.25ratio4),缩放(zoom)的变换矩阵很简单:

(2.13)

缩放变换的源代码如下,因为和转置的那段程序很类似,程序中的注释就简单一些。

float  ZoomRatio=0.25f; //缩放比例,初始化为0.25

BOOL Zoom(HWND hWnd)

{

DLGPROC                                 dlgInputBox = NULL;

       DWORD                             OffBits,SrcBufSize,DstBufSize,DstLineBytes;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                                       lpPtr;

       HLOCAL                             hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                                       lpTempPtr;

       DWORD                             Wold,Hold,Wnew,Hnew;

       HDC                                          hDc;

       HFILE                                        hf;

       DWORD                             x0,y0,x1,y1;

       float                                    num1;

       BITMAPFILEHEADER         DstBf;

       BITMAPINFOHEADER        DstBi;

//出现对话框,输入缩放比例

       dlgInputBox = (DLGPROC) MakeProcInstance ( (FARPROC)InputBox, ghInst );

       DialogBox (ghInst, "INPUTBOX", hWnd, dlgInputBox);

       FreeProcInstance ( (FARPROC) dlgInputBox );

       num1=(float)(1.0/ZoomRatio);

       //原图宽度和高度

       Wold=bi.biWidth;

       Hold=bi.biHeight;

       //新图宽度和高度

       Wnew = (DWORD)(Wold*ZoomRatio+0.5);

     Hnew = (DWORD)(Hold*ZoomRatio+0.5);

       OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

       SrcBufSize=OffBits+bi.biHeight*LineBytes;

       ImgWidth=Wnew;

       ImgHeight=Hnew;

       DstLineBytes=(DWORD)WIDTHBYTES(Wnew*bi.biBitCount);

       DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD)+

(DWORD)DstLineBytes*Hnew);

if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL)

{

            MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK|

MB_ICONEXCLAMATION);

return FALSE;

}

lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       lpPtr=(char *)lpImgData;

       lpTempPtr=(char *)lpTempImgData;

       memset(lpTempPtr,(BYTE)255,DstBufSize);

       memcpy(lpTempPtr,lpPtr,OffBits);

       //头信息中做一些必要的改变,这一点非常重要

       memcpy((char *)&DstBf,(char *)&bf,sizeof(BITMAPFILEHEADER));

       memcpy((char *)&DstBi,(char *)&bi,sizeof(BITMAPINFOHEADER));

       DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER);

       DstBi.biWidth=Wnew;

       DstBi.biHeight=Hnew;

memcpy(lpTempPtr,(char *)&DstBi,sizeof(BITMAPINFOHEADER));

       for(y1=0;y1<Hnew;y1++)

              for(x1=0;x1<Wnew;x1++){

                     x0= (DWORD)(x1*num1);

                     y0= (DWORD)(y1*num1);

                     if( (x0>=0) && (x0<Wold) && (y0>=0) && (y0<Hold))

                     {

                            lpPtr=(char*)lpImgData+

(SrcBufSize-LineBytes-y0*LineBytes)+x0;

                            lpTempPtr=(char *)lpTempImgData+

(DstBufSize-DstLineBytes-y1*DstLineBytes)+x1;

                            *lpTempPtr=*lpPtr;

                     }

              }

hDc=GetDC(hWnd);

     if(hBitmap!=NULL)

       DeleteObject(hBitmap);

hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);

hf=_lcreat("c:\\zoom.bmp",0);

       _lwrite(hf,(LPSTR)&DstBf,sizeof(BITMAPFILEHEADER));

       _lwrite(hf,(LPSTR)lpTempImgData,DstBufSize);

       _lclose(hf);

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

由于放大图象时产生了新的象素,以及浮点数的操作,得到的坐标可能并不是整数,这一点我们在介绍旋转时就提到了。我们采用的做法是找与之最临近的点。实际上,更精确的做法是采用插值(interpolation),即利用邻域的象素来估计新的象素值。其实我们前面的做法也是一种插值,称为最邻近插值(Nearest Neighbour Interpolation)。下面先介绍线形插值(Linear Interpolation)

线形插值使用原图中两个值来构造所求坐标处的值。举一个一维的例子。如图2.16所示,如果已经知道了两点x0x2处的函数值f(x0)f(x2),现在要求x1处的函数值f(x1)。我们假设函数是线形的,利用几何知识可以知道

f(x1)=(f(x2)-f(x0))(x1-x0)/(x2-x0)+f(x0)

(2.13)

在图象处理中需要将线形插值扩展到二维的情况,即采用双线形插值(Bilinear Intrepolation),图2.17为双线形插值的示意图。

2.16   线形插值的示意图

.217    双线形插值的示意图

已知abcd四点的灰度,要求e点的灰度,可以先在水平方向上由ab线形插值求出gcd线形插值求出f,然后在垂直方向上由gf线形插值求出e

线形插值基于这样的假设:原图的灰度在两个象素之间是线形变化的。一般情况下,这种插值的效果还不错。更精确的方法是采用曲线插值(Curvilinear Interpolation),即认为象素之间的灰度变化规律符合某种曲线,但这种处理的计算量是很大的。

关于插值,我们就介绍到这里,有兴趣的读者可以参考“数值分析”方面的书籍。

USC does not screen or control the content herein and does not take responsibility for any inaccurate, offensive, infringing, or objectionable content, all of which is the sole responsibility of the author or the users who post content on this website.