- Visual C++數字圖像模式識別技術詳解
- 馮偉興 唐墨 賀波等編著
- 11379字
- 2018-12-31 15:10:06
2.2 Visual C++數字圖像處理
本節將在2.1節基礎上介紹如何用Visual C++進行數字圖像處理應用程序的開發。重點介紹Windows下BMP圖像文件格式,以及如何用Visual C++對該數字圖像文件進行讀取,為后續內容打下基礎。
2.2.1 BMP圖像文件
BMP位圖文件格式是Windows系統交換圖像數據的一種標準圖像文件存儲格式,在Windows環境下運行的所有圖像處理軟件都支持這種格式。Windows 3.0以前的BMP位圖文件格式與顯示設備有關,因此把它稱為設備相關位圖(Device-dependent Bitmap,DDB)文件格式。Windows 3.0以后的BMP位圖文件格式與顯示設備無關,因此把這種BMP位圖文件格式稱為設備無關位圖(Deviceindependet Bitmap,DIB)格式,目的是為了讓Windows能夠在任何類型的顯示設備上顯示BMP位圖文件。BMP位圖文件默認的文件擴展名是bmp。
BMP位圖文件是由4個部分組成:位圖文件頭(Bitmapfile Header)、位圖信息頭(Bitmap-information Header)、調色板(Palette)和像素數據(Image Data)。如圖2-15所示。

圖2-15 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數據結構定義位圖信息頭,它包含位圖的大小、壓縮類型和顏色格式等信息,其結構定義如下。
typedefstruct 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結構,其定義如下。
typedefstruct tagRGBQUAD{ BYTE rgbBlue; /*指定藍色分量*/ BYTE rgbGreen; /*指定綠色分量*/ BY7E rgbRed; /*指定紅色分量*/ BYgE rgbReserved; /*保留值*/ }RGBQUAD;
對于24位真彩色圖像不使用調色板,因為位圖中的RGB值就代表了每個像素的顏色。因此BITMAPINFOHEADER后直接是像素數據。
4.像素數據
緊跟在調色板之后的是圖像數據字節陣列,用BYTE數據結構存儲。圖像的每一掃描行由表示圖像的連續像素字節組成,每一行的字節數取決于圖像的顏色數目和用像素表示的圖像寬度。掃描行是由底向上存儲的,這就是說,數據存放是從下到上,從左到右。從文件中最先讀到的圖像數據是位圖最下面的左邊的第一個像素,然后是左邊的第二個像素,而最后讀到的圖像數據是位圖最上面一行的最右邊的一個像素。
2.2.2 位圖文件讀取
本節將實現在多文檔應用程序中打開一個位圖文件的功能。這里將用到Visual C++提供的另一個強大工具ClassWizard。
【例2-2】位圖文件的讀取
本例的目的是講解如何基于Visual C++ 6.0進行圖像處理應用程序的開發。這里構造了一個標準圖像處理類并以打開一幅數字圖像為例解釋了如何基于該類構造圖像處理應用程序。
(1)文檔類中添加成員函數
設計步驟
[1]在Visual C++集成開發環境(IDE)的【View】菜單中選擇【ClassWizard】,在操作類名(Class name)中選擇文檔類“CDemo1Doc”,操作對象(object IDs)選擇“CDemo1Doc”,對應消息(Messages)選擇“OnOpenDocument”,該消息在點擊應用程序中“文件”菜單的“打開”菜單項時產生。該過程如圖2-16所示。
[2]點擊添加函數【Add Function】按鈕,在類C Demo1Doc加入OnOpenDocument成員函數,該函數在程序中出現“OnOpenDocument”消息時執行。該過程如圖2-16所示。

圖2-16 類編輯對話框
該成員函數默認生成代碼如下。
BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if(!CDocument::OnOpenDocument(lpszPathName)) return FALSE; // TODO: Add your specialized creation code here return TRUE; }
(2)數字圖像處理類的創建
設計步驟
由于MFC沒有提供合適的數字圖像處理類,因此,需要定義一個用于數字圖像處理的新類。
[1]首先在Visual C++集成開發環境(IDE)的【Insert】菜單選擇創建新類【New Class】。新類類型【Class type】選擇一般類“Generic Class”。類名【Name】框鍵入“ImageDib”。
[2]單擊【ok】按鈕即可生成該類。該過程如圖2-17所示。

