實驗二:圖像直接灰度映射及直方圖分析
環境設置:
- Visual Studio 2017: C++
- OpenCV 4.1.1
- Photoshop CC 2018
任務:
- 圖像線性灰度映射
- 圖像非線性灰度變換(對數變換和指數變換)
- 圖像灰度直方圖統計,計算圖像的熵,RGB分量直方圖統計
- 圖像直方圖分析
- 圖像直方圖均衡化和直方圖規定化
- 與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);
}
- PS均衡化後
- 灰度直方圖比較
- RGB直方圖比較(以R爲例
參考資料:
-
OpenCV 滑動條Trackbar C/C++/Python:https://blog.csdn.net/u012005313/article/details/69675803#C2
-
Matlab 圖像增強(對數變換):https://blog.csdn.net/weixin_39830846/article/details/88710594
-
圖像處理-基本算法之指數變換:https://blog.csdn.net/fuyun_613/article/details/7659073
-
OpenCV直方圖計算:https://www.w3cschool.cn/opencv/opencv-nugy2d82.html
-
opencv計算圖像互信息熵:https://blog.csdn.net/chongshangyunxiao321/article/details/51104462
-
直方圖均衡化算法原理與實現:https://blog.csdn.net/lz0499/article/details/82283873
` -
PS均衡化後[外鏈圖片轉存中…(img-htSNiGrK-1569545403281)]
-
灰度直方圖比較[外鏈圖片轉存中…(img-KvC4WyeP-1569545403283)]
-
RGB直方圖比較(以R爲例)[外鏈圖片轉存中…(img-NLmHDkyl-1569545403284)]
參考資料:
- OpenCV 滑動條Trackbar C/C++/Python:https://blog.csdn.net/u012005313/article/details/69675803#C2
- Matlab 圖像增強(對數變換):https://blog.csdn.net/weixin_39830846/article/details/88710594
- 圖像處理-基本算法之指數變換:https://blog.csdn.net/fuyun_613/article/details/7659073
- OpenCV直方圖計算:https://www.w3cschool.cn/opencv/opencv-nugy2d82.html
- opencv計算圖像互信息熵:https://blog.csdn.net/chongshangyunxiao321/article/details/51104462
- 直方圖均衡化算法原理與實現:https://blog.csdn.net/lz0499/article/details/82283873
- 圖像算法(二):直方圖均衡化和直方圖規定化(匹配):https://blog.csdn.net/xuan_zizizi/article/details/82747897