VC++圖象處理編程

VC++圖象處理編程(一)

 

基本概念

前言

數字圖像處理技術與理論是計算機應用的一個重要領域,許多工程應用都涉及到圖像處理,一直有一個強烈的願望,想系統的寫一個關於數字圖像處理的講座,由於工作學習很忙,時至今日才得以實現。

  “圖”是物體透射光或反射光的分佈,“像”是人的視覺系統對圖的接收在大腦中形成的印象或認識。圖像是兩者的結合。人類獲取外界信息是靠聽覺、視覺、觸覺、嗅覺、味覺等,但絕大部分(約80%左右)來自視覺所接收的圖像信息。圖像處理就是對圖像信息進行加工處理,以滿足人的視覺心理和實際應用的需要。簡單的說,依靠計算機對圖像進行各種目的的處理我們就稱之爲數字圖像處理。早期的數字圖像處理的目的是以人爲對象,爲了滿足人的視覺效果而改善圖像的質量,處理過程中輸入的是質量差的圖像,輸出的是質量好的圖像,常用的圖像處理方法有圖像增強、復原等。隨着計算機技術的發展,有一類圖像處理是以機器爲對象,處理的目的是使機器能夠自動識別目標,這稱之爲圖像的識別,因爲這其中要牽涉到一些複雜的模式識別的理論,所以我們後續的講座只討論其中最基本的內容。由於在許多實際應用的編程中往往都要涉及到數字圖像處理,涉及到其中的一些算法,這也是許多編程愛好者感興趣的一個內容,我們這個講座就是討論如何利用微軟的Visual C++開發工具來實現一些常用的數字圖像處理算法,論述了圖像處理的理論,同時給出了VC實現的源代碼。本講座主要的內容分爲基礎篇、中級篇和高級篇,具體包含的主要內容有:

1. 圖像文件的格式;

2. 圖像編程的基礎-操作調色板;

3. 圖像數據的讀取、存儲和顯示、如何獲取圖像的尺寸等;

4. 利用圖像來美化界面;

5. 圖像的基本操作:圖像移動、圖像旋轉、圖像鏡像、圖像的縮放、圖像的剪切板操作;

6. 圖像顯示的各種特技效果;

7. 圖像的基本處理:圖像的二值化、圖像的亮度和對比度的調整、圖像的邊緣增強、如何得到圖像的直方圖、圖像直方圖的修正、圖像的平滑、圖像的銳化等、圖像的僞彩色、彩色圖像轉換爲黑白圖像、物體邊緣的搜索等等;

8. 二值圖像的處理:腐蝕、膨脹、細化、距離變換等;

9. 圖像分析:直線、圓、特定物體的識別;

10.JEPG、GIF、PCX等格式文件相關操作;

11.圖像文件格式的轉換;

12.圖像的常用變換:付利葉變換、DCT變換、沃爾什變換等;

13.AVI視頻流的操作;

圖像處理技術博大精深,不僅需要有很強的數學功底,還需要熟練掌握一門計算機語言,在當前流行的語言中,我個人覺的Visual C++這個開發平臺是圖像開發人員的首選工具。本講座只是起到拋磚引玉的作用,希望和廣大讀者共同交流。

圖象的文件格式

