圖片直方圖均衡化


對於輸入像素點r和輸出像素點s都在灰度級 [0,L-1]之間,r = 0 代表黑色, r = L - 1代表白色。對於r和s的變換形式為:

r和s滿足一下條件:

利用反函數來從s推r時,有以下定義:

r和s滿足條件:

條件a是為了保證輸出的灰度級不少於輸入,這是為了防止二義性。條件b是為了保證輸出的灰度范圍和輸入的灰度范圍相同。條件a`也是為了保證s到r 也是一一對應的,防止二義性。實驗中采用8bit整性的像素分布,不一定滿足這個情況。

在實驗中會類似左邊的圖像,出現多個輸入的r,輸出同一個s值。而在理論的約束上應該和右圖相似,r與s一一對應。
對於一副灰度圖像,

分別代表輸入圖像和輸出圖像的像素點的概率分布,我們簡稱為PDF。對於已知的和滿足公式:

(1)
直方圖均衡化的采用的公式如下:

(2)
其中w是積分假變量,L-1是最大的灰度級。
為什么要這么做呢。由萊布尼茲准則,我們知道上限的定積分的導數是被積函數在該上限的值。

(3)
我們將的結果帶入(1)中,

(4)

2 結論

使用(2)公式后,輸出圖像的像素點s分布是均勻的,PDF為1/(L-1)。

3 離散直方圖均衡化采用公式

對於離散的直方圖均衡化采用的公式為:

(5)
其中MN是總的像素點個數

編碼實現

1 統計輸入的灰度圖像的像素信息

void calHistInfo(const Mat& src, vector<unsigned int>& calVec, vector<unsigned int >& calVecBefor,unsigned int LMax){
	int width = src.cols;
	int height = src.rows;
	int piexl = 0;
	for (int h = 0; h < height; h++) 
	{
		for (int w = 0; w < width; w++) {
			piexl = (int)src.at<uchar>(h, w);
			calVec[round(piexl)] += 1;
		}
	}
	
	calVecBefor[0] = calVec[0];
	for (int i = 1; i < calVec.size(); i++)
	{

		calVecBefor[i] = calVecBefor[i - 1] + calVec[i];
	}
	cout << "統計完畢" << endl;
}

這里我用了兩個vector數組calVec 和calVecBefor ,calVec是用於記錄每個灰度級別的像素個數,
calVecBefor是記錄在當前像素r個數和r之前的像素之和。LMax是用於記錄有多少個灰度級別,這里LMax =256。

2 根據直方圖統計信息繪制折線圖

void showHistChart(Mat& canvas,unsigned int LMax,vector<unsigned int> calVec,Scalar sca, int thikness)
{
//橫軸是灰度級
//縱軸是像素的分布情況,我采取的方法是像素數目最多的為單位1
	int canvas_height = canvas.rows;
	int canvas_step = canvas.cols / LMax;
	auto it = max_element(calVec.begin(), calVec.end());
	unsigned int calMax = *it;

	//開始畫折線圖
	for (int i = 0; i < LMax-1; i++) 
	{
		line(canvas, Point(i * canvas_step, canvas_height - (canvas_height*(1.0)*calVec[i]/calMax)),
			Point((i + 1) * canvas_step, canvas_height - (canvas_height * (1.0) * calVec[i+1] / calMax)),
			sca, thikness, 8);
	}
}

其中canvas是畫布,首先要找到像素點最多的灰度級別和該級別像素的個數calMax,以及根據畫布大小和灰度級個數確定步長canvas_step,sca是畫折線使用的顏色, thikness是折線的粗細,參數8是渲染方式。

3 確定像素點r與s之間的映射

void createRSTable(const vector<unsigned int>& calVecBefor,
	unordered_map<unsigned int,unsigned int>& table_rs,unsigned int LMax)
{
	double total = (double)calVecBefor[LMax - 1];
	for (int i =0;i<LMax;i++) 
	{
		double s = LMax*(double)calVecBefor[i] * (1.0) / total;
		if (round(s) <= 255) {
			table_rs.insert(make_pair(i, round(s)));
		}
		else 
		{
			table_rs.insert(make_pair(i, 255));
		}
		
	}
}

這里我采用的unordered_map用於記錄輸入的r與輸出的s之間的映射關系。當然這里也可以用數組之類的記錄,記錄方法就是公式(5)。

4 灰度圖像的直方圖均衡化

void iHistImp(Mat& src, Mat&dst, unordered_map<unsigned int,unsigned int>& table_rs) {

	dst = Mat::zeros(src.size(), src.type());
	int width = src.cols;
	int height = src.rows;
	int piexl = 0;
	for (int h = 0; h < height; h++)
	{
		for (int w = 0; w < width; w++) {
			piexl = (int)src.at<uchar>(h, w);

				auto it = table_rs.find(piexl);
				if (it != table_rs.end())
				{
					dst.at<uchar>(h, w) = it->second;
				}

		}
	}
}

這里通過遍歷灰度輸入的輸入像素點r,來找到相應的輸出s。

5 灰度圖像直方圖均衡化的結果

這里我除了使用自己實現的直方圖均衡化,還調用opencv中直方圖均衡化的api,來對比實驗效果。
實驗前后的折線圖:

紅色是直方圖均衡化前的像素統計,藍色是均衡化后的像素統計。

6 彩色圖像的直方圖均衡化

彩色圖像的直方圖均衡化,我是將輸入的彩色圖像分成bgr三個通道,分別進行直方圖均衡化,然后再將結果合並再一起。

/*
* 彩色圖像的直方圖均衡化
*/
void BGRHist(Mat& src, Mat& dst) 
{
	//將彩色圖像的按照BGR三個通道切分
	vector<Mat> splitMat;
	split(src,splitMat);

	//分別對bgr 三個通道進行直方圖均衡化
	vector<Mat> mergeMat;
	split(src, mergeMat);
	for (int i = 0; i < 3; i++) 
	{
		unsigned int LMax = 256;
		vector<unsigned int> calVec(LMax, 0);
		vector<unsigned int > calVecBefor(LMax, 0);//用於記錄灰度級l之前的所以的像素點個數
		calHistInfo(splitMat[i], calVec, calVecBefor, LMax);
		//構建輸入像素r 與輸出像素s 之間的 一一映射表
		unordered_map<unsigned int, unsigned int > table_rs;
		createRSTable(calVecBefor, table_rs, LMax);
		iHistImp(splitMat[i],mergeMat[i], table_rs);
	}
	merge(mergeMat,dst);
}

彩色圖像直方圖均衡化的結果:

實驗中的問題以及需要改進的地方

1、在實驗的像素的統計過程和r與s轉換的過程,可以采用基於指針的方式遍歷。

2、彩色圖像的直方圖均衡化效果不是很好,可以嘗試新的算法。

3、r與s的映射表可以轉成用數組存放,可能要快點。

4、可以將r->s的值存為一個灰度級的映射表

5、可以對直方圖均衡化的結果再進行直方圖均衡化,可以設置迭代次數

直方圖匹配(直方圖規定化)

后面有空再實現

局部直方圖處理

后面再實現

后記

1、使用高頻強調濾波,然后再使用直方圖均衡,可以更好的處理圖片。《數字圖像處理·第三版》P181 有介紹類似的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM