1. 程式人生 > >openCV直方圖均衡化

openCV直方圖均衡化

1、什麼是直方圖均衡化

直方圖是對影象畫素的統計分佈,它統計了每個畫素(0到L-1)的數量。直方圖均衡化就是將原始的直方圖拉伸,使之均勻分佈在全部灰度範圍內,從而增強影象的對比度。直方圖均衡化的中心思想是把原始影象的的灰度直方圖從比較集中的某個區域變成在全部灰度範圍內的均勻分佈。

在一幅影象中,明亮影象的直方圖傾向於灰度級高的一側,灰暗影象的直方圖傾向於灰度級低的一側,如果一副影象佔有全部可能的灰度級並且分佈均勻,則這樣的影象有高對比度和多變的灰度色調。直方圖均衡化這種方法通常用來增加影象的區域性對比度。所以這種方法對於影象前景和背景都太亮或者太暗的情況非常有用,使目標區域從背景脫離出來。

 講到這裡不得不引入累計分佈函式 CDF,其定義為:

     對於連續函式,所有小於等於a的值,其出現概率的和。F(a) = P(x<=a)。

     針對於灰度級從[0,L-1]的影象,則是統計各個灰度級的累計分佈概率,灰度值k(0<k<L-1)的累計概率是P(0), P(1),P(2).....P(K)之和。

2、實現過程

STEP: 1、統計直方圖個個灰度級出現的次數 2、累計歸一化的直方圖, 3、重新計算畫素值 我們來看看C程式碼(簡化版)是如何一步一步實現的:
for(int i=0;i<height;i++){
	for(int j=0;j<width;j++){
		//s[i][j] 為畫素值
		n[ s[i][j] ]++;
	}
}

for(i=0;i<L;i++){
	//p[i]表示灰度級為i的畫素在整影象中出現的概率,
	p[i] = n[i] / (width*height);
}

//step 2 
for(i=0;i<L;i++){
	for(k=0;k<=i; k++){
		//c[]儲存的是累計的歸一化直方圖
		c[i] += p[k];
	}
}

//step 3
//set the new pixel
for(int i=0;i<height;i++){
	for(int j=0;j<width;j++){
		//max is the max value of s[i][j]
		s[i][j] = c[ s[i][j] ] *(max-min) + min;
	}
}

示意圖如下:

左邊是原始畫素值,Ni是統計的某個灰度級數量,Pi是出現的概率 ,sumPi是累計的歸一化概率,最後是重新分配的畫素值。


3、matlab程式碼實現

matlab實現:

%讀入影象並進行直方圖繪製
% ZhangFL  at SWPU 2017-07-03
%
clear;
img = imread('test.tif');
[height,width] = size(img);%return (rows,cols)


numberPix = zeros(1, 256);
for i=1:height
    for j=1:width
        numberPix( img(i,j) + 1 ) = numberPix( img(i,j)+1 ) + 1;%畫素值有0,所以+1
    end
end

p = zeros(1,256);
for i=1:256
    p(i) = numberPix(i) / (width * height);
end;

    
pSum = zeros(1,256);
for i=1:256
    if i == 1
        pSum(i) = p(i);
    else
        pSum(i) = pSum(i-1) + p(i);
    end;
end
%累計分佈取整
newPix = uint8(pSum.*255 + 0.5);%change double to int 
for i=1:height
    for j=1:width
        imgNew(i,j) = newPix( img(i,j) );
    end
end

subplot(2,2,1),imshow(img),title('原影象');
subplot(2,2,2),imhist(img),title('原影象直方圖');
subplot(2,2,3),imshow(imgNew),title('直方圖均衡化影象');
subplot(2,2,4),imhist(imgNew),title('新的直方圖');

結果圖:


4、openCV程式碼實現

首先對於灰度圖,我們可以直接處理,或者將彩色圖作為灰度圖讀入處理,前提是我們只想得到灰度圖。由於直方圖均衡化中原始影象和目標影象都必須是單通道,因此,如果我們想要處理彩色圖,需要先將三通道分離,對其分別進行均衡化操作,然後再合成三通道影象。

這裡補充說下,openCV 中函式:IplImage* cvLoadImage( const char* filename, int flags=CV_LOAD_IMAGE_COLOR );

        採用IplImage *img = cvLoadImage(picName)預設值是CV_LOAD_IMAGE_COLOR  讀取無論原始影象的通道數是多少,都將被轉換為3個通道讀入。

所以我們如果想按灰度圖讀入應該採用:IplImage *img = cvLoadImage(picName,CV_LOAD_IMAGE_GRAYSCALE); 之前由於忽略了這一點,明明將影象分離三通道,並且分別儲存了下來,然後再讀取,結果通道數還是3,糾結了好久,,,這就是沒有加flag的禍害啊。所以,最好還是加上flag.

首先openCV沒有直方圖直接顯示的函式,所以我們需要建立直方圖來自定義繪圖,函式如下:

/*
	 width:直方圖寬度     height:直方圖高度     scale:
*/
IplImage* showImageHistogram(IplImage** image, int width, int height ,int scale){

	int dims = 1;
	int histSize = 256;
	float frange[] = { 0, 255 };
	float* ranges[] = { frange };
	//建立一個直方圖     CV_HIST_ARRAY多維密集陣列
	CvHistogram*  hist = cvCreateHist(dims, &histSize, CV_HIST_ARRAY, ranges);
	//根據輸入影象計算直方圖
	cvCalcHist(image, hist);


	//繪製直方圖區域
	IplImage* histImage = cvCreateImage(cvSize(width*scale, height), IPL_DEPTH_8U, 1);
	//直方圖背景區域置位白色
	cvRectangle(histImage, cvPoint(0, 0), cvPoint(histImage->width, histImage->height), CV_RGB(255,255,255), CV_FILLED);
	//獲取最大值
	float maxHistValue = 0;
	cvGetMinMaxHistValue(hist, NULL, &maxHistValue, NULL,NULL);
	//繪製各個灰度級的直方圖
	for (int i = 0; i < width; i++){
		float value = cvQueryHistValue_1D(hist, i);
		int drawHeight = cvRound((value / maxHistValue) * height);
		cvRectangle(histImage, cvPoint(i*scale, height-1), cvPoint((i+1)*scale -1, height-drawHeight), cvScalar(i, 0, 0, 0), CV_FILLED);
	}
	return histImage;

}

直接處理灰度圖:

void histGrayChange(){
	const char* picName = "test.tif";//test.tif lenaRGB.tif
	//採用IplImage *img = cvLoadImage(picName)預設值是CV_LOAD_IMAGE_COLOR  讀取無論原始影象的通道數是多少,都將被轉換為3個通道讀入。
	//IplImage *img = cvLoadImage(picName);

	//******以灰度影象讀入,強制轉換為單通道*****
	IplImage *img = cvLoadImage(picName,CV_LOAD_IMAGE_GRAYSCALE);
	if (img == NULL){
		cout << "Load File Failed." << endl;
	}
	cout << "ChannelL:" << img->nChannels;


	IplImage* imgDst = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	//直方圖均衡化
	cvEqualizeHist(img, imgDst);
	
	cvNamedWindow("Origin", CV_WINDOW_AUTOSIZE);
	cvShowImage("Origin", img);

	cvNamedWindow("Result", CV_WINDOW_AUTOSIZE);
	cvShowImage("Result", imgDst);

	//
	int histImageWidth = 255;
	int histImageHeight = 150;
	int histImageScale = 2;
	IplImage *histImage1 = showImageHistogram(&img, histImageWidth, histImageHeight, histImageScale);
	cvNamedWindow("Hist1", CV_WINDOW_AUTOSIZE);
	cvShowImage("Hist1", histImage1);

	IplImage *histImage2 = showImageHistogram(&imgDst, histImageWidth, histImageHeight, histImageScale);
	cvNamedWindow("Hist2", CV_WINDOW_AUTOSIZE);
	cvShowImage("Hist2", histImage2);


	cvWaitKey();

	cvDestroyWindow("Origin"); cvReleaseImage(&img);
	cvDestroyWindow("Result"); cvReleaseImage(&imgDst);

}
showImageHistogram()函式是直方圖繪製函式,後面會講到。


彩色圖直方圖均衡化:

void histColorChange(){
	const char* picName = "lenaRGB.tif";
	//採用IplImage *img = cvLoadImage(picName)預設值是CV_LOAD_IMAGE_COLOR  讀取無論原始影象的通道數是多少,都將被轉換為3個通道讀入。
	//IplImage *img = cvLoadImage(picName,CV_LOAD_IMAGE_GRAYSCALE);
	IplImage *img = cvLoadImage(picName);
	if (img == NULL){
		cout << "Load File Failed." << endl;
	}
	IplImage* imgChannel[4] = { NULL, NULL, NULL, NULL };
	IplImage* imgDst = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3);
		
	//建立單通道影象
	for (int i = 0; i < img->nChannels; i++){
		imgChannel[i] = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	}
	//影象三通道分離
	cvSplit(img, imgChannel[0], imgChannel[1], imgChannel[2], imgChannel[3]);//BGRA

	for (int i = 0; i < img->nChannels; i++){
		//直方圖均衡化中原始影象和目標影象都必須是單通道  
		cvEqualizeHist(imgChannel[i], imgChannel[i]);
	}
	//通道組合
	cvMerge(imgChannel[0], imgChannel[1], imgChannel[2], NULL, imgDst);
	
	cvNamedWindow("Origin", CV_WINDOW_AUTOSIZE);
	cvShowImage("Origin",img);

	//直方圖均衡化
	cvNamedWindow("Hist", CV_WINDOW_AUTOSIZE);
	cvShowImage("Hist", imgDst);

	cvWaitKey();

}