- Visual C++數字圖像模式識別典型案例詳解
- 馮偉興 梁洪 王臣業編著
- 7393字
- 2018-12-31 19:38:55
1.3.2 Visual C++數字圖像處理類
本節將首先介紹典型的BMP圖像文件格式,并圍繞BMP圖像文件格式給出Visual C++平臺下的數字圖像處理基本類的一個實現實例,以此作為后續數字圖像模式識別實際案例的編程基礎。
1.BMP圖像文件
BMP位圖文件格式是Windows系統交換圖像數據的一種標準圖像文件存儲格式,在Windows環境下運行的所有圖像處理軟件都支持這種格式。Windows 3.0以前的BMP位圖文件格式與顯示設備有關,因此稱其為設備相關位圖(Device-dependent Bitmap,DDB)文件格式。Windows 3.0以后的BMP位圖文件格式與顯示設備無關,因此把這種BMP位圖文件格式稱為設備無關位圖(Device-independet Bitmap,DIB)格式,目的是為了讓Windows能夠在任何類型的顯示設備上顯示BMP位圖文件。BMP位圖文件默認的文件擴展名是.bmp。
BMP位圖文件由4部分組成:位圖文件頭(Bitmap-file Header)、位圖信息頭(Bitmap-information Header)、調色板數據(Palette Data)和像素數據(Image Data),如圖1-6所示。

