關鍵詞
直方圖(histogram):直方圖是圖像的灰度——像素數統計圖,即對於每個灰度值,統計在圖像中具有該灰度值的像素個數,並繪制成圖形,稱為灰度直方圖(簡稱直方圖)。
直方圖模型(Gray-level histogram)表示圖像中不同灰度級出現的相對頻率。
詳細介紹
直方圖均衡化通常用來擴大圖像的動態范圍。容易想到的是,當一幅圖像的灰度值大部分集中在一道灰度區間里時,因為像素的亮度都相近,會使圖像的細節不明顯,整幅圖像看起來很模糊。簡而言之,直方圖均衡化是通過拉伸像素強度分布范圍來增強圖像對比度的一種方法。
數字圖像是離散化的數值矩陣,它的直方圖可被視為一個離散函數,表示數字圖像中每個灰度級與其出現概率見的統計關系。假設一副數字圖像f(x,y)的像素總數為N,rk表示第k個灰度級對應的灰度,nk表示灰度為rk的像素個數(頻數),若用橫坐標表示灰度級,用縱坐標表示頻數,則直方圖可被定義為P(rk)=nk/N,其中,P(rk)表示灰度rk出現的相對頻數即概率。直方圖在一定的程度上能夠反映數字圖像的概貌性描述,包括圖像的灰度范圍、灰度分布、整幅圖像的亮度均值和明暗對比度等,並可以此為基礎進行分析來得出對圖像進一步處理的重要依據。直方圖均衡化也叫做直方圖均勻化,就是把給定圖像的直方圖變成均勻分布的直方圖,是一種較為常用的灰度增強算法。直方圖均衡化通常包括以下三個主要步驟。
(1)預處理。輸入圖像,計算圖像的直方圖。
(2)灰度變換表。根據輸入圖像的直方圖計算灰度值變換表。
(3)查表變換。執行變換x'=H(x),表示堆在步驟1中得到的直方圖使用步驟2得到的灰度值變換表進行查表的操作,通過遍歷整幅圖像的每一個像素元,將原始圖像灰度值x放入變換表H(x),可得到變換后得到的新灰度值x'。
根據信息論,圖像在經過直方圖均衡化后,將會包含更多的信息量進而能突出某些圖像特征。假設圖像具有n級灰度,其第i級灰度出現的概率為p,則該級灰度所含的信息量為:
整幅圖像的信息量為:
信息論已經證明,具有均勻分布直方圖的圖像,其信息量H最大。即當p0=p1=...=pn-1=1/n時,上式有最大值。
直方圖模型
記rk∈[0,1]為灰度級,灰度級為rk的像素個數。則直方圖:h(rk)=nk。則歸一化的直方圖:
n:像素總數;pk(rk):原始圖像灰度分布的概率密度函數。如果將rk歸一化到[0,1]之間,則rk便可以視作區間[0,1]的隨機變量。直方圖均衡化處理:假設原圖的灰度化值變量為r,變換后新圖的灰度值變量為s,我們希望尋找一個灰度變換函數T:s=T(r),使得概率密度函數pr(r)變換成希望的概率密度函數ps(s)。灰度變換函數應該滿足:
(1)T(r)在區間[0,1]中單調遞增且單值;
(2)∀r∈[0,1],有T(r)∈[0,1]。
滿足以上條件的一個重要的直方圖均衡化灰度變換函數(均勻分布的隨機變量)為,(原始圖像灰度r的累積分布函數(CDF))
根據該方程可以由原圖像的各像素直接得到直方圖均衡化給灰度所占百分比。例子:
統計原始圖像的直方圖:
8個灰度級,總計64*64=4096點。注意:離散均衡不可能拉平。
僅存5個灰級,宏觀拉平,微觀不可能平,層次減少,對比度提高。均衡化的直方圖:
小結:1)因為直方圖是近似的概率密度函數,所以用離散灰度級進行變換時很少得到完全平坦的結果; 2) 變換后灰度級減少,即出現灰度“簡並”現象,造成一些灰度層次的損失。直方圖均衡化是一種非線性變換。直方圖均衡的特點:增加像素灰度值的動態范圍,提高圖像對比度。
均衡化優點 能自動增強整個圖像的對比度,但具體的增強效果不易控制,處理的結果是全局均衡的直方圖,實際中需特定形狀的直方圖,從而有選擇的增強某個灰度值范圍內的對比度。分別對原始直方圖和規定化處理后的直方圖進行均衡化處理。
直方圖均衡化的缺陷:不能用於交互方式的圖象增強應用,因為直方圖均衡化只能產生唯一一個結果。 恆定值直方圖近似 希望通過一個指定的函數(如高斯函數)或用交互圖形產生一個特定的直方圖。根據這個直方圖確定一個灰度級變換T(r),使由T產生的新圖象的直方圖符合指定的直方圖。
示例程序

