图象的几何变换.docx
- 文档编号:25847007
- 上传时间:2023-06-16
- 格式:DOCX
- 页数:33
- 大小:325.85KB
图象的几何变换.docx
《图象的几何变换.docx》由会员分享,可在线阅读,更多相关《图象的几何变换.docx(33页珍藏版)》请在冰豆网上搜索。
图象的几何变换
第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,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的量化级别少一些,就可以实现图象信息的压缩。
这一点将在第9章介绍图象压缩时仔细研究,这里就不深入讨论了。
而RGB的表示方法就做不到这一点,因为RGB三个分量同等重要,缺了谁也不行。
YUV和RGB之间有着如下的对应关系
(2.3)
(2.4)
当RGB三个分量的大小一样时,假设都是a,代入公式(2.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 如图2.5所示。 容易看出,图象区域的x范围从0到width-|tx|,对应原图的范围从|tx|到width; 图2.5 tx≤0,ty≤0的情况 (3) 0 如图2.6所示。 容易看出,图象区域的x范围从tx到width,对应原图的范围从0到width-tx; 图2.6 0 (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的源代码如下: int xOffset=0,yOffset=0; BOOLTranslation(HWNDhWnd) { 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); //OffBits为BITMAPINFOHEADER结构长度加调色板的大小 OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); BufSize=OffBits+bi.biHeight*LineBytes;//要开的缓冲区的大小 //为新产生的位图分配缓冲区内存 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为FALSE时,表示x方向已经移出了可显示的范围 xVisible=TRUE; 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为FALSE时,表示y方向已经移出了可显示的范围 yVisible=TRUE; 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.2旋转 旋转(rotation)有一个绕着什么转的问题,通常的做法是以图象的中心为圆心旋转,举个例子,图2.7旋转30度(顺时针方向)后如图2.8所示: 图2.7 旋转前的图 图2.8 旋转后的图 可以看出,旋转后图象变大了。 另一种做法是不让图象变大,转出的部分被裁剪掉。 如图2.9所示。 我们采用第一种做法,首先给出变换矩阵。 在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,如图2.10所示,r为该点到原点的距离,在旋转过程中,r保持不变;b为r与x轴之间的夹角。 图2.9旋转后保持原图大小, 转出的部分被裁掉 图2.10 旋转示意图 旋转前: x0=rcosb;y0=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) 要注意的是,因为新图变大,所以上面公式中出现了wold,hold,wnew,hnew,它们分别表示原图(old)和新图(new)的宽、高。 我们从图2.8中容易看出: wnew=max(|x4-x1|,|x3-x2|);hnew=max(|y4-y1|,|y3-y2|)。 (2.8)的逆变换为 (2.9) 这样,对于新图中的每一点,我们就可以根据公式(2.9)求出对应原图中的点,得到它的灰度。 如果超出原图范围,则填成白色。 要注意的是,由于有浮点运算,计算出来点的坐标可能不是整数,采用取整处理,即找最接近的点,这样会带来一些误差(图象可能会出现锯齿)。 更精确的方法是采用插值,将在图象缩放时介绍。 源程序如下: #definePI3.1415926535 #defineRADIAN(angle)((angle)*PI/180.0)//角度到弧度转化的宏 BOOLRotation(HWNDhWnd) { 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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 图象 几何 变换