第二章 图的几何变换.docx

上传人:b****7 文档编号:9187349 上传时间:2023-02-03 格式:DOCX 页数:31 大小:78.80KB
下载 相关 举报
第二章 图的几何变换.docx_第1页
第1页 / 共31页
第二章 图的几何变换.docx_第2页
第2页 / 共31页
第二章 图的几何变换.docx_第3页
第3页 / 共31页
第二章 图的几何变换.docx_第4页
第4页 / 共31页
第二章 图的几何变换.docx_第5页
第5页 / 共31页
点击查看更多>>
下载资源
资源描述

第二章 图的几何变换.docx

《第二章 图的几何变换.docx》由会员分享,可在线阅读,更多相关《第二章 图的几何变换.docx(31页珍藏版)》请在冰豆网上搜索。

第二章 图的几何变换.docx

第二章图的几何变换

第二章图象的几何变换

这一章我们将介绍图象的几何变换,包括图象的平移,旋转,镜象变换,转置,放缩等。

如果你熟悉矩阵运算,你将发现,实现这些变换是非常容易的。

1.平移(translation)

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

图1.平移的示意图

如图1所示,初始坐标为(x0,y0)的点经过平移(tx,ty)(以向右,向下为正方向)后,坐标变为(x1,y1)。

这两点之间的关系是x1=x0+tx;y1=y0+ty

以矩阵的形式表示为

=

*

(1)

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

=

*

(2)

这是因为:

我们想知道的是平移后的图象中每个像素的颜色。

例如我们想知道,新图中左上角点的RGB值是多少?

很显然,该点是原图中的某一点经过平移后得到的,这两点的颜色肯定是一样的,所以只要知道了原图那点的RGB值即可。

那么到底新图中的左上角点对应原图中的哪一点呢?

将左上角点的坐标(0,0)代入公式

(2),得到x0=-tx;y0=-ty;

所以新图中的(0,0)点的颜色和原图中(-tx,-ty)的一样。

这样就存在一个问题:

如果新图中有一点(x1,y1),按照公式

(2)得到的(x0,y0)不在原图中该怎么办?

通常的做法是,把该点的RGB值统一设成(0,0,0)或者(255,255,255)。

另一个问题是:

平移后的图象是否要放大?

一种做法是不放大,移出的部分被截断,如下图所示,图2为原图,图3为移动后的图。

这种处理,文件大小不会改变。

图2.移动前的图

图3.移动后的图

还有一种做法是:

将图象放大,使得能够显示下所有部分。

如图4所示。

图4.移动后图象被放大

这种处理,文件大小要改变。

设原图的宽和高分别是w1,h1则新图的宽和高变为w1+|tx|

和h1+|ty|,加绝对值符号是因为tx,ty有可能为负(即向左,向上移动)。

下面的函数Translation采用的是第一种做法,即移出的部分被截断。

在给出源代码之前,先说明一个问题。

如果你用过Photoshop,CorelPhotoPaint等图象处理软件,你可能听说过“灰度图”(grayscale)这个词。

灰度图是指只含亮度信息,不含色彩信息的图象,就象我们平时看到的黑白照片。

亮度由暗到明,变化是连续的,因此,要表示灰度图,就需要把亮度值进行量化。

通常划分成0到255共256个级别,0最暗(全黑),255最亮(全白)。

.bmp格式的文件中,并没有灰度图这个概念,但是,我们可以很容易的用.bmp文件来表示灰度图。

方法是用256色的调色板,只不过这个调色板有点特殊,每一项的RGB值都是相同的。

也就是说RGB值从(0,0,0),(1,1,1)一直到(255,255,255)。

(0,0,0)是全黑色,(255,255,255)是全白色,中间的是灰色。

这样,灰色图就可以用256色图来表示了。

为什么会这样呢?

难道是一种巧合?

其实并不是。

在表示颜色的方法中,除了RGB外,还有一种叫YUV的表示方法,用的也很多。

电视信号中用的就是一种类似于YUV的颜色表示方法。

这种表示方法中,Y分量的物理含义就是亮度,U和V分量代表了色差信号(你不必了解什么是色差,只要知道有这么一个概念就可以了)。

使用这种表示方法有很多好处,最主要的有两点:

1.因为Y代表了亮度,所以,Y分量包含了灰度图的所有信息,只用Y分量就完全能够表示出一幅灰度图来。

当同时考虑U,V分量时,就能够表示出彩色信息来。

这样,用同一种表示方法可以很方便的在灰度和彩色图之间切换,而RGB表示方法就做不到这一点了。

2.人眼对于亮度信号非常敏感,而对色差信号的敏感程度相对较弱。

也就是说,图象的主要信息包含在Y分量中。

这就提示了我们,如果在对YUV信号进行量化时,可以“偏心”一点,让Y的量化级别多一些(谁让它重要呢?

)而让UV的量化级别少一些,就可以实现图象信息的压缩,这一点将在图象压缩那一章中仔细研究,这里就不深入讨论了。

而RGB的表示方法就做不到这一点,因为RGB三个分量同等重要,缺了谁也不行。

YUV和RGB之间有着如下的对应关系

=

*

(3)

=

*

(4)

当RGB三个分量的大小一样时,假设都是a,代入公式(3),得到Y=a,U=0,V=0。

你现在该明白我前面所说不是巧合的原因了吧。

使用灰度图有一个好处,那就是方便。

首先RGB的值都一样,其次,图象数据即调色板索引值,也就是实际的RGB值,也就是亮度值;另外因为是256色的调色板,所以图象数据中一个字节代表一个像素,很齐整,如果是2色图或16色图,还要拼凑字节,很麻烦。

如果是彩色的256色图,由于图象处理后有可能会产生不属于这256种颜色的新颜色,就更麻烦了,这一点,今后你就会有深刻体会的。

所以,做图象处理时,一般采用灰度图。

为了将重点放在算法本身上,今后给出的程序如不做特殊说明,都是针对256级灰度图的,其它颜色的情况,你可以自己想一想,把算法补全。

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

好了,言归正传,下面给出Translation的源代码。

算法的思想是先将所有区域填成白色,然后找平移后显示区域的左上角点(x0,y0)和右下角点(x1,y1)。

分几种情况。

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

1.tx≤-width

很显然,图象完全移出了屏幕,不用做任何处理

2.-width

图5.tx≤0,ty≤0的情况

容易看出,图象区域的x范围从0到width-|tx|,对应原图的范围从|tx|到width

3.0

图6.0

容易看出,图象区域的x范围从tx到width,对应原图的范围从0到width-tx

4.tx≥width

很显然,图象完全移出了屏幕,不用做任何处理

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

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

2.-height

3.0

4.ty≥height,图象完全移出了屏幕,不用做任何处理

这种做法利用了位图存储的连续性,即同一行的像素在内存中是相邻的。

利用memcpy函数,从(x0,y0)点开始,一次可以拷贝一整行(宽度为x1-x0),然后将内存指针移到(x0,y0+1)处,拷贝下一行,这样拷贝(y1-y0)行就完成了全部操作,避免了一个一个像素的计算,提高了效率。

Translation的源代码如下:

intxOffset=0,yOffset=0;

BOOLTranslation(HWNDhWnd)

{

DLGPROCdlgInputBox=NULL;

DWORDOffBits,BufSize;

LPBITMAPINFOHEADERlpImgData;

LPSTRlpPtr;

HLOCALhTempImgData;

LPBITMAPINFOHEADERlpTempImgData;

LPSTRlpTempPtr;

intSrcX0,SrcY0,SrcX1,SrcY1,DstX0,DstY0,DstX1,DstY1;

intRectWidth,RectHeight;

BOOLxVisible,yVisible;

HDChDc;

HFILEhf;

inti;

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

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

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

FreeProcInstance((FARPROC)dlgInputBox);

//OffBits为BITMAPINFOHEADER结构长度加调色板的大小

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

BufSize=bf.bfSize-sizeof(BITMAPFILEHEADER);//要开的缓冲区的大小

//为新产生的位图分配缓冲区内存if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

MessageBox(hWnd,"Errorallocmemory!

","ErrorMessage",MB_OK|

MB_ICONEXCLAMATION);

returnFALSE;//失败,返回

}

//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);

xVisible=TRUE;//xVisible为FALSE时,表示x方向已经移出了可显示的范围

if(xOffset<=-bi.biWidth)

xVisible=FALSE;

elseif(xOffset<=0){

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

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

}

elseif(xOffset

DstX0=xOffset;

DstX1=bi.biWidth;

}

else

xVisible=FALSE;

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

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

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

yVisible=TRUE;//yVisible为FALSE时,表示y方向已经移出了可显示的范围

if(yOffset<=-bi.biHeight)

yVisible=FALSE;

elseif(yOffset<=0){

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

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

}

elseif(yOffset

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

//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);

returnTRUE;

}

2.旋转(rotation)

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

图7.旋转前的图

图8.旋转后的图

可以看出,旋转后图象变大了。

另一种做法是不让图象变大,转出的部分被裁剪掉。

如图9所示:

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

我们采用第一种做法,首先给出变换矩阵。

先来看一下,在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,如图10所示,r为该点到原点的距离,在旋转过程中,r保持不变。

b为r与x轴之间的角度。

图10.旋转示意图

旋转前:

x0=r*cos(b);y0=r*sin(b)

旋转a角度后:

x1=r*cos(b-a)=r*cos(b)*cos(a)+r*sin(b)*sin(a)=x0*cos(a)+y0*sin(a);

y1=r*sin(b-a)=r*sin(b)*cos(a)-r*cos(b)*sin(a)=-x0*sin(a)+y0*cos(a);

以矩阵的形式表示

=

*

(5)

上面的公式中,坐标系I是以图象的中心为原点,向右为x轴正方向,向上为y轴正方向。

它和以图象左上角点,向右为x轴正方向,向下为y轴正方向的坐标系II之间的转换关系如何呢?

如图11所示

图11.两种坐标系间的转换关系

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

=

*

(6)

逆变换为

=

*

(7)

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

第一,将坐标系I变成II;

第二,将该点顺时针旋转a角;

第三,将坐标系II变回I,这样,我们就得到了变换矩阵,是上面三个矩阵的级联。

=

*

*

*

=

(8)

要注意的是,因为新图变大,所以上面公式中出现了Wold,Hold,Wnew,Hnew,表示原图和新图的宽高。

我们从图8中容易看出,Wnew=max(|x4-x1|,|x3-x2|);Hnew=max(|y4-y1|,|y3-y2|).

(8)的逆变换为

=

*

*

*

=

(9)

这样,对于新图中的每一点,我们就可以根据公式(9)求出对应的原图中的点,得到它的灰度,如果超出原图范围,则填成白色。

要注意的是,由于有浮点运算,计算出来点的坐标可能不是整数,采用取整处理,即找最接近的点,这样会带来一些误差(图象可能会出现锯齿),更精确的方法是采用插值,将在介绍图象缩放时讲到。

下面是源程序

#definePI3.1415926535

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

BOOLRotation(HWNDhWnd)

{

DLGPROCdlgInputBox=NULL;

DWORDOffBits,SrcBufSize,DstBufSize,DstLineBytes;

LPBITMAPINFOHEADERlpImgData;

LPSTRlpPtr;

HLOCALhTempImgData;

LPBITMAPINFOHEADERlpTempImgData;

LPSTRlpTempPtr;

floatSrcX1,SrcY1,SrcX2,SrcY2,SrcX3,SrcY3,SrcX4,SrcY4;

floatDstX1,DstY1,DstX2,DstY2,DstX3,DstY3,DstX4,DstY4;

DWORDWold,Hold,Wnew,Hnew;

HDChDc;

HFILEhf;

DWORDx0,y0,x1,y1;

floatcosa,sina;//cos(a),sin(a)

floatnum1,num2;

BITMAPFILEHEADERDstBf;

BITMAPINFOHEADERDstBi;

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

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);

//计算矩阵(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);

//OffBits为BITMAPINFOHEADER结构长度加调色板的大小

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

SrcBufSize=bf.bfSize-sizeof(BITMAPFILEHEADER);

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

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,"Errorallocmemory!

","ErrorMessage",MB_OK|

MB_ICONEXCLAMATION);

returnFALSE;//失败,返回

}

//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);

//得到新的BIT

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 文学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1