1 #pragma once 2 #include "opencv2/highgui/highgui.hpp" 3 #include "opencv2/imgproc/imgproc.hpp" 4 #include <iostream> 5 6 using namespace std; 7 using namespace cv; 8 9 #define WINDOW_NAME1 "【原始圖】" 10 11 void drawHis(Mat srcImage, Mat dstImage) 12 { 13 //將色調量化為30個等級,將飽和度量化為32個等級 14 int hueBinNum = 30;//色調的直方圖直條數量 15 int saturationBinNum = 32;//飽和度的直方圖直條數量 16 int histSize[] = { hueBinNum, saturationBinNum }; 17 // 定義色調的變化范圍為0到179 18 float hueRanges[] = { 0, 180 }; 19 //定義飽和度的變化范圍為0(黑、白、灰)到255(純光譜顏色) 20 float saturationRanges[] = { 0, 256 }; 21 const float* ranges[] = { hueRanges, saturationRanges }; 22 MatND dstHist; 23 //參數准備,calcHist函數中將計算第0通道和第1通道的直方圖 24 int channels[] = { 0, 1 }; 25 26 //【3】正式調用calcHist,進行直方圖計算 27 calcHist(&dstImage,//輸入的數組 28 1, //數組個數為1 29 channels,//通道索引 30 Mat(), //不使用掩膜 31 dstHist, //輸出的目標直方圖 32 2, //需要計算的直方圖的維度為2 33 histSize, //存放每個維度的直方圖尺寸的數組 34 ranges,//每一維數值的取值范圍數組 35 true, // 指示直方圖是否均勻的標識符,true表示均勻的直方圖 36 false);//累計標識符,false表示直方圖在配置階段會被清零 37 38 //【4】為繪制直方圖准備參數 39 double maxValue = 0;//最大值 40 minMaxLoc(dstHist, 0, &maxValue, 0, 0);//查找數組和子數組的全局最小值和最大值存入maxValue中 41 int scale = 10; 42 Mat histImg = Mat::zeros(saturationBinNum * scale, hueBinNum * 10, CV_8UC3); 43 44 //【5】雙層循環,進行直方圖繪制 45 for (int hue = 0; hue < hueBinNum; hue++) 46 for (int saturation = 0; saturation < saturationBinNum; saturation++) 47 { 48 float binValue = dstHist.at<float>(hue, saturation);//直方圖組距的值 49 int intensity = cvRound(binValue * 255 / maxValue);//強度 50 51 //正式進行繪制 52 rectangle(histImg, Point(hue * scale, saturation * scale), 53 Point((hue + 1) * scale - 1, (saturation + 1) * scale - 1), 54 Scalar::all(intensity), FILLED); 55 } 56 57 //【6】顯示效果圖 58 imshow("素材圖", srcImage); 59 imshow("H-S 直方圖", histImg); 60 } 61 62 void drawHis1(Mat srcImage, string name) 63 { 64 MatND dstHist; // 在cv中用CvHistogram *hist = cvCreateHist 65 int dims = 1; // 一維 66 float hranges[] = { 0, 255 }; // 灰度區間 67 const float* ranges[] = { hranges }; // 這里需要為const類型 68 int size = 256; // 灰度級數 69 int channels = 0; 70 71 calcHist(&srcImage, 1, &channels, Mat(), dstHist, dims, &size, ranges); // cv 中是cvCalcHist 72 int scale = 1; 73 74 Mat dstImage(size * scale, size, CV_8U, Scalar(0)); // 全0矩陣 75 76 double minValue = 0; 77 double maxValue = 0; 78 minMaxLoc(dstHist, &minValue, &maxValue, 0, 0); // 在cv中用的是cvGetMinMaxHistValue 79 80 int hpt = saturate_cast<int>(0.9 * size); 81 for (int i = 0; i < 256; i++) 82 { 83 float binValue = dstHist.at<float>(i); // 注意hist中是float類型 而在OpenCV1.0版中用cvQueryHistValue_1D 84 int realValue = saturate_cast<int>(binValue * hpt / maxValue); 85 rectangle(dstImage, Point(i * scale, size - 1), Point((i + 1) * scale - 1, size - realValue), Scalar(255)); 86 } 87 imshow(name, dstImage); 88 } 89 90 void drawHisrgb(Mat srcImage) 91 { 92 int bins = 256; 93 int hist_size[] = { bins }; 94 float range[] = { 0, 256 }; 95 const float* ranges[] = { range }; 96 MatND redHist, grayHist, blueHist; 97 int channels_r[] = { 0 }; 98 99 //【3】進行直方圖的計算(紅色分量部分) 100 calcHist(&srcImage, 1, channels_r, Mat(), //不使用掩膜 101 redHist, 1, hist_size, ranges, 102 true, false); 103 104 //【4】進行直方圖的計算(綠色分量部分) 105 int channels_g[] = { 1 }; 106 calcHist(&srcImage, 1, channels_g, Mat(), // do not use mask 107 grayHist, 1, hist_size, ranges, 108 true, // the histogram is uniform 109 false); 110 111 //【5】進行直方圖的計算(藍色分量部分) 112 int channels_b[] = { 2 }; 113 calcHist(&srcImage, 1, channels_b, Mat(), // do not use mask 114 blueHist, 1, hist_size, ranges, 115 true, // the histogram is uniform 116 false); 117 118 //-----------------------繪制出三色直方圖------------------------ 119 //參數准備 120 double maxValue_red, maxValue_green, maxValue_blue; 121 minMaxLoc(redHist, 0, &maxValue_red, 0, 0); 122 minMaxLoc(grayHist, 0, &maxValue_green, 0, 0); 123 minMaxLoc(blueHist, 0, &maxValue_blue, 0, 0); 124 int scale = 1; 125 int histHeight = 256; 126 Mat histImage = Mat::zeros(histHeight, bins * 3, CV_8UC3); 127 128 //正式開始繪制 129 for (int i = 0; i < bins; i++) 130 { 131 //參數准備 132 float binValue_red = redHist.at<float>(i); 133 float binValue_green = grayHist.at<float>(i); 134 float binValue_blue = blueHist.at<float>(i); 135 int intensity_red = cvRound(binValue_red * histHeight / maxValue_red); //要繪制的高度 136 int intensity_green = cvRound(binValue_green * histHeight / maxValue_green); //要繪制的高度 137 int intensity_blue = cvRound(binValue_blue * histHeight / maxValue_blue); //要繪制的高度 138 139 //繪制紅色分量的直方圖 140 rectangle(histImage, Point(i * scale, histHeight - 1), 141 Point((i + 1) * scale - 1, histHeight - intensity_red), 142 Scalar(255, 0, 0)); 143 144 //繪制綠色分量的直方圖 145 rectangle(histImage, Point((i + bins) * scale, histHeight - 1), 146 Point((i + bins + 1) * scale - 1, histHeight - intensity_green), 147 Scalar(0, 255, 0)); 148 149 //繪制藍色分量的直方圖 150 rectangle(histImage, Point((i + bins * 2) * scale, histHeight - 1), 151 Point((i + bins * 2 + 1) * scale - 1, histHeight - intensity_blue), 152 Scalar(0, 0, 255)); 153 154 } 155 156 //在窗口中顯示出繪制好的直方圖 157 imshow("圖像的RGB直方圖", histImage); 158 } 159 160 void Hisbijiao() 161 { 162 Mat srcImage_base, hsvImage_base; 163 Mat srcImage_test1, hsvImage_test1; 164 Mat srcImage_test2, hsvImage_test2; 165 Mat hsvImage_halfDown; 166 167 //【2】載入基准圖像(srcImage_base) 和兩張測試圖像srcImage_test1、srcImage_test2,並顯示 168 srcImage_base = imread("1.jpg", 1); 169 srcImage_test1 = imread("2.jpg", 1); 170 srcImage_test2 = imread("3.jpg", 1); 171 //顯示載入的3張圖像 172 imshow("基准圖像", srcImage_base); 173 imshow("測試圖像1", srcImage_test1); 174 imshow("測試圖像2", srcImage_test2); 175 176 // 【3】將圖像由BGR色彩空間轉換到 HSV色彩空間 177 cvtColor(srcImage_base, hsvImage_base, COLOR_BGR2HSV); 178 cvtColor(srcImage_test1, hsvImage_test1, COLOR_BGR2HSV); 179 cvtColor(srcImage_test2, hsvImage_test2, COLOR_BGR2HSV); 180 181 //【4】創建包含基准圖像下半部的半身圖像(HSV格式) 182 hsvImage_halfDown = hsvImage_base(Range(hsvImage_base.rows / 2, hsvImage_base.rows - 1), Range(0, hsvImage_base.cols - 1)); 183 184 //【5】初始化計算直方圖需要的實參 185 // 對hue通道使用30個bin,對saturatoin通道使用32個bin 186 int h_bins = 50; int s_bins = 60; 187 int histSize[] = { h_bins, s_bins }; 188 // hue的取值范圍從0到256, saturation取值范圍從0到180 189 float h_ranges[] = { 0, 256 }; 190 float s_ranges[] = { 0, 180 }; 191 const float* ranges[] = { h_ranges, s_ranges }; 192 // 使用第0和第1通道 193 int channels[] = { 0, 1 }; 194 195 // 【6】創建儲存直方圖的 MatND 類的實例: 196 MatND baseHist; 197 MatND halfDownHist; 198 MatND testHist1; 199 MatND testHist2; 200 201 // 【7】計算基准圖像,兩張測試圖像,半身基准圖像的HSV直方圖: 202 calcHist(&hsvImage_base, 1, channels, Mat(), baseHist, 2, histSize, ranges, true, false); 203 normalize(baseHist, baseHist, 0, 1, NORM_MINMAX, -1, Mat()); 204 205 calcHist(&hsvImage_halfDown, 1, channels, Mat(), halfDownHist, 2, histSize, ranges, true, false); 206 normalize(halfDownHist, halfDownHist, 0, 1, NORM_MINMAX, -1, Mat()); 207 208 calcHist(&hsvImage_test1, 1, channels, Mat(), testHist1, 2, histSize, ranges, true, false); 209 normalize(testHist1, testHist1, 0, 1, NORM_MINMAX, -1, Mat()); 210 211 calcHist(&hsvImage_test2, 1, channels, Mat(), testHist2, 2, histSize, ranges, true, false); 212 normalize(testHist2, testHist2, 0, 1, NORM_MINMAX, -1, Mat()); 213 214 215 //【8】按順序使用4種對比標准將基准圖像的直方圖與其余各直方圖進行對比: 216 for (int i = 0; i < 4; i++) 217 { 218 //進行圖像直方圖的對比 219 int compare_method = i; 220 double base_base = compareHist(baseHist, baseHist, compare_method); 221 double base_half = compareHist(baseHist, halfDownHist, compare_method); 222 double base_test1 = compareHist(baseHist, testHist1, compare_method); 223 double base_test2 = compareHist(baseHist, testHist2, compare_method); 224 //輸出結果 225 printf(" 方法 [%d] 的匹配結果如下:\n\n 【基准圖 - 基准圖】:%f, 【基准圖 - 半身圖】:%f,【基准圖 - 測試圖1】: %f, 【基准圖 - 測試圖2】:%f \n-----------------------------------------------------------------\n", i, base_base, base_half, base_test1, base_test2); 226 } 227 228 printf("檢測結束。"); 229 } 230 231 Mat g_srcImage; Mat g_hsvImage; Mat g_hueImage; 232 int g_bins = 30;//直方圖組距 233 void on_BinChange(int, void*) 234 { 235 //【1】參數准備 236 MatND hist; 237 int histSize = MAX(g_bins, 2); 238 float hue_range[] = { 0, 180 }; 239 const float* ranges = { hue_range }; 240 241 //【2】計算直方圖並歸一化 242 calcHist(&g_hueImage, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false); 243 normalize(hist, hist, 0, 255, NORM_MINMAX, -1, Mat()); 244 245 //【3】計算反向投影 246 MatND backproj; 247 calcBackProject(&g_hueImage, 1, 0, hist, backproj, &ranges, 1, true); 248 249 //【4】顯示反向投影 250 imshow("反向投影圖", backproj); 251 252 //【5】繪制直方圖的參數准備 253 int w = 400; int h = 400; 254 int bin_w = cvRound((double)w / histSize); 255 Mat histImg = Mat::zeros(w, h, CV_8UC3); 256 257 //【6】繪制直方圖 258 for (int i = 0; i < g_bins; i++) 259 { 260 rectangle(histImg, Point(i * bin_w, h), Point((i + 1) * bin_w, h - cvRound(hist.at<float>(i) * h / 255.0)), Scalar(100, 123, 255), -1); 261 } 262 263 //【7】顯示直方圖窗口 264 imshow("直方圖", histImg); 265 } 266 267 void drawhr() 268 { 269 //【1】讀取源圖像,並轉換到 HSV 空間 270 g_srcImage = imread("3.png", 1); 271 if (!g_srcImage.data) 272 { 273 printf("讀取圖片錯誤,請確定目錄下是否有imread函數指定圖片存在~! \n"); 274 // return false; 275 } 276 cvtColor(g_srcImage, g_hsvImage, COLOR_BGR2HSV); 277 278 //【2】分離 Hue 色調通道 279 g_hueImage.create(g_hsvImage.size(), g_hsvImage.depth()); 280 int ch[] = { 0, 0 }; 281 mixChannels(&g_hsvImage, 1, &g_hueImage, 1, ch, 1); 282 283 //【3】創建 Trackbar 來輸入bin的數目 284 namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE); 285 createTrackbar("色調組距 ", WINDOW_NAME1, &g_bins, 180, on_BinChange); 286 on_BinChange(0, 0);//進行一次初始化 287 288 //【4】顯示效果圖 289 imshow(WINDOW_NAME1, g_srcImage); 290 291 }

1 #include "histogram.h" 2 3 int main() 4 { 5 Mat srcImage, dstImage; 6 srcImage = imread("1.png", 1); 7 if (!srcImage.data) 8 { 9 printf("讀取圖片錯誤,請確定目錄下是否有imread函數指定圖片存在~! \n"); 10 return false; 11 } 12 13 /* 二維 */ 14 // 轉換為 HLS(HIS) 通道圖像 15 Mat img; 16 img = imread("1.jpg"); 17 Mat hlsimg; 18 cvtColor(img, hlsimg, COLOR_RGB2HSV); 19 drawHis(img, hlsimg); 20 21 /* rgb直方圖 */ 22 drawHisrgb(img); 23 24 /* 直方圖比較 */ 25 Hisbijiao(); 26 27 /* 反向投影 */ 28 drawhr(); 29 30 /* 實驗2補 */ 31 cvtColor(srcImage, srcImage, COLOR_BGR2GRAY); // 灰度化圖像 32 drawHis1(srcImage, "原始的灰度圖的直方圖"); // 繪畫一維直方圖 33 imshow("原始圖", srcImage); 34 equalizeHist(srcImage, dstImage); 35 imshow("經過直方圖均衡化后的圖", dstImage); 36 drawHis1(dstImage, "均衡化后圖像的直方圖"); // 繪畫一維直方圖 37 38 waitKey(); 39 destroyAllWindows(); 40 return 0; 41 }
---------------------------------------------------------continue---------------------------------------------------
參考文獻
[1] 劉衍琦,詹福宇,王建德.計算機視覺與深度學習實戰:以MATLAB、Python為工具[M].北京:電子工業出版社.2019.11.
[2]阮秋琦,阮宇智.數字圖像處理.電子工業出版社[引用日期2020-06-06].