圖2-17 生成新類對話框
[3]在“ImageDib”類的頭文件“ImageDib.h”中編輯該類的結構如下。
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(); //清理空間 };
[4]在“ImageDib”類的代碼文件“ImageDib.cpp”中編輯該類中函數的定義如下。
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); } 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,size of(BITMAPFILEHEADER)); //為m_lpDib分配空間,讀取DIB進內存 if(m_lpDib!=NULL)delete []m_lpDib; m_lpDib=new BYTE[file.GetLength() -size of(BITMAPFILEHEADER)]; file.Read(m_lpDib,file.GetLength() -size of(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+size of(BITMAPINFOHEADER)); MakePalette(); } //m_pImgData指向DIB的位圖數據起始位置 m_pImgData = (LPBYTE)m_lpDib+size of(BITMAPINFOHEADER) + size of(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 = size of(BITMAPFILEHEADER) + size of(BITMAPINFOHEADER) + size of(RGBQUAD) * m_nColorTableLength; try { //文件頭結構寫進文件 file.Write((LPVOID) &bmfh,size of(BITMAPFILEHEADER)); //文件信息頭結構寫進文件 file.Write(m_lpBmpInfoHead,size of(BITMAPINFOHEADER)); //如果有顏色表的話,顏色表寫進文件 if(m_nColorTableLength!=0) file.Write(m_lpColorTable,size of(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 * size of(WORD) + m_nColorTableLength * size of(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::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 [size of(BITMAPINFOHEADER) + size of(RGBQUAD) * m_nColorTableLength+imgBufSize]; //填寫位圖信息頭BITMAPINFOHEADER結構 m_lpBmpInfoHead = (LPBITMAPINFOHEADER) m_lpDib; m_lpBmpInfoHead->biSize = size of(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+size of(BITMAPINFOHEADER)); memcpy(m_lpColorTable,lpColorTable,size of(RGBQUAD) * m_nColorTableLength); MakePalette(); } //m_pImgData指向DIB的位圖數據起始位置 m_pImgData = (LPBYTE)m_lpDib+size of(BITMAPINFOHEADER)+ size of(RGBQUAD) * m_nColorTableLength; //將新位圖數據拷貝至新的DIB中 memcpy(m_pImgData,pImgData,imgBufSize); }
(3)數字圖像顯示功能實現
設計步驟
[1]首先,在“CDemo1Doc”類的頭文件“Demo1Doc.h”中包含“ImageDib類”的聲明文件“ImageDib.h”。
#include "ImageDib.h"
[2]然后,在Visual C++集成開發環境的工作區(Workspace)中選中類“CDemo1Doc”,單擊鼠標右鍵,彈出操作框如圖2-18所示。

圖2-18 添加類新成員操作框
[3]在彈出的操作框中選中【ADD Member Variable】,彈出類新成員對話框,如圖2-19所示。

圖2-19 添加類新成員對話框
[4]在變量類型【Variable Type】輸入“ImageDib”,變量名稱【Variable Name】輸入“m_dib”,其他默認。單擊【ok】按鈕即可完成在“CDemo1Doc”類中添加“ImageDib”類成員變量“m_dib”。
[5]隨后對“CDemo1Doc”類的“OnOpenDocument”函數代碼重新編寫如下。
BOOL CDemo1Doc::OnOpenDocument(LPCTSTR lpszPathName) { if(m_dib.Read(lpszPathName) == TRUE) { SetModifiedFlag(FALSE); // start offwith unmodified return TRUE; } else return0; }
[6]OnOpenDocument函數僅是實現將數字圖像讀入內存,如果在文檔所對應視窗內進行數字圖像顯示,還需對視窗類C Demo1View的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 }
[7]運行程序;選擇【文件】菜單中的【打開】,打開示例圖像“demo.bmp”。運行結果如圖2-20所示。

圖2-20 打開位圖文件程序運行結果
2.2.3 圖像增強
圖像增強是圖像模式識別中非常重要的圖像預處理過程。圖像增強的目的是通過對圖像中的信息進行處理,使得有利于模式識別的信息得到增強,不利于模式識別的信息得到抑制,擴大圖像中不同物體特征之間的差別,為圖像的信息提取及其識別奠定良好的基礎。圖像增強按實現方法不同可分為點增強、空域增強和頻域增強。
1.點增強
點增強主要指圖像灰度變換和幾何變換。
圖像的灰度變換也稱為點運算、對比度增強或對比度拉伸,它是圖像數字化軟件和圖像顯示軟件的重要組成部分。
灰度變換是一種既簡單又重要的技術,它能讓用戶改變圖像數據占據的灰度范圍。一幅輸入圖像經過灰度變換后將產生一幅新的輸出圖像,由輸入像素點的灰度值決定相應的輸出像素點的灰度值?;叶茸儞Q不會改變圖像內的空間關系。
圖像的幾何變換是圖像處理中的另一種基本變換。它通常包括圖像的平移、圖像的鏡像、圖像的縮放和圖像的旋轉。通過圖像的幾何變換可以實現圖像的最基本的坐標變換及縮放功能。
(1)圖像灰度變換類
灰度變換可以按照預定的方式改變一幅圖像的灰度直方圖。除了灰度級的改變是根據某種特定的灰度變換函數進行之外,灰度變換可以看作是“從像素到像素”的復制操作。如果輸入圖像為A(x,y),輸出圖像為B(x,y),則灰度變換可表示為:B(x,y)=f[A(x,y)]。其中,函數f(D)稱為灰度變換函數,它描述了輸入灰度值和輸出灰度值之間的轉換關系。一旦灰度變換函數確定,該灰度變換就完全被確定下來了。
本節設計一個圖像灰度變換類,其目的是將關圖像的灰度變換操作的所有函數封裝到該類中。圖像灰度變換的具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自“ImageDib”類的“GrayTrans”類進行有關圖像的灰度變換操作的算法編程與實現,如圖2-21所示。

圖2-21 添加GrayTrans類示意圖
[3]在文件“GrayTrans.h”中定義該類如下。
代碼2-3 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); };
[4]將“GrayTrans.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【灰度變換】菜單及其子菜單【二值化】、【直方圖】、【直方圖均衡】、【反轉】、【閾值變換】、【窗口變換】、【分段線性拉伸】,如圖2-22所示。
[6]雙擊上述菜單項,設置其ID值,如圖2-23所示。上述菜單項中,【二值化】ID值定義為id_Binary;【直方圖】ID值定義為id_HistogramDraw;【直方圖均衡】定義為id_HistgramAver;【反轉】ID值定義為id_ImageReverse;【閾值變換】ID值定義為id_ImgThresh;【窗口變換】ID值定義為id_ThresholdWindow;【分段線性拉伸】ID值定義為id_LinearStrecth。

圖2-22 灰度變換菜單

圖2-23 設置菜單項ID值
各子函數代碼的實現可參見本書附錄所附程序。
(2)圖像幾何變換類
本節設計一個圖像幾何變換類,其目的是將關圖像的幾何變換操作的所有函數封裝到該類中。圖像幾何變換的具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自“ImageDib”類的“GeometryTrans”類進行有關圖像的幾何變換操作的算法編程與實現,如圖2-24所示。
[3]在文件“GeometryTrans.h”中定義該類如下。
代碼2-4 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旋轉角度 };

圖2-24 添加GeometryTrans類示意圖
[4]將“GeometryTrans.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【幾何變換】菜單及其子菜單【平移】、【水平鏡像】、【垂直鏡像】、【縮放】、【旋轉】、【順時針旋轉90度】、【逆時針旋轉90度】、【旋轉180度】、【任意角度旋轉】,如圖2-25所示。

圖2-25 幾何變換菜單
[6]雙擊上述菜單項,設置其ID值,如圖2-26所示。上述菜單項中,【平移】ID值定義為id_Move;【水平鏡像】ID值定義為id_HorizontalMirror;【垂直鏡像】定義為id_VerticalMirror;【縮放】ID值定義為id_Zoom;【順時針旋轉90度】ID值定義為id_Clockwise90;【逆時針旋轉90度】ID值定義為id_Anticlockwise90;【旋轉180度】ID值定義為id_Rotate180;【任意角度旋轉】ID值定義為id_FreeRotate。