一.圖象的文件格式

  要利用計算機對數字化圖像進行處理,首先要對圖像的文件格式要有清楚的認識,因爲我們前面說過,自然界的圖像以模擬信號的形式存在,在用計算機進行處理以前,首先要數字化,比如攝像頭(CCD)攝取的信號在送往計算機處理前,一般情況下要經過數模轉換,這個任務常常由圖像採集卡完成,它的輸出一般爲裸圖的形式;如果用戶想要生成目標圖像文件,必須根據文件的格式做相應的處理。隨着科技的發展,數碼像機、數碼攝像機已經進入尋常百姓家,我們可以利用這些設備作爲圖像處理系統的輸入設備來爲後續的圖像處理提供信息源。無論是什麼設備,它總是提供按一定的圖像文件格式來提供信息,比較常用的有BMP格式、JPEG格式、GIF格式等等,所以我們在進行圖像處理以前,首先要對圖像的格式要有清晰的認識,只有在此基礎上纔可以進行進一步的開發處理。

  在講述圖像文件格式前,先對圖像作一個簡單的分類。除了最簡單的圖像外,所有的圖像都有顏色,而單色圖像則是帶有顏色的圖像中比較簡單的格式,它一般由黑色區域和白色區域組成,可以用一個比特表示一個像素,“1”表示黑色,“0”表示白色,當然也可以倒過來表示,這種圖像稱之爲二值圖像。我們也可以用8個比特(一個字節)表示一個像素,相當於把黑和白等分爲256個級別,“0”表示爲黑,“255”表示爲白,該字節的數值表示相應像素值的灰度值或亮度值,數值越接近“0”,對應像素點越黑,相反,則對應像素點越白,此種圖像我們一般稱之爲灰度圖像。單色圖像和灰度圖像又統稱爲黑白圖像,與之對應存在着彩色圖像,這種圖像要複雜一些,表示圖像時,常用的圖像彩色模式有RGB模式、CMYK模式和HIS模式,一般情況下我們只使用RGB模式,R對應紅色,G對應綠色,B對應藍色,它們統稱爲三基色,這三中色彩的不同搭配,就可以搭配成各種現實中的色彩,此時彩色圖像的每一個像素都需要3個樣本組成的一組數據表示,其中每個樣本用於表示該像素的一個基本顏色。

  對於現存的所有的圖像文件格式,我們在這裏主要介紹BMP圖像文件格式,並且文件裏的圖像數據是未壓縮的,因爲圖像的數字化處理主要是對圖像中的各個像素進行相應的處理,而未壓縮的BMP圖像中的像素數值正好與實際要處理的數字圖像相對應,這種格式的文件最合適我們對之進行數字化處理。請讀者記住,壓縮過的圖像是無法直接進行數字化處理的,如JPEG、GIF等格式的文件,此時首先要對圖像文件解壓縮,這就要涉及到一些比較複雜的壓縮算法。後續章節中我們將針對特殊的文件格式如何轉換爲BMP格式的文件問題作專門的論述,經過轉換,我們就可以利用得到的未壓縮的BMP文件格式進行後續處理。對於JPEG、GIF等格式,由於涉及到壓縮算法,這要求讀者掌握一定的信息論方面的知識,如果展開的話,可以寫一本書,限於篇幅原因,我們只作一般性的講解,有興趣的朋友可以參考相關書籍資料。

二.BMP文件結構

1 BMP文件的組成

  BMP文件由文件頭、位圖信息頭、顏色信息和圖形數據四部分組成。文件頭主要包含文件的大小、文件類型、圖像數據偏離文件頭的長度等信息;位圖信息頭包含圖象的尺寸信息、圖像用幾個比特數值來表示一個像素、圖像是否壓縮、圖像所用的顏色數等信息。顏色信息包含圖像所用到的顏色表,顯示圖像時需用到這個顏色表來生成調色板,但如果圖像爲真彩色,既圖像的每個像素用24個比特來表示,文件中就沒有這一塊信息,也就不需要操作調色板。文件中的數據塊表示圖像的相應的像素值,需要注意的是:圖像的像素值在文件中的存放順序爲從左到右,從下到上,也就是說,在BMP文件中首先存放的是圖像的最後一行像素,最後才存儲圖像的第一行像素,但對與同一行的像素,則是按照先左邊後右邊的的順序存儲的;另外一個需要讀者朋友關注的細節是:文件存儲圖像的每一行像素值時,如果存儲該行像素值所佔的字節數爲4的倍數,則正常存儲,否則,需要在後端補0,湊足4的倍數。

2. BMP文件頭

BMP文件頭數據結構含有BMP文件的類型、文件大小和位圖起始位置等信息。其結構定義如下:

typedef struct tagBITMAPFILEHEADER

{

WORD bfType; // 位圖文件的類型,必須爲“BMP”

DWORD bfSize; // 位圖文件的大小,以字節爲單位

WORD bfReserved1; // 位圖文件保留字,必須爲0

WORD bfReserved2; // 位圖文件保留字,必須爲0

DWORD bfOffBits; // 位圖數據的起始位置,以相對於位圖文件頭的偏移量表示,以字節爲單位

} BITMAPFILEHEADER;該結構佔據14個字節。

3. 位圖信息頭

BMP位圖信息頭數據用於說明位圖的尺寸等信息。其結構如下:

typedef struct tagBITMAPINFOHEADER{

DWORD biSize; // 本結構所佔用字節數

LONG biWidth; // 位圖的寬度,以像素爲單位

LONG biHeight; // 位圖的高度,以像素爲單位

WORD biPlanes; // 目標設備的平面數不清,必須爲1

WORD biBitCount// 每個像素所需的位數,必須是1(雙色), 4(16色),8(256色)或24(真彩色)之一

DWORD biCompression; // 位圖壓縮類型,必須是 0(不壓縮),1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一

DWORD biSizeImage; // 位圖的大小,以字節爲單位

LONG biXPelsPerMeter; // 位圖水平分辨率,每米像素數

LONG biYPelsPerMeter; // 位圖垂直分辨率,每米像素數

DWORD biClrUsed;// 位圖實際使用的顏色表中的顏色數

DWORD biClrImportant;// 位圖顯示過程中重要的顏色數

} BITMAPINFOHEADER;該結構佔據40個字節。

注意:對於BMP文件格式,在處理單色圖像和真彩色圖像的時候,無論圖象數據多麼龐大,都不對圖象數據進行任何壓縮處理,一般情況下,如果位圖採用壓縮格式,那麼16色圖像採用RLE4壓縮算法,256色圖像採用RLE8壓縮算法。

