版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
VC數(shù)字圖像處理編程講座之一
本講座系統(tǒng)介紹了如何使用Visual
C++對bmp、gif、jpg等常見格式圖像進(jìn)行處理
前
言
數(shù)字圖像處理技術(shù)與理論是計(jì)算機(jī)應(yīng)用的一個(gè)重要領(lǐng)域,許多工程應(yīng)用都涉及到圖像處理,一直有一個(gè)強(qiáng)烈的愿望,想系統(tǒng)的寫一個(gè)關(guān)于數(shù)字圖像處理的講座,由于工作學(xué)習(xí)很忙,時(shí)至今日才得以實(shí)現(xiàn)。
“圖”是物體透射光或反射光的分布,“像”是人的視覺系統(tǒng)對圖的接收在大腦中形成的印象或認(rèn)識。圖像是兩者的結(jié)合。人類獲取外界信息是靠聽覺、視覺、觸覺、嗅覺、味覺等,但絕大部分(約80%左右)來自視覺所接收的圖像信息。圖像處理就是對圖像信息進(jìn)行加工處理,以滿足人的視覺心理和實(shí)際應(yīng)用的需要。簡單的說,依靠計(jì)算機(jī)對圖像進(jìn)行各種目的的處理我們就稱之為數(shù)字圖像處理。早期的數(shù)字圖像處理的目的是以人為對象,為了滿足人的視覺效果而改善圖像的質(zhì)量,處理過程中輸入的是質(zhì)量差的圖像,輸出的是質(zhì)量好的圖像,常用的圖像處理方法有圖像增強(qiáng)、復(fù)原等。隨著計(jì)算機(jī)技術(shù)的發(fā)展,有一類圖像處理是以機(jī)器為對象,處理的目的是使機(jī)器能夠自動(dòng)識別目標(biāo),這稱之為圖像的識別,因?yàn)檫@其中要牽涉到一些復(fù)雜的模式識別的理論,所以我們后續(xù)的講座只討論其中最基本的內(nèi)容。由于在許多實(shí)際應(yīng)用的編程中往往都要涉及到數(shù)字圖像處理,涉及到其中的一些算法,這也是許多編程愛好者感興趣的一個(gè)內(nèi)容,我們這個(gè)講座就是討論如何利用微軟的Visual
C++開發(fā)工具來實(shí)現(xiàn)一些常用的數(shù)字圖像處理算法,論述了圖像處理的理論,同時(shí)給出了VC實(shí)現(xiàn)的源代碼。本講座主要的內(nèi)容分為基礎(chǔ)篇、中級篇和高級篇,具體包含的主要內(nèi)容有:
1.
圖像文件的格式;
2.
圖像編程的基礎(chǔ)-操作調(diào)色板;
3.
圖像數(shù)據(jù)的讀取、存儲(chǔ)和顯示、如何獲取圖像的尺寸等;
4.
利用圖像來美化界面;
5.
圖像的基本操作:圖像移動(dòng)、圖像旋轉(zhuǎn)、圖像鏡像、圖像的縮放、圖像的剪切板操作;
6.
圖像顯示的各種特技效果;
7.
圖像的基本處理:圖像的二值化、圖像的亮度和對比度的調(diào)整、圖像的邊緣增強(qiáng)、如何得到圖像的直方圖、圖像直方圖的修正、圖像的平滑、圖像的銳化等、圖像的偽彩色、彩色圖像轉(zhuǎn)換為黑白圖像、物體邊緣的搜索等等;
8.
二值圖像的處理:腐蝕、膨脹、細(xì)化、距離變換等;
9.
圖像分析:直線、圓、特定物體的識別;
10.JEPG、GIF、PCX等格式文件相關(guān)操作;
11.圖像文件格式的轉(zhuǎn)換;
12.圖像的常用變換:付利葉變換、DCT變換、沃爾什變換等;
13.AVI視頻流的操作;
圖像處理技術(shù)博大精深,不僅需要有很強(qiáng)的數(shù)學(xué)功底,還需要熟練掌握一門計(jì)算機(jī)語言,在當(dāng)前流行的語言中,我個(gè)人覺的Visual
C++這個(gè)開發(fā)平臺(tái)是圖像開發(fā)人員的首選工具。本講座只是起到拋磚引玉的作用,希望和廣大讀者共同交流。
VC數(shù)字圖像處理編程講座之二
在這一講中作者詳細(xì)介紹了BMP、GIF、JPG等圖像的文件格式
第一節(jié)
圖像的文件格式
要利用計(jì)算機(jī)對數(shù)字化圖像進(jìn)行處理,首先要對圖像的文件格式要有清楚的認(rèn)識,因?yàn)槲覀兦懊嬲f過,自然界的圖像以模擬信號的形式存在,在用計(jì)算機(jī)進(jìn)行處理以前,首先要數(shù)字化,比如攝像頭(CCD)攝取的信號在送往計(jì)算機(jī)處理前,一般情況下要經(jīng)過數(shù)模轉(zhuǎn)換,這個(gè)任務(wù)常常由圖像采集卡完成,它的輸出一般為裸圖的形式;如果用戶想要生成目標(biāo)圖像文件,必須根據(jù)文件的格式做相應(yīng)的處理。隨著科技的發(fā)展,數(shù)碼像機(jī)、數(shù)碼攝像機(jī)已經(jīng)進(jìn)入尋常百姓家,我們可以利用這些設(shè)備作為圖像處理系統(tǒng)的輸入設(shè)備來為后續(xù)的圖像處理提供信息源。無論是什么設(shè)備,它總是提供按一定的圖像文件格式來提供信息,比較常用的有BMP格式、JPEG格式、GIF格式等等,所以我們在進(jìn)行圖像處理以前,首先要對圖像的格式要有清晰的認(rèn)識,只有在此基礎(chǔ)上才可以進(jìn)行進(jìn)一步的開發(fā)處理。
在講述圖像文件格式前,先對圖像作一個(gè)簡單的分類。除了最簡單的圖像外,所有的圖像都有顏色,而單色圖像則是帶有顏色的圖像中比較簡單的格式,它一般由黑色區(qū)域和白色區(qū)域組成,可以用一個(gè)比特表示一個(gè)像素,“1”表示黑色,“0”表示白色,當(dāng)然也可以倒過來表示,這種圖像稱之為二值圖像。我們也可以用8個(gè)比特(一個(gè)字節(jié))表示一個(gè)像素,相當(dāng)于把黑和白等分為256個(gè)級別,“0”表示為黑,“255”表示為白,該字節(jié)的數(shù)值表示相應(yīng)像素值的灰度值或亮度值,數(shù)值越接近“0”,對應(yīng)像素點(diǎn)越黑,相反,則對應(yīng)像素點(diǎn)越白,此種圖像我們一般稱之為灰度圖像。單色圖像和灰度圖像又統(tǒng)稱為黑白圖像,與之對應(yīng)存在著彩色圖像,這種圖像要復(fù)雜一些,表示圖像時(shí),常用的圖像彩色模式有RGB模式、CMYK模式和HIS模式,一般情況下我們只使用RGB模式,R對應(yīng)紅色,G對應(yīng)綠色,B對應(yīng)藍(lán)色,它們統(tǒng)稱為三基色,這三中色彩的不同搭配,就可以搭配成各種現(xiàn)實(shí)中的色彩,此時(shí)彩色圖像的每一個(gè)像素都需要3個(gè)樣本組成的一組數(shù)據(jù)表示,其中每個(gè)樣本用于表示該像素的一個(gè)基本顏色。
對于現(xiàn)存的所有的圖像文件格式,我們在這里主要介紹BMP圖像文件格式,并且文件里的圖像數(shù)據(jù)是未壓縮的,因?yàn)閳D像的數(shù)字化處理主要是對圖像中的各個(gè)像素進(jìn)行相應(yīng)的處理,而未壓縮的BMP圖像中的像素?cái)?shù)值正好與實(shí)際要處理的數(shù)字圖像相對應(yīng),這種格式的文件最合適我們對之進(jìn)行數(shù)字化處理。請讀者記住,壓縮過的圖像是無法直接進(jìn)行數(shù)字化處理的,如JPEG、GIF等格式的文件,此時(shí)首先要對圖像文件解壓縮,這就要涉及到一些比較復(fù)雜的壓縮算法。后續(xù)章節(jié)中我們將針對特殊的文件格式如何轉(zhuǎn)換為BMP格式的文件問題作專門的論述,經(jīng)過轉(zhuǎn)換,我們就可以利用得到的未壓縮的BMP文件格式進(jìn)行后續(xù)處理。對于JPEG、GIF等格式,由于涉及到壓縮算法,這要求讀者掌握一定的信息論方面的知識,如果展開的話,可以寫一本書,限于篇幅原因,我們只作一般性的講解,有興趣的朋友可以參考相關(guān)書籍資料。
一、BMP文件結(jié)構(gòu)
1.
BMP文件組成
BMP文件由文件頭、位圖信息頭、顏色信息和圖形數(shù)據(jù)四部分組成。文件頭主要包含文件的大小、文件類型、圖像數(shù)據(jù)偏離文件頭的長度等信息;位圖信息頭包含圖象的尺寸信息、圖像用幾個(gè)比特?cái)?shù)值來表示一個(gè)像素、圖像是否壓縮、圖像所用的顏色數(shù)等信息。顏色信息包含圖像所用到的顏色表,顯示圖像時(shí)需用到這個(gè)顏色表來生成調(diào)色板,但如果圖像為真彩色,既圖像的每個(gè)像素用24個(gè)比特來表示,文件中就沒有這一塊信息,也就不需要操作調(diào)色板。文件中的數(shù)據(jù)塊表示圖像的相應(yīng)的像素值,需要注意的是:圖像的像素值在文件中的存放順序?yàn)閺淖蟮接遥瑥南碌缴?,也就是說,在BMP文件中首先存放的是圖像的最后一行像素,最后才存儲(chǔ)圖像的第一行像素,但對與同一行的像素,則是按照先左邊后右邊的的順序存儲(chǔ)的;另外一個(gè)需要讀者朋友關(guān)注的細(xì)節(jié)是:文件存儲(chǔ)圖像的每一行像素值時(shí),如果存儲(chǔ)該行像素值所占的字節(jié)數(shù)為4的倍數(shù),則正常存儲(chǔ),否則,需要在后端補(bǔ)0,湊足4的倍數(shù)。
2.
BMP文件頭
BMP文件頭數(shù)據(jù)結(jié)構(gòu)含有BMP文件的類型、文件大小和位圖起始位置等信息。其結(jié)構(gòu)定義如下:
typedef
struct
tagBITMAPFILEHEADER
{
WORD
bfType;
//
位圖文件的類型,必須為“BM”
DWORD
bfSize;
//
位圖文件的大小,以字節(jié)為單位
WORD
bfReserved1;
//
位圖文件保留字,必須為0
WORD
bfReserved2;
//
位圖文件保留字,必須為0
DWORD
bfOffBits;
//
位圖數(shù)據(jù)的起始位置,以相對于位圖文件頭的偏移量表示,以字節(jié)為單位
}
BITMAPFILEHEADER;該結(jié)構(gòu)占據(jù)14個(gè)字節(jié)。
3.
位圖信息頭
BMP位圖信息頭數(shù)據(jù)用于說明位圖的尺寸等信息。其結(jié)構(gòu)如下:
typedef
struct
tagBITMAPINFOHEADER{
DWORD
biSize;
//
本結(jié)構(gòu)所占用字節(jié)數(shù)
LONG
biWidth;
//
位圖的寬度,以像素為單位
LONG
biHeight;
//
位圖的高度,以像素為單位
WORD
biPlanes;
//
目標(biāo)設(shè)備的平面數(shù)不清,必須為1
WORD
biBitCount//
每個(gè)像素所需的位數(shù),必須是1(雙色),
4(16色),8(256色)或24(真彩色)之一
DWORD
biCompression;
//
位圖壓縮類型,必須是
0(不壓縮),1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一
DWORD
biSizeImage;
//
位圖的大小,以字節(jié)為單位
LONG
biXPelsPerMeter;
//
位圖水平分辨率,每米像素?cái)?shù)
LONG
biYPelsPerMeter;
//
位圖垂直分辨率,每米像素?cái)?shù)
DWORD
biClrUsed;//
位圖實(shí)際使用的顏色表中的顏色數(shù)
DWORD
biClrImportant;//
位圖顯示過程中重要的顏色數(shù)
}
BITMAPINFOHEADER;該結(jié)構(gòu)占據(jù)40個(gè)字節(jié)。
注意:對于BMP文件格式,在處理單色圖像和真彩色圖像的時(shí)候,無論圖象數(shù)據(jù)多么龐大,都不對圖象數(shù)據(jù)進(jìn)行任何壓縮處理,一般情況下,如果位圖采用壓縮格式,那么16色圖像采用RLE4壓縮算法,256色圖像采用RLE8壓縮算法。
4.
顏色表
顏色表用于說明位圖中的顏色,它有若干個(gè)表項(xiàng),每一個(gè)表項(xiàng)是一個(gè)RGBQUAD類型的結(jié)構(gòu),定義一種顏色。RGBQUAD結(jié)構(gòu)的定義如下:
typedef
struct
tagRGBQUAD
{
BYTErgbBlue;//
藍(lán)色的亮度(值范圍為0-255)
BYTErgbGreen;
//
綠色的亮度(值范圍為0-255)
BYTErgbRed;
//
紅色的亮度(值范圍為0-255)
BYTErgbReserved;//
保留,必須為0
}
RGBQUAD;
顏色表中RGBQUAD結(jié)構(gòu)數(shù)據(jù)的個(gè)數(shù)由BITMAPINFOHEADER
中的biBitCount項(xiàng)來確定,當(dāng)biBitCount=1,4,8時(shí),分別有2,16,256個(gè)顏色表項(xiàng),當(dāng)biBitCount=24時(shí),圖像為真彩色,圖像中每個(gè)像素的顏色用三個(gè)字節(jié)表示,分別對應(yīng)R、G、B值,圖像文件沒有顏色表項(xiàng)。位圖信息頭和顏色表組成位圖信息,BITMAPINFO結(jié)構(gòu)定義如下:
typedef
struct
tagBITMAPINFO
{
BITMAPINFOHEADER
bmiHeader;
//
位圖信息頭
RGBQUAD
bmiColors[1];
//
顏色表
}
BITMAPINFO;
注意:RGBQUAD數(shù)據(jù)結(jié)構(gòu)中,增加了一個(gè)保留字段rgbReserved,它不代表任何顏色,必須取固定的值為“0”,同時(shí),RGBQUAD結(jié)構(gòu)中定義的顏色值中,紅色、綠色和藍(lán)色的排列順序與一般真彩色圖像文件的顏色數(shù)據(jù)排列順序恰好相反,既:若某個(gè)位圖中的一個(gè)像素點(diǎn)的顏色的描述為“00,00,ff,00”,則表示該點(diǎn)為紅色,而不是藍(lán)色。
5.
位圖數(shù)據(jù)
位圖數(shù)據(jù)記錄了位圖的每一個(gè)像素值或該對應(yīng)像素的顏色表的索引值,圖像記錄順序是在掃描行內(nèi)是從左到右,掃描行之間是從下到上。這種格式我們又稱為Bottom_Up位圖,當(dāng)然與之相對的還有Up_Down形式的位圖,它的記錄順序是從上到下的,對于這種形式的位圖,也不存在壓縮形式。位圖的一個(gè)像素值所占的字節(jié)數(shù):當(dāng)biBitCount=1時(shí),8個(gè)像素占1個(gè)字節(jié);當(dāng)biBitCount=4時(shí),2個(gè)像素占1個(gè)字節(jié);當(dāng)biBitCount=8時(shí),1個(gè)像素占1個(gè)字節(jié);當(dāng)biBitCount=24時(shí),1個(gè)像素占3個(gè)字節(jié),此時(shí)圖像為真彩色圖像。當(dāng)圖像不是為真彩色時(shí),圖像文件中包含顏色表,位圖的數(shù)據(jù)表示對應(yīng)像素點(diǎn)在顏色表中相應(yīng)的索引值,當(dāng)為真彩色時(shí),每一個(gè)像素用三個(gè)字節(jié)表示圖像相應(yīng)像素點(diǎn)彩色值,每個(gè)字節(jié)分別對應(yīng)R、G、B分量的值,這時(shí)候圖像文件中沒有顏色表。上面我已經(jīng)講過了,Windows規(guī)定圖像文件中一個(gè)掃描行所占的字節(jié)數(shù)必須是4的倍數(shù)(即以字為單位),不足的以0填充,圖像文件中一個(gè)掃描行所占的字節(jié)數(shù)計(jì)算方法:
DataSizePerLine=
(biWidth*
biBitCount+31)/8;//
一個(gè)掃描行所占的字節(jié)數(shù)
位圖數(shù)據(jù)的大小按下式計(jì)算(不壓縮情況下):
DataSize=
DataSizePerLine*
biHeight。
上述是BMP文件格式的說明,搞清楚了以上的結(jié)構(gòu),就可以正確的操作圖像文件,對它進(jìn)行讀或?qū)懖僮髁恕?/p>
二、GIF圖像文件格式
GIF圖象格式的全稱為Graphics
Interchange
Format,從這個(gè)名字可以看出,這種圖像格式主要是為了通過網(wǎng)絡(luò)傳輸圖像而設(shè)計(jì)的。GIF文件不支持24位真彩色圖像,最多只能存儲(chǔ)256色的圖像或灰度圖像;GIF格式文件也無法存儲(chǔ)CMY和HIS模型的圖像數(shù)據(jù);另外,GIF圖像文件的各種數(shù)據(jù)區(qū)域一般沒有固定的數(shù)據(jù)長度和存儲(chǔ)順序,所以為了方便程序?qū)ふ覕?shù)據(jù)區(qū),將數(shù)據(jù)區(qū)中的第一個(gè)字節(jié)作為標(biāo)志符;最后需要讀者注意的是GIF文件存儲(chǔ)圖像數(shù)據(jù)是有二種排列順序:順序排列或交叉排列。交叉排列的方式適合網(wǎng)絡(luò)傳輸,這樣一來允許用戶在不完全掌握圖像數(shù)據(jù)之前,獲取當(dāng)前圖像的輪廓數(shù)據(jù)。
GIF文件格式分為87和89兩個(gè)版本,對于87這個(gè)版本,該文件主要是有五個(gè)部分組成,它,們是按順序出現(xiàn)的:文件頭塊、邏輯屏幕描述塊、可選擇的調(diào)色板塊、圖像數(shù)據(jù)塊、最后是標(biāo)志文件結(jié)束的尾塊,該塊總是取固定的值3BH。其中第一和第二兩個(gè)塊用GIF圖像文件頭結(jié)構(gòu)描述:
GIFHEADER:{
DB
Signature;
//該字段占六個(gè)字節(jié),為了用于指明圖像為GIF格式,前三個(gè)字符必須為“GIF”,后三字符用于指定是哪個(gè)版本,87或89。
DW
ScreenWidth;//
DW
ScreenDepth;//占兩個(gè)字節(jié),以像素為單位表示圖像的寬、高
DB
GlobalFlagByte;//該字節(jié)的各個(gè)位用于調(diào)色版的描述
DB
BackGroundColor;//代表圖象的背景顏色的索引
DB
AspectRatio;圖像的長寬比
}
GIF格式中的調(diào)色板有通用調(diào)色板和局部調(diào)色板之分,因?yàn)镚IF格式允許一個(gè)文件中存儲(chǔ)多個(gè)圖像,因此有這兩種調(diào)色板,其中通用調(diào)色板適于文件中的所有圖像,而局部調(diào)色板只適用于某一個(gè)圖像。格式中的數(shù)據(jù)區(qū)域一般分為四個(gè)部分,圖像數(shù)據(jù)識別區(qū)域,局部調(diào)色板數(shù)據(jù),采用壓縮算法得到的圖象數(shù)據(jù)區(qū)域和結(jié)束標(biāo)志區(qū)域。
在GIF89版本中,它包含七個(gè)部分,分別是文件頭、通用調(diào)色板數(shù)據(jù)、圖像數(shù)據(jù)區(qū)和四個(gè)補(bǔ)充數(shù)據(jù)區(qū),它們主要是用于提示程序如何處理圖像的。
三、JEPG圖像文件
JEPG簡稱為聯(lián)合攝影專家小組,作為一種技術(shù),主要用于數(shù)字化圖像的標(biāo)準(zhǔn)編碼,JPEG主要采用有損的壓縮編碼方式,它比GIF、BMP圖像文件要復(fù)雜的多,這不是短短的幾頁篇幅可以將清楚的,萬幸的是,我們可以通過一些別的方法將該格式轉(zhuǎn)化為BMP格式。讀者需要知道的是在對JEPG文件格式編碼時(shí),通常需要分為以下四步:顏色轉(zhuǎn)化、DCT變換、量化、編碼。
以上介紹了一些常用的圖像文件,對比較復(fù)雜的格式,如GIF和JEPG,僅僅作了極其浮淺的介紹,后文我們會(huì)和它們作進(jìn)一步的接觸。實(shí)際應(yīng)用中,還有許多圖像格式,文章中都沒有提到,讀者如果需要做進(jìn)一步的研究,還需要參考一些關(guān)于圖像格式方面的資料。
VC數(shù)字圖像處理編程講座之三
本節(jié)主要講述如何操作BMP文件,如對其讀、寫和顯示等
BMP圖像的基本操作
上一講我們主要介紹了圖像的格式,其中重點(diǎn)說明了BMP文件的存儲(chǔ)格式,同時(shí)對JEPG和GIF等常用格式作了簡單的介紹。本節(jié)主要講述如何操作BMP文件,如對其讀、寫和顯示等。
在實(shí)現(xiàn)數(shù)字圖象處理的過程中,主要是通過對圖像中的每一個(gè)像素點(diǎn)運(yùn)用各種圖像處理算法來達(dá)到預(yù)期的效果,所以進(jìn)行圖像處理的第一步,也是我們最關(guān)心的問題,是如何得到圖像中每一個(gè)像素點(diǎn)的亮度值;為了觀察和驗(yàn)證處理的圖像效果,另一個(gè)需要解決的問題是如何將處理前后的圖像正確的顯示出來。我們這章內(nèi)容就是解決這些問題。
隨著科技的發(fā)展,圖像處理技術(shù)已經(jīng)滲透到人類生活的各個(gè)領(lǐng)域并得到越來越多的應(yīng)用,但是突出的一個(gè)矛盾是圖像的格式也是越來越多,目前圖像處理所涉及的主要的圖像格式就有很多種,如TIF、JEMP、BMP等等,一般情況下,為了處理簡單方便,進(jìn)行數(shù)字圖像處理所采用的都是BMP格式的圖像文件(有時(shí)也稱為DIB格式的圖像文件),并且這種格式的文件是沒有壓縮的。我們通過操作這種格式的文件,可以獲取正確顯示圖像所需的調(diào)色板信息,圖像的尺寸信息,圖像中各個(gè)像素點(diǎn)的亮度信息等等,有了這些數(shù)據(jù),開發(fā)人員就可以對圖像施加各種處理算法,進(jìn)行相應(yīng)的處理。如果特殊情況下需要處理其它某種格式的圖像,如GIF、JEMP等格式的圖象文件,可以首先將該格式轉(zhuǎn)換為BMP格式,然后再進(jìn)行相應(yīng)的處理。這一點(diǎn)需要讀者清楚。
BMP格式的圖像文件又可以分為許多種類,如真彩色位圖、256色位圖,采用RLE(游程編碼)壓縮格式的BMP位圖等等。由于在實(shí)際的工程應(yīng)用和圖像算法效果驗(yàn)證中經(jīng)常要處理的是256級并且是沒有壓縮的BMP灰度圖像,例如通過黑白采集卡采集得到的圖像就是這種格式,所以我們在整個(gè)講座中范例所處理的文件格式都是BMP灰度圖像。如果讀者對這種格式的位圖能夠作到熟練的操作,那么對于其余形式的BMP位圖的操作也不會(huì)很困難。
BMP灰度圖像作為Windows環(huán)境下主要的圖像格式之一,以其格式簡單,適應(yīng)性強(qiáng)而倍受歡迎。正如我們在上一講中介紹過的那樣,這種文件格式就是每一個(gè)像素用8bit表示,顯示出來的圖像是黑白效果,最黑的像素的灰度(也叫作亮度)值為“0”,最白的像素的灰度值為“255”,整個(gè)圖像各個(gè)像素的灰度值隨機(jī)的分布在“0”到“255”的區(qū)間中,越黑的像素,其灰度值越接近于“0”,越白(既越亮)的像素,其灰度值越接近于“255”;與此對應(yīng)的是在該文件類型中的顏色表項(xiàng)的各個(gè)RGB分量值是相等的,并且顏色表項(xiàng)的數(shù)目是256個(gè)。
在進(jìn)行圖像處理時(shí),操作圖像中的像素值就要得到圖像陣列;經(jīng)過處理后的圖像的像素值需要存儲(chǔ)起來;顯示圖像時(shí)要正確實(shí)現(xiàn)調(diào)色板、得到位圖的尺寸信息等。結(jié)合這些問題,下面我們針對性的給出了操作灰度BMP圖像時(shí)的部分函數(shù)實(shí)現(xiàn)代碼及注釋。
一、
BMP位圖操作
首先我們回顧一下上講中的重要信息:BMP位圖包括位圖文件頭結(jié)構(gòu)BITMAPFILEHEADER、位圖信息頭結(jié)構(gòu)BITMAPINFOHEADER、位圖顏色表RGBQUAD和位圖像素?cái)?shù)據(jù)四部分。處理位圖時(shí)要根據(jù)文件的這些結(jié)構(gòu)得到位圖文件大小、位圖的寬、高、實(shí)現(xiàn)調(diào)色板、得到位圖像素值等等。這里要注意的一點(diǎn)是在BMP位圖中,位圖的每行像素值要填充到一個(gè)四字節(jié)邊界,即位圖每行所占的存儲(chǔ)長度為四字節(jié)的倍數(shù),不足時(shí)將多余位用0填充。
有了上述知識,可以開始編寫圖像處理的程序了,關(guān)于在VC的開發(fā)平臺(tái)上如何開發(fā)程序的問題這里不再贅述,筆者假定讀者都具有一定的VC開發(fā)經(jīng)驗(yàn)。在開發(fā)該圖像處理程序的過程中,筆者沒有采用面向?qū)ο蟮姆椒?,雖然面向?qū)ο蟮姆椒梢詫?shù)據(jù)封裝起來,保護(hù)類中的數(shù)據(jù)不受外界的干擾,提高數(shù)據(jù)的安全性,但是這種安全性是以降低程序的執(zhí)行效率為代價(jià)的,為此,我們充分利用了程序的文檔視圖結(jié)構(gòu),在程序中直接使用了一些API函數(shù)來操作圖像。在微軟的MSDN中有一個(gè)名為Diblook的例子,該例子演示了如何操作Dib位圖,有興趣的讀者可以參考一下,相信一定會(huì)有所收獲。
啟動(dòng)Visual
C++,生成一個(gè)名為Dib的多文檔程序,將CDibView類的基類設(shè)為CscrollView類,這樣作的目的是為了在顯示位圖時(shí)支持滾動(dòng)條,另外在處理圖像應(yīng)用程序的文檔類(CDibDoc.h)中聲明如下宏及公有變量:
#define
WIDTHBYTES(bits)
(((bits)
+
31)
/
32
*
4)//計(jì)算圖像每行象素所占的字節(jié)數(shù)目;
HANDLE
m_hDIB;//存放位圖數(shù)據(jù)的句柄;
CPalette*
m_palDIB;//指向調(diào)色板Cpalette類的指針;
CSize
m_sizeDoc;//初始化視圖的尺寸,該尺寸為位圖的尺寸;
最后將程序的字符串表中的字符串資源IDR_DibTYPE修改為:“\nDib\nDib\nDib
Files(*.bmp;*.dib)\n.bmp\nDib.Document\nDib
Document”。這樣作的目的是為了在程序文件對話框中可以選擇BMP或DIB格式的位圖文件。
1、
讀取灰度BMP位圖
可以根據(jù)BMP位圖文件的結(jié)構(gòu),操作BMP位圖文件并讀入圖像數(shù)據(jù),為此我們充分利用了VC的文檔視圖結(jié)構(gòu),重載了文擋類的OnOpenDocument()函數(shù),這樣用戶就可以在自動(dòng)生成程序的打開文件對話框中選擇所要打開的位圖文件,然后程序?qū)⒆詣?dòng)調(diào)用該函數(shù)執(zhí)行讀取數(shù)據(jù)的操作。該函數(shù)的實(shí)現(xiàn)代碼如下所示:
BOOL
CDibDoc::OnOpenDocument(LPCTSTR
lpszPathName)
{
LOGPALETTE
*pPal;//定義邏輯調(diào)色板指針;
pPal=new
LOGPALETTE;//初始化該指針;
CFile
file;
CFileException
fe;
if
(!file.Open(lpszPathName,
CFile::modeRead
|
CFile::shareDenyWrite,
&fe))
{//以“讀”的方式打開文件;
AfxMessageBox("圖像文件打不開!");
return
FALSE;
}
DeleteContents();//刪除文擋;
BeginWaitCursor();
BITMAPFILEHEADER
bmfHeader;//定義位圖文件頭結(jié)構(gòu);
LPBITMAPINFO
lpbmi;
DWORD
dwBitsSize;
HANDLE
hDIB;
LPSTR
pDIB;//指向位圖數(shù)據(jù)的指針;
BITMAPINFOHEADER
*bmhdr;//指向位圖信息頭結(jié)構(gòu)的指針
dwBitsSize
=
file.GetLength();//得到文件長度
if
(file.Read((LPSTR)&bmfHeader,
sizeof(bmfHeader))
!=sizeof(bmfHeader))
return
FALSE;//讀取位圖文件的文件頭結(jié)構(gòu)信息;
if
(bmfHeader.bfType
!=
0x4d42)
//檢查該文件是否為BMP格式的文件;
return
FALSE;
hDIB=(HANDLE)
::GlobalAlloc(GMEM_MOVEABLE
|
GMEM_ZEROINIT,
dwBitsSize);
//為讀取圖像文件數(shù)據(jù)申請緩沖區(qū)
if
(hDIB
==
0)
{
return
FALSE;
}
pDIB
=
(LPSTR)
::GlobalLock((HGLOBAL)hDIB);
//得到申請的緩沖區(qū)的指針;
if
(file.ReadHuge(pDIB,
dwBitsSize
-
sizeof(BITMAPFILEHEADER))
!=
dwBitsSize
-
sizeof(BITMAPFILEHEADER)
)
{
::GlobalUnlock((HGLOBAL)hDIB);
hDIB=NULL;
return
FALSE;
}//此時(shí)pDIB數(shù)據(jù)塊中讀取的數(shù)據(jù)包括位圖頭信息、位圖顏色表、圖像像素的灰度值;
bmhdr=(BITMAPINFOHEADER*)pDIB;//為指向位圖信息頭結(jié)構(gòu)的指針賦值;
::GlobalUnlock((HGLOBAL)hDIB);
if
((*bmhdr).biBitCount!=8)//驗(yàn)證是否為8bit位圖
{
AfxMessageBox("該文件不是灰度位圖格式!");
return
FALSE;
}
m_hDIB=hDIB;//將內(nèi)部變量數(shù)據(jù)賦于全局變量;
//下面是記錄位圖的尺寸;
m_sizeDoc.x=bmhdr->biWidth;
m_sizeDoc.y=bmhdr->biHeight;
//下面是根據(jù)顏色表生成調(diào)色板;
m_palDIB=new
Cpalette;
pPal->palVersion=0x300;//填充邏輯顏色表
pPal->palNumEntries=256;
lpbmi=(LPBITMAPINFO)bmhdr;
for(int
i=0;i<256;i++)
{//每個(gè)顏色表項(xiàng)的R、G、B值相等,并且各個(gè)值從“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);
//根據(jù)讀入的數(shù)據(jù)得到位圖的寬、高、顏色表;
if(pPal)
delete
pPal;
EndWaitCursor();
SetPathName(lpszPathName);//設(shè)置存儲(chǔ)路徑
SetModifiedFlag(FALSE);
//
設(shè)置文件修改標(biāo)志為FALSE
return
TRUE;
}
上面的方法是通過CFile類對象的操作來讀取位圖文件的,它需要分析位圖中的文件頭信息,從而確定需要讀取的圖像長度。這種方法相對來說有些繁瑣,其實(shí)還可以以一種相對簡單的方法讀取位圖數(shù)據(jù),首先在程序的資源中定義DIB類型資源,然后添加位圖到該類型中,將圖像數(shù)據(jù)以資源的形式讀取出來,這時(shí)候就可以根據(jù)所獲取的數(shù)據(jù)中的位圖信息結(jié)構(gòu)來獲取、顯示圖像數(shù)據(jù)了。下面的函數(shù)實(shí)現(xiàn)了以資源形式裝載圖像文件數(shù)據(jù),該函數(shù)的實(shí)現(xiàn)代碼如下所示:
/////////////////////////////////////////////////////////////////
HANDLE
LoadDIB(UINT
uIDS,
LPCSTR
lpszDibType)
{
LPCSTR
lpszDibRes
=MAKEINTRESOURCE(uIDS);//根據(jù)資源標(biāo)志符確定資源的名字;
HINSTANCE
hInst=AfxGetInstanceHandle();//得到應(yīng)用程序的句柄;
HRSRC
hRes=::FindResource(hInst,lpszDibRes,
lpszDibType);//獲取資源的句柄,這里lpszDibType為資源的名字“DIB”;
If(hRes==NULL)
return
NULL
HGLOBAL
hData=::LoadResource(hInst,
hRes);//轉(zhuǎn)載資源數(shù)據(jù)并返回該句柄;
return
hData;
}
2、
灰度位圖數(shù)據(jù)的存儲(chǔ)
為了將圖像處理后所得到的像素值保存起來,我們重載了文檔類的OnSaveDocument()函數(shù),這樣用戶在點(diǎn)擊Save或SaveAs子菜單后程序自動(dòng)調(diào)用該函數(shù),實(shí)現(xiàn)圖像數(shù)據(jù)的存儲(chǔ)。該函數(shù)的具體實(shí)現(xiàn)如下:
///////////////////////////////////////////////////////////////////
BOOL
CDibDoc::OnSaveDocument(LPCTSTR
lpszPathName)
{
CFile
file;
CFileException
fe;
BITMAPFILEHEADER
bmfHdr;
//
位圖文件頭結(jié)構(gòu);
LPBITMAPINFOHEADER
lpBI;//指向位圖頭信息結(jié)構(gòu)的指針;
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);
//圖像的文件信息所占用的字節(jié)數(shù);
DWORD
dwBmBitsSize;//BMP文件中位圖的像素所占的字節(jié)數(shù)
dwBmBitsSize=WIDTHBYTES((lpBI->biWidth)*((DWORD)lpBI->biBitCount))
*lpBI->biHeight;//
存儲(chǔ)時(shí)位圖所有像素所占的總字節(jié)數(shù)
dwDIBSize
+=
dwBmBitsSize;
//BMP文件除文件信息結(jié)構(gòu)外的所有數(shù)據(jù)占用的總字節(jié)數(shù);
lpBI->biSizeImage
=
dwBmBitsSize;
//
位圖所有像素所占的總字節(jié)數(shù)
//以下五句為文件頭結(jié)構(gòu)填充值
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);
//位圖數(shù)據(jù)距離文件頭的偏移量;
file.Write((LPSTR)&bmfHdr,
sizeof(BITMAPFILEHEADER));//向文件中寫文件頭信息;
file.WriteHuge(lpBI,
dwDIBSize);
//將位圖信息(信息頭結(jié)構(gòu)、顏色表、像素?cái)?shù)據(jù))寫入文件;
::GlobalUnlock((HGLOBAL)
m_hDIB);
EndWaitCursor();
SetModifiedFlag(FALSE);
//
將文檔設(shè)為“干凈”標(biāo)志,表示此后文檔不需要存盤提示;
return
TRUE;
}
二、
調(diào)色板的操作
通過上面的操作,我們已經(jīng)可以獲取圖像中的數(shù)據(jù)了,現(xiàn)在的又一個(gè)問題是如何在窗口中顯示出圖像數(shù)據(jù)?;叶葓D像要正確顯示,必須實(shí)現(xiàn)邏輯調(diào)色板和系統(tǒng)調(diào)色板。首先我們介紹一下邏輯調(diào)色板結(jié)構(gòu)LOGPALETTE,該結(jié)構(gòu)定義如下:
typedef
struct
tagLOGPALETTE
{
WORD
palVersion;//調(diào)色板的板本號,應(yīng)該指定該值為0x300;
WORD
palNumEntries;//調(diào)色板中的表項(xiàng)數(shù),對于灰度圖像該值為256;
PALETEENTRY
palPalEntry[1];//調(diào)色板中的顏色表項(xiàng),由于該表項(xiàng)的數(shù)目不一定,所以這里數(shù)組長度定義為1,灰度圖像對應(yīng)的該數(shù)組的長度為256;
}LOGPALETTE;
顏色表項(xiàng)結(jié)構(gòu)PALETTEENTRY定義了調(diào)色板中的每一個(gè)顏色表項(xiàng)的顏色和使用方式,定義如下:
typedef
struct
tagPALETTEENTRY
{
BYTE
peRed;
//R分量值;
BYTE
peGreen;
//G分量值;
BYTE
peBlue;
//B分量值;
BYTE
peFlags;
//
該顏色被使用的方式,一般情況下設(shè)為“0”;
}PALETTEENTRY;
Windows系統(tǒng)使用調(diào)色板管理器來管理與調(diào)色板有關(guān)的操作,通常活動(dòng)窗口的調(diào)色板即是當(dāng)前系統(tǒng)調(diào)色板,所有的非活動(dòng)窗口都必須按照此系統(tǒng)調(diào)色板來顯示自己的顏色,此時(shí)調(diào)色板管理器將自動(dòng)的用系統(tǒng)調(diào)色板中的最近似顏色來映射相應(yīng)的顯示顏色。如果窗口或應(yīng)用程序按自己的調(diào)色板顯示顏色,就必須將自己的調(diào)色板載入到系統(tǒng)調(diào)色板中,這種操作叫作實(shí)現(xiàn)調(diào)色板,實(shí)現(xiàn)調(diào)色板包括兩個(gè)步驟,既首先將調(diào)色板選擇到設(shè)備上下文中,然后在設(shè)備上下文中實(shí)現(xiàn)它??梢酝ㄟ^CDC::SelectPalette()、CDC::RealizePalette()或相應(yīng)的API函數(shù)來實(shí)現(xiàn)上述的兩個(gè)步驟。在實(shí)現(xiàn)調(diào)色板的過程中,通過在主框架類中處理Windows定義的消息WM_QUERYNEWPALETTE
、WM_PALETTECHANGED及視圖類中處理自定義消息WM_DOREALIZE(該消息在主框架窗口定義如下:#define
WM_REALIZEPAL
(WM_USER+101))來實(shí)現(xiàn)調(diào)色板的操作。當(dāng)系統(tǒng)需要處理調(diào)色板的變化時(shí),將向程序的主窗口發(fā)送WM_QUERYNEWPALETTE
、WM_PALETTECHANGED,例如當(dāng)某一窗口即將激活時(shí),主框架窗口將收到WM_QUERYNEWPALETTE消息,通知該窗口將要收到輸入焦點(diǎn),給它一次機(jī)會(huì)實(shí)現(xiàn)其自身的邏輯調(diào)色板;當(dāng)系統(tǒng)調(diào)色板改變后,主框架窗口將收到WM_PALETTECHANGED消息,通知其它窗口系統(tǒng)調(diào)色板已經(jīng)改變,此時(shí)每一窗口都應(yīng)該實(shí)現(xiàn)其邏輯調(diào)色板,重畫客戶區(qū)。
由于上述的調(diào)色板變更消息是發(fā)往主框架窗口的,所以我們只能在主窗口中響應(yīng)這兩個(gè)消息,然后由主框架窗口通知各個(gè)視窗,使得程序激活時(shí)能自動(dòng)裝載自己的調(diào)色板。我們定義的用戶消息WM_REALIZEPAL用于主框架窗口通知視窗它已經(jīng)收到調(diào)色板變更消息,視窗應(yīng)該協(xié)調(diào)其調(diào)色板。下面我們給出了各個(gè)消息的響應(yīng)處理函數(shù)的具體實(shí)現(xiàn)代碼和注釋:
//////////////////////////////////////////////////////////
void
CMainFrame::OnPaletteChanged(CWnd*
pFocusWnd)
{//總實(shí)現(xiàn)活動(dòng)視的調(diào)色板
CMDIFrameWnd::OnPaletteChanged(pFocusWnd);
CMDIChildWnd*
pMDIChildWnd
=
MDIGetActive();//得到活動(dòng)的子窗口指針;
if
(pMDIChildWnd
==
NULL)
return
CView*
pView
=
pMDIChildWnd->GetActiveView();//得到視圖的指針;
ASSERT(pView
!=
NULL);
SendMessageToDescendants(WM_DOREALIZE,
(WPARAM)pView->m_hWnd);
//通知所有子窗口系統(tǒng)調(diào)色板已改變
}
////////////////////////////////////////////////
BOOL
CMainFrame::OnQueryNewPalette()//提供實(shí)現(xiàn)系統(tǒng)調(diào)色板的機(jī)會(huì)
{
//
實(shí)現(xiàn)活動(dòng)視的調(diào)色板
CMDIChildWnd*
pMDIChildWnd
=
MDIGetActive();//得到活動(dòng)的子窗口指針;
if
(pMDIChildWnd
==
NULL)
return
FALSE;//no
active
MDI
child
frame
(no
new
palette)
CView*
pView
=
pMDIChildWnd->GetActiveView();//得到活動(dòng)子窗口的視圖指針;
ASSERT(pView
!=
NULL);
//通知活動(dòng)視圖實(shí)現(xiàn)系統(tǒng)調(diào)色板
pView->SendMessage(WM_DOREALIZE,
(WPARAM)pView->m_hWnd);
return
TRUE;
}
/////////////////////////////////////////////////
BOOL
CDibView::OnDoRealize(WPARAM
wParam,
LPARAM)//實(shí)現(xiàn)系統(tǒng)調(diào)色板
{
ASSERT(wParam
!=
NULL);
CDibDoc*
pDoc
=
GetDocument();
if
(pDoc->m_hDIB
==
NULL)
return
FALSE;
//
must
be
a
new
document
CPalette*
pPal
=
pDoc->m_palDIB;
//調(diào)色板的顏色表數(shù)據(jù)在InitDIBData()函數(shù)中實(shí)現(xiàn)
if
(pPal
!=
NULL)
{
CMainFrame*
pAppFrame
=
(CMainFrame*)
AfxGetApp()->m_pMainWnd;//得到程序的主框架指針;
ASSERT_KINDOF(CMainFrame,
pAppFrame);
CClientDC
appDC(pAppFrame);//獲取主框架的設(shè)備上下文;
CPalette*
oldPalette
=
appDC.SelectPalette(pPal,
((HWND)wParam)
!=
m_hWnd);
//只有活動(dòng)視才可以設(shè)為"FALSE",即根據(jù)活動(dòng)視的調(diào)色板設(shè)為"前景"調(diào)色板;
if
(oldPalette
!=
NULL)
{
UINT
nColorsChanged
=
appDC.RealizePalette();//實(shí)現(xiàn)系統(tǒng)調(diào)色板
if
(nColorsChanged
>
0)
pDoc->UpdateAllViews(NULL);//更新視圖
appDC.SelectPalette(oldPalette,
TRUE);
//將原系統(tǒng)調(diào)色板置為背景調(diào)色板
}
else
{
TRACE0(“\tSelectPalette
failed
in”);
}
return
TRUE;
}
注:在調(diào)用API函數(shù)顯示位圖時(shí),不要忘記設(shè)置邏輯調(diào)色板,即"背景"調(diào)色板,否則位圖將無法正確顯示,讀者可以從后面的顯示部分的實(shí)現(xiàn)看出我們在顯示時(shí)實(shí)現(xiàn)了邏輯調(diào)色板。上述的處理相對來說比較繁瑣復(fù)雜,可能對于初學(xué)者來說也比較難于理解,所以如果我們的程序僅僅限于處理灰度圖象,可以采用另外一種相對簡單的辦法,即在文檔類的初始化階段定義一個(gè)灰度調(diào)色板,然后在設(shè)備上下文中實(shí)現(xiàn)它,這樣作的好處是在度取灰度位圖時(shí)可以不再考慮文件中的顏色表信息,提高了文件讀取速度,筆者在開發(fā)一個(gè)基于機(jī)器視覺的項(xiàng)目時(shí)采用的就是這種方法,取的了比較滿意的效果。首先定義一個(gè)指向邏輯顏色表結(jié)構(gòu)LOGPALETTE的指針pPal,填充該指針,然后將該指針與調(diào)色板指針聯(lián)系起來,該方法的具體實(shí)現(xiàn)如下:
/////////////////////////////////////////////////////////
CDibDoc::CDibDoc()
{
……….
LOGPALETTE
*Pal;
Pal=new
LOGPALETTE;
m_palDIB=new
Cpalette;
pPal->palVersion=0x300;
pPal->palNumEntries=256;
for(int
i=0;i<256;i++)
{//每個(gè)顏色表項(xiàng)的R、G、B值相等,并且各個(gè)值從“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位圖數(shù)據(jù)可以通過設(shè)備上下文CDC對象的成員函數(shù)CDC::Bitblt()或CDC::StretchBlt()來實(shí)現(xiàn),也可以通過API函數(shù)SetDIBBitsToDevice()或StretchDIBBits()來實(shí)現(xiàn),函數(shù)中具體所用到的各個(gè)參數(shù)的意義可以參考MSDN。其中StretchDIBBits()和CDC::StretchBlt()可以將圖像進(jìn)行放大和縮小顯示。當(dāng)從文檔中裝入位圖文件時(shí),CDIBView類的OnInitialUpdate函數(shù)將被調(diào)用,因此可以在該函數(shù)中實(shí)現(xiàn)對視圖尺寸的設(shè)置,用于正確的顯示位圖,然后就可以在視圖類的OnDraw()函數(shù)中正確的顯示位圖了。這兩個(gè)函數(shù)的具體實(shí)現(xiàn)代碼分別如下所示:
/////////////////////////////////////////////////////////////
void
CDIBView::OnInitialUpdate()
{
CscrollView::OnInitalUpdate();
CDIBDoc
*pDoc=GetDocument();
If(pDoc->m_hDIB==NULL)//如果位圖數(shù)據(jù)為空,設(shè)置m_sizeDoc的默認(rèn)尺寸;
pDoc->m_sizeDoc.cx=pDoc->m_sizeDoc.cy=100;
SetScrollSizes(MM_TEXT,pDoc->
m_sizeDoc);
}
/////////////////////////////////////////////////////////////
void
CDIBView::OnDraw(CDC
*pDC)
{
BITMAPINFOHEADER
*lpDIBHdr;//位圖信息頭結(jié)構(gòu)指針;
BYTE
*lpDIBBits;//指向位圖像素灰度值的指針;
BOOL
bSuccess=FALSE;
CPalette*OldPal=NULL;//調(diào)色板指針;
HDC
hDC=pDC->GetSafeHdc();//獲取當(dāng)前設(shè)備上下文的句柄;
CDIBDoc
*pDoc=GetDocument();//獲取活動(dòng)文檔的指針;
If(pDoc->m_hDIB
==NULL)
{//判斷圖像數(shù)據(jù)是否為空;
AfxMessageBox("圖像數(shù)據(jù)不能為空,請首先讀取圖像數(shù)據(jù)!");
return;
}
lpDIBHdr=(
BITMAPINFOHEADER
*)GlobalLock(pDoc->m_hDIB);//得到圖像的位圖頭信息
lpDIBBits=lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//獲取保存圖像像素值的緩沖區(qū)的指針;
if(pDoc->
m_palDIB)
{//如果存在調(diào)色板信息,實(shí)現(xiàn)邏輯調(diào)色板;
OldPal=pDC->
SelectPalette(pDoc->
m_palDIB,TRUE);
PDC->RealizePalette();
}
else
{
AfxMessageBox("圖像的調(diào)色板數(shù)據(jù)不能為空,請首先讀取調(diào)色板信息!");
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)//恢復(fù)調(diào)色板;
PDC->SelectPalette(OldPal,FALSE);
retrun;
}
四、
小結(jié)
在本期講座里我們主要介紹了如何操作灰度位圖,它具有較強(qiáng)的代表性,同時(shí)為后續(xù)的圖像處理編程的學(xué)習(xí)作了必要的準(zhǔn)備工作,經(jīng)過學(xué)習(xí),對于如何操作其它類型的BMP格式的圖像文件,可以達(dá)到舉一反三的作用。
VC數(shù)字圖像處理編程講座之四
在這一講中作者進(jìn)一步深入講解了圖像特效的顯示技術(shù)......
BMP圖像顯示的特效操作
上期講座中我們主要講述了BMP圖像數(shù)據(jù)的存取、圖像的顯示和調(diào)色板的操作等內(nèi)容,在上面的學(xué)習(xí)基礎(chǔ)上,我們可以進(jìn)一步深化,學(xué)習(xí)并掌握圖像特效顯示技術(shù)。有了這種技術(shù),可以用來在今后的項(xiàng)目開發(fā)中美化我們的軟件界面,提高軟件的視覺效果。在如今的商業(yè)軟件中,幾乎每一幅圖像的顯示都采用了圖像特效顯示,例如讀者比較熟悉的Windows的屏幕保護(hù)程序就采用了各種各樣的圖像特效顯示,使人感到眼花繚亂和耳目一新。專業(yè)圖像處理軟件更是提供了豐富的顯示方式供用戶使用,可以方便的在程序中實(shí)現(xiàn)圖像的特效顯示,如PhotoShop
、Authorware等。本節(jié)主要介紹如何實(shí)現(xiàn)圖像的浮雕、雕刻、百頁窗、旋轉(zhuǎn)、掃描、柵條、馬賽克、和漸顯漸隱顯示等效果。通過這期講座的學(xué)習(xí),讀者朋友們也可以自己動(dòng)手制作擁有特效顯示效果的軟件了。
圖像的顯示我們講過主要有BitBlt()、SetDIBitsToDevice()和StretchDIBits()等函數(shù)。需要讀者注意的是,在特效顯示時(shí),并不是每個(gè)顯示函數(shù)都適宜,BitBlt()函數(shù)主要是用來顯示設(shè)備無關(guān)位圖(DDB),后兩個(gè)函數(shù)用來顯示設(shè)備無關(guān)位圖(DIB)。由于我們講座里處理的是設(shè)備無關(guān)位圖,所以我們主要關(guān)心的是后兩個(gè)函數(shù)的應(yīng)用,其中SetDIBitsToDevice()使用起來較死板,遠(yuǎn)不如StretchDIBits()用的靈活,并且對大多數(shù)的特效顯示無能為力,所以為了實(shí)現(xiàn)圖像的特效顯示效果,需要使用StretchDIBits()函數(shù)來顯示圖像,具體什么原因,我想可能是微軟在實(shí)現(xiàn)這些函數(shù)時(shí)使用的方法不同吧。這些函數(shù)如何使用,各個(gè)參數(shù)的含義,可以參考微軟的MSDN。
實(shí)現(xiàn)圖像的特殊效果的顯示的基本思路是要么是操作圖像的像素,要么是對圖像分塊按一定的方向或次序,分階段的顯示或擦除對應(yīng)的圖像塊。對于第二種顯示的思路,其中的要點(diǎn)是:1.劃分圖像塊;2.確定圖像塊的操作次序;3.顯示或清除對應(yīng)的圖像塊;4.在兩個(gè)連續(xù)顯示的圖像塊之間插入一個(gè)固定的延遲。其中圖像塊的劃分決定了圖像的顯示方式,圖像塊的顯示順序決定了顯示的方向和細(xì)分的依據(jù)。不同的效果決定了不同的分塊方法和顯示次序,我們將在后面的各種特效顯示中介紹如何分塊和決定次序。為了使圖像的顯示過程明顯的表現(xiàn)出來,實(shí)現(xiàn)顯示的特效,就需要在圖像塊的依此顯示中插入固定的延遲。也許讀者朋友會(huì)想到利用sleep()函數(shù)或用Settime()來實(shí)現(xiàn)延遲,由于Windows是個(gè)基于消息的多任務(wù)操作系統(tǒng),這些方法所產(chǎn)生的延遲時(shí)間對于圖像的顯示來說是不精確的,為了實(shí)現(xiàn)與機(jī)器無關(guān)的更精確的時(shí)間延遲,可以采用timeGetTime()函數(shù)來產(chǎn)生微秒級的延遲。使用這個(gè)函數(shù)時(shí)為了編譯不產(chǎn)生錯(cuò)誤,要在連接設(shè)置中引入“Winmm.lib”庫,并要包含頭文件“Mmsystem.h”。這里我們首先給出一個(gè)延遲函數(shù),它用來實(shí)現(xiàn)固定時(shí)間的延遲:
void
DelayTime(DWORD
time)
{
DWORD
BeginTime
,EndTime;
BeginTime=timeGetTime();//得到當(dāng)前的系統(tǒng)時(shí)間、單位為微秒;
do
{
EndTime=TimeGetTime();//再次得到當(dāng)前的系統(tǒng)時(shí)間;
}
while((EndTime-BeginTime)
<time)//判斷延遲時(shí)間是否已經(jīng)結(jié)束;
}
一、操作位圖的像素實(shí)現(xiàn)顯示的特效
我們首先介紹直接操作圖像中的像素的灰度值來實(shí)現(xiàn)圖像顯示的特效、這里我們主要介紹如何實(shí)現(xiàn)圖像的浮雕和雕刻效果。經(jīng)??措娨暤呐笥褌儾恢⒁獾?jīng)]有,有些電視連續(xù)劇在每集片頭或片尾部分都有顯示一些特殊效果的圖像,比如前一陣子中央一套放的《長征》和《康熙王朝》,這些特效稱為"圖像的浮雕效果"和"圖像的雕刻效果",經(jīng)過這些特效處理后的圖像增強(qiáng)了觀眾們的視覺效果,它們看上去仿佛是使用3D技術(shù)作的,這也許就是為什么這種技術(shù)那么流行的原因吧。其實(shí),我們完全可以用一些簡單的數(shù)字圖像處理算法來實(shí)現(xiàn)這些看似復(fù)雜高深的顯示效果。下面以一個(gè)標(biāo)準(zhǔn)的Lena灰度圖像為原圖,給出了處理后的效果圖,同時(shí)給出了VC開發(fā)平臺(tái)上的部分實(shí)現(xiàn)源代碼。
1."浮雕"圖像
"浮雕"圖象效果是指圖像的前景前向凸出背景。所謂的"浮雕"概念是指標(biāo)繪圖像上的一個(gè)像素和它左上方的那個(gè)像素之間差值的一種處理過程,為了使圖像保持一定的亮度并呈現(xiàn)灰色,我在處理過程中為這個(gè)差值加了一個(gè)數(shù)值為128的常量。需要讀者注意的是,當(dāng)設(shè)置一個(gè)像素值的時(shí)候,它和它左上方的像素都要被用到,為了避免用到已經(jīng)設(shè)置過的像素,應(yīng)該從圖像的右下方的像素開始處理,下面是實(shí)現(xiàn)的源代碼:
void
CDibView::OnFDImage()
//產(chǎn)生"浮雕"效果圖函數(shù)
{
HANDLE
data1handle;//用來存放圖像數(shù)據(jù)的句柄;
LPBITMAPINFOHEADER
lpBi;//圖像的信息頭結(jié)構(gòu);
CDibDoc
*pDoc=GetDocument();//得到文擋指針;
HDIB
hdib;//用來存放圖像數(shù)據(jù)的句柄;
unsigned
char
*pData;//指向原始圖像數(shù)據(jù)的指針;
unsigned
char
*data;//指向處理后圖像數(shù)據(jù)的指針;
hdib=pDoc->m_hDIB;//拷貝存放已經(jīng)讀取的圖像文件數(shù)據(jù)句柄;
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);//獲取圖像信息頭
pData=(unsigned
char*)FindDIBBits((LPSTR)lpBi);
//FindDIBBits是我定義的一個(gè)函數(shù)、根據(jù)圖像的結(jié)構(gòu)得到位圖的灰度值數(shù)據(jù)、
pDoc->SetModifiedFlag(TRUE);
//設(shè)置文檔修改標(biāo)志為“真”、為后續(xù)的修改存盤作準(zhǔn)備;
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);//聲明一個(gè)緩沖區(qū)用來暫存處理后的圖像數(shù)據(jù);
data=(unsigned
char*)GlobalLock((HGLOBAL)data1handle);//得到該緩沖區(qū)的指針;
AfxGetApp()->BeginWaitCursor();
int
i,j,buf;
for(
i=lpBi->biHeight;
i>=2;
i--)//從圖像右下角開始對圖像的各個(gè)像素進(jìn)行“浮雕”處理;
for(
j=lpBi->biWidth;
j>=2;
j--)
{
//浮雕處理
buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i+1)*WIDTHBYTES(lpBi->biWidth*8)+j-1)+128;
if(buf>255)
buf=255;
if(buf<0)buf=0;
*(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
for(
j=0;
jbiHeight;
j++)
for(
i=0;
ibiWidth;
i++)
//重新寫回原始圖像的數(shù)據(jù)緩沖區(qū);
*(pData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);AfxGetApp()->EndWaitCursor();
pDoc->m_hDIB
=hdib//將處理過的圖像數(shù)據(jù)寫回pDoc中的圖像緩沖區(qū);
GlobalUnlock((HGLOBAL)hdib);//解鎖、釋放緩沖區(qū);
GlobalUnlock((HGLOBAL)data1handle);
GlobalFree((HGLOBAL)hdib);
GlobalFree((HGLOBAL)data1handle);
Invalidate(TRUE);//顯示圖像
}
2."雕刻"圖像
上面講述了通過求一個(gè)像素和它左上方像素之間的差值并加上一個(gè)常數(shù)的方法生成"浮雕"效果的灰度圖像,"雕刻"圖像與之相反,它是通過取一個(gè)像素和它右下方的像素之間的差值并加上一個(gè)常數(shù),這里我也取128,經(jīng)過這樣處理,就可以得到"雕刻"圖像,這時(shí)候圖像的前景凹陷進(jìn)背景之中。同樣需要讀者注意的是為了避免重復(fù)使用處理過的圖像像素,處理圖像時(shí)要從圖像的左上方的像素開始處理。實(shí)現(xiàn)代碼如下:
void
CDibView::OnDKImage()
{
//
TODO:
Add
your
command
handler
code
here
HANDLE
data1handle;//這里的內(nèi)部變量與前面的含義一致、這里不再贅述;
LPBITMAPINFOHEADER
lpBi;
CDibDoc
*pDoc=GetDocument();
HDIB
hdib;
unsigned
char
*pData;
unsigned
char
*data;
hdib=pDoc->m_hDIB;//拷貝圖像數(shù)據(jù)的句柄;
lpBi=(LPBITMAPINFOHEADER)GlobalLock((HGLOBAL)hdib);
pData=(unsigned
char*)FindDIBBits((LPSTR)lpBi);
pDoc->SetModifiedFlag(TRUE);
data1handle=GlobalAlloc(GMEM_SHARE,WIDTHBYTES(lpBi->biWidth*8)*lpBi->biHeight);//申請緩沖區(qū);
data=(unsigned
char*)GlobalLock((HGLOBAL)data1handle);//得到新的緩沖去的指針;AfxGetApp()->BeginWaitCursor();
int
i,j,buf;
for(
i=0;i<=lpBi->biHeight-2;
i++)//對圖像的各個(gè)像素循環(huán)進(jìn)行"雕刻"處理;
for(
j=0;j<=lpBi->biWidth-2;
j++)
{
buf=*(pData+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)-*(pData+(lpBi->biHeight-i-1)*WIDTHBYTES(lpBi->biWidth*8)+j+1)+128;//“雕刻”處理;
if(buf>255)
buf=255;
if(buf<0)buf=0;
*(data+(lpBi->biHeight-i)*WIDTHBYTES(lpBi->biWidth*8)+j)=(BYTE)buf;
}
for(
j=0;
jbiHeight;
j++)
for(
i=0;
ibiWidth;
i++)//重新將處理后的圖像數(shù)據(jù)寫入原始的圖像緩沖區(qū)內(nèi);*(pData+i*WIDTHBYTES(lpBi->biWidth*8)+j)=*(data+i*WIDTHBYTES(lpBi->biWidth*8)+j);
pDoc->m_hDIB
=hdib//將處理過的圖像數(shù)據(jù)寫回pDoc中的圖像緩沖區(qū);
GlobalUnlock((HGLOBAL)hdib);//解鎖、釋放緩沖區(qū);
GlobalUnlock((HGLOBAL)data1handle);
GlobalFree((HGLOBAL)hdib);
GlobalFree((HGLOBAL)data1handle);
Invalidate(TRUE);//顯示圖像
}
3.圖像的旋轉(zhuǎn)
根據(jù)圖像像素的位置來調(diào)節(jié)該位置的灰度可以實(shí)現(xiàn)許多顯示的特效,例如圖像的鏡像、翻轉(zhuǎn)等。灰度圖像旋轉(zhuǎn)就是根據(jù)這一個(gè)思想實(shí)現(xiàn)的,它是指把定義的圖像繞某一點(diǎn)以逆時(shí)針或順時(shí)針方向旋轉(zhuǎn)一定的角度,通常是指繞圖像的中心以逆時(shí)針方向旋轉(zhuǎn)。首先根據(jù)旋轉(zhuǎn)的角度、圖像對角線的長度計(jì)算旋轉(zhuǎn)后的圖像的最大寬度、高度,根據(jù)旋轉(zhuǎn)后圖象最大的寬度、高度生成新的緩沖區(qū),假設(shè)圖像的左上角為(left,
top),右下角為(right,
bottom),則圖像上任意點(diǎn)(x,
y)繞其中心(xcenter,
ycenter)逆時(shí)針旋轉(zhuǎn)angle角度后,新的坐標(biāo)位置(x1,
y1)的計(jì)算公式為:
xcenter
=
(width+1)/2+left;
ycenter
=
(height+1)/2+top;
x1
=
(x-xcenter)
cosθ
-
(y
-
ycenter)
sinθ+xcenter;
y1
=
(x-xcenter)
sinθ+
(y-
ycenter)
cosθ+
ycenter;
與圖像的鏡像變換相類似,下一步就是把原圖中的(x,y)處象素的灰度值讀入新緩沖區(qū)的(x1,y1)點(diǎn)處。注意在新緩沖區(qū)中與原圖沒有對應(yīng)的象素點(diǎn)的值用白色或指定的灰度代替。
二、圖像的分塊顯示和清除
1.
圖像的掃描顯示和清除
掃描顯示圖像是最基本的特效顯示方法,它表現(xiàn)為圖像一行行(或一列列)地顯示出來或從屏幕上清除掉,有種大戲院種的拉幕效果。根據(jù)掃描的方向的不同,可以分為上、下、左、右、水平平分和垂直平分等六種掃描。這里以向下移動(dòng)為例,分別介紹顯示和清除的實(shí)現(xiàn)。其余的掃描效果可以依次類推。向下掃描顯示的實(shí)現(xiàn)方法是:從圖像的底部開始將圖像一行一行的復(fù)制到目標(biāo)區(qū)域的頂部。每復(fù)制一行后,復(fù)制的行數(shù)便要增加一行,并加上一些延遲;向下移動(dòng)清除的實(shí)現(xiàn)方法是圖像向下移動(dòng)顯示,并在顯示區(qū)域的上部畫不斷增高的矩形。
1)掃描顯示的代碼:
CdibView::OnImageDownScan()
{
CDibDoc
*pDoc=GetDocument();
HDIB
hdib;
CClientDC
pDC(this);
hdib=pDoc->m_hDIB;//獲取圖像數(shù)據(jù)句柄;
BITMAPINFOHEADER
*lpDIBHdr;//位圖信息頭結(jié)構(gòu)指針;
BYTE
*lpDIBBits;//指向位圖像素灰度值的指針;
HDC
hDC=pDC.GetSafeHdc();//獲取當(dāng)前設(shè)備上下文的句柄;
lpDIBHdr=(
BITMAPINFOHEADER
*)GlobalLock(hdib);//得到圖像的位圖頭信息;
lpDIBBits=(BYTE*)lpDIBHdr+sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD);//獲取指向圖像像素值;
SetStretchBltMode(hDC,COLORONCOLOR);
//顯示圖像;
for(int
i=0;i<lpDIBHdr->biHeight;i++)
{
//每次循環(huán)顯示圖象的“0”到“i”行數(shù)據(jù);
SetDIBitsToDevice
(hDC,0,0,lpDIBHdr->biWidth,
lpDIBHdr->biHeight,
0,
0,0,
i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS
);
DelayTime(50);//延遲;
}
GlobalUnlock(hdib);
return;
}
2)清除代碼:
…………………//由于篇幅的限制,省略了與上面的相同代碼
Cbrush
brush(crWhite);//定義一個(gè)“白色”的刷子;
Cbrush
*oldbrush=pDC->SelectObject(&brush);
for(int
i=0;i
<
lpDIBHdr->biHeight
;i++)
{//每次循環(huán)將目標(biāo)區(qū)域中的“0”到“i”行刷成“白色”;
pDC->Rectangle(0,0,lpDIBHdr->biWidth,lpDIBHdr->biHeight);
DelayTime(50);
}
…………………
2.
百頁窗效果
所謂百頁窗顯示效果,就如同關(guān)閉和開啟百頁窗一樣,圖像被分為一條條或一列列地分別顯示或清除掉,根據(jù)顯示時(shí)以行或列為單位可以將該效果分為垂直或水平兩種方式。以垂直百頁窗為例來說明如何實(shí)現(xiàn)這種特效顯示。實(shí)現(xiàn)垂直百頁窗顯示時(shí),需要將圖像垂直等分為n部分由上向下掃描顯示,其中每一部分包括m個(gè)條、這個(gè)n可以根據(jù)具體應(yīng)用時(shí)的需要來決定、m既為圖像的高度除n。掃描顯示時(shí),依照差值進(jìn)行掃描顯示,即第k次顯示k-1、k*m-1、…k*n-1條掃描線。同樣,垂直百頁窗清除的實(shí)現(xiàn)與垂直百頁窗的顯示相似,不同的是將繪制位圖換成畫矩形而已。在下面的例子中,我將圖像的分成8份。
…………………
int
m=8;
int
n=lpDIBHdr->biHeight/m;//圖像的高度能夠整除8;
for(int
l=1;l<=m;l++)
for(int
k=0;k<n;k++)
{
//每次循環(huán)依次顯示圖像中的k-1、k*m-1、…k*n-1行;
StretchDIBits
(hDC,0,4*k+l-1,lpDIBHdr->biWidth,1,
0,
lpDIBHdr->biHeight-4*k-l+1,lpDIBHdr->biWidth,1,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//juanlianxiaoguo
DelayTime(50);
}
…………………
3.柵條顯示特效
柵條特效是移動(dòng)特效的復(fù)雜組合,可以分為垂直柵條和水平柵條兩類。它的基本思想是將圖像分為垂直或水平的的小條,奇數(shù)條向上或向左顯示/清除,偶數(shù)條向下或向右顯示/清除。當(dāng)然也可以規(guī)定進(jìn)行相反的方向顯示/清除。下面的代碼是實(shí)現(xiàn)垂直柵條的例子:
…………………
int
m=8;
for(int
i=0;i<=lpDIBHdr->biHeight;i++)
for(int
j=0;j<=lpDIBHdr->biWidth;j+=m)
{//向下顯示偶數(shù)條;
StretchDIBits
(hDC,j,0,m,i,j,lpDIBHdr->biHeight-i,
m,i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//juanlianxiaoguo
j=j+m;
//向上顯示奇數(shù)條;
StretchDIBits
(hDC,j,lpDIBHdr->biHeight-i,m,i,j,0,
m,i,
lpDIBBits,(LPBITMAPINFO)lpDIBHdr,
DIB_RGB_COLORS,
SRCCOPY);//
DelayTime(20);
}…………………
4.馬賽克效果
馬賽克顯示是指圖像被分成許多的小塊,它們以隨機(jī)的次序顯示出來,直到圖像顯示完畢。實(shí)現(xiàn)馬賽克的效果主要解決的問題是如何定義顯示隨機(jī)序列的小方塊,這個(gè)問題的解決可以在定義過小方塊的基礎(chǔ)上,用一個(gè)數(shù)組來記錄各個(gè)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2026山東事業(yè)單位統(tǒng)考壽光市招聘30人備考題庫及答案詳解(奪冠系列)
- 2025至2030中國AI醫(yī)療影像診斷市場供需狀況及前景預(yù)測研究報(bào)告
- 2026年安慶師范大學(xué)附屬龍城幼兒園招聘1名備考題庫有答案詳解
- 2026北京市環(huán)球時(shí)報(bào)新媒體部實(shí)習(xí)生招募備考題庫完整答案詳解
- 2026中國聯(lián)通牟定分公司招聘3人備考題庫及參考答案詳解一套
- 2026天津中醫(yī)藥大學(xué)第三批招聘15人備考題庫及參考答案詳解1套
- 2026上海復(fù)旦大學(xué)基礎(chǔ)醫(yī)學(xué)院招聘實(shí)驗(yàn)室管理員崗位1人備考題庫及答案詳解(新)
- 2026山東菏澤行健高級中學(xué)教師招聘備考題庫完整參考答案詳解
- 2026上半年安徽事業(yè)單位聯(lián)考泗縣招聘39人備考題庫參考答案詳解
- 2026四川南充營華物業(yè)管理有限公司招聘工作人員的28人備考題庫完整參考答案詳解
- 北京通州產(chǎn)業(yè)服務(wù)有限公司招聘考試備考題庫及答案解析
- 河北省NT名校聯(lián)合體2025-2026學(xué)年高三上學(xué)期1月月考英語(含答案)
- 2025-2026學(xué)年滬科版八年級數(shù)學(xué)上冊期末測試卷(含答案)
- 途虎養(yǎng)車安全培訓(xùn)課件
- 衛(wèi)生管理研究論文
- 2025-2026學(xué)年人教版(新教材)小學(xué)數(shù)學(xué)二年級下冊(全冊)教學(xué)設(shè)計(jì)(附教材目錄P161)
- 委托市場調(diào)研合同范本
- 畜牧安全培訓(xùn)資料課件
- 2025年度黨支部書記述職報(bào)告
- 2026四川省引大濟(jì)岷水資源開發(fā)限公司公開招聘易考易錯(cuò)模擬試題(共500題)試卷后附參考答案
- 2026年安徽糧食工程職業(yè)學(xué)院高職單招職業(yè)適應(yīng)性考試備考試題及答案詳解
評論
0/150
提交評論