數字影像處理(極簡) 第三章 BMP檔案的讀取與顯示(docx)

山上一縷煙發表於2020-11-20

建議先修課程:高等數學(微積分)、線性代數。
參考書目:
1、影像工程(上冊)——影像處理(第4版) 章毓晉 清華大學出版社


連結:https://pan.baidu.com/s/1hEMGRUotQFL_RtGap6JaUg
提取碼:0000

三 BMP檔案的讀取與顯示

BMP檔案是一個非常簡單的影像儲存格式。學習處理影像時,我們先從最簡單的格式入手。處理其它複雜的格式時,往往採用先解壓縮再進行後續處理的方法。解壓縮後的資料往往與BMP等非壓縮格式(通常是,壓縮的BMP很少見)相仿。
BMP檔案主要應用於Windows平臺。因此,繼續下面的學習之前,你應當具備一些Windows程式設計的知識。

BMP檔案的結構分為四部分:
【1】檔案頭。
【2】資訊頭。
【3】調色盤(可選)。
【4】影像資料。

檔案頭的格式定義在<wingdi.h>中:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
其中
<minwindef.h>:
#define far
#define FAR far
typedef unsigned long DWORD;
typedef unsigned short WORD;
這個結構的長度固定為14位元組。各個成員的解釋如下:

bfType
指定檔案型別,其值為42 4D,即BM。也就是說所有.bmp檔案的頭兩個位元組都是“BM”。

bfSize
指定檔案大小(包括檔案頭)。該值為DWORD值,即雙字(4位元組),因此BMP檔案的大小最大為4 GB(232位元組)。

bfReserved1,bfReserved2
保留字,這裡不考慮。

bfOffBits
為從檔案頭到實際的點陣圖資料的偏移位元組數。

資訊頭的格式也定義在<wingdi.h>中:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
其中
<winnt.h>:
typedef long LONG;
這個結構的長度固定為40位元組。各個成員的解釋如下:

biSize
值為常量40,指定此結構的長度。

biWidth,biHeight
指定影像的寬和高(單位:畫素)。

biPlanes
值固定為1,這裡不考慮。

biBitCount
指定表示顏色時要用到的位數,取值可以為:1(黑白二色圖)、4(16色圖)、8(256色)、24(真彩色圖)。

biCompression
指定點陣圖是否壓縮。Windows點陣圖可以採用壓縮格式,但用得不多。我們只討論不壓縮的情況,biCompression為BI_RGB。
<wingdi.h>:
#define BI_RGB 0L
#define BI_RLE8 1L
#define BI_RLE4 2L
#define BI_BITFIELDS 3L
#define BI_JPEG 4L
#define BI_PNG 5L

biSizeImage
在影像為壓縮格式時,刻畫點陣圖資料的長度。影像未壓縮時,此值為零。

biXPelsPerMeter,biYPelsPerMeter
指定目標裝置的水平和垂直解析度。單位:每米的畫素個數。

biClrUsed
指定本影像實際用到的顏色數(決定調色盤陣列元素的個數),如果該值為零,則用到的顏色數為2的biBitCount次方。
一個規範的BMP檔案應當只有在真彩色時才將該值置為零。

biClrImportant
指定本影像中重要的顏色數量。該值通常為零,即認為所有的顏色都是重要的。

當影像不是真彩色時,在資訊頭之後還有調色盤。調色盤實際上是一個陣列,共有biClrUsed個元素(如果該值為零,則有2的biBitCount次方個元素)。陣列中每個元素的是一個RGBQUAD結構,佔4個位元組,其定義位於<wingdi.h>:
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
一個標準的256灰度影像,其調色盤的第0到255項的前三項rgbBlue、rgbGreen、rgbRed均分別為0到255。後續的點陣圖資料中,每1個位元組代表1個畫素,索引值正好就是灰度值。

對於用到調色盤的點陣圖,影像資料就是該畫素顏色在調色盤中的索引值;對於真彩色圖,影像資料就是實際的RGB值。下面就2色、16色、256色點陣圖和真彩色點陣圖分別介紹。
2色點陣圖,用1位就可以表示該畫素的顏色(一般0表示黑,1表示白),一個位元組可以表示8個畫素。
16色點陣圖,用4位可以表示一個畫素的顏色,所以一個位元組可以表示2個畫素。
256色點陣圖,一個位元組表示1個畫素。
真彩色圖,三個位元組表示1個畫素。

但是每一行的位元組數並不簡單等於寬度×單個畫素佔用的空間。BMP格式要求:每一行的位元組數必須是4的整數倍。如果不是,則需要在一行的點陣圖資料後補齊。

BMP檔案的資料是從下到上,從左到右的。也就是說,從檔案中最先讀到的是影像最下面一行的左邊第一個畫素,然後是左邊第二個畫素……接下來是倒數第二行左邊第一個畫素,左邊第二個畫素……依次類推,最後得到的是最上面一行的最右一個畫素。

