實驗二:圖像直接灰度映射及直方圖分析

實驗二:圖像直接灰度映射及直方圖分析

環境設置:

  • Visual Studio 2017: C++
  • OpenCV 4.1.1
  • Photoshop CC 2018

任務:

  1. 圖像線性灰度映射
  2. 圖像非線性灰度變換(對數變換和指數變換)
  3. 圖像灰度直方圖統計,計算圖像的熵,RGB分量直方圖統計
  4. 圖像直方圖分析
  5. 圖像直方圖均衡化和直方圖規定化
  6. 與Photoshop直方圖均衡化的對比

代碼及答案:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <cmath>

using namespace std;
using namespace cv;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// =====================================================================
// 1. LinearGrayMapping 
// ---------------------------------------------------------------------
// 公式:g(x,y)=(d-c)/(b-a)*[f(x,y)-a]+c

// LinearGrayMapping 全局變量
const int slider_max_linear = 255;
int output_min = 0;
int output_max = 255;
Mat img_linear;
Mat res_linear;

// LinearGrayMapping 計算和顯示函數
void LinearGrayMapping(Mat& input)
{
	// 克隆input
	Mat output = input.clone();
	int rows = output.rows;
	int cols = output.cols*output.channels();

	// 如圖像連續則合爲一行
	if (output.isContinuous())
	{
		cols = cols * rows;
		rows = 1;
	}

	// 求輸入圖像灰度範圍 [input_min,input_max]
	int input_max = 0;
	int input_min = 255;
	for (int i = 0; i < rows; i++)
	{
		uchar*data = input.ptr<uchar>(i);
		for (int j = 0; j < cols; j++)
		{
			if (data[j] > input_max)	input_max = data[j];
			if (data[j] < input_min)	input_min = data[j];
		}
	}

	// 映射 [input_min,input_max] 至 [output_min,output_max]
	for (int i = 0; i < rows; i++)
	{
		uchar*data = input.ptr<uchar>(i);
		uchar*dataout = output.ptr<uchar>(i);
		for (int j = 0; j < cols; j++)
		{
			int temp; // 防止數值溢出
			temp = (output_max - output_min) / (input_max - input_min) * (data[j] - input_min) + output_min;
			if (temp > 255)	dataout[j] = 255; 
			else if (temp < 0)	dataout[j] = 0;
			else dataout[j] = temp;
		}
	}

	// 顯示映射後圖像
	namedWindow("Linear Gray Mapping", WINDOW_NORMAL);
	imshow("Linear Gray Mapping", output);
}

// LinearGrayMapping 最小值滑動條回調函數
void onTrackbar_linear_SetMin(int pos_min, void*)
{
	output_min = pos_min;
	LinearGrayMapping(img_linear);
}

// LinearGrayMapping 最大值滑動條回調函數
void onTrackbar_linear_SetMax(int pos_max, void*)
{
	output_max = pos_max;
	LinearGrayMapping(img_linear);
}


// ======================================================================
// 2. LogGrayMapping & ExpGrayMapping
// ----------------------------------------------------------------------
// 2.1 LogGrayMapping 
// ----------------------------------
// 公式:g(x,y)=a+b*ln[f(x,y)+1]

// LogGrayMapping 參數
const double a_log = 0;
const double b_log = 40;

void LogGrayMapping(Mat& input)
{
	// 克隆input
	Mat output = input.clone();
	int rows = output.rows;
	int cols = output.cols*output.channels();

	// 如圖像連續則合爲一行
	if (output.isContinuous())
	{
		cols = cols * rows;
		rows = 1;
	}
	
	// 對數變換
	for (int i = 0; i < rows; i++)
	{
		uchar*data = input.ptr<uchar>(i);
		uchar*dataout = output.ptr<uchar>(i);
		for (int j = 0; j < cols; j++)
		{
			int temp; // 防止數值溢出
			temp = (int)(a_log + log(data[j] + 1) * b_log);
			//cout << temp<<" ";
			if (temp > 255)	dataout[j] = 255;
			else if (temp < 0)	dataout[j] = 0;
			else dataout[j] = temp;
		}
	}

	// 顯示映射後圖像
	namedWindow("Log Gray Mapping", WINDOW_NORMAL);
	imshow("Log Gray Mapping", output);
	
	// 結束工作
	waitKey(0);
	destroyAllWindows();
}