圖2-26 設置菜單項ID值
各子函數代碼的實現可參見本書附錄所附程序。
2.空域增強
一幅數字圖像包括光譜、空間、時間等三類基本信息。對于一幅灰度圖像,其光譜信息是以像素的灰度值來體現的,對光譜信息的增強可以通過前面介紹的各種灰度變換方法來實現,如直方圖均衡化和直方圖規定化可以通過改變像素的灰度值以達到信息增強的目的。圖像間的差值運算可以提供圖像的動態信息(即時間信息)。本節將要介紹對圖像的空間信息進行增強的方法。
(1)圖像空域增強
圖像的空間信息可以反映圖像中物體的位置、形狀、大小等特征,而這些特征可以通過一定的物理模式來描述。例如,物體的邊緣輪廓由于灰度值變化劇烈一般出現高頻率特征,而一個比較平滑的物體內部由于灰度值比較均一則呈現低頻率特征。因此,根據需要可以分別增強圖像的高頻和低頻特征。對圖像的高頻增強可以突出物體的邊緣輪廓,從而起到銳化圖像的作用,例如,對于人臉的比對查詢,就需要通過高頻增強技術突出五官的輪廓。相應地,對圖像的低頻部分進行增強可以對圖像進行平滑處理,一般用于圖像的噪聲消除。
圖像的空域增強是應用模板卷積方法對每一像素的鄰域進行處理完成的,一般可分為線性和非線性兩類。無論采用什么樣的增強方法,其實現步驟大體相同,具體過程如下:
1)將模板在圖像中漫游移動,并將模板中心與每個像素依次重合(邊緣像素除外);
2)將模板中的各個系數與其對應的像素一一相乘,并將所有結果相加(或進行其他四則運算);
3)將2)中的結果賦給圖像中對應模板中心位置的像素。
圖2-27給出了應用模板進行濾波的示意圖。2-27a是一幅圖像的一小部分,共9個像素,Pi(i=0,1,...,8)表示像素的灰度值。2-27b表示一個3×3的模板,ki(i=0,1,...,8)稱為模板系數,模板的大小一般取奇數(如3×3,5×5等)?,F將模板在圖像中漫游,并使k0與圖2-27a所示的P0像素重合,即可由下式計算輸出圖像(增強圖像)中與P0相對應的像素的灰度值r:
對每個像素按上式進行計算即可得到增強圖像中所有像素的灰度值。

圖2-27 空域模板濾波示意圖
(2)圖像空域增強類
本節設計一個圖像空域增強類,其目的是將關圖像的空域變換操作的所有函數封裝到該類中。圖像空域增強的具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自“ImageDib”類的“ImageEnhance”類進行有關圖像的空域增強操作的算法編程與實現,如圖2-28所示。

圖2-28 添加ImageEnhance類示意圖
[3]在文件“ImageEnhance.h”中定義該類如下:
代碼2-5 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]將“ImageEnhance.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【圖像空域增強】菜單及其子菜單【鄰域平均】、【中值平均】、【掩模平滑】、【梯度銳化】、【拉普拉斯銳化】,如圖2-29所示。
[6]雙擊上述菜單項,設置其ID值,如圖2-30所示。上述菜單項中,【高斯噪聲】ID值定義為id_GaussNoise;【椒鹽噪聲】ID值定義為id_PepperSaltNosie;【鄰域平均】ID值定義為id_PowerSmooth;【中值平均】ID值定義為id_MedianSmooth;【掩模平滑】ID值定義為id_ChooseMaskSmooth;【梯度銳化】ID值定義為id_GradeSharp;【拉普拉斯銳化】ID值定義為id_LaplaceSharp。

圖2-29 圖像空域增強菜單

圖2-30 設置菜單項ID值
各子函數代碼的實現可參見本書附錄所附程序。
3.頻域增強
圖像空域增強一般只是對數字圖像進行局部增強,而圖像頻域增強則可以對圖像進行全局增強。
頻域增強技術是在數字圖像的頻率域空間對圖像進行濾波,因此需要將圖像從空間域變換到頻率域,一般通過傅里葉變換即可實現。在頻率域空間的濾波與空域濾波一樣可以通過卷積實現,因此傅里葉變換和卷積理論是頻域濾波技術的基礎。
假定函數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)的低頻分量,就可以使圖像顯得比較平滑,即低通濾波。
在介紹具體的濾波器之前,先根據以上的描述給出頻域濾波的主要步驟:
1)對原始圖像f(x,y)進行傅里葉變換得到F(u,v);
2)將F(u,v)與傳遞函數H(u,v)進行卷積運算得到G(u,v);
3)將G(u,v)進行傅里葉反變換得到增強圖像g(x,y)。
圖像頻域增強類
本節設計一個圖像頻域增強類,其目的是將關圖像的頻域變換操作的所有函數封裝到該類中。圖像頻域增強的具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自“ImageDib”類的“ImageFreqEnhance”類進行有關圖像的頻域增強操作的算法編程與實現,如圖2-31所示。