在應用程式的GUI中顯示點陣圖,需要用到<wingdi.h>中的Windows API函式StretchDIBits:
StretchDIBits函式將DIB,JPEG或PNG影像中畫素矩形的顏色資料複製到指定的目標矩形。如果目標矩形大於源矩形,則此函式會拉伸顏色資料的行和列以適合目標矩形。如果目標矩形小於源矩形,則此函式使用指定的柵格操作壓縮行和列。
其語法如下:
int StretchDIBits(
HDC hdc,
int xDest,
int yDest,
int DestWidth,
int DestHeight,
int xSrc,
int ySrc,
int SrcWidth,
int SrcHeight,
const VOID * lpBits,
const BITMAPINFO* lpbmi,
UINT iUsage,
DWORD rop
);
其中
<winnt.h>:
#define VOID void
<minwindef.h>:
typedef unsigned int UINT;

每個引數的解釋如下:

hdc
目標裝置上下文的控制程式碼。
裝置上下文也稱裝置描述表,是一種Windows資料結構,其中包含有關裝置(例如顯示器或印表機)的繪圖屬性的資訊。 所有繪圖呼叫都通過裝置上下文物件進行,該物件封裝用於繪製線條、形狀和文字的Windows API。裝置上下文允許Windows中與裝置無關的繪圖。裝置上下文可用於繪製到螢幕、印表機或元檔案(metafile)。

xDest
目標矩形左上角的x座標(以邏輯單位表示)。

yDest
目標矩形左上角的y座標(以邏輯單位表示)。

DestWidth
目標矩形的寬度,以邏輯單位為單位。

DestHeight
目標矩形的高度,以邏輯單位為單位。

xSrc
影像中源矩形的x座標(以畫素為單位)。

ySrc
影像中源矩形的y座標(以畫素為單位)。

SrcWidth
影像中源矩形的寬度(以畫素為單位)。

SrcHeight
影像中源矩形的高度(以畫素為單位)。

lpBits
指向影像位的指標,影像位儲存為位元組陣列。

lpbmi
指向包含有關DIB資訊的BITMAPINFO結構的指標。

iUsage
指定是否提供了BITMAPINFO結構的bmiColors成員。如果提供,則指定bmiColors是否包含顯式的紅色,綠色,藍色(RGB)值或索引。iUsage引數必須是以下值之一:
DIB_PAL_COLORS
該陣列包含進入源裝置上下文的邏輯調色盤的16位索引。
DIB_RGB_COLORS
顏色表包含文字RGB值。

rop
光柵操作程式碼,指定如何將源畫素,目標裝置上下文的當前畫筆和目標畫素組合在一起以形成新影像。

以MFC(提示:MFC已經被市場淘汰,這裡只做演示用。除非維護老專案,否則不應選用MFC。)為例,在檢視類(負責前臺響應)的OnDraw函式中,使用StretchDIBits顯示點陣圖,則每次視窗被重繪時就會顯示指定的點陣圖:
void CTestView::OnDraw(CDC* pDC) {
CTESTDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc) return;

// TODO: add draw code for native data here
if (nullptr == lpBitsInfo) return;
LPVOID lpBits = (LPVOID)&lpBitsInfo->bmiColors[lpBitsInfo->bmiHeader.biClrUsed];
StretchDIBits(pDC->GetSafeHdc(),
	0, 0, lpBitsInfo->bmiHeader.biWidth, lpBitsInfo->bmiHeader.biHeight,
	0, 0, lpBitsInfo->bmiHeader.biWidth, lpBitsInfo->bmiHeader.biHeight,
	lpBits, lpBitsInfo, DIB_RGB_COLORS, SRCCOPY);

}
其中
<minwindef.h>:
typedef void far LPVOID;
<wingdi.h>:
#define DIB_RGB_COLORS 0 /
color table in RGBs /
#define DIB_PAL_COLORS 1 /
color table in palette indices /
#define SRCCOPY (DWORD)0x00CC0020 /
dest = source */

此程式碼中,原影像與目標顯示區域是按照1:1的比例來顯示的,無放縮。

lpbmi引數這裡為lpBitsInfo,它是一個BITMAPINFO結構的指標,此結構定義於<wingdi.h>:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;
第一個成員bmiHeader便是BMP檔案的資訊頭;第二個成員的長度通常遠遠大於1,包含調色盤(如果有的話)和點陣圖資料。

lpBits引數這裡為lpBits,是一個void型指標。這裡指向調色盤(如果有)之後的點陣圖資料的首個位元組。

處理影像內容時要注意:如要訪問畫素點<i,j>的資料,則其在記憶體中的位置應當為:
S+L_LINE (h-1-i)+j
其中S為點陣圖資料實際的開始位置,即上文的lpBits;L_LINE為一行的位元組數:
L_LINE=(wb+31)/32×4
w為影像的寬度,b為點陣圖的位數(1、4、8、24)。
可見,L_LINE是對齊之後的長度。wb是一整行畫素佔用的位數。而當32 | wb時,上式也可寫為
L_LINE=(wb+31)/32×4=wb/32×4=8wb
此時一行的長度已經按4位元組對齊。多出來的31會被整除運算截斷。容易看出:已對齊的情況下,一旦一行的資料再多出1位,那麼就要多佔用4個位元組。

如果需要遍歷一個BMP影像的每一個畫素點<i,j>,那麼迴圈應當這樣寫:
for (LONG i = 0; i < h; ++i)
for (LONG j = 0; j < w; ++j) {

}
i對應行編號,j對應列編號。
注意:一幅w×h的影像,其水平畫素(列數)和垂直畫素(行數)的取值範圍分別是[0, w-1]和[0, h-1]之間的整數。

相關文章