【數字影象處理】三.MFC實現影象灰度、取樣和量化功能詳解

Eastmount發表於2015-05-28
        本文主要講述基於VC++6.0 MFC影象處理的應用知識,主要結合自己大三所學課程《數字影象處理》及課件進行講解,主要通過MFC單文件檢視實現顯示BMP格式圖片,並通過Bitmap進行灰度處理、圖片取樣和量化功能。
        個人認為對初學者VC++6.0可能還是很值得學習的工具,所以採用它來講解,而不是VS或C#。同時文章比較詳細基礎,希望該篇文章對你有所幫助~
       【數字影象處理】一.MFC詳解顯示BMP格式圖片
       【數字影象處理】二.MFC單文件分割視窗顯示圖片
        
免費資源下載地址:
        http://download.csdn.net/detail/eastmount/8748403


一. 單文件顯示BMP圖片

        第一步:新建專案"MFC AppWizard(exe)",專案名為ImageProcessing,在應用程式型別中選擇"單個文件",點選"確定"。在左欄的"資源檢視"中,點選"Menu->IDR_MAINFRAM"可以檢視並修改選單檢視。


        第二步:向CImageProcessingView類新增成員變數和成員函式。在右欄的"類檢視"右鍵ImageProcessingView新增函式或直接在ImageProcessingView.h中直接新增public成員變數和成員函式。新增程式碼如下:
// Implementation
public:
	//新增成員函式
	void ShowBitmap(CDC* pDC,CString BmpName); //顯示點陣圖函式

	//新增成員變數
	CString EntName;     //影象副檔名
	CString BmpName;     //影象檔名稱
	CBitmap m_bitmap;    //建立點陣圖物件
        同時採用類檢視新增後,會自動在XXXView.h中新增函式定義,在XXXView.cpp中新增函式實現程式碼。



        第三步:編輯ImageProcessingView.cpp中ShowBitmap()函式。通過它顯示BMP圖片,其中程式碼及詳細註釋如下:
//****************顯示BMP格式圖片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
	//定義bitmap指標 呼叫函式LoadImage裝載點陣圖
	HBITMAP m_hBitmap;
	m_hBitmap = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
		
	/*************************************************************************/
	/* 1.要裝載OEM影象,則設此引數值為0  OBM_ OEM點陣圖 OIC_OEM圖示 OCR_OEM游標
	/* 2.BmpName要裝載圖片的檔名                  
	/* 3.裝載影象型別: 
	/*   IMAGE_BITMAP-裝載點陣圖 IMAGE_CURSOR-裝載游標 IMAGE_ICON-裝載圖示    
	/* 4.指定圖示或游標的畫素寬度和長度 以畫素為單位    
	/* 5.載入選項:
	/*   IR_LOADFROMFILE-指明由lpszName指定檔案中載入影象
	/*   IR_DEFAULTSIZE-指明使用影象預設大小
	/*   LR_CREATEDIBSECTION-當uType引數為IMAGE_BITMAP時,建立一個DIB項
	/**************************************************************************/

	if( m_bitmap.m_hObject )
	{
		m_bitmap.Detach();           //切斷CWnd和視窗聯絡
	}
	m_bitmap.Attach(m_hBitmap);      //將控制程式碼HBITMAP m_hBitmap與CBitmap m_bitmap關聯
	
	//邊界
	CRect rect;
	GetClientRect(&rect);

	//圖片顯示(x,y)起始座標
	int m_showX=0;
	int m_showY=0;
	int m_nWindowWidth = rect.right - rect.left;   //計算客戶區寬度
	int m_nWindowHeight = rect.bottom - rect.top;  //計算客戶區高度
		
	//定義並建立一個記憶體裝置環境DC
	CDC dcBmp;
	if( !dcBmp.CreateCompatibleDC(pDC) )   //建立相容性的DC
		return;
	BITMAP m_bmp;                          //臨時bmp圖片變數
	m_bitmap.GetBitmap(&m_bmp);            //將圖片載入點陣圖中
	CBitmap *pbmpOld = NULL;      
	dcBmp.SelectObject(&m_bitmap);         //將點陣圖選入臨時記憶體裝置環境
	
	//圖片顯示呼叫函式stretchBlt
	pDC->StretchBlt(0,0,m_bmp.bmWidth,m_bmp.bmHeight,&dcBmp,0,0,
		m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
	
	/*******************************************************************************/
	/* BOOL StretchBlt(int x,int y,int nWidth,int nHeight,CDC* pSrcDC, 
	/*                 int xSrc,int ySrc,int nSrcWidth,int nSrcHeight,DWORD dwRop );
	/* 1.引數x、y點陣圖目標矩形左上角x、y的座標值      
	/* 2.nWidth、nHeigth點陣圖目標矩形的邏輯寬度和高度      
	/* 3.pSrcDC表示源裝置CDC指標                          
	/* 4.xSrc、ySrc表示點陣圖源矩形的左上角的x、y邏輯座標值 
	/* 5.dwRop表示顯示點陣圖的光柵操作方式 SRCCOPY用於直接將點陣圖複製到目標環境中            
	/*******************************************************************************/
	
	dcBmp.SelectObject(pbmpOld);           //恢復臨時DC的點陣圖
	DeleteObject(&m_bitmap);               //刪除記憶體中的點陣圖
	dcBmp.DeleteDC();                      //刪除CreateCompatibleDC得到的圖片DC


	/**
	 * 面程式碼為後面顯示第二張圖片
	 */

}
        第四步:設定開啟BMP圖片函式。"檢視"->"建立類嚮導"(Ctrl+W)->選擇"類名"CImageProcessing->在命令物件ID中雙擊"ID_FILE_OPEN"->自動生成預設成員函式OnFileOpen,訊息為COMMAND。雙擊成員函式(Member Functions)進入函式編輯。
        編輯ImageProcessingView.cpp函式實現開啟圖片,程式碼如下:
//****************開啟檔案****************//
void CImageProcessingView::OnFileOpen() 
{
	//兩種格式的檔案:bmp gif
    CString filter;  
    filter="所有檔案(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";  
    CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,filter,NULL);            
  
    //按下確定按鈕 dlg.DoModal() 函式顯示對話方塊  
    if( dlg.DoModal() == IDOK )  
    {  
        BmpName = dlg.GetPathName();     //獲取檔案路徑名   如D:\pic\abc.bmp  
        EntName = dlg.GetFileExt();      //獲取副檔名  
        EntName.MakeLower();             //將副檔名轉換為一個小寫字元  
        Invalidate();                    //呼叫該函式就會呼叫OnDraw重繪畫圖  
    }  	
}
        第五步:在ImageProcessingView.cpp中找到OnDraw()函式,通過OnDraw()函式呼叫ShowBitmap()函式顯示圖片。程式碼如下:
void CImageProcessingView::OnDraw(CDC* pDC)
{
	CImageProcessingDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
	if (!pDoc) return;  
    if( EntName.Compare(_T("bmp")) == 0 )      //bmp格式  
    {  
        ShowBitmap(pDC,BmpName);               //顯示圖片  
    }  
}
        第六步:此時點選執行,同時點選檔案-開啟,即可顯示圖片如下圖所示:

        PS:這是非常著名的一張圖片萊娜圖(Lenna),全圖是一張花花公子封面的裸圖,後成為數字影象處理的標誌圖片。哈哈~至於BMP圖片格式參照第一篇文章


二. 讀取BMP圖片和儲存圖片

        BMP圖片格式如下圖所示:(參考自己文庫)

        在很多處理中,都需要獲取BMP影象的一些資料,如影象寬度、高度、畫素大小等,後面的處理與之相關,主要的是ReadBmp函式。
       第一步:在XXXView.h中新增BMP格式影象相關的成員變數和成員函式,其中成員函式通過類檢視右鍵新增,成員變數可以在XXXView.h中直接複製。
// Implementation
public:
	//新增成員函式
	void ShowBitmap(CDC* pDC,CString BmpName); //顯示點陣圖函式
	bool ReadBmp();                            //用來讀取bmp個手機圖片
	bool SaveBmp(LPCSTR lpFileName);           //用來儲存bmp格式圖片

	//新增成員變數
	CString EntName;     //影象副檔名
	CString BmpName;     //影象檔名稱
	CBitmap m_bitmap;    //建立點陣圖物件

	int	m_nWidth;		//影象實際寬度
	int	m_nHeight;		//影象實際高度
	int	m_nDrawWidth;	//影象顯示寬度
	int	m_nDrawHeight;	//影象顯示高度
	DWORD m_nImage;		//影象資料的位元組數 只含點陣圖
	DWORD m_nSize;      //影象檔案大小
	int m_nLineByte;    //影象一行所佔位元組數
	int	m_nBitCount;    //影象每個畫素所佔位數
	int	m_nPalette;     //點陣圖實際使用的顏色表中的顏色數
	
	BYTE *m_pImage;         //讀入圖片資料後的指標
	BITMAPFILEHEADER bfh;   //全域性變數檔案頭
	BITMAPINFOHEADER bih;   //全域性變數資訊頭
	RGBQUAD m_pPal;         //顏色表指標
        第二步:在ImageProcessingView.cpp中實現ReadBmp函式和SaveBmp函式。