圖1-6 BMP圖像文件結構
1)位圖頭文件
Visual C++中用BITMAPFILEHEADER數據結構定義位圖頭文件,它包含文件類型、文件大小和存放位置等信息,其結構如下:
typedef struct tagBITMAPFILEHEADER{ WORD bfType; /*說明文件的類型*/ DWORD bfSize; /*說明文件的大小,以字節為單位*/ WORD bfReserved1; /*保留,設置為0*/ WORD bfReserved2; /*保留,設置為0*/ DWORD bfOffBits; /*說明從BITMAPFILEHEADER結構開始到實際圖像數據陣列字節 /*間的字節偏移量*/ }BITMAPFILEHEADER;
這個結構的長度是固定的,為14個字節,其中WORD為無符號16位二進制數,DWORD為無符號32位二進制整數。
2)位圖信息頭
Visual C++中用BITMAPINFOHEADER數據結構定義位圖信息頭,它包含位圖的大小、壓縮類型和顏色格式等信息,其結構如下:
typedef struct BITMAPINFOHEADER{ DWORD biSize; /*BITMAPINFOHEADER結構所需要的字節數*/ LONG biWidth; /*圖像的寬度,以像素為單位*/ LONG biHeight; /*圖像的高度,以像素為單位*/ WORD biPlanes; /*目標設備位平面數,其值設置為l*/ WORD biBitCount; /*每像素位數,為1、4、8或24之一*/ DWORD biCompression; /*壓縮類型,0為不壓縮*/ DWORD biSizeImage; /*壓縮圖像大小的字節數,非壓縮圖像為0*/ LONG biXPelsPerMeter; /*水平分辨率*/ LONG biYPelsPerMeter; /*垂直分辨率*/ DWORD biClrUsed; /*使用的色彩數*/ DWORD biClrImportant; /*重要色彩數,0表示都重要*/ } BITMAPINFOHEADER;
3)調色板
Visual C++中,調色板實際上定義為一個數組,共有biClrUsed個元素,每個元素的類型是一個RGBQUAD結構,其定義如下:
typedef struct tagRGBQUAD{ BYTE rgbBlue; /*指定藍色分量(值范圍為0~255)*/ BYTE rgbGreen; /*指定綠色分量(值范圍為0~255)*/ BYTE rgbRed; /*指定紅色分量(值范圍為0~255)*/ BYTE rgbReserved; /*保留值,必須為0*/ }RGBQUAD;
對于24位真彩色圖像,其不使用調色板,因為位圖中的RGB值就代表了每個像素的顏色,因此BITMAPINFOHEADER后直接是像素數據。
4)像素數據
緊跟在調色板之后的是圖像數據字節陣列,用BYTE數據結構存儲。圖像的每一掃描行由表示圖像的連續像素字節組成,每一行的字節數取決于圖像的顏色數目和用像素表示的圖像寬度。掃描行是由底向上存儲的,這就是說,數據存放是從下到上,從左到右。從文件中最先讀到的圖像數據是位圖最下面的最左邊的第一個像素,然后是最左邊的第二個像素,而最后讀到的圖像數據是位圖最上面一行的最右邊的一個像素。
2.數字圖像處理類
本書所采用的數字圖像處理類的類名為ImageDib。在ImageDib類的頭文件ImageDib.h中編輯該類的結構,如代碼1-3所示。
代碼1-3 ImageDib類頭文件
class ImageDib { //成員變量 public: unsigned char * m_pImgData; //圖像數據指針 LPRGBQUAD m_lpColorTable; //圖像顏色表指針 int m_nBitCount; //每像素占的位數 private: LPBYTE m_lpDib; //指向DIB的指針 HPALETTE m_hPalette; //邏輯調色板句柄 int m_nColorTableLength; //顏色表長度(多少個表項) public: int m_imgWidth; //圖像的寬,以像素為單位 int m_imgHeight; //圖像的高,以像素為單位 LPBITMAPINFOHEADER m_lpBmpInfoHead; //圖像信息頭指針 //成員函數 public: ImageDib(); //構造函數 ~ImageDib(); //析構函數 BOOL Read(LPCTSTR lpszPathName); //DIB讀函數 BOOL Write(LPCTSTR lpszPathName); //DIB寫函數 int ComputeColorTabalLength(int nBitCount);//計算顏色表的長度 BOOL Draw(CDC* pDC, CPoint origin, CSize size); //圖像繪制 CSize GetDimensions(); //讀取圖像維數 void ReplaceDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); //用新的數據替換DIB private: void MakePalette(); //創建邏輯調色板 void Empty(); //清理空間 };
ImageDib類的函數體文件ImageDib.cpp中編輯該類的實現如代碼1-4所示。
代碼1-4 ImageDib類函數體
ImageDib::ImageDib() { m_lpDib=NULL; //初始化m_lpDib為空 m_lpColorTable=NULL; //顏色表指針為空 m_pImgData=NULL; //圖像數據指針為空 m_lpBmpInfoHead=NULL; //圖像信息頭指針為空 m_hPalette = NULL; //調色板為空 } ImageDib::~ImageDib() { //釋放m_lpDib所指向的內存緩沖區 if(m_lpDib != NULL) delete [] m_lpDib; //如果有調色板,釋放調色板緩沖區 if(m_hPalette != NULL) ::DeleteObject(m_hPalette); } ImageDib::ImageDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData) { //如果沒有位圖數據傳入,我們認為是空的DIB,此時不分配DIB內存 if(pImgData==NULL){ m_lpDib=NULL; m_lpColorTable=NULL; m_pImgData=NULL; // 圖像數據 m_lpBmpInfoHead=NULL; // 圖像信息頭 m_hPalette = NULL; } else{//如果有位圖數據傳入 //則為圖像的寬、高、每像素位數等成員變量賦值 m_imgWidth=size.cx; m_imgHeight=size.cy; m_nBitCount=nBitCount; //根據每像素位數,計算顏色表長度 m_nColorTableLength=ComputeColorTabalLength(nBitCount); //每行像素所占字節數,必須擴展成4的倍數 int lineByte=(m_imgWidth*nBitCount/8+3)/4*4; //位圖數據緩沖區的大?。▓D像大?。?/span> int imgBufSize=m_imgHeight*lineByte; //為m_lpDib一次性分配內存,生成DIB結構 m_lpDib=new BYTE [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength+imgBufSize]; //填寫BITMAPINFOHEADER結構 m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib; m_lpBmpInfoHead->biSize = sizeof(BITMAPINFOHEADER); m_lpBmpInfoHead->biWidth = m_imgWidth; m_lpBmpInfoHead->biHeight = m_imgHeight; m_lpBmpInfoHead->biPlanes = 1; m_lpBmpInfoHead->biBitCount = m_nBitCount; m_lpBmpInfoHead->biCompression = BI_RGB; m_lpBmpInfoHead->biSizeImage = 0; m_lpBmpInfoHead->biXPelsPerMeter = 0; m_lpBmpInfoHead->biYPelsPerMeter = 0; m_lpBmpInfoHead->biClrUsed = m_nColorTableLength; m_lpBmpInfoHead->biClrImportant = m_nColorTableLength; //調色板句柄初始化為空,有顏色表時,MakePalette()函數要生成新的調色板 m_hPalette = NULL; //如果有顏色表,則將顏色表復制進DIB的顏色表位置 if(m_nColorTableLength!=0){ //m_lpColorTable指向DIB顏色表的起始位置 m_lpColorTable=(LPRGBQUAD)(m_lpDib+sizeof (BITMAPINFOHEADER)); //顏色表復制 memcpy(m_lpColorTable,lpColorTable,sizeof(RGBQUAD)* m_nColorTableLength); //創建邏輯調色板 MakePalette(); } //m_pImgData指向DIB位圖數據起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER)+ sizeof(RGBQUAD) * m_nColorTableLength; //復制圖像數據進DIB位圖數據區 memcpy(m_pImgData,pImgData,imgBufSize); } } BOOL ImageDib::Read(LPCTSTR lpszPathName) { //讀模式打開圖像文件 CFile file; if (!file.Open(lpszPathName, CFile::modeRead | CFile::shareDenyWrite)) return FALSE; BITMAPFILEHEADER bmfh; //讀取BITMAPFILEHEADER結構到變量bmfh中 int nCount=file.Read((LPVOID) &bmfh, sizeof(BITMAPFILEHEADER)); //為m_lpDib分配空間,讀取DIB進內存 if(m_lpDib!=NULL) delete []m_lpDib; m_lpDib=new BYTE[file.GetLength() -sizeof(BITMAPFILEHEADER)]; file.Read(m_lpDib, file.GetLength() -sizeof(BITMAPFILEHEADER)); //m_lpBmpInfoHead位置為m_lpDib起始位置 m_lpBmpInfoHead = (LPBITMAPINFOHEADER)m_lpDib; //為成員變量賦值 m_imgWidth=m_lpBmpInfoHead->biWidth; m_imgHeight=m_lpBmpInfoHead->biHeight; m_nBitCount=m_lpBmpInfoHead->biBitCount; //計算顏色表長度 m_nColorTableLength= ComputeColorTabalLength(m_lpBmpInfoHead->biBitCount); //如果有顏色表,則創建邏輯調色板 m_hPalette = NULL; if(m_nColorTableLength!=0){m_lpColorTable= (LPRGBQUAD)(m_lpDib+sizeof(BITMAPINFOHEADER)); MakePalette(); } //m_pImgData指向DIB的位圖數據起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength; return TRUE; } BOOL ImageDib::Write(LPCTSTR lpszPathName) { //以寫模式打開文件 CFile file; if (!file.Open(lpszPathName, CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive)) return FALSE; //填寫文件頭結構 BITMAPFILEHEADER bmfh; bmfh.bfType = 0x4d42; // 'BM' bmfh.bfSize = 0; bmfh.bfReserved1 = bmfh.bfReserved2 = 0; bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength; try { //文件頭結構寫進文件 file.Write((LPVOID) &bmfh, sizeof(BITMAPFILEHEADER)); //文件信息頭結構寫進文件 file.Write(m_lpBmpInfoHead, sizeof(BITMAPINFOHEADER)); //如果有顏色表的話,顏色表寫進文件 if(m_nColorTableLength!=0) file.Write(m_lpColorTable, sizeof(RGBQUAD) * m_nColorTableLength); //位圖數據寫進文件 int imgBufSize=(m_imgWidth*m_nBitCount/8+3)/4*4*m_imgHeight; file.Write(m_pImgData, imgBufSize); } catch(CException* pe) { pe->Delete(); AfxMessageBox("write error"); return FALSE; } //函數返回 return TRUE; } void ImageDib::MakePalette() { //如果顏色表長度為0,則不創建邏輯調色板 if(m_nColorTableLength == 0) return; //刪除舊的邏輯調色板句柄 if(m_hPalette != NULL) ::DeleteObject(m_hPalette); //申請空間,根據顏色表生成LOGPALETTE結構 LPLOGPALETTE pLogPal = (LPLOGPALETTE) new char[2 * sizeof(WORD) + m_nColorTableLength * sizeof(PALETTEENTRY)]; pLogPal->palVersion = 0x300; pLogPal->palNumEntries = m_nColorTableLength; LPRGBQUAD m_lpDibQuad = (LPRGBQUAD) m_lpColorTable; for(int i = 0; i < m_nColorTableLength; i++) { pLogPal->palPalEntry[i].peRed = m_lpDibQuad->rgbRed; pLogPal->palPalEntry[i].peGreen = m_lpDibQuad->rgbGreen; pLogPal->palPalEntry[i].peBlue = m_lpDibQuad->rgbBlue; pLogPal->palPalEntry[i].peFlags = 0; m_lpDibQuad++; } //創建邏輯調色板 m_hPalette = ::CreatePalette(pLogPal); //釋放空間 delete pLogPal; } int ImageDib::ComputeColorTabalLength(int nBitCount) { int colorTableLength; switch(nBitCount) { case 1: colorTableLength = 2; break; case 4: colorTableLength = 16; break; case 8: colorTableLength = 256; break; case 16: case 24: case 32: colorTableLength = 0; break; default: ASSERT(FALSE); } ASSERT((colorTableLength >= 0) && (colorTableLength <= 256)); return colorTableLength; } BOOL ImageDib::Draw(CDC* pDC, CPoint origin, CSize size) { HPALETTE hOldPal=NULL; //舊的調色板句柄 if(m_lpDib == NULL) return FALSE; //如果DIB為空,則返回0 if(m_hPalette != NULL) { //如果DIB有調色板 //將調色板選進設備環境中 hOldPal=::SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE); pDC->RealizePalette(); } pDC->SetStretchBltMode(COLORONCOLOR);//設置位圖伸縮模式 //將DIB在pDC所指向的設備上進行顯示 ::StretchDIBits(pDC->GetSafeHdc(), origin.x, origin.y, size.cx, size.cy, 0, 0, m_lpBmpInfoHead->biWidth, m_lpBmpInfoHead->biHeight,m_pImgData, (LPBITMAPINFO) m_lpBmpInfoHead, DIB_RGB_COLORS, SRCCOPY); if(hOldPal!=NULL) //恢復舊的調色板 ::SelectPalette(pDC->GetSafeHdc(), hOldPal, TRUE); return TRUE; } CSize ImageDib::GetDimensions() { if(m_lpDib == NULL) return CSize(0, 0); return CSize(m_imgWidth, m_imgHeight); } void ImageDib::Empty() { //釋放DIB內存緩沖區 if(m_lpDib != NULL) { delete [] m_lpDib; m_lpDib=NULL; m_lpColorTable=NULL; m_pImgData=NULL; m_lpBmpInfoHead=NULL; } //釋放邏輯調色板緩沖區 if(m_hPalette != NULL){ ::DeleteObject(m_hPalette); m_hPalette = NULL; } } void ImageDib::ReplaceDib(CSize size, int nBitCount, LPRGBQUAD lpColorTable,unsigned char *pImgData) { //釋放原DIB所占空間 Empty(); //成員變量賦值 m_imgWidth=size.cx; m_imgHeight=size.cy; m_nBitCount=nBitCount; //計算顏色表的長度 m_nColorTableLength=ComputeColorTabalLength(nBitCount); //每行像素所占字節數,擴展成4的倍數 int lineByte=(m_imgWidth*nBitCount/8+3)/4*4; //位圖數據的大小 int imgBufSize=m_imgHeight*lineByte; //為m_lpDib重新分配空間,以存放新的DIB m_lpDib=new BYTE [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * m_nColorTableLength+imgBufSize]; //填寫位圖信息頭BITMAPINFOHEADER結構 m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib; m_lpBmpInfoHead->biSize = sizeof(BITMAPINFOHEADER); m_lpBmpInfoHead->biWidth = m_imgWidth; m_lpBmpInfoHead->biHeight = m_imgHeight; m_lpBmpInfoHead->biPlanes = 1; m_lpBmpInfoHead->biBitCount = m_nBitCount; m_lpBmpInfoHead->biCompression = BI_RGB; m_lpBmpInfoHead->biSizeImage = 0; m_lpBmpInfoHead->biXPelsPerMeter = 0; m_lpBmpInfoHead->biYPelsPerMeter = 0; m_lpBmpInfoHead->biClrUsed = m_nColorTableLength; m_lpBmpInfoHead->biClrImportant = m_nColorTableLength; //調色板置空 m_hPalette = NULL; //如果有顏色表,則將顏色表復制至新生成的DIB,并創建邏輯調色板 if(m_nColorTableLength!=0){ m_lpColorTable=(LPRGBQUAD)(m_lpDib+sizeof(BITMAPINFOHEADER)); memcpy(m_lpColorTable,lpColorTable,sizeof(RGBQUAD)* m_nColorTableLength); MakePalette(); } //m_pImgData指向DIB的位圖數據起始位置 m_pImgData = (LPBYTE)m_lpDib+sizeof(BITMAPINFOHEADER)+ sizeof(RGBQUAD) * m_nColorTableLength; //將新位圖數據復制至新的DIB中 memcpy(m_pImgData,pImgData,imgBufSize); }
3.數字圖像處理類的應用
本實例將實現在多文檔應用程序中打開一個位圖文件并顯示的功能。
設計步驟
[1] 在文檔類CDemo1Doc類的頭文件Demo1Doc.h中包含ImageDib類的聲明文件ImageDib.h。
#include "ImageDib.h"
[2] 在Visual C++集成開發環境(IDE)的“View”菜單中選擇“ClassWizard”命令,在操作類名(Class name)中選擇文檔類CDemo1Doc,操作對象(Object IDs)選擇CDemo1Doc,對應消息(Messages)選擇OnOpenDocument,該消息在單擊應用程序中“文件”菜單的“打開”菜單項時產生。
單擊添加函數 按鈕,在類CDemo1Doc中加入OnOpenDocument成員函數,該函數在程序中出現OnOpenDocument消息時執行。該過程如圖1-7所示。

圖1-7 類編輯對話框
該成員函數默認生成代碼如下:
BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE; // TODO: Add your specialized creation code here return TRUE; }
[3] 在Visual C++集成開發環境(IDE)的工作區(Workspace)中選中類CDemo1Doc,右擊,彈出的快捷菜單如圖1-8所示。

圖1-8 添加類新成員操作框
在彈出的快捷菜單中單擊“Add Member Variable”命令,彈出類新成員對話框,如圖1-9所示。

圖1-9 添加類新成員對話框
在變量類型“Variable Type”中輸入“ImageDib”,變量名稱“Variable Name”中輸入“m_dib”,其他缺省。單擊按鈕即可完成在CDemo1Doc類中添加ImageDib類成員變量m_dib的操作。
[4] 隨后對CDemo1Doc類的CDemo1Doc和OnOpenDocument函數代碼重新編寫,重新編寫后的代碼如下:
CDemo1Doc::CDemo1Doc() { // TODO: add one-time construction code here m_dib = new ImageDib; } BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if (m_dib.Read(lpszPathName) == TRUE) { SetModifiedFlag(FALSE); // start off with unmodified return TRUE; } else return 0; }
[5] OnOpenDocument函數僅實現將數字圖像讀入內存,如果在文檔所對應視窗內進行數字圖像顯示,還需對視窗類CDemo1View的OnDraw函數進行編程。首先在類CDemo1View的頭文件Demo1View.h中包含ImageDib類的聲明文件ImageDib.h。
然后對OnDraw函數進行編程,相關代碼如下:
void CDemo1View::OnDraw(CDC* pDC) { CDemo1Doc* pDoc = GetDocument(); //獲取文檔類指針 ImageDib pDib=pDoc->m_dib; //返回m_dib的指針 CSize sizeFileDib = pDib.GetDimensions(); //獲取DIB的尺寸 pDib.Draw(pDC, CPoint(0, 0), sizeFileDib); //顯示DIB }
[6] 運行程序,選擇菜單“文件”中的“打開”命令,打開本書附帶光盤中的demo.bmp文件。運行結果如圖1-10所示。

圖1-10 打開位圖文件程序運行結果
4.數字圖像處理類的擴展
在數字圖像基礎類上,常用的數字圖像功能進行了如下擴展。
(1)圖像灰度變換類
灰度變換可以按照預定的方式改變一幅圖像的灰度直方圖。除了灰度級的改變是根據某種特定的灰度變換函數進行之外,灰度變換可以看作是“從像素到像素”的復制操作。如果輸入圖像為A(x, y),輸出圖像為B(x, y),則灰度變換可表示為:B(x, y)= f[A(x, y)]。其中函數f( )稱為灰度變換函數,它描述了輸入灰度值和輸出灰度值之間的轉換關系。一旦灰度變換函數確定,該灰度變換就被完全確定。
本節設計的圖像灰度變換類名為GrayTrans。在GrayTrans類的頭文件GrayTrans.h中編輯該類的結構,如代碼1-5所示。
代碼1-5 GrayTrans函數
class GrayTrans:public ImageDib { public: //輸出圖像每像素位數 int m_nBitCountOut; //輸出圖像位圖數據指針 unsigned char * m_pImgDataOut; //輸出圖像顏色表 LPRGBQUAD m_lpColorTableOut; //輸出圖像的寬,以像素為單位 int m_imgWidthOut; //輸出圖像的高,以像素為單位 int m_imgHeightOut; //輸出圖像顏色表長度 int m_nColorTableLengthOut; public: //不帶參數的構造函數 GrayTrans(); //帶參數的構造函數 GrayTrans(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char*pImgData); //析構函數 ~GrayTrans(); //以像素為單位返回輸出圖像的寬和高 CSize GetDimensions(); //二值化 void BinaryImage(int threshold=128); //反轉 void RevImage(); //窗口變換 void ThresholdWindow( int bTop, int bBottom); //分段線性拉伸 void LinearStrech(CPoint point1,CPoint point2); private: //單通道數據線性拉伸 void LinearStrechForSnglChannel(unsigned char *pImgDataIn, unsigned char *pImgDataOut,int imgWidth,int imgHeight, CPoint point1,CPoint point2); };
各子函數代碼的實現可參見本書所附光盤。
(2)圖像幾何變換類
本節將設計一個圖像幾何變換類,其目的是將關于圖像的幾何變換操作的所有函數封裝到該類中。圖像幾何變換類名為GeometryTrans。在GeometryTrans類的頭文件GeometryTrans.h中編輯該類的結構,如代碼1-6所示。
代碼1-6 GeometryTrans函數
class GeometryTrans : public ImageDib { public: //輸出圖像每像素位數 int m_nBitCountOut; //輸出圖像位圖數據指針 unsigned char * m_pImgDataOut; //輸出圖像顏色表 LPRGBQUAD m_lpColorTableOut; //輸出圖像的寬 int m_imgWidthOut; //輸出圖像的高 int m_imgHeightOut; //輸出圖像顏色表長度 int m_nColorTableLengthOut; public: //構造函數 GeometryTrans(); //帶參數的構造函數 GeometryTrans(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char*pImgData); //析構函數 ~GeometryTrans(); //以像素為單位返回輸出圖像的寬和高 CSize GetDimensions(); //平移 void Move(int offsetX, int offsetY); //縮放 void Zoom(float ratioX, float ratioY);//縮放 //水平鏡像 void MirrorHorTrans(); //垂直鏡像 void MirrorVerTrans(); //順時針旋轉90度 void Clockwise90(); //逆時針旋轉90度 void Anticlockwise90(); //旋轉180 void Rotate180(); //0~360度之間任意角度旋轉 void Rotate(int angle);//angle旋轉角度 };
各子函數代碼的實現可參見本書所附光盤。
(3)圖像空域增強類
圖像的空間信息可以反映圖像中物體的位置、形狀、大小等特征,而這些特征可以通過一定的物理模式來描述。例如,物體的邊緣輪廓由于灰度值變化劇烈一般出現高頻率特征,而一個比較平滑的物體內部由于灰度值比較均一則呈現低頻率特征。因此,根據需要可以分別增強圖像的高頻和低頻特征。對圖像的高頻增強可以突出物體的邊緣輪廓,從而起到銳化圖像的作用,例如,對于人臉的比對查詢,就需要通過高頻增強技術突出五官的輪廓。相應地,對圖像的低頻部分進行增強可以對圖像進行平滑處理,一般用于圖像的噪聲消除。
圖像的空域增強是應用模板卷積方法對每一像素的鄰域進行處理完成的,一般可分為線性和非線性兩類。無論采用什么樣的增強方法,其實現步驟大體相同,具體過程如下:
(a)將模板在圖像中漫游移動,并將模板中心與每個像素依次重合(邊緣像素除外);
(b)將模板中的各個系數和與其對應的像素一一相乘,并將所有結果相加(或進行其他四則運算);
(c)將步驟(b)中的結果賦給圖像中對應模板中心位置的像素。
圖1-11為應用模板進行濾波的示意圖。1-11a所示是一幅圖像的一小部分,共9個像素,Pi(i=0,1,…,8)表示像素的灰度值。1-11b所示表示一個3×3的模板,Ki(i=0,1,…,8)稱為模板系數,模板的大小一般取奇數(如3×3,5×5等)?,F將模板在圖像中漫游,并使K0與圖1-11a所示的P0像素重合,即可由下式計算輸出圖像(增強圖像)中與P0相對應的像素的灰度值r:

圖1-11 空域模板濾波示意圖

對每個像素按上式進行計算即可得到增強圖像中所有像素的灰度值。
本節將設計一個圖像空域增強類,其目的是將關于圖像的空域變換操作的所有函數封裝到該類中。圖像空域增強類名為ImageEnhance。在ImageEnhance類的頭文件ImageEnhance.h中編輯該類的結構,如代碼1-7所示。
代碼1-7 ImageEnhance函數
class ImageEnhance:public ImageDib { public: int m_nBitCountOut; //輸出圖像每像素位數 unsigned char * m_pImgDataOut; //輸出圖像位圖數據指針 LPRGBQUAD m_lpColorTableOut; //輸出圖像顏色表 int m_nColorTableLengthOut; //輸出圖像顏色表長度 public: ImageEnhance(); //構造函數 ImageEnhance(CSize size,int nBitCount,LPRGBQUAD lpColorTable,unsigned char*pImgData); //帶參數的構造函數 ~ImageEnhance(); //析構函數 void NeiAveTemplate(int TempH, int TempW, int TempCX, int TempCY, float*fpTempArray, float fCoef); //采用均值模板進行圖像平滑 //中值濾波 BYTE FindMedianValue(unsigned char* lpbArray,int iArrayLen); void MedianSmooth(int iFilterH, int iFilterW, int iFilterCX, int iFilterCY); //拉普拉斯銳化,轉化為模板運算 void LapTemplate(int TempH, int TempW, int TempCX, int TempCY, float *fpTempArray,float fCoef); //梯度銳化 void GradeSharp(int Thresh); //選擇掩模平滑 void ChooseMaskSmooth(); };
各子函數代碼的實現可參見本書所附光盤。
(4)圖像頻域增強類
圖像空域增強一般只是對數字圖像進行局部增強,而圖像頻域增強則可以對圖像進行全局增強。
頻域增強技術是在數字圖像的頻率域空間對圖像進行濾波,因此需要將圖像從空間域變換到頻率域,一般通過傅里葉變換即可實現。在頻率域空間的濾波與空域濾波一樣可以通過卷積實現,因此傅里葉變換和卷積理論是頻域濾波技術的基礎。
假定函數f(x, y)與線性位不變算子h(x, y)的卷積結果是g(x, y),即
g(x, y) = h(x, y)*f(x, y)
相應的,由卷積定理可得到下述頻域關系:
G(u, v) = H(u, v)×F(u, v)
式中,G、H、F分別是函數g、h、f的傅里葉變換;H(u, v)稱為傳遞函數或濾波器函數。在圖像增強中,圖像函數f(x, y)是已知的,即待增強的圖像,因此F(u, v)可由圖像的傅里葉變換得到。實際應用中,首先需要確定的是H(u, v),然后就可以求得G(u, v),對G(u, v)求傅里葉反變換后即可得到增強的圖像g(x, y)。g(x, y)可以突出f(x, y)的某一方面的特征,如利用傳遞函數H(u, v)突出F(u, v)的高頻分量,以增強圖像的邊緣信息,即高通濾波;反之,如果突出F(u, v)的低頻分量,就可以使圖像顯得比較平滑,即低通濾波。
在介紹具體的濾波器之前,先根據以上的描述給出頻域濾波的主要步驟:
(a)對原始圖像f(x, y)進行傅里葉變換得到F(u, v);
(b)將F(u, v)與傳遞函數H(u, v)進行卷積運算得到G(u, v);
(c)將G(u, v)進行傅里葉反變換得到增強圖像g(x, y)。
本節設計一個圖像頻域增強類,其目的是將有關圖像的頻域變換操作的所有函數封裝到該類中。圖像空域增強類名為ImageFreqEnhance。在ImageFreqEnhance類的頭文件ImageFreqEnhance.h中編輯該類的結構,如代碼1-8所示。
代碼1-8 ImageFreqEnhance函數
class ImageFreqEnhance:public ImageDib { public: //輸出圖像每像素位數 int m_nBitCountOut; //輸出圖像位圖數據指針 unsigned char * m_pImgDataOut; //輸出圖像顏色表 LPRGBQUAD m_lpColorTableOut; int m_imgWidthOut; //輸出圖像的寬 int m_imgHeightOut;//輸出圖像的高 int m_nColorTableLengthOut;//輸出圖像顏色表長度 public: /傅里葉變換類對象 FourierTrans FFtTrans; public: //構造函數 ImageFreqEnhance(); //帶參數的構造函數 ImageFreqEnhance(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); CSize GetDimensions();//以像素為單位返回輸出圖像的寬和高 void InputImageData(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); //輸入原圖像數據 void IdealLowPassFilter(int nWidth, int nHeight, int nRadius); //理想低通濾波 void ButterLowPassFilter(int nWidth, int nHeight, int nRadius); //巴特沃斯低通濾波 void IdealHighPassFilter(int nWidth, int nHeight, int nRadius); //理想高通濾波 void ButterHighPassFilter(int nWidth, int nHeight, int nRadius); //巴特沃斯高通濾波; //析構函數 virtual ~ImageFreqEnhance(); };
各子函數代碼的實現可參見本書所附光盤。
(5)圖像形態學處理類
形態學是生物學的一個分支,常用來處理動物和植物的形狀和結構。數學形態學是一種應用于圖像處理和模式識別領域的新的方法。數學形態學是建立在嚴格的數學理論基礎上的科學。用于描述數學形態學的語言是集合論,利用數學形態學對物體幾何結構的分析過程就是主客體相互逼近的過程。利用數學形態學的幾個基本概念和運算,可將結構元素靈活地組合、分解,應用形態變換序列達到分析的目的。
數學形態學是以集合代數為基礎的,用集合的方法定量描述幾何結構的科學。數學形態學應用于數字圖像處理以后,開始用形態學來處理圖像,去描述某些區域的形狀,如邊界曲線、骨架結構和凸形外殼。另外,還可用形態學技術進行預測和快速處理如形態過濾、形態細化、形態修飾等。而這些處理都是基于一些基本運算實現的。
本節將設計一個圖像形態學類,其目的是將關于圖像的形態學變換操作的所有函數封裝到該類中。圖像形態學類名為Morphology。在Morphology類的頭文件Morphology.h中編輯該類的結構,如代碼1-9所示。
代碼1-9 Morphology函數
//結構元素對,該結構專門為擊中、擊不中變換而定義 struct ElementPair { int hitElement[9]; int missElement[9]; }; class Morphology:public ImageDib { public: //輸出圖像每像素位數 int m_nBitCountOut; //輸出圖像位圖數據指針 unsigned char * m_pImgDataOut; //輸出圖像顏色表 LPRGBQUAD m_lpColorTableOut; //輸出圖像的寬,以像素為單位 int m_imgWidthOut; //輸出圖像的高,以像素為單位 int m_imgHeightOut; //輸出圖像顏色表長度 int m_nColorTableLengthOut; //結構元素(模板)指針 int *m_maskBuf; //結構元素寬 int m_maskW; //結構元素高 int m_maskH; //定義8個方向的擊中、擊不中變換結構元素對 ElementPair m_hitMissTemp[8]; public: Morphology(); //不帶參數的構造函數 Morphology(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char*pImgData); //帶參數的構造函數 virtual ~Morphology(); //析構函數 public: CSize GetDimensions(); //返回輸出圖像的尺寸 void ImgErosion(unsigned char *imgBufIn,unsigned char *imgBufOut, int imgWidth,int imgHeight,int *TempBuf, int TempW, int TempH);//腐蝕 void ImgDilation(unsigned char *imgBufIn,unsigned char *imgBufOut,int imgWidth,int imgHeight,int *maskBuf, int maskW, int maskH);//膨脹 void Open();//二值開 void Close(); //二值閉 void ImgThinning(); //擊中、擊不中細化 void DefineElementPair();//定義擊中、擊不中變換的結構元素對 void HitAndMiss(unsigned char *imgBufIn, unsigned char *imgBufOut, int imgWidth,int imgHeight,ElementPair hitMissMask); //擊中、擊不中變換 };
各子函數代碼的實現可參見本書所附光盤。
(6)圖像分割類
圖像分割就是將圖像分成具有不同特性的區域,并提取出感興趣的區域的過程。圖像分割是一種重要的圖像處理技術,是圖像分析和理解的第一步。圖像分割在很多領域都有著廣泛的應用,如工業圖像處理、軍事圖像處理、生物醫學圖像處理、圖像傳輸、文本圖像分析處理和識別、身份鑒定、機器人視覺等。不同類型的圖像,有不同的分割方法,其中,一些分割方法也只適用于某些特殊類型的圖像分割。
早期的圖像分割方法可以分成兩大類:一是邊界法,應用這種方法時一般假設圖像分割結果的某個子區域在原來圖像中一定會有邊緣存在;二是區域法,應用這種方法時一般假設圖像分割結果的某個子區域一定會有相同的性質,而不同區域的像素則沒有共同的性質?,F在,隨著計算機處理能力的提高,涌現出了很多其他的方法,如基于模型的圖像分割,基于彩色分量、紋理的圖像分割以及基于人工智能的圖像分割方法等。
圖1-12所示的是圖像分割方法的框架。

圖1-12 圖像分割方法的框架
本節將設計一個圖像分割類,其目的是將關于圖像的分割操作的所有函數封裝到該類中。圖像分割類名為ImgSegment。在ImgSegment類的頭文件ImgSegment.h中編輯該類的結構,如代碼1-10所示。
代碼1-10 ImgSegment函數
class ImgSegment:public ImageDib { public: //輸出圖像每像素位數 int m_nBitCountOut; //輸出圖像位圖數據指針 unsigned char * m_pImgDataOut; //輸出圖像顏色表 LPRGBQUAD m_lpColorTableOut; //輸出圖像的寬 int m_imgWidthOut; //輸出圖像的高 int m_imgHeightOut; //輸出圖像顏色表長度 int m_nColorTableLengthOut; public: //不帶參數的構造函數 ImgSegment(); //帶參數的構造函數 ImgSegment(CSize size, int nBitCount, LPRGBQUAD lpColorTable, unsigned char *pImgData); //析構函數 virtual ~ImgSegment(); public: //以像素為單位返回輸出圖像的尺寸 CSize GetDimensions(); //自適應閾值分割 void AdaptThreshSeg(unsigned char *pImgData); //Roberts算子 void Roberts(); //Sobel算子 void Sobel(); //Prewitt算子 void Prewitt(); //Laplacian算子 void Laplacian(); public: //區域生長 void RegionGrow(CPoint SeedPos, int thresh); //曲線跟蹤 void EdgeTrace(); };
各子函數代碼的實現可參見本書所附光盤。