4. 顏色表

  顏色表用於說明位圖中的顏色,它有若干個表項,每一個表項是一個RGBQUAD類型的結構,定義一種顏色。RGBQUAD結構的定義如下:

typedef struct tagRGBQUAD {

BYTErgbBlue;// 藍色的亮度(值範圍爲0-255)

BYTErgbGreen; // 綠色的亮度(值範圍爲0-255)

BYTErgbRed; // 紅色的亮度(值範圍爲0-255)

BYTErgbReserved;// 保留,必須爲0

} RGBQUAD;

  顏色表中RGBQUAD結構數據的個數由BITMAPINFOHEADER 中的biBitCount項來確定,當biBitCount=1,4,8時,分別有2,16,256個顏色表項,當biBitCount=24時,圖像爲真彩色,圖像中每個像素的顏色用三個字節表示,分別對應R、G、B值,圖像文件沒有顏色表項。位圖信息頭和顏色表組成位圖信息,BITMAPINFO結構定義如下:

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader; // 位圖信息頭

RGBQUAD bmiColors[1]; // 顏色表

} BITMAPINFO;

  注意:RGBQUAD數據結構中,增加了一個保留字段rgbReserved,它不代表任何顏色,必須取固定的值爲“0”,同時,RGBQUAD結構中定義的顏色值中,紅色、綠色和藍色的排列順序與一般真彩色圖像文件的顏色數據排列順序恰好相反,既:若某個位圖中的一個像素點的顏色的描述爲“00,00,ff,00”,則表示該點爲紅色,而不是藍色。

5. 位圖數據

  位圖數據記錄了位圖的每一個像素值或該對應像素的顏色表的索引值,圖像記錄順序是在掃描行內是從左到右,掃描行之間是從下到上。這種格式我們又稱爲Bottom_Up位圖,當然與之相對的還有Up_Down形式的位圖,它的記錄順序是從上到下的,對於這種形式的位圖,也不存在壓縮形式。位圖的一個像素值所佔的字節數:當biBitCount=1時,8個像素佔1個字節;當biBitCount=4時,2個像素佔1個字節;當biBitCount=8時,1個像素佔1個字節;當biBitCount=24時,1個像素佔3個字節,此時圖像爲真彩色圖像。當圖像不是爲真彩色時,圖像文件中包含顏色表,位圖的數據表示對應像素點在顏色表中相應的索引值,當爲真彩色時,每一個像素用三個字節表示圖像相應像素點彩色值,每個字節分別對應R、G、B分量的值,這時候圖像文件中沒有顏色表。上面我已經講過了,Windows規定圖像文件中一個掃描行所佔的字節數必須是4的倍數(即以字爲單位),不足的以0填充,圖像文件中

一個掃描行所佔的字節數計算方法:

DataSizePerLine= (biWidth* biBitCount+31)/8;// 一個掃描行所佔的字節數

位圖數據的大小按下式計算(不壓縮情況下):

DataSize= DataSizePerLine* biHeight。

上述是BMP文件格式的說明,搞清楚了以上的結構,就可以正確的操作圖像文件,對它進行讀或寫操作了。

三.GIF圖象文件格式

  GIF圖象格式的全稱爲Graphics Interchange Format,從這個名字可以看出,這種圖像格式主要是爲了通過網絡傳輸圖像而設計的。GIF文件不支持24位真彩色圖像,最多隻能存儲256色的圖像或灰度圖像;GIF格式文件也無法存儲CMY和HIS模型的圖像數據;另外,GIF圖像文件的各種數據區域一般沒有固定的數據長度和存儲順序,所以爲了方便程序尋找數據區,將數據區中的第一個字節作爲標誌符;最後需要讀者注意的是GIF文件存儲圖像數據是有二種排列順序:順序排列或交叉排列。交叉排列的方式適合網絡傳輸,這樣一來允許用戶在不完全掌握圖像數據之前,獲取當前圖像的輪廓數據。

  GIF文件格式分爲87和89兩個版本,對於87這個版本,該文件主要是有五個部分組成,它,們是按順序出現的:文件頭塊、邏輯屏幕描述塊、可選擇的調色板塊、圖像數據塊、最後是標誌文件結束的尾塊,該塊總是取固定的值3BH。其中第一和第二兩個塊用GIF圖像文件頭結構描述:

GIFHEADER:{

DB Signature; //該字段佔六個字節,爲了用於指明圖像爲GIF格式,前三個字符必須爲“GIF”,後三字符用於指定是哪個版本,87或89。

DW ScreenWidth;//

DW ScreenDepth;//佔兩個字節,以像素爲單位表示圖像的寬、高

DB GlobalFlagByte;//該字節的各個位用於調色版的描述

DB BackGroundColor;//代表圖象的背景顏色的索引

DB AspectRatio;圖像的長寬比

}

  GIF格式中的調色板有通用調色板和局部調色板之分,因爲GIF格式允許一個文件中存儲多個圖像,因此有這兩種調色板,其中通用調色板適於文件中的所有圖像,而局部調色板只適用於某一個圖像。格式中的數據區域一般分爲四個部分,圖像數據識別區域,局部調色板數據,採用壓縮算法得到的圖象數據區域和結束標誌區域。

  在GIF89版本中,它包含七個部分,分別是文件頭、通用調色板數據、圖像數據區和四個補充數據區,它們主要是用於提示程序如何處理圖像的.

四.JEPG圖像文件

  JEPG簡稱爲聯合攝影專家小組,作爲一種技術,主要用於數字化圖像的標準編碼,JPEG主要採用有損的壓縮編碼方式,它比GIF、BMP圖像文件要複雜的多,這不是短短的幾頁篇幅可以將清楚的,萬幸的是,我們可以通過一些別的方法將該格式轉化爲BMP格式。讀者需要知道的是在對JEPG文件格式編碼時,通常需要分爲以下四步:顏色轉化、DCT變換、量化、編碼。

  以上介紹了一些常用的圖像文件,對比較複雜的格式,如GIF和JEPG,僅僅作了極其浮淺的介紹,後文我們會和它們作進一步的接觸。實際應用中,還有許多圖像格式,文章中都沒有提到,讀者如果需要做進一步的研究,還需要參考一些關於圖像格式方面的資料。

BMP圖像的基本操作

  上一講我們主要介紹了圖像的格式,其中重點說明了BMP文件的存儲格式,同時對JEPG和GIF等常用格式作了簡單的介紹。本節主要講述如何操作BMP文件,如對其讀、寫和顯示等。

  在實現數字圖象處理的過程中,主要是通過對圖像中的每一個像素點運用各種圖像處理算法來達到預期的效果,所以進行圖像處理的第一步,也是我們最關心的問題,是如何得到圖像中每一個像素點的亮度值;爲了觀察和驗證處理的圖像效果,另一個需要解決的問題是如何將處理前後的圖像正確的顯示出來。我們這章內容就是解決這些問題。

  隨着科技的發展,圖像處理技術已經滲透到人類生活的各個領域並得到越來越多的應用,但是突出的一個矛盾是圖像的格式也是越來越多,目前圖像處理所涉及的主要的圖像格式就有很多種,如TIF、JEMP、BMP等等,一般情況下,爲了處理簡單方便,進行數字圖像處理所採用的都是BMP格式的圖像文件(有時也稱爲DIB格式的圖像文件),並且這種格式的文件是沒有壓縮的。我們通過操作這種格式的文件,可以獲取正確顯示圖像所需的調色板信息,圖像的尺寸信息,圖像中各個像素點的亮度信息等等,有了這些數據,開發人員就可以對圖像施加各種處理算法,進行相應的處理。如果特殊情況下需要處理其它某種格式的圖像,如GIF、JEMP等格式的圖象文件,可以首先將該格式轉換爲BMP格式,然後再進行相應的處理。這一點需要讀者清楚。

  BMP格式的圖像文件又可以分爲許多種類,如真彩色位圖、256色位圖,採用RLE(遊程編碼)壓縮格式的BMP位圖等等。由於在實際的工程應用和圖像算法效果驗證中經常要處理的是256色並且是沒有壓縮的BMP灰度圖像,例如通過黑白採集卡採集得到的圖像就是這種格式,所以我們在整個講座中範例所處理的文件格式都是BMP灰度圖像。如果讀者對這種格式的位圖能夠作到熟練的操作,那麼對於其餘形式的BMP位圖的操作也不會很困難。

  BMP灰度圖像作爲Windows環境下主要的圖像格式之一,以其格式簡單,適應性強而倍受歡迎。正如我們在上一講中介紹過的那樣,這種文件格式就是每一個像素用8bit表示,顯示出來的圖像是黑白效果,最黑的像素的灰度(也叫作亮度)值爲“0”,最白的像素的灰度值爲“255”,整個圖像各個像素的灰度值隨機的分佈在“0”到“255”的區間中,越黑的像素,其灰度值越接近於“0”,越白(既越亮)的像素,其灰度值越接近於“255”;與此對應的是在該文件類型中的顏色表項的各個RGB分量值是相等的,並且顏色表項的數目是256個。

  在進行圖像處理時,操作圖像中的像素值就要得到圖像陣列;經過處理後的圖像的像素值需要存儲起來;顯示圖像時要正確實現調色板、得到位圖的尺寸信息等。結合這些問題,下面我們針對性的給出了操作灰度BMP圖像時的部分函數實現代碼及註釋。

一.BMP位圖操作