圖2-31 添加ImageFreqEnhance類示意圖
[3]在文件“ImageFreqEnhance.h”中定義該類如下。
代碼2-6 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(); };
[4]將“ImageFreqEnhance.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【圖像頻域增強】菜單及其子菜單【快速傅里葉變換】、【快速傅里葉反變換】、【理想低通濾波】、【巴特沃斯低通濾波】、【理想高通濾波】、【巴特沃斯高通濾波】、【哈爾小波變換】、【哈爾小波反變換】,如圖2-32所示。

圖2-32 圖像頻域增強菜單
[6]雙擊上述菜單項,設置其ID值,如圖2-33所示。上述菜單項中,【快速傅里葉變換】ID值定義為id_QuickFFt;【快速傅里葉反變換】ID值定義為id_QuickFFt_Reverse;【理想低通濾波】ID值定義為id_IdealLowPass;【巴特沃斯低通濾波】ID值定義為id_ButterLowPass;【理想高通濾波】ID值定義為id_IdealHighPass;【ButterWorth高通濾波】ID值定義為id_ButterHighPass;【哈爾小波變換】ID值定義為id_HarrWaveletTrans;【哈爾小波反變換】ID值定義為id_HarrWavRevTrans。
各子函數代碼的實現可參見本書附錄所附程序。