//***************讀取圖片資料*************//
bool CImageProcessingView::ReadBmp()
{
	//圖片讀出儲存其中的東西
	FILE *fp = fopen(BmpName,"rb");
	if(fp==0)
	{		
		AfxMessageBox("無法開啟檔案!",MB_OK,0);
		return 0; 
	}
	//讀取檔案頭 解決BMP格式倒置的方法
	fread(&bfh.bfType,sizeof(WORD),1,fp);
	fread(&bfh.bfSize,sizeof(DWORD),1,fp);
	fread(&bfh.bfReserved1,sizeof(WORD),1,fp);
	fread(&bfh.bfReserved2,sizeof(WORD),1,fp);
	fread(&bfh.bfOffBits,sizeof(DWORD),1,fp);
	//影象檔案的總位元組數
	m_nSize = bfh.bfSize;
	//判斷是否是bmp格式圖片
	if(bfh.bfType!=0x4d42)   //'BM'
	{
		AfxMessageBox("不是BMP格式圖片!",MB_OK,0);
		return 0;
	}
	//讀取資訊頭
	fread(&bih.biSize,sizeof(DWORD),1,fp);
	fread(&bih.biWidth,sizeof(LONG),1,fp);
	fread(&bih.biHeight,sizeof(LONG),1,fp);
	fread(&bih.biPlanes,sizeof(WORD),1,fp);
	fread(&bih.biBitCount,sizeof(WORD),1,fp);
	fread(&bih.biCompression,sizeof(DWORD),1,fp);
	fread(&bih.biSizeImage,sizeof(DWORD),1,fp);
	fread(&bih.biXPelsPerMeter,sizeof(LONG),1,fp);
	fread(&bih.biYPelsPerMeter,sizeof(LONG),1,fp);
	fread(&bih.biClrUsed,sizeof(DWORD),1,fp);
	fread(&bih.biClrImportant,sizeof(DWORD),1,fp);
	if(bih.biSize!=sizeof(bih))
	{
		AfxMessageBox("本結構所佔用位元組數出現錯誤");
		return 0;
	}
	//點陣圖壓縮型別,必須是 0(不壓縮) 1(BI_RLE8壓縮型別)或2(BI_RLE壓縮型別)之一
	if(bih.biCompression == BI_RLE8 || bih.biCompression == BI_RLE4)
	{
		AfxMessageBox("點陣圖被壓縮!");
		return 0;
	}
	//獲取影象高寬和每個畫素所佔位數
	m_nHeight = bih.biHeight;
	m_nWidth = bih.biWidth;
	m_nDrawHeight = bih.biHeight;
	m_nDrawWidth = bih.biWidth;
	m_nBitCount = bih.biBitCount;   //每個畫素所佔位數
	//計算影象每行畫素所佔的位元組數(必須是32的倍數)
	m_nLineByte = (m_nWidth*m_nBitCount+31)/32*4;
	//圖片大小 呼叫系統自帶的檔案頭 BITMAPFILEHEADER bfh; BITMAPINFOHEADER bih; 
	//否則用 BITMAPFILEHEADER_ bfh; BITMAPINFOHEADER_ bih;要 m_nImage = m_nLineByte * m_nHeight - 2;
	m_nImage = m_nLineByte * m_nHeight;
	//點陣圖實際使用的顏色表中的顏色數 biClrUsed
	m_nPalette = 0;                       //初始化
	if(bih.biClrUsed)
		m_nPalette = bih.biClrUsed;
	//申請點陣圖空間 大小為點陣圖大小 m_nImage
	//malloc只能申請4位元組的空間 (未知)
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fp);
	fclose(fp);
	return true;
}
       其中SaveBmp()函式程式碼如下:
//****************儲存檔案****************//
bool CImageProcessingView::SaveBmp(LPCSTR lpFileName) //lpFileName為點陣圖檔名
{
	//儲存bmp格式圖片 寫圖片過程 只處理24畫素的圖片 該圖片無調色盤
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(lpFileName,"wb");
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	//malloc只能申請4位元組的空間 (未知)
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	return true;
}
        第三步:新增儲存menu控制元件和函式。點選”檢視-建立類嚮導“,在ID列表中找到ID_FILE_SAVE,點選COMMAND(Message列表),雙擊新增預設成員函式OnFileSave,同時在Member Functions(成員函式)中雙擊該函式進入函式並編輯。新增如下程式碼:
//******************檔案儲存*****************//
void CImageProcessingView::OnFileSave() 
{
	// TODO: Add your command handler code here
	CString filter;
	filter="所有檔案(*.bmp,*.jpg)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";
	//重點: 1-檔案開啟 0-檔案儲存
	CFileDialog dlg(0,NULL,NULL,OFN_HIDEREADONLY,filter,NULL);   
	//按下確定按鈕
	if( dlg.DoModal() == IDOK ) {
		CString str;
		CString strName;
		CString filename;
		str = dlg.GetPathName();           //獲取檔案的路徑
		filename = dlg.GetFileTitle();     //獲取檔名
		int nFilterIndex=dlg.m_ofn.nFilterIndex;
		if( nFilterIndex == 2 )            //當使用者選擇檔案過濾器為".BMP"時
		{
			str = str + ".bmp";	           //自動加副檔名.bmp
			SaveBmp(str);                  //儲存bmp圖片 就是一個寫出圖片的過程 
			AfxMessageBox("圖片儲存成功",MB_OK,0);
		}
	}
}
        第四步:在XXXView.cpp中OnDraw()函式中呼叫讀取圖片函式。
       if( EntName.Compare(_T("bmp")) == 0 )      //bmp格式  
        {  

                ReadBmp();
                ShowBitmap(pDC,BmpName);               //顯示圖片  
        }  

        執行程式,開啟圖片點選儲存即可實現。重點是ReadBmp獲取一些重要引數。