回顧:BMP位圖包括位圖文件頭結構BITMAPFILEHEADER、位圖信息頭結構BITMAPINFOHEADER、位圖顏色表RGBQUAD和位圖像素數據四部分。處理位圖時要根據文件的這些結構得到位圖文件大小、位圖的寬、高、實現調色板、得到位圖像素值等等。這裏要注意的一點是在BMP位圖中,位圖的每行像素值要填充到一個四字節邊界,即位圖每行所佔的存儲長度爲四字節的倍數,不足時將多餘位用0填充。

  有了上述知識,可以開始編寫圖像處理的程序了,關於在VC的開發平臺上如何開發程序的問題這裏不再贅述,筆者假定讀者都具有一定的VC開發經驗。在開發該圖像處理程序的過程中,筆者沒有采用面向對象的方法,雖然面向對象的方法可以將數據封裝起來,保護類中的數據不受外界的干擾,提高數據的安全性,但是這種安全性是以降低程序的執行效率爲代價的,爲此,我們充分利用了程序的文檔視圖結構,在程序中直接使用了一些API函數來操作圖像。在微軟的MSDN中有一個名爲Diblook的例子,該例子演示瞭如何操作Dib位圖,有興趣的讀者可以參考一下,相信一定會有所收穫。

  啓動Visual C++,生成一個名爲Dib的多文檔程序,將CDibView類的基類設爲CscrollView類,這樣作的目的是爲了在顯示位圖時支持滾動條,另外在處理圖像應用程序的文檔類(CDibDoc.h)中聲明如下宏及公有變量:

#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)//計算圖像每行象素所佔的字節數目;

HANDLE m_hDIB;//存放位圖數據的句柄;

CPalette* m_palDIB;//指向調色板Cpalette類的指針;

CSize m_sizeDoc;//初始化視圖的尺寸,該尺寸爲位圖的尺寸;

  最後將程序的字符串表中的字符串資源IDR_DibTYPE修改爲:“/nDib/nDib/nDib Files(*.bmp;*.dib)/n.bmp/nDib.Document/nDib Document”。這樣作的目的是爲了在程序文件對話框中可以選擇BMP或DIB格式的位圖文件。

1、 讀取灰度BMP位圖

  可以根據BMP位圖文件的結構,操作BMP位圖文件並讀入圖像數據,爲此我們充分利用了VC的文檔視圖結構,重載了文擋類的OnOpenDocument()函數,這樣用戶就可以在自動生成程序的打開文件對話框中選擇所要打開的位圖文件,然後程序將自動調用該函數執行讀取數據的操作。該函數的實現代碼如下所示:

BOOL CDibDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

LOGPALETTE *pPal;//定義邏輯調色板指針;

pPal=new LOGPALETTE;//初始化該指針;

CFile file;

CFileException fe;

if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite, &fe))

{//以“讀”的方式打開文件;

AfxMessageBox("圖像文件打不開!");

return FALSE;

}

DeleteContents();//刪除文擋;

BeginWaitCursor();

BITMAPFILEHEADER bmfHeader;//定義位圖文件頭結構;

LPBITMAPINFO lpbmi;

DWORD dwBitsSize;

HANDLE hDIB;

LPSTR pDIB;//指向位圖數據的指針;

BITMAPINFOHEADER *bmhdr;//指向位圖信息頭結構的指針

dwBitsSize = file.GetLength();//得到文件長度

if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) !=sizeof(bmfHeader))

return FALSE;//讀取位圖文件的文件頭結構信息;

if (bmfHeader.bfType != 0x4d42) //檢查該文件是否爲BMP格式的文件;

return FALSE;

hDIB=(HANDLE) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwBitsSize);

//爲讀取圖像文件數據申請緩衝區

if (hDIB == 0)

{

return FALSE;

}

pDIB = (LPSTR) ::GlobalLock((HGLOBAL)hDIB);

//得到申請的緩衝區的指針;

if (file.ReadHuge(pDIB, dwBitsSize - sizeof(BITMAPFILEHEADER)) !=

dwBitsSize - sizeof(BITMAPFILEHEADER) )

{

::GlobalUnlock((HGLOBAL)hDIB);

hDIB=NULL;

return FALSE;

}//此時pDIB數據塊中讀取的數據包括位圖頭信息、位圖顏色表、圖像像素的灰度值;

bmhdr=(BITMAPINFOHEADER*)pDIB;//爲指向位圖信息頭結構的指針賦值;

::GlobalUnlock((HGLOBAL)hDIB);

if ((*bmhdr).biBitCount!=8)//驗證是否爲8bit位圖

{

AfxMessageBox("該文件不是灰度位圖格式!");

return FALSE;

}

m_hDIB=hDIB;//將內部變量數據賦於全局變量;

//下面是記錄位圖的尺寸;

m_sizeDoc.x=bmhdr->biWidth;

m_sizeDoc.y=bmhdr->biHeight;

