圖像編程學習筆記3――圖像旋轉
以下文字內容copy于<<數字圖像處理編程入門>>,code為自己實現,是win32控制臺程序。
旋轉(rotation)有一個繞著什么轉的問題,通常的做法是以圖象的中心為圓心旋轉,舉個例子,圖2.7旋轉30度(順時針方向)后如圖2.8所示:
可以看出,旋轉后圖象變大了。另一種做法是不讓圖象變大,轉出的部分被裁剪掉。如圖2.9所示。
我們采用第一種做法,首先給出變換矩陣。在我們熟悉的坐標系中,將一個點順時針旋轉a角后的坐標變換公式,如圖2.10所示,r為該點到原點的距離,在旋轉過程中,r保持不變;b為r與x軸之間的夾角。
旋轉前: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)求出對應原圖中的點,得到它的灰度。如果超出原圖范圍,則填成白色。要注意的是,由于有浮點運算,計算出來點的坐標可能不是整數,采用取整處理,即找最接近的點,這樣會帶來一些誤差(圖象可能會出現鋸齒)。更精確的方法是采用插值,將在圖象縮放時介紹。
源程序如下:
[cpp] view plaincopy- /**
- * 程序名: Rotation.cpp
- * 功 能: 實現灰度圖像的旋轉,如果超出原圖范圍,則用白色填充
- * 測試位圖為test.bmp放到工程目錄下
- */
- #include <iostream>
- #include <fstream>
- #include <cstring>
- #include <cmath>
- #include <windows.h>
- using namespace std;
- #define PI 3.1415926535
- #define RADIAN(angle) (((angle)*PI)/180.0)
- BITMAPFILEHEADER bmpFileHeader; //bmp文件頭
- BITMAPINFOHEADER bmpInfoHeader; //bmp信息頭
- RGBQUAD *pColorTable; //bmp顏色表
- unsigned char *pBmpData; //bmp位圖數據
- unsigned char *pNewBmpData; //旋轉后bmp位圖數據
- int newImgSize; //旋轉后圖像大小
- /**
- * 函數名: readBmp
- * 參 數: fileName--要讀取文件的文件名
- * 功 能: 讀取bmp位圖數據,成功返回TRUE,否則返回FALSE
- */
- BOOL readBmp(char *fileName)
- {
- FILE *fp = fopen(fileName,"rb"); //以二進制讀方式打開
- if (NULL == fp)
- {
- cout<<"The file is opened failure!"<<endl;
- return FALSE;
- }
- //讀取信息到相應的變量中
- fread(&bmpFileHeader,sizeof(BITMAPFILEHEADER),1,fp);
- fread(&bmpInfoHeader,sizeof(BITMAPINFOHEADER),1,fp);
- pColorTable = new RGBQUAD[256];
- fread(pColorTable,sizeof(RGBQUAD),256,fp);
- int imgSize = bmpInfoHeader.biSizeImage; //文圖數據的大小,4的倍數
- pBmpData = new unsigned char[imgSize];
- fread(pBmpData,sizeof(unsigned char),imgSize,fp);
- fclose(fp);
- return TRUE;
- }
- /**
- * 函數名: rotation
- * 參 數: rotAngle--要轉換的角度
- * 功 能: 實現bmp圖像的旋轉
- */
- void rotation(int rotAngle)
- {
- double cosa,sina,srcX[4],srcY[4],dstX[4],dstY[4],rad;
- int oldWidth,oldHeight,newWidth,newHeight;
- rad = (double)RADIAN(rotAngle);
- cosa = cos(rad);
- sina = sin(rad);
- //原圖寬與高
- oldWidth = bmpInfoHeader.biWidth;
- oldHeight = bmpInfoHeader.biHeight;
- //原圖四個角的坐標
- srcX[0] = -0.5 * oldWidth;
- srcY[0] = 0.5 * oldHeight;
- srcX[1] = 0.5 * oldWidth;
- srcY[1] = 0.5 * oldHeight;
- srcX[2] = 0.5 * oldWidth;
- srcY[2] = -0.5 * oldHeight;
- srcX[3] = -0.5 * oldWidth;
- srcY[3] = -0.5 * oldHeight;
- //新圖四個角坐標
- for(int i = 0; i < 4; i++ )
- {
- dstX[i] = cosa * srcX[i] + sina * srcY[i];
- dstY[i] = -sina * srcX[i] + cosa * srcY[i];
- // cout<<dstY[i]<<endl;
- }
- //新圖的寬與高,向上取整
- bmpInfoHeader.biWidth = newWidth = (int)(max(fabs(dstX[0] - dstX[2]),fabs(dstX[1] - dstX[3])) + 0.5);
- bmpInfoHeader.biHeight = newHeight = (int)(max(fabs(dstY[0] - dstY[2]),fabs(dstY[1] - dstY[3])) + 0.5);
- // cout<<newWidth<<newHeight<<endl;
- //新圖位圖數據大小
- bmpInfoHeader.biSizeImage = newImgSize = newHeight * ((newWidth * bmpInfoHeader.biBitCount + 31) / 32 * 4);
- pNewBmpData = new unsigned char[newImgSize];
- double temp1,temp2; //計算矩陣(2.9)中的兩個常數,這樣不用以后每次都計算了
- temp1 = -0.5 * newWidth * cosa - 0.5 * newHeight * sina + 0.5 * oldWidth;
- temp2 = 0.5 * newWidth * sina - 0.5 * newHeight * cosa + 0.5 * oldHeight;
- memset(pNewBmpData,(BYTE)255,newImgSize); //先全部填充成白色
- int x0,y0,x1,y1;
- unsigned char *pOldTemp,*pNewTemp;
- int oldLineByte,newLineByte;
- oldLineByte = (oldWidth * bmpInfoHeader.biBitCount + 31) / 32 * 4;
- newLineByte = (newWidth * bmpInfoHeader.biBitCount + 31) / 32 * 4;
- //把旋轉后的圖像數據對應存儲到pNewBmpData相應位置
- for(y1 = 0; y1 < newHeight; y1++)
- {
- for(x1 = 0; x1 < newWidth; x1++ )
- {
- x0 = (int)(x1 * cosa + y1 * sina + temp1);
- y0 = (int)(-x1 * sina + y1 * cosa + temp2);
- if((x0 >= 0 && x0 < oldWidth) && (y0 >= 0 && y0 < oldHeight)) //這里不能為<=oldWidth或oldHeight
- {
- pOldTemp = pBmpData + (oldHeight - 1 - y0) * oldLineByte + x0;
- pNewTemp = pNewBmpData + (newHeight - 1 - y1) * newLineByte + x1;
- *pNewTemp = *pOldTemp;
- }
- }
- }
- }
- /**
- * 函數名: writeBmp
- * 參 數: bmpName -- 旋轉后的bmp文件名
- * 功 能: 新建一個bmp文件,把旋轉后的圖像信息存入其中
- */
- void writeBmp(char *bmpName)
- {
- FILE *fp = fopen(bmpName,"wb"); //以二進制寫方式打開
- if(NULL == fp)
- cout<<"The file is opened failure"<<endl;
- //寫入選裝后圖像信息
- fwrite(&bmpFileHeader,sizeof(BITMAPFILEHEADER),1,fp);
- fwrite(&bmpInfoHeader,sizeof(BITMAPINFOHEADER),1,fp);
- fwrite(pColorTable,sizeof(RGBQUAD),256,fp);
- fwrite(pNewBmpData,sizeof(unsigned char),newImgSize,fp);
- fclose(fp);
- delete []pColorTable;
- delete []pNewBmpData;
- delete []pBmpData;
- }
- /**
- * 函數名: work
- * 參 數: 無
- * 功 能: 實現處理工作
- */
- void work()
- {
- char readBmpName[] = "test.bmp";
- if ( !readBmp(readBmpName))
- cout<<"The file "<<readBmpName<<"is read failure"<<endl;
- cout<<"please input the angle to rotate(Clockwise):";
- int rotAngle;
- cin>>rotAngle;
- rotation(rotAngle);
- char writeBmpName[] = "test_new.bmp";
- writeBmp(writeBmpName);
- }
- int main()
- {
- work();
- return 0;
- }