// -----------------------------------
// 2.2 ExpGrayMapping
// -----------------------------------
// 公式:g(x,y)=b^(c*[f(x,y)-a])-1

// ExpGrayMapping 參數
const double a_exp = 15;
const double b_exp = 1.05;
const double c_exp = 0.5;

void ExpGrayMapping(Mat& input)
{
	// 克隆input
	Mat output = input.clone();
	int rows = output.rows;
	int cols = output.cols*output.channels();

	// 如圖像連續則合爲一行
	if (output.isContinuous())
	{
		cols = cols * rows;
		rows = 1;
	}

	// 指數變換
	for (int i = 0; i < rows; i++)
	{
		uchar*data = input.ptr<uchar>(i);
		uchar*dataout = output.ptr<uchar>(i);
		for (int j = 0; j < cols; j++)
		{
			int temp; // 防止數值溢出
			temp = (int)(pow(b_exp,c_exp*(data[j]-a_exp))-1);
			//cout << temp<<" ";
			if (temp > 255)	dataout[j] = 255;
			else if (temp < 0)	dataout[j] = 0;
			else dataout[j] = temp;
		}
	}

	// 顯示映射後圖像
	namedWindow("Exp Gray Mapping", WINDOW_NORMAL);
	imshow("Exp Gray Mapping", output);

}