//下面是根據顏色表生成調色板;

m_palDIB=new Cpalette;

pPal->palVersion=0x300;//填充邏輯顏色表

pPal->palNumEntries=256;

lpbmi=(LPBITMAPINFO)bmhdr;

for(int i=0;i<256;i++)

{//每個顏色表項的R、G、B值相等,並且各個值從“0”到“255”序列展開;

Pal->palPalentry[i].peRed=lpbmi->bmiColors[i].rgbRed;

pPal->palPalentry[i].peGreen=lpbmi->bmiColors[i].rgbGreen;

pPal->palPalentry[i].peBlue= lpbmi->bmiColors[i].rgbBlue;;

pPal->palPalentry[i].peFlags=0;

}

m_palDIB->CreatePalette(pPal);

//根據讀入的數據得到位圖的寬、高、顏色表;

if(pPal)

delete pPal;

EndWaitCursor();

SetPathName(lpszPathName);//設置存儲路徑

SetModifiedFlag(FALSE); // 設置文件修改標誌爲FALSE

return TRUE;

}

  上面的方法是通過CFile類對象的操作來讀取位圖文件的,它需要分析位圖中的文件頭信息,從而確定需要讀取的圖像長度。這種方法相對來說有些繁瑣,其實還可以以一種相對簡單的方法讀取位圖數據,首先在程序的資源中定義DIB類型資源,然後添加位圖到該類型中,將圖像數據以資源的形式讀取出來,這時候就可以根據所獲取的數據中的位圖信息結構來獲取、顯示圖像數據了。下面的函數實現了以資源形式裝載圖像文件數據,該函數的實現代碼如下所示:

HANDLE LoadDIB(UINT uIDS, LPCSTR lpszDibType)

{

LPCSTR lpszDibRes =MAKEINTRESOURCE(uIDS);//根據資源標誌符確定資源的名字;

HINSTANCE hInst=AfxGetInstanceHandle();//得到應用程序的句柄;

HRSRC hRes=::FindResource(hInst,lpszDibRes, lpszDibType);//獲取資源的句柄,這裏lpszDibType爲資源的名字“DIB”;

If(hRes==NULL)

return NULL

HGLOBAL hData=::LoadResource(hInst, hRes);//轉載資源數據並返回該句柄;

return hData;

}

2、 灰度位圖數據的存儲

 爲了將圖像處理後所得到的像素值保存起來,我們重載了文檔類的OnSaveDocument()函數,這樣用戶在點擊Save或SaveAs子菜單後程序自動調用該函數,實現圖像數據的存儲。該函數的具體實現如下:

BOOL CDibDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

CFile file;

CFileException fe;

BITMAPFILEHEADER bmfHdr; // 位圖文件頭結構;

LPBITMAPINFOHEADER lpBI;//指向位圖頭信息結構的指針;

DWORD dwDIBSize;;

if (!file.Open(lpszPathName, CFile::modeCreate |CFile::modeReadWrite | CFile::shareExclusive, &fe))

{

AfxMessageBox("文件打不開");

return FALSE;

}//以讀寫的方式打開文件;

BOOL bSuccess = FALSE;

BeginWaitCursor();

lpBI = (LPBITMAPINFOHEADER) ::GlobalLock((HGLOBAL) m_hDIB);

if (lpBI == NULL)

return FALSE;

dwDIBSize = *(LPDWORD)lpBI + 256*sizeof(RGBQUAD);

//圖像的文件信息所佔用的字節數;

DWORD dwBmBitsSize;//BMP文件中位圖的像素所佔的字節數

dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount))

*lpBI->biHeight;// 存儲時位圖所有像素所佔的總字節數

dwDIBSize += dwBmBitsSize; //BMP文件除文件信息結構外的所有數據佔用的總字節數;

lpBI->biSizeImage = dwBmBitsSize; // 位圖所有像素所佔的總字節數

//以下五句爲文件頭結構填充值

bmfHdr.bfType =0x4d42; // 文件爲"BMP"類型

bmfHdr.bfSize = dwDIBSize + sizeof(BITMAPFILEHEADER);//文件總長度

bmfHdr.bfReserved1 = 0;

bmfHdr.bfReserved2 = 0;

bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + lpBI->biSize

+ 256*sizeof(RGBQUAD);

//位圖數據距離文件頭的偏移量;

file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER));//向文件中寫文件頭信息;

file.WriteHuge(lpBI, dwDIBSize);

//將位圖信息(信息頭結構、顏色表、像素數據)寫入文件;

::GlobalUnlock((HGLOBAL) m_hDIB);

EndWaitCursor();

SetModifiedFlag(FALSE); // 將文檔設爲“乾淨”標誌,表示此後文檔不需要存盤提示;

return TRUE;

}

