直方圖均衡化(OpenCV4)


關鍵詞  

  直方圖(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 }
histogram
 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 }
main

 

 

 ---------------------------------------------------------continue---------------------------------------------------

參考文獻 

[1] 劉衍琦,詹福宇,王建德.計算機視覺與深度學習實戰:以MATLAB、Python為工具[M].北京:電子工業出版社.2019.11.

[2]阮秋琦,阮宇智.數字圖像處理電子工業出版社[引用日期2020-06-06].

 

 

 

 

 

 

 

 

  

 

 

 

 

 

  

 


免責聲明!

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



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