// =================================================================================
// 3. 圖像灰度直方圖統計 (DrawHist),計算圖像的熵 (Entropy) ,RGB分量直方圖統計 (RGBHist)
// ---------------------------------------------------------------------------------
// 3.1 DrawHist
// ------------------------------------
Mat DrawHist(Mat img_calcHist)
{
	// 設置cv::CalcHist() 的參數並使用
	int histSize = 256;
	float range[] = { 0,256 };
	const float* histRange = { range };
	bool uniform = true;
	bool accumulate = false;
	Mat gray_hist;

	calcHist(&img_calcHist, 1, 0, Mat(), gray_hist, 1, &histSize, &histRange, uniform, accumulate);

	// 創建直方圖
	int hist_w = 512; int hist_h = 400;
	int bin_w = cvRound((double)hist_w / histSize);
	Mat histImage(hist_h, hist_w, CV_8UC1, Scalar(0, 0, 0));

	// 歸一化直方圖
	normalize(gray_hist, gray_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

	// 繪製直方圖
	for (int i = 1; i < histSize; i++)
	{
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(gray_hist.at<float>(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(gray_hist.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);
	}

	// 顯示直方圖
	namedWindow("calcHist Demo", WINDOW_AUTOSIZE);
	imshow("calcHist Demo", histImage);

	// 結束工作
	waitKey(0);
	destroyAllWindows();

	// 返回直方圖
	return gray_hist;
}
// ------------------------------------
// 3.2 Entropy
// ------------------------------------
// 單幅圖像信息熵計算
double Entropy(Mat img)
{
	double temp[256] = { 0.0 };

	// 計算每個像素的累積值
	for (int m = 0; m < img.rows; m++)
	{
		// 有效訪問行列的方式
		const uchar* t = img.ptr<uchar>(m);
		for (int n = 0; n < img.cols; n++)
		{
			int i = t[n];
			temp[i] = temp[i] + 1;
		}
	}

	// 計算每個像素的概率
	for (int i = 0; i < 256; i++)
	{
		temp[i] = temp[i] / (img.rows*img.cols);
	}

	double result = 0;

	// 計算圖像信息熵
	for (int i = 0; i < 256; i++)
	{
		if (temp[i] == 0.0)
			result = result;
		else
			result = result - temp[i] * (log(temp[i]) / log(2.0)); // H=-SIGMA[P(k)*log2(P(k))]
	}

	return result;
}
// ------------------------------------
// 3.3 RGBHist
// ------------------------------------
void RGBHist(Mat img_calcHist_rgb)
{
	// 分割圖像
	vector<Mat> bgr_planes;
	split(img_calcHist_rgb, bgr_planes);

	// 設置cv::CalcHist() 的參數並使用
	int histSize_rgb = 256;
	float range_rgb[] = { 0,256 };
	const float* histRange_rgb = { range_rgb };
	bool uniform_rgb = true;
	bool accumulate_rgb = false;
	Mat b_hist, g_hist, r_hist;

	calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize_rgb, &histRange_rgb, uniform_rgb, accumulate_rgb);
	calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize_rgb, &histRange_rgb, uniform_rgb, accumulate_rgb);
	calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize_rgb, &histRange_rgb, uniform_rgb, accumulate_rgb);

	// 創建直方圖
	int hist_w_rgb = 512; int hist_h_rgb = 400;
	int bin_w_rgb = cvRound((double)hist_w_rgb / histSize_rgb);
	Mat histImage_rgb(hist_h_rgb, hist_w_rgb, CV_8UC3, Scalar(0, 0, 0));

	// 歸一化直方圖
	normalize(b_hist, b_hist, 0, histImage_rgb.rows, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, histImage_rgb.rows, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, histImage_rgb.rows, NORM_MINMAX, -1, Mat());

	// 繪製直方圖
	for (int i = 1; i < histSize_rgb; i++)
	{
		line(histImage_rgb, Point(bin_w_rgb*(i - 1), hist_h_rgb - cvRound(b_hist.at<float>(i - 1))),
			Point(bin_w_rgb*(i), hist_h_rgb - cvRound(b_hist.at<float>(i))),
			Scalar(255, 0, 0), 2, 8, 0);
		line(histImage_rgb, Point(bin_w_rgb*(i - 1), hist_h_rgb - cvRound(g_hist.at<float>(i - 1))),
			Point(bin_w_rgb*(i), hist_h_rgb - cvRound(g_hist.at<float>(i))),
			Scalar(0, 255, 0), 2, 8, 0);
		line(histImage_rgb, Point(bin_w_rgb*(i - 1), hist_h_rgb - cvRound(r_hist.at<float>(i - 1))),
			Point(bin_w_rgb*(i), hist_h_rgb - cvRound(r_hist.at<float>(i))),
			Scalar(255, 0, 255), 2, 8, 0);
	}
		// 顯示直方圖
		namedWindow("calcHist RGB Demo", WINDOW_AUTOSIZE);
		imshow("calcHist RGB Demo", histImage_rgb);

		// 結束工作
		waitKey(0);
		destroyAllWindows();
}

// ================================================================================
// 5. 直方圖均衡化 (EqualizeHistogram), 直方圖規定化 (HistMatch)
// --------------------------------------------------------------------------------
// 5.1 EqualizeHistogram
// ---------------------------------------
void GetHistogram(const Mat &image, int *histogram)
{
	memset(histogram, 0, 256 * sizeof(int));

	//計算直方圖的數組結果,存入數組histogram
	int pixelCount = image.cols*image.rows;
	uchar *imageData = image.data;
	for (int i = 0; i <= pixelCount - 1; ++i)
	{
		int gray = imageData[i];
		histogram[gray]++;
	}
}

