【數字影象處理】四.MFC對話方塊繪製灰度直方圖

Eastmount發表於2015-05-31
        本文主要講述基於VC++6.0 MFC影象處理的應用知識,主要結合自己大三所學課程《數字影象處理》及課件進行回憶講解,主要通過MFC單文件檢視實現點選彈出對話方塊繪製BMP圖片的灰度直方圖,再獲取平均灰度、中指灰度和標準差等值。文章比較詳細基礎,希望該篇文章對你有所幫助~
       【數字影象處理】一.MFC詳解顯示BMP格式圖片
       【數字影象處理】二.MFC單文件分割視窗顯示圖片
       【數字影象處理】三.MFC實現影象灰度、取樣和量化功能詳解
        
免費資源下載地址:
        http://download.csdn.net/detail/eastmount/8757243


一. 程式執行結果

        該篇文章主要是在上一篇文章基礎上進行的講解,其中當開啟一張BMP影象後,點選”直方圖“-》”顯示原圖直方圖“如下。





二. 灰度直方圖原理

        什麼是灰度直方圖?
        灰度直方圖(histogram)是灰度級的函式,描述的是影象中每種灰度級畫素的個數,反映影象中每種灰度出現的頻率。橫座標是灰度級,縱座標是灰度級出現的頻率。


        對於連續影象,平滑地從中心的高灰度級變化到邊緣的低灰度級。直方圖定義為:

        其中A(D)為閾值面積函式:為一幅連續影象中被具有灰度級D的所有輪廓線所包圍的面積。對於離散函式,固定ΔD為1,則:H(D)=A(D)-A(D+1)
        色彩直方圖是高維直方圖的特例,它統計色彩的出現頻率,即色彩概率分佈資訊。
        通常這需要一定的量化過程,將色彩分成若干互不重疊的種類。一般不直接在RGB色彩空間中統計,而是在將亮度分離出來後,對代表色彩部分的資訊進行統計,如在HSI空間的HS子空間、YUV空間的UV子空間,以及其它反映人類視覺特點的彩色空間表示中進行。
        其中直方圖的計算方法如下:
        依據定義,若影象具有L(通常L=256,即8位灰度級)級灰度,則大小為MxN的灰度影象f(x,y)的灰度直方圖hist[0…L-1]可用如下計算獲得。
        1、初始化 hist[k]=0; k=0,…,L-1 
        2、統計 hist[f(x,y)]++; x=0,…,M-1, y =0,…,N-1 
        3、歸一化 hist[f(x,y)]/=M*N 

        那麼說了這麼多,直方圖究竟有什麼作用呢?
        在使用輪廓線確定物體邊界時,通過直方圖更好的選擇邊界閾值,進行閾值化處理;對物體與背景有較強對比的景物的分割特別有用;簡單物體的面積和綜合光密度IOD可以通過影象的直方圖求得。

三. 程式實現