二.調色板的操作

  通過上面的操作,我們已經可以獲取圖像中的數據了,現在的又一個問題是如何在窗口中顯示出圖像數據。灰度圖像要正確顯示,必須實現邏輯調色板和系統調色板。首先我們介紹一下邏輯調色板結構LOGPALETTE,該結構定義如下:

typedef struct tagLOGPALETTE

{

WORD palVersion;//調色板的板本號,應該指定該值爲0x300;

WORD palNumEntries;//調色板中的表項數,對於灰度圖像該值爲256;

PALETEENTRY palPalEntry[1];//調色板中的顏色表項,由於該表項的數目不一定,所以這裏數組長度定義爲1,灰度圖像對應的該數組的長度爲256;

}LOGPALETTE;

顏色表項結構PALETTEENTRY定義了調色板中的每一個顏色表項的顏色和使用方式,定義如下:

typedef struct tagPALETTEENTRY

{

BYTE peRed; //R分量值;

BYTE peGreen; //G分量值;

BYTE peBlue; //B分量值;

BYTE peFlags; // 該顏色被使用的方式,一般情況下設爲“0”;

}PALETTEENTRY;

  Windows系統使用調色板管理器來管理與調色板有關的操作,通常活動窗口的調色板即是當前系統調色板,所有的非活動窗口都必須按照此係統調色板來顯示自己的顏色,此時調色板管理器將自動的用系統調色板中的最近似顏色來映射相應的顯示顏色。如果窗口或應用程序按自己的調色板顯示顏色,就必須將自己的調色板載入到系統調色板中,這種操作叫作實現調色板,實現調色板包括兩個步驟,既首先將調色板選擇到設備上下文中,然後在設備上下文中實現它。可以通過CDC::SelectPalette()、CDC::RealizePalette()或相應的API函數來實現上述的兩個步驟。在實現調色板的過程中,通過在主框架類中處理Windows定義的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及視圖類中處理自定義消息WM_DOREALIZE(該消息在主框架窗口定義如下:#define WM_REALIZEPAL (WM_USER+101))來實現調色板的操作。當系統需要處理調色板的變化時,將向程序的主窗口發送WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如當某一窗口即將激活時,主框架窗口將收到WM_QUERYNEWPALETTE消息,通知該窗口將要收到輸入焦點,給它一次機會實現其自身的邏輯調色板;當系統調色板改變後,主框架窗口將收到WM_PALETTECHANGED消息,通知其它窗口系統調色板已經改變,此時每一窗口都應該實現其邏輯調色板,重畫客戶區。

  由於上述的調色板變更消息是發往主框架窗口的,所以我們只能在主窗口中響應這兩個消息,然後由主框架窗口通知各個視窗,使得程序激活時能自動裝載自己的調色板。我們定義的用戶消息WM_REALIZEPAL用於主框架窗口通知視窗它已經收到調色板變更消息,視窗應該協調其調色板。下面我們給出了各個消息的響應處理函數的具體實現代碼和註釋:

//////////////////////////////////////////////////////////////////////////                     

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)                     

{//總實現活動視的調色板                                                                               

CMDIFrameWnd::OnPaletteChanged(pFocusWnd);                                  

CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;

if (pMDIChildWnd == NULL)                                                                       

return;                                                                                                       

CView* pView = pMDIChildWnd->GetActiveView();//得到視圖的指針;

ASSERT(pView != NULL);

SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd);

//通知所有子窗口系統調色板已改變

}

////////////////////////////////////////////////

BOOL CMainFrame::OnQueryNewPalette()//提供實現系統調色板的機會

{

// 實現活動視的調色板

CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;

if (pMDIChildWnd == NULL)

return FALSE;//no active MDI child frame (no new palette)

CView* pView = pMDIChildWnd->GetActiveView();//得到活動子窗口的視圖指針;

ASSERT(pView != NULL);

//通知活動視圖實現系統調色板

pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);

return TRUE;

}

/////////////////////////////////////////////////

BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//實現系統調色板

