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<tx≤0:如圖2.5所示。容易看出,圖象區域的x範圍從0到width-|tx|,對應原圖的範圍從|tx|到width;
圖2.5 tx≤0,ty≤0的情況
(3) 0< tx<width:如圖2.6所示。容易看出,圖象區域的x範圍從tx到width,對應原圖的範圍從0到width - tx;
圖2.6 0< tx<width,0<ty<height的情況
(4) tx≥width:很顯然,圖象完全移出了螢幕,不用做任何處理。
y方向是對應的(height表示圖象的高度):
(1) ty≤-height,圖象完全移出了螢幕,不用做任何處理;
(2) -height<ty≤0,圖象區域的y範圍從0到height-|ty|,對應原圖的範圍從|ty|到height;
(3) 0<ty<height,圖象區域的y範圍從ty到height,對應原圖的範圍從0到height-ty;
(4) ty≥height,圖象完全移出了螢幕,不用做任何處理。
這種做法利用了位圖存儲的連續性,即同一行的象素在記憶體中是相鄰的。利用memcpy函數,從(x0,y0)點開始,一次可以拷貝一整行(寬度為x1-x0),然後将記憶體指針移到(x0,y0+1)處,拷貝下一行。這樣拷貝(y1-y0)行就完成了全部操作,避免了一個一個象素的計算,提高了效率。
CODE:(注:該程式需要一副bmp格式的灰階圖像,并放到工程目錄下,檔案名為nv1.bmp)
/**
* 程式名: Translation.cpp
* 功 能: 實作bmp格式灰階圖檔的平移,移出部分用白色填充
*/
#include <iostream>
#include <cstdio>
#include <fstream>
#include <cstring>
#include <windows.h>
using namespace std;
BITMAPFILEHEADER bmpFileHeader; //位圖檔案頭
BITMAPINFOHEADER bmpInfoHeader; //位圖資訊頭
RGBQUAD *pColorTable = new RGBQUAD[256]; //顔色表指針
unsigned char *pBmpData; //圖像資料指針
unsigned char *pBmpData1; //平移後圖像資料指針
unsigned char *pTemp,*pTemp1; //臨時指針
int width,height,imgSize; //圖像寬,高,實際大小,imgSize必須為4的倍數,bmp格式檔案結構規定
int srcX[2],srcY[2],dstX[2],dstY[2]; //平移前後位置
* 函數名: readBmp
* 參 數: bmpFileName--指向讀入bmp檔案的檔案名指針
* 功 能: 讀入一個bmp檔案,獲得相應資料
*/
bool readBmp(char *bmpFileName)
{
FILE *fp = fopen(bmpFileName,"rb"); //以二進制讀方式打開指定的圖像檔案
if(NULL == fp)
{
printf("%s is not exist!",bmpFileName);
return FALSE;
}
fread(&bmpFileHeader,sizeof(BITMAPFILEHEADER),1,fp); //讀取位圖頭資訊放入bmpFileHeader,注:指針也相應移動
fread(&bmpInfoHeader,sizeof(BITMAPINFOHEADER),1,fp); //讀取位圖資訊頭放入bmpInfoHeader
width = bmpInfoHeader.biWidth; //寬
height = bmpInfoHeader.biHeight; //高
fread(pColorTable,sizeof(RGBQUAD),256,fp); //讀取顔色表放入pColorTable
// int bytePerLine = (bmpInfoHeader.biWidth * bmpInfoHeader.biBitCount + 31) / 32 * 4;
pBmpData = new unsigned char [imgSize = bmpInfoHeader.biSizeImage];
pBmpData1 = new unsigned char [imgSize];
memset(pBmpData1,(BYTE)255,sizeof(char)*imgSize); //把新的圖像資訊用255(白色)填充,平移後沒有圖像的區域就是白色了
fread(pBmpData,sizeof(char),bmpInfoHeader.biSizeImage,fp); //讀取圖像資訊放入pBmpData
fclose(fp); //記住要關閉檔案
return TRUE;
}
* 函數名: translation
* 參 數: tx--平移的x距離,ty--平移的y距離
* 功 能: 實作平移,并把平移後圖像資訊寫入pBmpData1
void translation(int tx,int ty)
bool xVisible = TRUE,yVisible = TRUE;
//xVisible為FALSE時,表示x方向已經移出了可顯示的範圍
if(tx <= -width)
xVisible = FALSE;
else if(tx <= 0)
dstX[0] = 0; //表示移動後,有圖區域的左上角點的x坐标
dstX[1] = width + tx; //表示移動後,有圖區域的右下角點的x坐标
else if(tx < width)
dstX[0] = tx;
dstX[1] = width;
else
srcX[0] = dstX[0] - tx; //對應DstX0在原圖中的x坐标
srcX[1] = dstX[1] - tx; //對應DstX1在原圖中的x坐标
int rectWidth = srcX[1] - srcX[0]; //有圖區域的寬度
//y的和x類似,就不加注釋了
if(ty <= -height)
yVisible = FALSE;
else if(ty <= 0)
dstY[0] = 0;
dstY[1] = height + ty;
else if(ty < height)
dstY[0] = ty;
dstY[1] = height;
else
srcY[0] = dstY[0] - ty;
srcY[1] = dstY[1] - ty;
int rectHeight = srcY[1] - srcY[0];
int lineBytes = (width * bmpInfoHeader.biBitCount + 31) / 32 * 4; //每行所占的位元組數,必須為4的倍數
if(xVisible && yVisible)
for(int i = 0; i < rectHeight; i++ )
{
//pTemp指向要拷貝的那一行的最左邊的象素對應在原圖中的位
//置。特别要注意的是,由于.bmp是上下颠倒的,
pTemp = pBmpData + (height - 1 - (srcY[0] + i)) * lineBytes + srcX[0];
//pTemp1指向要拷貝的那一行的最左邊的象素對應在新圖中的位置。同樣要注意上面的問題。
pTemp1 = pBmpData1 + (height - 1 - (dstY[0] + i)) * lineBytes + dstX[0];
memcpy(pTemp1,pTemp,rectWidth); //從pTemp中複制大小為rectWidth的資料到pTemp1,這裡就是copy圖像的一行資料
}
* 函數名: writeBmp
* 功 能: 建立一個bmp檔案,把平移後的圖像資訊寫入,生成一個新的bmp
void writeBmp()
char writeBmpName[] = "new.bmp";
FILE *fp = fopen(writeBmpName,"wb"); //以二進制寫方式打開指定的圖像檔案
if(NULL == fp)
cout<<"file not exist!";
return ;
//寫入BMP檔案資料
fwrite(&bmpFileHeader,sizeof(BITMAPFILEHEADER),1,fp);
fwrite(&bmpInfoHeader,sizeof(BITMAPINFOHEADER),1,fp);
fwrite(pColorTable,sizeof(RGBQUAD),256,fp);
fwrite(pBmpData1,sizeof(char),imgSize,fp);
fclose(fp);
//釋放記憶體
delete []pColorTable;
delete []pBmpData1;
delete []pBmpData;
* 函數名: work
* 功 能: 處理
void work()
int x,y;
char readBmpName[] = "nv1.bmp";
if ( !readBmp(readBmpName) )
printf("Bmp file reads faliure");
printf("the distance of translation,cx,cy:"); //讀入平移的x和y
scanf("%d %d",&x,&y);
translation(x,y);
writeBmp();
int main()
work();
return 0;