void EqualizeHistogram(const Mat &srcImage, Mat &dstImage)
{
	CV_Assert(srcImage.type() == CV_8UC1);
	dstImage.create(srcImage.size(), srcImage.type());

	// 計算直方圖
	int histogram[256];
	GetHistogram(srcImage, histogram);

	// 計算分佈函數(也就是變換函數f(x))
	int numberOfPixel = srcImage.rows*srcImage.cols;
	int LUT[256];
	LUT[0] = 1.0*histogram[0] / numberOfPixel * 255;
	int sum = histogram[0];
	for (int i = 1; i <= 255; ++i)
	{
		sum += histogram[i];

		LUT[i] = 1.0*sum / numberOfPixel * 255;
	}

	// 灰度變換
	uchar *dataOfSrc = srcImage.data;
	uchar *dataOfDst = dstImage.data;
	for (int i = 0; i <= numberOfPixel - 1; ++i)
		dataOfDst[i] = LUT[dataOfSrc[i]];

	// 顯示源圖像與輸出圖像與他們的直方圖
	namedWindow("Source Image");
	imshow("Source Image", srcImage);
	DrawHist(srcImage);
	namedWindow("Equalize");
	imshow("Equalize", dstImage);
	DrawHist(dstImage);

	// 結束工作
	waitKey(0);
	destroyAllWindows();
}

// ---------------------------------------------------
// 5.2 HistMatch
// ---------------------------------------------------
Mat HistMatch(Mat &srcImage, Mat &targetImage)
{
	int rows = srcImage.rows;
	int cols = srcImage.cols;
	int rows_t = targetImage.rows;
	int cols_t = targetImage.cols;
	Mat resultImage(rows, cols, srcImage.type());

	int Hist[256];
	double HistP[256];
	double HistPSum[256];
	int Hist_t[256];
	double HistP_t[256];
	double HistPSum_t[256];
	int HistMatch[256];

	int i, j;
	for (i = 0; i < 256; i++)
	{
		Hist[i] = 0;
		HistP[i] = 0.0;
		HistPSum[i] = 0.0;
		Hist_t[i] = 0;
		HistP_t[i] = 0.0;
		HistPSum_t[i] = 0.0;
		HistMatch[i] = 0;
	}
	//求每個像素的頻數
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			Hist[(int)srcImage.at<uchar>(i, j)]++;
		}
	}
	for (i = 0; i < rows_t; i++)
	{
		for (j = 0; j < cols_t; j++)
		{
			Hist_t[(int)targetImage.at<uchar>(i, j)]++;
		}
	}
	//每個像素的概率
	for (i = 0; i < 256; i++)
	{
		HistP[i] = (double)Hist[i] / (rows*cols);
		HistP_t[i] = (double)Hist[i] / (rows_t*cols_t);
	}

	//每個像素的累計概率
	HistPSum[0] = HistP[0];
	HistPSum_t[0] = HistP_t[0];
	for (i = 1; i < 256; i++)
	{
		HistPSum[i] = HistP[i] + HistPSum[i - 1];
		HistPSum_t[i] = HistP_t[i] + HistPSum_t[i - 1];
	}
	//求匹配度
	for (i = 0; i < 256; i++)
	{
		double min = 255.0;
		int flag = 0;
		for (j = 0; j < 256; j++)
		{
			if (min > abs(HistPSum[i] - HistPSum_t[j]))
			{
				min = abs(HistPSum[i] - HistPSum_t[j]);
				flag = j;
			}
		}
		HistMatch[i] = flag;

	}
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			resultImage.at<uchar>(i, j) = HistMatch[(int)srcImage.at<uchar>(i, j)];
		}
	}

	// 顯示源圖像與輸出圖像
	namedWindow("Source Image");
	namedWindow("HistMatch");
	imshow("Source Image", srcImage);
	imshow("HistMatch", resultImage);

	// 結束工作
	waitKey(0);
	destroyAllWindows();

	return resultImage;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////