圖2-33 設置菜單項ID值
2.2.4 圖像形態學處理
數學形態學是一種應用于圖像處理和模式識別領域的新的方法。形態學是生物學的一個分支,常用來處理動物和植物的形狀和結構。數學形態學是建立在嚴格的數學理論基礎上的科學。用于描述數學形態學的語言是集合論,利用數學形態學對物體幾何結構的分析過程就是主客體相互逼近的過程。利用數學形態學的幾個基本概念和運算,將結構元素靈活地組合、分解,應用形態變換序列達到分析的目的。
數學形態學是以集合代數為基礎的,用集合的方法定量描述幾何結構的科學。數學形態學應用于數字圖像處理以后,用形態學來處理圖像去描述某些區域的形狀如邊界曲線、骨架結構和凸形外殼。另外,還可用形態學技術進行預測和快速處理如形態過濾、形態細化、形態修飾等。而這些處理都是基于一些基本運算實現的。
1.數學形態學的基本概念
用于描述形態學的語言是集合論。集合代表圖像中物體的形狀,例如:在二值圖像中所有的黑色像素點的集合就是這幅圖像的完整描述。在二值圖像中,當前集合是指二維整形空間的成員,集合中的每個元素就是一個二維變量,用(x,y)表示。按規則代表圖像中的一個黑色像素點?;叶葦底謭D像可以用三維集合來表示。在這種情況下,集合中每個元素的前兩個元素表示像素點的坐標,第三個變量代表離散的灰度值。在更高維的空間集合中可以包括其他的圖像屬性,如顏色和時間。
形態學運算的質量取決于所選取的結構元素和形態變換。結構元素的選擇要根據具體情況來確定,而形態運算的選擇必須滿足一些基本的約束條件。這些約束條件稱為圖像定量分析的原則。下面列出了數學形態學的幾條定量分析原則:
(1)平移不變性
設待分析的圖像為X,Φ表示某種圖像變換或運算,Φ(X)表示X經變換或運算后的新圖像。設h為一矢量,Xh表示將圖像X平移一個位移矢量后的結果,那么,平移不變性原則可表示為
Φ(Xh)=[Φ(X)]h
此式說明,圖像X先平移然后變換的結果與圖像先變換后平移的結果相一致。
(2)尺度變換不變性
設縮放因子λ是一個正的實常數,λX表示對圖像X所做的相似變換,則尺度變換不變性可表示為
如果設圖像運算Φ為結構元素B對X的腐蝕(記為XΘB),則Φλ為結構元素λB對X的腐蝕,則上式具體化為
(3)局部知識原理
如果Z是一個圖形(閉集),則相對于Z存在另一個閉集Z',使得對于圖形X有下式成立:
(Φ (X∩Z))∩Z'=Φ(X)∩Z'
可以將Z理解為一個“掩?!?。在實際中,觀察某一個對象時,每次只能觀察一個局部,即某一掩模覆蓋的部分。該原則要求對每種確定的變換或運算Φ,當掩模Z選定以后,都能找到一個相應的模板Z',使得通過Z'所觀察到的局部性質,即(Φ(X∩Z))∩Z'與整體性質Φ(X)∩Z'相一致。
(4)半連續原理
在研究一幅圖像時,常采用逐步逼近的方法,即對圖像X的研究往往需要通過一系列圖像X1,X2,…,Xn,…的研究實現,其中諸個Xn逐步逼近X。半連續原理要求各種圖像變換后應滿足這樣的性質:對真實圖像X的處理結果應包含在對一系列圖像Xn的處理結果內。
(5)形態運算的基本性質
除了一些特殊情況外,數學形態學處理一般都是不可逆的。實際上,對圖像進行重構的思想在該情況下是不恰當的。任何形態處理的目的都是通過變換法去除不感興趣的信息,保留感興趣的信息。在形態運算中的幾個關鍵性質如下。
■遞增性:XY
Φ(X)
Φ(Y)
X,Y∈ξ(E)
■反擴展性:Φ(X)X
X∈ξ(E)
■冪等性:Φ[Φ (X)]=Φ(X)
其中,ξ(E)表示歐幾里得(Euclidean)空間E的冪級。
2.數學形態學的表示
集合論是數學形態學的基礎,這里首先對集合論的一些基本概念作一總結性的概括介紹。對于形態處理的討論,將從兩個最基本的模加處理和模減處理開始。它們是以后大多數形態處理的基礎。
(1)集合
具有某種性質的確定的有區別的事物的全體。如果某種事物不存在,稱為空集。集合常用大寫字母A,B,C,…表示,空集用Φ表示。
設E為一自由空間,ξ(E)是由集合空間E所構成的冪集,集合X,B∈ξ(E),則集合X和B之間只能有以下三種形式(如圖2-34所示):
1)集合B1擊中X,表示為B1X。

圖2-34 集合表示形式
2)集合B2離于X,表示為B2Xc。
3)集合B3含于X,表示為B3X。
(2)元素
構成集合的每一個事物稱之為元素。元素常用小寫字母A,B,C,…表示,應注意的是,任何事物都不是空集的元素。
(3)平移轉換
設A和B是兩個二維集合,A和B中的元素分別是
a=(a1,a2),b=(b1,b2)
定義x=(x1,X2),對集合A的平移轉換為
(A)x={c|c=a+x,a∈A}
(4)子集
當且僅當集合A的所有元素都屬于B時,稱A是B的子集。
(5)補集
定義集合A的補集為
Ac={x | xA}
(6)差集
定義集合A和B的差集為
A?b={x | x∈A,xB}=A∩Bc
(7)映像
定義集合A的映像為?,定義為?
?={x | x=?a,a∈A}
(8)并集
由集合A和B的所有元素組成的集合稱為A和B的并集。
(9)交集
由集合A和B的公共元素組成的集合稱為A和B的交集。
3.圖像形態學類的設計
本節設計一個圖像形態學類,其目的是將關圖像的形態學變換操作的所有函數封裝到該類中。圖像形態學變換的具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自“ImageDib”類的“Morphology”類進行有關圖像的形態學變換操作的算法編程與實現,如圖2-35所示。
[3]在文件“Morphology.h”中定義該類如下。

圖2-35 添加Morphology類示意圖
代碼2-7 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);//擊中擊不中變換 };
[4]將“Morphology.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【形態學】菜單及其子菜單【腐蝕】、【膨脹】、【開運算】、【閉運算】、【擊中擊不中細化】,如圖2-36所示。