1.建立直方圖對話方塊

       第一步:建立Dialog
        將檢視切換到ResourceView介面,選中Dialog右鍵滑鼠新建一個Dialog,並新建一個名為IDD_DIALOG_ZFT,設定成下圖對話方塊。


        右鍵新增屬性如下:
        對話方塊-原始直方圖-IDD_DIALOG_ZFT
        組框-RGB-IDC_STATIC_RGB
        影象-框架-IDC_STATIC_KJ-蝕刻(重點:有它才能新增直方圖在此處,注意GetDlgItem()函式中是IDC而不是IDD對話方塊)
        新增蝕刻線(影象蝕刻形成的直線)形如圖中的3個矩形框,並新增靜態文字:Red、Green、Blue、紅、綠、藍、畫素、平均灰度、中值灰度、標準差;這些靜態文字都是IDC_STATIC且為預設屬性
        新增紅色4個值(Static)、綠色4個值、藍色4個值,分別為:
        IDC_STATIC_XS_RED(GREEN BLUE)對應畫素XS
        IDC_STATIC_PJHD_RED(GREEN BLUE)對應平均灰度PJHD
        IDC_STATIC_ZZHD_RED(GREED BLUE)對應中值灰度ZZHD
        IDC_STATIC_BZC_RED(GREEN BLUE)對應標準差BZC

       第二步:建立類嚮導MFC ClassWizard
        (1) 在對話方塊資源模板空白區雙擊滑鼠(Ctrl+W),建立一個新類,命名為CImageZFTDlg會自動生成它的.h和.cpp檔案。在類嚮導中選中類名CImageZFTDlg,IDs為CImageZFTDlg,WM_INITDIALOG建立這個函式用於初始化。
        (2) 開啟類嚮導,選擇Member Variables頁面,新增如下變數,型別均為CString。
        畫素 m_redXS、m_greenXS、m_blueXS
        標準差 m_redBZC、m_greeenBZC、m_blueBZC
        平均灰度 m_redPJHD、m_greenPJHD、m_bluePJHD
        中值灰度 m_redZZHD、m_greenZZHD、m_blueZZHD

        (3) 在View.cpp中新增直方圖的標頭檔案 #include "ImageZFTDlg.h"

        第三步:設定選單欄呼叫直方圖對話方塊
        (1) 將檢視切換到ResourceView介面,選中Menu,在IDR_MAINFRAM中新增選單項“直方圖”,選單屬性中選擇“彈出”,在“直方圖”中新增子選單“顯示原圖直方圖”。
        (2) 設定其屬性為ID_ZFT_YT(顯示直方圖原圖),同時建立類嚮導,選擇ID_ZFT_YT(IDs),通過COMMAND建立顯示直方圖函式OnZftYt()。


       第四步:新增程式碼及計算4個值
        在ImageProcessingView.cpp中新增如下程式碼,註釋中有如何求平均灰度、中值灰度和標準差的訊息演算法過程。
//引用顯示直方圖標頭檔案
#include "ImageZFTDlg.h"
#include "math.h"

/*全域性變數在TestZFTDlg.cpp中引用 用extern*/
int Red[256],Green[256],Blue[256];