int main()
{
	// =================================================================================================
	// 1. 線性灰度變換
	// -------------------------------------------------------------------------------------------------
	// 設定原圖像路徑(用"//"或"\\")
	char route_linear[] = "../Project2//線性變換.jpg";
	cout << "當前進行線性灰度變換,源圖像路徑:" << route_linear << endl;

	// 讀入圖像並創建源圖像窗口
	img_linear = imread(route_linear, IMREAD_GRAYSCALE);
	namedWindow("Source Image", WINDOW_NORMAL);

	// 創建輸出窗口和滑動條
	namedWindow("Linear Gray Mapping", WINDOW_NORMAL);
	createTrackbar("Gray Min", "Linear Gray Mapping", &output_min, slider_max_linear, onTrackbar_linear_SetMin);
	createTrackbar("Gray Max", "Linear Gray Mapping", &output_max, slider_max_linear, onTrackbar_linear_SetMax);

	//顯示源圖像
	imshow("Source Image", img_linear);

	//結束工作
	waitKey(0);
	destroyAllWindows();
	// =================================================================================================
	// 2. 圖像冪律灰度變換
	// -------------------------------------------------------------------------------------------------
	// 2.1 對數變換
	// -------------------------------------------------
	// 設定原圖像路徑(用"//"或"\\")
	char route_log[] = "../Project2//對數變換.jpg";
	cout << "當前進行對數灰度變換,源圖像路徑:" << route_log << endl;

	// 讀入圖像並創建源圖像窗口
	Mat img_log = imread(route_log, IMREAD_GRAYSCALE);
	namedWindow("Source Image", WINDOW_NORMAL);

	// 創建輸出窗口
	namedWindow("Log Gray Mapping", WINDOW_NORMAL);

	// 顯示源圖像
	imshow("Source Image", img_log);

	// 處理圖像
	LogGrayMapping(img_log);



	// --------------------------------------------------
	// 2.2 指數變換
	// --------------------------------------------------
	// 設定原圖像路徑(用"//"或"\\")
	char route_exp[] = "../Project2//指數變換.jpg";
	cout << "當前進行指數灰度變換,源圖像路徑:" << route_exp << endl;

	// 讀入圖像並創建源圖像窗口
	Mat img_exp = imread(route_exp, IMREAD_GRAYSCALE);
	namedWindow("Source Image", WINDOW_NORMAL);

	// 創建輸出窗口
	namedWindow("Exp Gray Mapping", WINDOW_NORMAL);

	// 顯示源圖像
	imshow("Source Image", img_exp);

	// 調用 ExpGrayMaapping 函數處理圖像
	ExpGrayMapping(img_exp);

	// 結束工作
	waitKey(0);
	destroyAllWindows();

	// ===========================================================================
	// 3. 圖像灰度直方圖統計,計算圖像的熵,RGB分量直方圖統計
	// --------------------------------------------------------------------------
	// 3.1 直方圖統計
	// ---------------------------------
	// 設定路徑
	char route_calcHist[] = "../Project2//直方圖規定化1.jpg";
	cout << "當前進行直方圖統計,源圖像路徑:" << route_calcHist << endl;

	// 讀入源圖像並顯示源圖像
	Mat img_calcHist = imread(route_calcHist, IMREAD_GRAYSCALE);
	namedWindow("Source Image", WINDOW_NORMAL);
	imshow("Source Image", img_calcHist);

	DrawHist(img_calcHist);
	   
	// -------------------------------------
	// 3.2 計算圖像的熵
	// -------------------------------------
	// 設定路徑
	char route_entropy[] = "../Project2//直方圖規定化1.jpg";
	cout << "當前進行圖像熵的計算,源圖像路徑:" << route_entropy << endl;

	//讀入源圖像並調用Entropy函數計算
	Mat img_entropy = imread(route_entropy, IMREAD_GRAYSCALE);
	cout << "該圖像的熵爲:" << Entropy(img_entropy) << endl;

	// -------------------------------------
	// 3.3 RGB分量直方圖統計
	// -------------------------------------
	// 設定路徑
	char route_calcHist_rgb[] = "../Project2//RGB分量直方圖.jpg";
	cout << "當前進行RGB分量直方圖統計,源圖像路徑:" << route_calcHist_rgb << endl;

	// 讀入源圖像並顯示源圖像
	Mat img_calcHist_rgb = imread(route_calcHist_rgb, 1);
	namedWindow("Source Image", WINDOW_NORMAL);
	imshow("Source Image", img_calcHist_rgb);

	RGBHist(img_calcHist_rgb);

	// ===============================================================================================
	// 4. 圖像直方圖分析
	// -----------------------------------------------------------------------------------------------
	// (1)直方圖分佈範圍廣的動態範圍廣,分佈集中的地方擁有對應灰度值的像素個數多
	// (2)直方圖分佈與圖像的旋轉、平移等僅改變位置的操作無關
	// (3)可以從直方圖分佈中瞭解到圖像的曝光信息
	// (4)……

	// ===============================================================================================
	// 5. 圖像直方圖均衡化和直方圖規定化
	// -----------------------------------------------------------------------------------------------
	// 5.1 圖像直方圖均衡化
	// ------------------------------------------------
	// 設置路徑及輸入圖像
	char route_equal[] = "../Project2//直方圖均衡化penguin.jpg";
	Mat img_equal = imread(route_equal, IMREAD_GRAYSCALE);

	// 設置輸出圖像
	Mat output_equal = img_equal.clone();

	// 調用 EquailizeHistogram
	EqualizeHistogram(img_equal, output_equal);

	// ------------------------------------------------
	// 5.2 直方圖規定化
	// ------------------------------------------------
	// 設置路徑及輸入圖像
	char route_match_1[] = "../Project2//直方圖規定化1.jpg";
	char route_match_2[] = "../Project2//直方圖規定化2.jpg";
	Mat img_match_1 = imread(route_match_1, IMREAD_GRAYSCALE);
	Mat img_match_2 = imread(route_match_2, IMREAD_GRAYSCALE);

	// 設置輸出圖像
	Mat output_match = img_match_1.clone();

	// 調用 HistMatch 函數
	output_match = HistMatch(img_match_1, img_match_2);

	}


  1. PS均衡化後
    在這裏插入圖片描述
  2. 灰度直方圖比較
    在這裏插入圖片描述
  3. RGB直方圖比較(以R爲例
    在這裏插入圖片描述

參考資料:

  1. OpenCV 滑動條Trackbar C/C++/Python:https://blog.csdn.net/u012005313/article/details/69675803#C2

  2. Matlab 圖像增強(對數變換):https://blog.csdn.net/weixin_39830846/article/details/88710594

  3. 圖像處理-基本算法之指數變換:https://blog.csdn.net/fuyun_613/article/details/7659073

  4. OpenCV直方圖計算:https://www.w3cschool.cn/opencv/opencv-nugy2d82.html

  5. opencv計算圖像互信息熵:https://blog.csdn.net/chongshangyunxiao321/article/details/51104462

  6. 直方圖均衡化算法原理與實現:https://blog.csdn.net/lz0499/article/details/82283873
    `

  7. PS均衡化後[外鏈圖片轉存中…(img-htSNiGrK-1569545403281)]

  8. 灰度直方圖比較[外鏈圖片轉存中…(img-KvC4WyeP-1569545403283)]

  9. RGB直方圖比較(以R爲例)[外鏈圖片轉存中…(img-NLmHDkyl-1569545403284)]

參考資料:

  1. OpenCV 滑動條Trackbar C/C++/Python:https://blog.csdn.net/u012005313/article/details/69675803#C2
  2. Matlab 圖像增強(對數變換):https://blog.csdn.net/weixin_39830846/article/details/88710594
  3. 圖像處理-基本算法之指數變換:https://blog.csdn.net/fuyun_613/article/details/7659073
  4. OpenCV直方圖計算:https://www.w3cschool.cn/opencv/opencv-nugy2d82.html
  5. opencv計算圖像互信息熵:https://blog.csdn.net/chongshangyunxiao321/article/details/51104462
  6. 直方圖均衡化算法原理與實現:https://blog.csdn.net/lz0499/article/details/82283873
  7. 圖像算法(二):直方圖均衡化和直方圖規定化(匹配):https://blog.csdn.net/xuan_zizizi/article/details/82747897
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章