{

ASSERT(wParam != NULL);

CDibDoc* pDoc = GetDocument();

if (pDoc->m_hDIB == NULL)

return FALSE; // must be a new document

CPalette* pPal = pDoc->m_palDIB;

//調色板的顏色表數據在InitDIBData()函數中實現

if (pPal != NULL)

{

CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;//得到程序的主框架指針;

ASSERT_KINDOF(CMainFrame, pAppFrame);

CClientDC appDC(pAppFrame);//獲取主框架的設備上下文;

CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);

//只有活動視纔可以設爲"FALSE",即根據活動視的調色板設爲"前景"調色板;

if (oldPalette != NULL)

{

UINT nColorsChanged = appDC.RealizePalette();//實現系統調色板

if (nColorsChanged > 0)

pDoc->UpdateAllViews(NULL);//更新視圖

appDC.SelectPalette(oldPalette, TRUE);

//將原系統調色板置爲背景調色板

}

else

{

TRACE0(“/tSelectPalette failed in”);

}

return TRUE;

}

  注:在調用API函數顯示位圖時,不要忘記設置邏輯調色板,即"背景"調色板,否則位圖將無法正確顯示,讀者可以從後面的顯示部分的實現看出我們在顯示時實現了邏輯調色板。上述的處理相對來說比較繁瑣複雜,可能對於初學者來說也比較難於理解,所以如果我們的程序僅僅限於處理灰度圖象,可以採用另外一種相對簡單的辦法,即在文檔類的初始化階段定義一個灰度調色板,然後在設備上下文中實現它,這樣作的好處是在度取灰度位圖時可以不再考慮文件中的顏色表信息,提高了文件讀取速度,筆者在開發一個基於機器視覺的項目時採用的就是這種方法,取的了比較滿意的效果。首先定義一個指向邏輯顏色表結構LOGPALETTE的指針pPal,填充該指針,然後將該指針與調色板指針聯繫起來,該方法的具體實現如下:

/////////////////////////////////////////////////////////

CDibDoc::CDibDoc()

{

……………………….

LOGPALETTE *Pal;

Pal=new LOGPALETTE;

m_palDIB=new Cpalette;

pPal->palVersion=0x300;

pPal->palNumEntries=256;

for(int i=0;i<256;i++)

{//每個顏色表項的R、G、B值相等,並且各個值從“0”到“255”序列展開;

Pal->palPalentry[i].peRed=i;

pPal->palPalentry[i].peGreen=i;

pPal->palPalentry[i].peBlue=i;

pPal->palPalentry[i].peFlags=0;

}

m_palDIB->CreatePalette(pPal);

…………………..

}

三.圖象的顯示

 顯示DIB位圖數據可以通過設備上下文CDC對象的成員函數CDC::Bitblt()或CDC::StretchBlt()來實現,也可以通過API函數SetDIBBitsToDevice()或StretchDIBBits()來實現,函數中具體所用到的各個參數的意義可以參考MSDN。其中StretchDIBBits()和CDC::StretchBlt()可以將圖像進行放大和縮小顯示。當從文檔中裝入位圖文件時,CDIBView類的OnInitialUpdate函數將被調用,因此可以在該函數中實現對視圖尺寸的設置,用於正確的顯示位圖,然後就可以在視圖類的OnDraw()函數中正確的顯示位圖了。這兩個函數的具體實現代碼分別如下所示:

/////////////////////////////////////////////////////////////

void CDIBView::OnInitialUpdate()

{

CscrollView::OnInitalUpdate();

CDIBDoc *pDoc=GetDocument();

If(pDoc->m_hDIB==NULL)//如果位圖數據爲空,設置m_sizeDoc的默認尺寸;

pDoc->m_sizeDoc.cx=pDoc->m_sizeDoc.cy=100;

SetScrollSizes(MM_TEXT,pDoc-> m_sizeDoc);

}

/////////////////////////////////////////////////////////////

void CDIBView::OnDraw(CDC *pDC)

{

BITMAPINFOHEADER *lpDIBHdr;//位圖信息頭結構指針;

BYTE *lpDIBBits;//指向位圖像素灰度值的指針;

BOOL bSuccess=FALSE;

CPalette*OldPal=NULL;//調色板指針;

HDC hDC=pDC->GetSafeHdc();//獲取當前設備上下文的句柄;

CDIBDoc *pDoc=GetDocument();//獲取活動文檔的指針;

If(pDoc->m_hDIB ==NULL)

{//判斷圖像數據是否爲空;

AfxMessageBox("圖像數據不能爲空,請首先讀取圖像數據!");

return;

}

lpDIBHdr=( BITMAPINFOHEADER *)GlobalLock(pDoc->m_hDIB);//得到圖像的位圖頭信息

lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//獲取保存圖像像素值的緩衝區的指針;

if(pDoc-> m_palDIB)

{//如果存在調色板信息,實現邏輯調色板;

OldPal=pDC-> SelectPalette(pDoc-> m_palDIB,TRUE);

PDC->RealizePalette();

}

else

{

AfxMessageBox("圖像的調色板數據不能爲空,請首先讀取調色板信息!");

return ;

}

SetStretchBltMode(hDC,COLORONCOLOR);

//顯示圖像

BSuccess=StretchDIBBits(hDC,0,0,pDoc-> m_sizeDoc.cx, pDoc-> m_sizeDoc.cy,

0, pDoc-> m_sizeDoc.cy,0, pDoc-> m_sizeDoc.cy,

lpDIBBits,(LPBITMAPINFO)lpDIBHdr,

DIB_RGB_COLORS,

SRCCOPY);

GlobalUnlock(pDoc->m_hDIB);

If(OldPal)//恢復調色板;

PDC->SelectPalette(OldPal,FALSE);

retrun;

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章