三. 影象灰度處理

1.灰度影象概念

        什麼叫灰度圖?任何顏色都有紅、綠、藍三原色組成,假如原來某點的顏色為RGB(R,G,B),那麼我們可以通過下面幾種方法,將其轉換為灰度:
        浮點演算法:Gray=R*0.3+G*0.59+B*0.11
        整數方法:Gray=(R*30+G*59+B*11)/100
        移位方法:Gray=(R*28+G*151+B*77)>>8;
        平均值法:Gray=R+G+B/3;(此程式採用演算法)

        僅取綠色:Gray=G;
        通過上述任一種方法求得Gray後,將原來的RGB(R,G,B)中的R,G,B統一用Gray替換,形成新的顏色RGB(Gray,Gray,Gray),用它替換原來的RGB(R,G,B)就是灰度圖了。
        改變象素矩陣的RGB值,來達到彩色圖轉變為灰度圖
        加權平均值演算法:根據光的亮度特性,其實正確的灰度公式應當是:
                                                R=G=B=R*0.299+G*0.587+B0.144
        為了提高速度我們做一個完全可以接受的近似,公式變形如下:R=G=B=(R*3+G*6+B)/10 
        真正的24位真彩圖與8位的灰度圖的區別就在於,真彩圖檔案中沒有調色盤,灰度圖有調色盤,真彩圖中的象素矩陣是RGB值,灰度圖中的象素矩陣是調色盤索引值。原始碼只簡單的改變象素矩陣的RGB值,來達到彩色圖轉為灰度圖,並沒有新增調色盤;該程式未實現新增了調色盤。

2.灰度處理原始碼

        第一步:在前面的程式碼基礎上繼續,先在ImageProcessingView.h中新增成員變數m_bitmaplin和BmpNameLin,因為後面處理操作是處理備份檔案與原圖進行比較。
// Implementation
public:
	//新增成員函式
	void ShowBitmap(CDC* pDC,CString BmpName); //顯示點陣圖函式
    bool ReadBmp();                       //用來讀取bmp個手機圖片
    bool SaveBmp(LPCSTR lpFileName);           //用來儲存bmp格式圖片

	//新增成員變數
	CString EntName;     //影象副檔名
	CString BmpName;     //影象檔名稱
	CBitmap m_bitmap;    //建立點陣圖物件

	CBitmap m_bitmaplin;   //建立臨時點陣圖物件進行處理
	CString BmpNameLin;    //儲存影象副本檔案
        第二步:在ImageProcessingView.cpp中ShowBitmap()函式前新增變數numPicture和level。
/*************************************************************/
/* numPicture變數顯示圖片數量
/* 0-提示錯誤或未開啟圖片 1-顯示一張圖片 2-顯示兩張圖片和處理
/*************************************************************/
int numPicture = 0;

/*************************************************************/
/* level變數顯示具體的處理操作,每個處理函式中賦值該變數
/* 0-顯示2張圖片 1-顯示灰度圖片 3-顯示圖片取樣
/* 2 4 8 16 32 64-不同量化等級量化圖片
/*************************************************************/ 
int level = 0;    

//****************顯示BMP格式圖片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
    ....
}
        第三步:修改ImageProcessingView.cpp中OnFileOpen()函式,新增臨時變數名和顯示一張圖片標誌變數。程式碼如下:
//****************開啟檔案****************//
void CImageProcessingView::OnFileOpen() 
{
    CString filter;  
    filter="所有檔案(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";  
    CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,filter,NULL);             
    if( dlg.DoModal() == IDOK )  
    {  
        BmpName = dlg.GetPathName();     
        BmpNameLin = "picture.bmp";      //臨時變數名
		numPicture=1;                    //顯示一張圖片
		EntName = dlg.GetFileExt();       
        EntName.MakeLower();              
        Invalidate();                     
    }  	
}
        第四步:將檢視切換到ResourceView介面,選中Menu->在IDR_MAINFRAME中新增選單”顯示“,雙擊它在選單屬性中選擇”彈出“。在”顯示“的子選單中新增:
        雙圖顯示--ID_SHOW_TWO(ID)--預設屬性
        灰度圖片--ID_SHOW_HD(ID)--預設屬性


        第五步:點選"檢視"->"建立類嚮導"(Ctrl+W),選擇CImageProcessing類,然後ID_SHOW_TWO,雙擊COMMAND(Message),生成預設成員函式。

        在XXXView.cpp中實現OnShowTwo()函式,程式碼如下:
//****************顯示兩張圖片****************//
void CImageProcessingView::OnShowTwo() 
{
	//如果沒有匯入圖片直接點選雙顯 提示錯誤
	if(numPicture==0)
	{
		AfxMessageBox("載入圖片後才能顯示2張圖片!");
		return;
	}
	AfxMessageBox("顯示兩張圖片!",MB_OK,0);
	numPicture = 2;    //全域性變數 顯示兩圖
	level =0;          //level=0雙顯
	Invalidate();      //呼叫Invalidate 每秒呼叫一次OnDraw畫圖
}
        第六步:同上面相同的方法,"檢視"->”建立類嚮導“->ID_SHOW_HD(ID)->COMMAND(Message),預設成員函式名。在XXXView.cpp新增程式碼如下:
/********************************************************************************************/
/* 祥見http://blog.csdn.net/xiakq/article/details/2956902有詳細的灰度演算法                   
/* 其中24位的圖片灰度時,採用如下演算法:                                                       
/* 1.平均值演算法 R=G=B=(R+G+B)/3                                                              
/* 2.快速演算法 R=G=B=(R+G+B+128)/4>>2                                                          
/* 3.加權平均值演算法 根據光的亮度特性,其實正確的灰度公式應當是R=G=B=R*0.299+G*0.587+B0.144   
/*   為了提高速度我們做一個完全可以接受的近似,公式變形如下 R=G=B=(R*3+G*6+B)/10            
/* 4.精確加權平均值演算法 R=G=B=R*0.299+G*0.587+B0.144                                        
/********************************************************************************************/

//**灰度影象就是 R=G=B且為三者的1/3 level=1時灰度影象**//
void CImageProcessingView::OnShowHd() 
{
	if(numPicture==0)
	{
		AfxMessageBox("載入圖片後才能灰度圖片!",MB_OK,0);
		return;
	}
	AfxMessageBox("灰度影象!",MB_OK,0);
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	//讀取檔案
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	//灰度影象
	unsigned char color;
	unsigned char red,green,blue;

	/********************************************************************/
	/* 注意:原來下面所有操作都是for( i=0; i<m_nWidth*m_nHeight; i++ )  
	/* 後發現如果圖片最後一行沒有完整的一行資料,會出現影象變多或變少   
	/* 但影象的總畫素為m_nImage,如果是m_nImage/3就可以保證所有畫素都有 
	/********************************************************************/

	for(int i=0; i < m_nImage/3; i++ )
	{
		fread(&red,sizeof(char),1,fpo);
		fread(&green,sizeof(char),1,fpo);
		fread(&blue,sizeof(char),1,fpo);

		color=(red+green+blue)/3;
		red=color;
		green=color;  
		blue=color;

		fwrite(&red,sizeof(char),1,fpw);
		fwrite(&green,sizeof(char),1,fpw);
		fwrite(&blue,sizeof(char),1,fpw);
	}
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=1;
	Invalidate();
}
        第七步:修改ShowBitmap()函式中雙顯部分,新增如下程式碼:
//****************顯示BMP格式圖片****************//
void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)
{
        ....

	/**
	 * 面程式碼為後面顯示第二張圖片
	 */

	if(numPicture==2) {
		//顯示圖片函式LoadImage
		HBITMAP m_hBitmapChange;
		if(level==0) //顯示2張圖 BmpNameLin原圖
		{
			m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,
				LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
		}
		else
		if(level==1) //灰度圖片 BmpNameLin臨時圖片
		{
			m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
				LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
		}
		if( m_bitmap.m_hObject ) {
			m_bitmap.Detach();            //m_bitmap為建立的點陣圖物件
		}
		m_bitmap.Attach(m_hBitmapChange);
		//定義並建立一個記憶體裝置環境
		CDC dcBmp;
		if( !dcBmp.CreateCompatibleDC(pDC) )   //建立相容性的DC
			return;
		BITMAP m_bmp;                          //臨時bmp圖片變數
		m_bitmap.GetBitmap(&m_bmp);            //將圖片載入點陣圖中
		CBitmap *pbmpOld = NULL;
		dcBmp.SelectObject(&m_bitmap);         //將點陣圖選入臨時記憶體裝置環境

		//如果圖片太大顯示大小為固定640*640 否則顯示原圖大小
		if(m_nDrawWidth<650 && m_nDrawHeight<650)
			pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,
				m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
		else
			pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,
				m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); 
		//恢復臨時DC的點陣圖
		dcBmp.SelectObject(pbmpOld);           
	}

}
        雙顯和灰度執行效果如下圖所示:



四. 圖片量化處理

