天天看點

圖像程式設計學習筆記2——bmp位圖平移

2.1 平移

平移(translation)變換大概是幾何變換中最簡單的一種了。

如圖2.1所示,初始坐标為(x0,y0)的點經過平移(tx,ty)(以向右,向下為正方向)後,坐标變為(x1,y1)。這兩點之間的關系是x1=x0+tx,y1=y0+ty。

圖像程式設計學習筆記2——bmp位圖平移

圖2.1    平移的示意圖

以矩陣的形式表示為

圖像程式設計學習筆記2——bmp位圖平移

(2.1)

我們更關心的是它的逆變換:

圖像程式設計學習筆記2——bmp位圖平移

(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——bmp位圖平移

圖2.2     移動前的圖

圖像程式設計學習筆記2——bmp位圖平移

圖2.3     移動後的圖

還有一種做法是:将圖象放大,使得能夠顯示下所有部分,如圖2.4所示。

圖像程式設計學習筆記2——bmp位圖平移

圖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——bmp位圖平移

(2.3)

圖像程式設計學習筆記2——bmp位圖平移

(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——bmp位圖平移

圖2.5     tx≤0,ty≤0的情況

(3)    0< tx<width:如圖2.6所示。容易看出,圖象區域的x範圍從tx到width,對應原圖的範圍從0到width - tx;

圖像程式設計學習筆記2——bmp位圖平移

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

繼續閱讀