直方图均衡化(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].

 

 

 

 

 

 

 

 

  

 

 

 

 

 

  

 

posted @ 2020-10-18 22:01  望星草  阅读(1269)  评论(0编辑  收藏  举报