/**************************************************/
/* 新增直方圖顯示功能,並在直方圖下方顯示相關資訊 
/* 如平均灰度、中值灰度、標準差和畫素總數         
/* ID_ZFT_YT:直方圖原圖顯示                       
/**************************************************/
void CImageProcessingView::OnZftYt() 
{
	if(numPicture==0) {
		AfxMessageBox("載入圖片後才能顯示原圖直方圖!",MB_OK,0);
		return;
	}
	AfxMessageBox("顯示原圖直方圖!",MB_OK,0);
	CImageZFTDlg dlg;

	//開啟臨時的圖片
	FILE *fpo = fopen(BmpName,"rb");
	fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);
	fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);
	
	int i,j;
	for(j=0;j<256;j++) { //定義陣列並清零
		Red[j]=0;
		Green[j]=0;
		Blue[j]=0;
	}
	
	//計算4個資料
	unsigned char red,green,blue;
	int IntRed,IntGreen,IntBlue;                  //強制轉換
	double sumRedHD=0,sumGreenHD=0,sumBlueHD=0;   //記錄影素總的灰度值和
	for(i=0; i<m_nImage/3; i++ ) 
	{
		fread(&red,sizeof(char),1,fpo);
		IntRed=int(red);
		sumRedHD=sumRedHD+IntRed;
		if( IntRed>=0 && IntRed<256 ) Red[IntRed]++; //畫素0-255之間
		
		fread(&green,sizeof(char),1,fpo);
		IntGreen=int(green);
		sumGreenHD=sumGreenHD+IntGreen;
		if( IntGreen>=0 && IntGreen<256 ) Green[IntGreen]++;
		
		fread(&blue,sizeof(char),1,fpo);
		IntBlue=int(blue);
		sumBlueHD=sumBlueHD+IntBlue;
		if( IntBlue>=0 && IntBlue<256 ) Blue[IntBlue]++;
	}
	fclose(fpo);
	
	//畫素:int型轉換為CString型 
	dlg.m_redXS.Format("%d",m_nImage);
	dlg.m_greenXS.Format("%d",m_nImage);
	dlg.m_blueXS.Format("%d",m_nImage);
	
	//平均灰度值:計算24位bmp圖片的灰度值,我是記錄RGB中的所有平均值	
	float pinRedHD,pinGreenHD,pinBlueHD; 
	pinRedHD=sumRedHD*3/m_nImage;
	pinGreenHD=sumGreenHD*3/m_nImage;     //平均灰度=總灰度/總畫素
	pinBlueHD=sumBlueHD*3/m_nImage;
	
	dlg.m_redPJHD.Format("%.2f",pinRedHD);
	dlg.m_greenPJHD.Format("%.2f",pinGreenHD);
	dlg.m_bluePJHD.Format("%.2f",pinBlueHD);
	
	/****************************************************************/
	/* 中值灰度:演算法重點(黃凱大神提供)                              
	/* 中值灰度:所有畫素中的中位數,應該所有畫素排序找到中間的灰度值 
	/* 演算法:num[256]記錄各灰度出現次數,sum+=num[i],找到sum=總畫素/2 
	/****************************************************************/
	int sumRedZZHD=0,sumGreenZZHD=0,sumBlueZZHD=0;
	int redZZHD,greenZZHD,blueZZHD;
	for(i=0;i<256;i++)
	{
		sumRedZZHD=sumRedZZHD+Red[i];
		if(sumRedZZHD>=m_nImage/6)          //m_nImage被分成3份RGB並且sum=總畫素/2
		{
			redZZHD=i;
			break;
		}
	}
	for(i=0;i<256;i++)
	{
		sumGreenZZHD=sumGreenZZHD+Green[i];
		if(sumGreenZZHD>=m_nImage/6)          //m_nImage被分成3份RGB並且sum=總畫素/2
		{
			greenZZHD=i;
			break;
		}
	}
	for(i=0;i<256;i++)
	{
		sumBlueZZHD=sumBlueZZHD+Blue[i];
		if(sumBlueZZHD>=m_nImage/6)          //m_nImage被分成3份RGB並且sum=總畫素/2
		{
			blueZZHD=i;
			break;
		}
	}
	
	dlg.m_redZZHD.Format("%d",redZZHD);
	dlg.m_greenZZHD.Format("%d",greenZZHD);
	dlg.m_blueZZHD.Format("%d",blueZZHD);
	
	/******************************************************************/
	/*標準差:標準差=方差的算術平方根                                   
	/*       方差s^2=[(x1-x)^2+(x2-x)^2+......(xn-x)^2]/n             
	/* 演算法:不用開m_nImage陣列進行計算 用num[256]中數進行             
	/* 方差=(平均灰度-i)*(平均灰度-i)*Red[i]  有Red[i]個灰度值為i的數 
	/******************************************************************/
	float redBZC,greenBZC,blueBZC;       //標準差
	double redFC=0,greenFC=0,blueFC=0;    //方差
	for(i=0;i<256;i++)
	{
		redFC=redFC+(pinRedHD-i)*(pinRedHD-i)*Red[i];   //有Red[i]個畫素i
		greenFC=greenFC+(pinGreenHD-i)*(pinGreenHD-i)*Green[i];
		blueFC=blueFC+(pinBlueHD-i)*(pinBlueHD-i)*Blue[i];
	}
	
	redBZC=sqrt(redFC*3/m_nImage);
	greenBZC=sqrt(greenFC*3/m_nImage);
	blueBZC=sqrt(blueFC*3/m_nImage);
	
	dlg.m_redBZC.Format("%.2lf",redBZC);
	dlg.m_greenBZC.Format("%.2lf",greenBZC);
	dlg.m_blueBZC.Format("%.2lf",blueBZC);	

	//重點必須新增該語句才能彈出對話方塊
	if(dlg.DoModal()==IDOK)
	{

	}
}
        第五步:此時執行結果如下圖所示,開啟圖片可以顯示引數。