圖2-36 灰度變換菜單
[6]雙擊上述菜單項,設置其ID值,如圖2-37所示。上述菜單項中,【腐蝕】ID值定義為id_Erosion;【膨脹】ID值定義為id_Dilation;【開運算】ID值定義為id_Open;【閉運算】ID值定義為id_Close;【擊中擊不中細化】ID值定義為id_Thinning。

圖2-37 設置菜單項ID值
各子函數代碼的實現可參見本書附錄所附程序。
2.2.5 圖像分割
圖像分割是一種重要的圖像處理技術,是圖像分析和理解的第一步。圖像分割在很多領域有著廣泛的應用,如工業圖像處理、軍事圖像處理、生物醫學圖像處理、圖像傳輸、文本圖像分析處理和識別、身份鑒定、機器人視覺等。不同類型的圖像,有不同的分割方法對其進行分割,而同時,某些分割方法也只適用于某些特殊類型的圖像分割。
圖像分割就是將圖像分成具有不同特性的區域,并提取出感興趣的區域的過程。早期的圖像分割方法可以分成兩大類:一是邊界法,應用這種方法時一般假設圖像分割結果的某個子區域在原來圖像中一定會有邊緣存在;二是區域法,應用這種方法時一般假設圖像分割結果的某個子區域一定會有相同的性質,而不同區域的像素則沒有共同的性質。
現在,隨著計算機處理能力的提高,涌現出了很多其他的方法,如基于模型的圖像分割、基于彩色分量、紋理的圖像分割,以及基于人工智能的圖像分割方法等。
圖2-38所示是圖像分割的方法框架。

圖2-38 圖像分割方法框架
圖像分割類的設計
本節設計一個圖像分割類,其目的是將關圖像的分割操作的所有函數封裝到該類中。不同的圖像分割方法及其具體成員函數將在后幾節中依次介紹。首先介紹該類的創建。
設計步驟
[1]打開2.1.5節創建的工程文件demo1。
[2]在工作區(Workspace)的類視圖(Class View)編輯區中,選中【demo classes】后單擊鼠標右鍵并選擇【New Class】。添加繼承自ImageDib類的“ImgSegment”類進行有關圖像分割操作的算法編程與實現,如圖2-39所示。
[3]在文件“ImgSegment.h”中定義該類如下。

圖2-39 添加ImgSegment類示意圖
代碼2-8 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(); };
[4]將“ImgSegment.h”頭文件包含進“demoView.cpp”文件中。
[5]利用資源管理器,在菜單條上加入【圖像分割】菜單及其子菜單【直方圖閾值分割】【自適應閾值分割】、【Robert算子】、【Sobel算子】、【Prewitt算子】、【Laplacian算子】、【邊界跟蹤】、【區域生長】,如圖2-40所示。

圖2-40 圖像分割菜單
[6]雙擊上述菜單項,設置其ID值,如圖2-41所示。上述菜單項中,【直方圖閾值分割】ID值定義為id_HistThreshSeg;【自適應閾值分割】ID值定義為id_AdaptiveThreshold;【Robert算子】ID值定義為id_Robert;【Sobel算子】ID值定義為id_Sobel;【Prewitt算子】ID值定義為id_Prewitt;【Laplacian算子】ID值定義為id_Laplacian;【邊界跟蹤】ID值定義為id_EdgeTrace;【區域生長】ID值定義為id_RegionGrow。

圖2-41 設置菜單項ID值
各子函數代碼的實現可參見本書附錄所附程序。
- Go語言高效編程:原理、可觀測性與優化
- Production Ready OpenStack:Recipes for Successful Environments
- 前端架構:從入門到微前端
- Banana Pi Cookbook
- Cassandra Data Modeling and Analysis
- Microsoft System Center Orchestrator 2012 R2 Essentials
- 人人都懂設計模式:從生活中領悟設計模式(Python實現)
- 深度學習:Java語言實現
- 小程序,巧應用:微信小程序開發實戰(第2版)
- Essential C++(中文版)
- Instant Zurb Foundation 4
- 基于GPU加速的計算機視覺編程:使用OpenCV和CUDA實時處理復雜圖像數據
- Keil Cx51 V7.0單片機高級語言編程與μVision2應用實踐
- Building a Media Center with Raspberry Pi
- 精益軟件開發管理之道