(參考我的文庫:http://wenku.baidu.com/view/80b18961f5335a8102d220a0

1.量化基本概念

        影象數字化包括量化和取樣兩個過程,其中:
        量化:幅值f(x,y)的離散化,f(x,y)表示靜止灰度影象的空間座標
        取樣:對空間連續座標(x,y)的離散化

        
一幅行數為M、列數為N影象大小為M×N的矩陣形式為:(其中矩陣中每個元素代表一個畫素)

        該工程所有的處理都基於24位的bmp格式圖片的處理,24為表示biBitCount=24,1個畫素佔3個位元組(red、green、blue)。

        如圖量化級不同產生的灰度也不同,量化是使連續訊號的幅度用有限級的數碼錶示的過程。
        量化等級=2:使用2種灰度級(0~255)表示圖片,小於128的取0,大於等於128的取128。把點陣圖資料塊所有資料在臨時圖片中取值,在顯示即可。
        量化等級=4:使用4種灰度級顯示圖片,就會發現圖片分層為4種顏色。同時,0-64區間取0,64-128區間取64,128-192區間取128,192-255區間取192。
        量化的取值各不相同,我採用的是最簡單的取值。其它方法可自己去查閱資料。





2.量化處理原始碼

        第一步:設定選單欄。將試圖切換到ResourceView介面--選中Menu--IDR_MAINFRAME中新增選單“量化”--雙擊它在選單屬性中選擇“彈出”。在“顯示”的子選單中新增:屬性為預設屬性。
        量化 Level 2--ID_LH_2       量化 Level 4--ID_LH_4
        量化 Level 8--ID_LH_8       量化 Level 16--ID_LH_16
        量化 Level 32--ID_LH_32   量化 Level 64--ID_LH_64


        第二步:建立類嚮導。檢視->建立類導向(Ctrl+W)->CXXXView(類名)->ID_LH_2->COMMAND(Messages)->預設成員函式名。相同方法分別為量化等級2、4、8、16、32、64建立類導向。

        第三步:在ImageProcessingView.cpp中編輯灰度函式。程式碼如下:
        核心流程是開啟兩張圖片原圖(BmpName)和臨時圖片(BmpNameLin),然後讀取原圖資訊頭賦值給臨時處理圖片,在讀取原圖m_nImage整個畫素矩陣,量化處理每個畫素(即分等級量化),最後檔案寫量化後的畫素矩陣給BmpNameLin,在賦值全域性變數level\numPicture和呼叫Invalidate()重繪影象即可。
//****************量化 量化等級為2****************//
void CImageProcessingView::OnLh2() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=2!",MB_OK,0);
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	//讀取檔案
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	//malloc只能申請4位元組的空間
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	//等級2量化
	for(int i=0; i<m_nImage; i++ ) {
		//24位的為調色盤為真彩圖 Red Green Blue 為3位元組 
		//量化等級為2取中間值為 64 和 192
		if(m_pImage[i]<128) { 
			m_pImage[i]=0;
		}
		else if(m_pImage[i]>=128) {
			m_pImage[i]=128;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=2;
	Invalidate();
}

//****************量化 量化等級為4****************//
void CImageProcessingView::OnLh4() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=4!",MB_OK,0);
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	//等級4量化
	for(int i=0; i<m_nImage; i++ ) {
		if(m_pImage[i]<64) {
			m_pImage[i]=0;
		}
		else if( (m_pImage[i]>=64) && (m_pImage[i]<128) ) {
			m_pImage[i]=64;
		}
		else if( (m_pImage[i]>=128) && (m_pImage[i]<192) ) {
			m_pImage[i]=128;
		}
		else if(m_pImage[i]>=192) {
			m_pImage[i]=192;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=4;
	Invalidate();
}

//****************量化 量化等級為8****************//
void CImageProcessingView::OnLh8() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=8!",MB_OK,0);
	//開啟臨時的圖片 讀取檔案
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	//malloc只能申請4位元組的空間 (未知)
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	//等級8量化
	for(int i=0; i<m_nImage; i++ ) {
		if(m_pImage[i]<32) {
			m_pImage[i]=0;
		}
		else if( (m_pImage[i]>=32) && (m_pImage[i]<64) ) {
			m_pImage[i]=32;
		}
		else if( (m_pImage[i]>=64) && (m_pImage[i]<96) ) {
			m_pImage[i]=64;
		}
		else if( (m_pImage[i]>=96) && (m_pImage[i]<128) ) {
			m_pImage[i]=96;
		}
		else if( (m_pImage[i]>=128) && (m_pImage[i]<160) ) {
			m_pImage[i]=128;
		}
		else if( (m_pImage[i]>=160) && (m_pImage[i]<192) ) {
			m_pImage[i]=160;
		}
		else if( (m_pImage[i]>=192) && (m_pImage[i]<224) ) {
			m_pImage[i]=192;
		}
		else if(m_pImage[i]>=224) {
			m_pImage[i]=224;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=8;
	Invalidate();
}

//****************量化 量化等級為16****************//
void CImageProcessingView::OnLh16() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=16!",MB_OK,0);
	int i,j;
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	for( i=0; i<m_nImage; i++ ) {
		j=16;
		while(j<=256)
		{
			if(m_pImage[i]<j) 
			{
				if(m_pImage[i]<16) 
					m_pImage[i]=0;
				else 
					m_pImage[i]=j-16;
				break;
			}
			else j+=16;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=16;
	Invalidate();
}

//****************量化 量化等級為32****************//
void CImageProcessingView::OnLh32() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=32!",MB_OK,0);
	int i,j;
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	//讀取檔案
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	//等級32化
	for( i=0; i<m_nImage; i++ )
	{
		j=8;
		while(j<=256)
		{
			if(m_pImage[i]<j) 
			{
				if(m_pImage[i]<8) 
					m_pImage[i]=0;
				else 
					m_pImage[i]=j-8;
				break;
			}
			else j+=8;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=32;
	Invalidate();
}

//****************量化 量化等級為64****************//
void CImageProcessingView::OnLh64() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能量化!",MB_OK,0);
		return;
	}
	AfxMessageBox("量化等級Level=64!",MB_OK,0);
	int i,j;
	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	FILE *fpw = fopen(BmpNameLin,"wb+");
	//讀取檔案
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
	fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
	m_pImage=(BYTE*)malloc(m_nImage);
	fread(m_pImage,m_nImage,1,fpo);
	//等級64量化
	for( i=0; i<m_nImage; i++ )
	{
		j=4;
		while(j<=256)
		{
			if(m_pImage[i]<j) 
			{
				if(m_pImage[i]<16) 
					m_pImage[i]=0;
				else 
					m_pImage[i]=j-4;
				break;
			}
			else j+=4;
		}
	}
	fwrite(m_pImage,m_nImage,1,fpw);
	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level=64;
	Invalidate();
}
        第四步:修改ShowBitmap()函式,顯示量化處理。新增如下程式碼:
if(level==0) //顯示2張圖 BmpNameLin原圖
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else
if(level==1) //灰度圖片 BmpNameLin臨時圖片
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化2
if(level==2)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化4
if(level==4)  
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化8
if(level==8)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化16
if(level==16)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化32
if(level==32)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
else         //量化64
if(level==64)
{
	m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
		LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
}
        執行效果如下圖,當量化Level=2時很明顯的兩種灰度顏色,Level=4有4種顏色。


五. 影象取樣功能

(參考我的文庫:http://wenku.baidu.com/view/b3ef4e1f964bcf84b9d57baf)

1.影象取樣概念

        該工程所有的處理都基於24位的bmp格式圖片的處理,24為表示biBitCount=24,1個畫素佔3個位元組(red、green、blue)。如圖一張512*512的原圖,保持灰度級256不變後的各種取樣。輸入取樣座標:如16*16,它的含義是原圖512*512畫素,現在組成一個新的圖片為16*16畫素,(512/16=32,512/16=32)則每32*32組成一個新的區域。共有這種區域16*16個,取樣的方法有2種:
        a.把這個32*32區域全部賦值成左上角那個畫素,這樣圖片的大小不變,困難在於賦值要4層迴圈。(專案中採用的就是這種方法)
        b.把這個32*32區域的左上角取出來,組成一個新的圖片,共有16*16個畫素,這張圖片的大小要變小,只有16*16個畫素。但難點在於同時要把bmp檔案頭中的圖片大小、資訊頭中的長寬畫素改變、偏移量等資訊更新。


        又如下圖所示:
        原圖8*8的矩陣要處理成3*3的矩陣,則迴圈先處理第一二行,①②④⑤為3*3處理,去左上角的RGB,③⑥為2*3的處理;重點是原圖讀取一維陣列需要轉成二維陣列賦值處理;最後再處理最後一行資料。取樣中公式為:
        //獲取填充顏色 相當於一次讀取一個畫素的RGB值再乘3跳3個位元組
        red=m_pImage[(X+Y*m_nWidth)*3];
        green=m_pImage[(X+Y*m_nWidth)*3+1];
        blue=m_pImage[(X+Y*m_nWidth)*3+2];
        //填出影象迴圈 小區域中的長寬迴圈
        //(X+Y*m_nWidth)*3跳到該小區域 再賦值3*3小區域的RGB 同一區域RGB相同
        m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=red; m++;
        m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=green; m++;
        m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=blue; m++;


        PS:難點是還未處理剩餘部分的取樣。

2.影象取樣程式碼

        第一步:設定選單欄
        a.將檢視切換到ResourceView介面--選中Menu--IDR_MAINFRAME中新增選單“取樣”--雙擊它在選單屬性中選擇“彈出
        b.在“取樣”的子選單中新增:屬性為預設屬性。ID_CY--圖片取樣。
        c.建立類導向:檢視--建立類導向(Ctrl+W)--CImageProcessingView(類名)--ID_CY--COMMAND(Messages)--預設成員函式名。生成void CImageProcessingView::OnCy()取樣函式。
        第二步:設定取樣對話方塊
        a.將試圖切換到ResourceView介面--選中Dialog,右鍵滑鼠新建一個Dialog,並新建一個名為IDD_DIALOG_CY。編輯框(X)IDC_EDIT_CYX 和 (Y)IDC_EDIT_CYY,確定為預設按鈕。設定成下圖對話方塊:

        b.在對話方塊資源模板空白區域雙擊滑鼠—Create a new class建立一個新類--命名為CImageCYDlg。會自動生成它的.h和.cpp檔案。類嚮導Ctrl W--類名:CImageCYDlg--CImageCYDlg(IDs)—WM_INITDLAOG建立這個函式可以用於初始化。

 
        c.開啟類嚮導Ctrl+W--選擇MemberVariables頁面,類名:CImageCYDlg--Add Variables--設定成:
                IDC_EDIT_CYX--int--m_xPlace
                IDC_EDIT_CYY--int--m_yPlace
        d.在View.cpp中新增取樣的標頭檔案#include "ImageCYDlg.h"

        第三步:在ImageProcessingView.cpp中新增程式碼

//****************圖片取樣****************//
void CImageProcessingView::OnCy() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能取樣!",MB_OK,0);
		return;
	}
	CImageCYDlg dlg;     //定義取樣對話方塊
	//顯示對話方塊
	if( dlg.DoModal()==IDOK ) {
		//取樣座標最初為圖片的自身畫素
		if( dlg.m_xPlace==0 || dlg.m_yPlace==0 ) {
			AfxMessageBox("輸入圖片畫素不能為0!",MB_OK,0);
			return;
		}
		if( dlg.m_xPlace>m_nWidth || dlg.m_yPlace>m_nHeight ) {
			AfxMessageBox("圖片畫素不能為超過原圖長寬!",MB_OK,0);
			return;
		}
		AfxMessageBox("圖片取樣!",MB_OK,0);
		//開啟臨時的圖片 讀取檔案
		FILE *fpo = fopen(BmpName,"rb");
		FILE *fpw = fopen(BmpNameLin,"wb+");
		fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
		fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
		fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);
		fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);
		fread(m_pImage,m_nImage,1,fpo);
	
		/*圖片取樣*/
		int numWidth,numHeight;     //圖片此區間取相同的畫素點
		int numSYWidth,numSYHeight; //剩餘期間區域 

		/*********************************************************/
		/* 表示numWidth*numHeight為一個區域 該區域顏色相同       
		/* 如 512/512=1 512/512=1 1*1為一個區域                  
		/* dlg.m_xPlace*dlg.m_yPlace 表示新的(x,y)座標         
		/* numSYWidth表示剩餘空間 該區域統一為一個顏色           
		/*********************************************************/

		numWidth=m_nWidth/dlg.m_xPlace;        
		numHeight=m_nHeight/dlg.m_yPlace;      
		numSYWidth=m_nWidth%dlg.m_xPlace;     
		numSYHeight=m_nHeight%dlg.m_yPlace;   
		int Y,X;
		int i,j,m,n;
		unsigned char red,green,blue;  //儲存三種顏色
	
		/* 有((m_xPlace * m_yPlace)+ 剩餘區域 )個小區域 */
		for( i=0; i<dlg.m_yPlace; i++ )       //高度
		{
			Y=numHeight*i;                    //獲取Y座標
			for( j=0; j<dlg.m_yPlace; j++ )   //寬度
			{
				X=numWidth*j;                 //獲取X座標
				/*獲取填充顏色*/
				red=m_pImage[(X+Y*m_nWidth)*3];
				green=m_pImage[(X+Y*m_nWidth)*3+1];
				blue=m_pImage[(X+Y*m_nWidth)*3+2];
				/*填出影象迴圈 小區域中的長寬迴圈*/
				for( n=0; n<numHeight; n++ )
				{
					for( m=0; m<numWidth*3; )
					{
						m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=red;
						m++;
						m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=green;
						m++;
						m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=blue;
						m++;
					}
				}
			}
		}
		fwrite(m_pImage,m_nImage,1,fpw);
		fclose(fpo);
		fclose(fpw);
		numPicture = 2;
		level=3;
		Invalidate();
	}
}
        第四步:修改ShowBitmap(CDC* pDC,CString BmpName)中的程式碼:
        else if(level==3) //圖片取樣
        {
          m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,
                 LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
        }
        執行效果如下圖所示,其中彩色圖片應該先灰度處理再進行其他操作。








        總結:後悔當初還沒有寫部落格,通過回憶幾年前的程式碼,很多當時的體會和思想都已不復存在了!可能你在百度文庫中看到類似的文章,因為那些都是我在2012年上傳的,最初是通過它進行分享程式設計知識的,後來發現了更好的CSDN而取代之。這篇文章感覺太詳細,有時候一直懷疑是不是失去了演算法的本質,不應該寫這麼詳細的文章,而更加精簡一點,但可能和從小記筆記有關,很難改過來了,慢慢改吧!
        最後還是希望文章對你有所幫助,如果文章有不足或錯誤之處,請海涵~
      (By:Eastmount 2015-5-28 下午點   http://blog.csdn.net/eastmount/
        


相關文章