2.建立對話方塊與View聯絡並繪製直方圖

        重點(極其重要*)
        (1) 如何在MFC中(View中)實現對子對話方塊的畫圖或直方圖響應?
        解決方法:在子對話方塊中.cpp檔案中實現畫圖響應,不要再View.cpp中實現,否則影象會以menu背景為座標,而在ImageZFTDlg.cpp中建立OnPaint函式實現畫圖,它預設會以子對話方塊為標準。
        (2) 如何把View.cpp中的圖片畫素直方圖資訊傳遞給子對話方塊ImageZFTDlg.cpp呢?
        解決方法:如果自定義ImageStruct.h中建立全域性變數,每個.cpp中引用該標頭檔案呼叫總是報錯(未知),所以我在View.h中建立一個全域性變數int Red[256];再在子檔案.cpp中函式裡呼叫該全域性變數即可extern int Red[256],這是非常重要的一個C語言知識。
        (3) 畫圖函式OnPaint()參考原始碼中詳細註釋。
        如何繪製座標軸、文字、影象,其實自己繪製而沒呼叫第三方庫還是挺有意思的。

        第一步:建立畫直方圖函式OnPaint
        開啟類嚮導(Ctrl+W),類名選擇CImageZFTDlg,IDs選擇CImageZFTDlg,在Message函式中建立WM_PAINT對映,預設函式名為OnPaint建立函式void CImageZFTDlg::OnPaint()

        第二步:繪製直方圖大致思想如下
        (1) 重點:獲取要繪製直方圖的位置和影象資源的對應號ID(IDC_STATIC_KJ 框架),我當時認為繪製直方圖只能繪製到”影象“控制元件IDC中,不能是對話方塊IDD。
        CWnd *pWnd = GetDlgItem(IDC_STATIC_KJ);
        CDC *pDC = pWnd->GetDC();

        (2) 獲取對話方塊矩形的長和寬
        CRect rectpic;
        GetDlgItem(IDC_STATIC_KJ)->GetWindowRect(&rectpic);

        (3) 建立畫筆物件並對畫筆進行顏色設定
        CPen *RedPen = new CPen();
        RedPen->CreatePen(PS_SOLID,1RGB(255,0,0));

        (4) 選中當前畫筆並儲存以前畫筆
        CGdiObject *RedOlderPen = pDC->SelectObject(RedPen);
        (5) 繪製直方圖(影象座標自己算)
        矩形 pDC->Rectangle(9,327,312,468);
        移動 pDC->MoveTo(15,331);
        直線 pDC->LineTo(15,488);
        文字 pDC->TextOut(15+48*i,450,str);

        (6) 恢復以前畫筆
        pDC->SelectObject(RedOlderPen);
        delete RedPen;
        ReleaseDC(pDC);


        第三步:原始碼與詳細註釋思想
        在ImageZFTDlg.cpp中修改OnPaint函式:
//****************繪製原圖直方圖*********************//
void CImageZFTDlg::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	// TODO: Add your message handler code here
	
	/********************************************************************************/
	/* 重點知識:(百度)                                                              
	/* 如何在View.cpp中把一個變數的值傳給其它對話方塊                                 
	/*                                                                              
	/* 錯誤一:在View.h中定義的pubic變數只能在View.cpp中用                           
	/* 錯誤二:定義一個Struct.h中存全域性變數,在2個函式中分別呼叫#include "Struct.h"   
	/*                                                                              
	/* 解決方法一: (CSDN 不會)引數用 A& a 兩個對話方塊裡都可以訪問a                   
	/* 解決方法二: (CSDN 不會)過載                                                  
	/*                                                                              
	/* 解決:在View.cpp中定義全域性變數 void CBmpDrawView::OnZftYt() 前面 並函式中操作 
	/*      在dialog的cpp中即void CTestZFTDlg::OnPaint()中在定義一個extern int a    
	/********************************************************************************/

	extern int Red[256],Green[256],Blue[256];

	/*寫在該空間中可以省略Invalidate()語句*/
	/*獲取控制元件的CDC指標*/
	CRect rectpic;
	GetDlgItem(IDC_STATIC_KJ)->GetWindowRect(&rectpic);
		
	int x,y;
	x=rectpic.Width();
	y=rectpic.Height();
		
	CWnd *pWnd=GetDlgItem(IDC_STATIC_KJ);
	CDC *pDC=pWnd->GetDC();
		
	/***********************/
	/*重點:畫直方圖 紅色
	/**********************/
	CPen *RedPen=new CPen();                              //建立畫筆物件
	RedPen->CreatePen(PS_SOLID,1,RGB(255,0,0));           //紅色畫筆
	CGdiObject *RedOlderPen=pDC->SelectObject(RedPen);    //選中當前紅色畫筆並儲存以前的畫筆
	
	/*畫圖*/
	pDC->Rectangle(9,16,312,147);      //畫一個矩形框
	pDC->MoveTo(15,20);                //繪製座標軸
	pDC->LineTo(15,128);               //Y豎軸
	pDC->LineTo(305,128);              //X橫軸
	
	pDC->MoveTo(305,128);              //繪製X箭頭
	pDC->LineTo(300,123);              //繪製上邊箭頭
	pDC->MoveTo(305,128); 
	pDC->LineTo(300,133);              //繪製下邊箭頭
	
	pDC->MoveTo(15,20);                //繪製Y箭頭
	pDC->LineTo(10,25);                //繪製左邊箭頭
	pDC->MoveTo(15,20);
	pDC->LineTo(20,25);                //繪製右邊箭頭
	
	/**********************************************************************/
	/* TextOut函式功能:                                                   
	/* 該函式用當前選擇的字型、背景顏色和正文顏色將一個字串寫到指定位置 
	/* BOOL TextOut(HDC hdc,int x,int y,LPCTSTR str,int numStr)           
	/* 表示:x起始座標,y起始座標,字串,字串中字元個數                   
	/*
	/* SetTextColor函式功能:                                              
	/* 設定指定裝置環境(HDC)的字型顏色                                    
	/* SetTextColor (HDC, COLORREF) 如:SetTextColor(HDC,RGB(255,0,0));    
	/**********************************************************************/
	
	CString str;
	int i;
	for(i=0;i<=5;i++)                    //寫X軸刻度線
	{
		str.Format("%d",i*50);               //0-255之間新增6個刻度值
		pDC->SetTextColor(RGB(255,0,255));   //設定字型顏色
		pDC->TextOut(15+48*i,130,str);       //輸出字型
		pDC->MoveTo(15+48*i,128);            //繪製X軸刻度
		pDC->LineTo(15+48*i,125);
	}
	for(i=0;i<=5;i++)                        //寫Y軸刻度線
	{
		pDC->MoveTo(15,128-20*i);            //繪製Y軸刻度
		pDC->LineTo(18,128-20*i);
	}
	
	/*繪製直方圖主要的程式碼*/
	for(i=1;i<256;i++)
	{
		pDC->MoveTo(15+i,128);
		if( (128-16) > (Red[i]/40) )
			pDC->LineTo(15+i,128-(Red[i]/40));   
		else
			pDC->LineTo(15+i,16);            //超過矩形的畫矩形高
	}
		

	/**********************/
	/*重點:畫直方圖 綠色
	/**********************/
	CPen *GreenPen=new CPen();                             //建立畫筆物件
	GreenPen->CreatePen(PS_SOLID,1,RGB(0,255,0));          //綠色畫筆
	CGdiObject *GreenOlderPen=pDC->SelectObject(GreenPen); 
	
	pDC->Rectangle(9,167,312,308);     //畫一個矩形框
	pDC->MoveTo(15,171);               //繪製座標軸
	pDC->LineTo(15,288);               //Y豎軸
	pDC->LineTo(305,288);              //X橫軸
	
	pDC->MoveTo(305,288);              //繪製X箭頭
	pDC->LineTo(300,283);              //繪製上邊箭頭
	pDC->MoveTo(305,288);
	pDC->LineTo(300,293);              //繪製下邊箭頭
	
	pDC->MoveTo(15,171);                //繪製Y箭頭
	pDC->LineTo(10,176);                //繪製左邊箭頭
	pDC->MoveTo(15,171);
	pDC->LineTo(20,176);                //繪製右邊箭頭
	
	for(i=0;i<=5;i++)                   //寫X軸刻度線
	{
		str.Format("%d",i*50);               //0-255之間新增6個刻度值
		pDC->SetTextColor(RGB(255,0,255));   //設定字型顏色
		pDC->TextOut(15+48*i,290,str);       //輸出字型
		
		pDC->MoveTo(15+48*i,288);            //繪製X軸刻度
		pDC->LineTo(15+48*i,285);
	}
	for(i=0;i<=5;i++)                        //寫Y軸刻度線
	{
		pDC->MoveTo(15,288-20*i);            //繪製Y軸刻度
		pDC->LineTo(18,288-20*i);
	}
	
	/*繪製直方圖主要的程式碼*/
	for(i=1;i<256;i++)
	{
		pDC->MoveTo(15+i,288);
		if( (288-167) > (Green[i]/40) )
			pDC->LineTo(15+i,288-(Green[i]/40));   
		else
			pDC->LineTo(15+i,167);            //超過矩形的畫矩形高
	}
		
		
	/**********************/
	/*重點:畫直方圖 藍色
	/***************((*****/
	CPen *BluePen=new CPen();                            //建立畫筆物件
	BluePen->CreatePen(PS_SOLID,1,RGB(0,0,255));         //藍色畫筆
	CGdiObject *BlueOlderPen=pDC->SelectObject(BluePen);  
	
	pDC->Rectangle(9,327,312,468);      //畫一個矩形框
	pDC->MoveTo(15,331);                //繪製座標軸
	pDC->LineTo(15,448);                //Y豎軸
	pDC->LineTo(305,448);               //X橫軸
	
	pDC->MoveTo(305,448);               //繪製X箭頭
	pDC->LineTo(300,443);               //繪製上邊箭頭
	pDC->MoveTo(305,448);
	pDC->LineTo(300,453);               //繪製下邊箭頭
	
	pDC->MoveTo(15,331);                //繪製Y箭頭
	pDC->LineTo(10,336);                //繪製左邊箭頭
	pDC->MoveTo(15,331);
	pDC->LineTo(20,336);                //繪製右邊箭頭
	
	
	for(i=0;i<=5;i++)                   //寫X軸刻度線
	{
		str.Format("%d",i*50);               //0-255之間新增6個刻度值
		pDC->SetTextColor(RGB(255,0,255));   //設定字型顏色
		pDC->TextOut(15+48*i,450,str);       //輸出字型
		
		pDC->MoveTo(15+48*i,448);            //繪製X軸刻度
		pDC->LineTo(15+48*i,445);
	}
	for(i=0;i<=5;i++)                        //寫Y軸刻度線
	{
		pDC->MoveTo(15,448-20*i);            //繪製Y軸刻度
		pDC->LineTo(18,448-20*i);
	}
	
	/*繪製直方圖主要的程式碼*/
	for(i=1;i<256;i++)
	{
		pDC->MoveTo(15+i,448);
		if( (448-327) > (Blue[i]/40) )
			pDC->LineTo(15+i,448-(Blue[i]/40));   
		else
			pDC->LineTo(15+i,327);            //超過矩形的畫矩形高
	}
		
			
	//恢復以前的畫筆
	pDC->SelectObject(RedOlderPen);
	pDC->SelectObject(GreenOlderPen);
	pDC->SelectObject(BlueOlderPen);
	delete RedPen;
	delete GreenPen;
	delete BluePen;
	ReleaseDC(pDC);
	return;
	
	// Do not call CDialog::OnPaint() for painting messages
}
        此時執行程式即可顯示直方圖。
        最後還是希望文章對你有所幫助,如果文章有不足或錯誤之處,請海涵~文章不僅僅講述了直方圖相關的知識,同時文章也給你提供了一種繪製座標影象的思想和詳細註釋。有時候一直懷疑回憶這些知識會讓我停滯不前,但心安即好,何必在意!
        從來沒有什麼終南捷徑和大神,真正的捷徑只有三個:堅持、專注、認真。其他的都是細枝末節,做到這三個,其他的自然而然都會擁有。——同學CY
      (By:Eastmount 2015-5-31 下午3點   http://blog.csdn.net/eastmount/
        

相關文章