1、图像直方图

直方图是对数据的集合 统计,并将统计结果分布于一系列预定义的 bins 中。这里的 数据 不仅仅指的是颜色灰度值 , 统计数据可能是任何能有效描述图像的特征(如梯度、方向等)。特别地,当图像直方图描述的是图像的各个灰度级的统计特性时,称之为灰度直方图。

结合灰度直方图了解直方图的一些具体细节(参考自OpenCV官网):
dims: 需要统计的特征的数目,仅统计灰度值(灰度图像)时dims=1,对于三通道图像dims=3。
bins: 每个特征空间 子区段 的数目,如:灰度图中每十六个连续的灰度值作为一个统计区间,则 bins = 16。
range: 每个特征空间的取值范围,灰度图像中, range = [0,255]。

2、颜色分布直方图的计算

OpenCV提供了计算图像直方图的API函数calcHist,其函数原型为:

void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask,OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false )

parameters:
第一个参数:源输入(图像)数组,必须是深度和大小均相同的CV_8U或者CV_32F(即uchar或者float)图片,每一个可以是任意通道的;
第二个参数:源输入数组中的图片个数;
第三个参数:用来计算直方图的通道维数的数组,第一张图片的通道由0到arrays[0].channels()-1列出,第二张图片的通道从arrays[0].channels()到arrays[0].channels()+arrays[1].channels()-1,以此类推,而该参数就是从通道序列中选取一子序列,参与直方图计算的是子序列而不是所有通道序列;
第四个参数:可选的掩模,如果该矩阵不是空的,则必须是8位的并且与arrays[i]的大小相等,掩模的非零值标记需要在直方图中统计的数组元素;
第五个参数:输出直方图,是一个稠密或者稀疏的dims维的数组;
第六个参数:直方图的维数,必须为正,并且不大于CV_MAX_DIMS(当前的OpenCV版本中为32,即最大可以统计32维的直方图);
第七个参数:用于指出直方图数组每一维的大小的数组,即指出每一维的bin的个数的数组;
第八个参数:用于指出直方图每一维的每个bin的上下界范围(数组)的数组,当直方图是均匀的(uniform =true)时,对每一维i指定直方图的第0个bin的下界和最后一个即第histSize[i]-1个bin的上界,也就是说对均匀直方图来说,每一个ranges[i]都是一个两个元素的数组【指出该维的上下界】。当直方图不是均匀的时,每一个ranges[i]数组都包含histSize[i]+1个元素;
第九个参数:直方图是否均匀的标志;【指定直方图每个bin统计的是否是相同数量的灰度级】;
第十个参数:累加标志;
详细的参数说明见OpenCV documentation。

单纯看文档中的参数说明和示例,对参数中的channels和dims还是不甚理解,于是借阅了下这篇博客,算是有了点眉目。其主要内容如下:

calcHist函数的channels参数和narrays以及dims共同来确定用于计算直方图的图像;首先dims是最终的直方图维数,narrays指出了arrays数组中图像的个数,其中每一幅图像都可以是任意通道的【只要最终dims不超过32即可】。

如果channels参数为0,则narrays和dims必须相等,否则弹出assert,此时计算直方图的时候取数组中每幅图像的第0通道。当channels不是0的时候,用于计算直方图的图像是arrays中由channels指定的通道的图像,channels与arrays中的图像的对应关系,如channels的参数说明的,将arrays中的图像从第0幅开始按照通道摊开排列起来,然后channels中的指定的用于计算直方图的就是这些摊开的通道。

假设有arrays中只有一幅三通道的图像image,那么narrays应该为1,如果是想计算3维直方图【最大也只能是3维的】,想将image的通道2作为第一维,通道0作为第二维,通道1作为第三维,则可以将channels设置为channesl={2,0,1};这样calcHist函数计算时就按照这个顺序来统计直方图。

图像的直方图计算如下:

/*计算hsv图像的直方图*/
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"using namespace std;
using namespace cv;class CalcHistogram
{
private:int histSize[3];         //直方图项的数量float hranges[2];        //h通道像素的最小和最大值float sranges[2];float vranges[2];const float *ranges[3];  //各通道的范围int channels[3];         //三个通道int dims;public:CalcHistogram(int hbins=30, int sbins=32, int vbins=32){histSize[0]=hbins; histSize[1]=sbins;histSize[2]=vbins;hranges[0]=0; hranges[1]=180;sranges[0]=0; sranges[1]=256;vranges[0]=0; vranges[1]=256;ranges[0]=hranges;ranges[1]=sranges;ranges[2]=vranges;channels[0]=0;channels[1]=1;channels[2]=2;dims=3;}Mat getHistogram(const Mat &image);void getHistogramImage(const Mat &image);
};Mat CalcHistogram::getHistogram(const Mat &image)
{Mat hist;calcHist(&image,1,channels,Mat(),hist,dims,histSize,ranges,true,      //直方图每一维的histSize是均匀的false);return hist;
}void CalcHistogram::getHistogramImage(const Mat &image)
{Mat hist=getHistogram(image);int scale = 4;int hbins=histSize[0];int sbins=histSize[1];int vbins=histSize[2]; float *hist_sta = new float[sbins];  float *hist_val = new float[vbins];  float *hist_hue = new float[hbins];  memset(hist_val, 0, vbins*sizeof(float));  memset(hist_sta, 0, sbins*sizeof(float));  memset(hist_hue, 0, hbins*sizeof(float));for( int s = 0; s < sbins; s++ )  {  for( int v = 0; v < vbins; v++ )  {  for(int h=0; h<hbins; h++)  {float binVal = hist.at<float>(h, s, v);  hist_hue[h] += binVal;hist_val[v] += binVal;  hist_sta[s] += binVal;}  }  }  double max_sta=0, max_val=0,max_hue=0;  for(int i=0; i<sbins; ++i)  {  if(hist_sta[i]>max_sta)  max_sta = hist_sta[i];  }  for(int i=0; i<vbins; ++i)  {  if(hist_val[i]>max_val)  max_val = hist_val[i];  }  for(int i=0; i<hbins; ++i)  {  if(hist_hue[i]>max_hue)  max_hue = hist_hue[i];  }  Mat sta_img = Mat::zeros(240, sbins*scale+20, CV_8UC3);  Mat val_img = Mat::zeros(240, vbins*scale+20, CV_8UC3);  Mat hue_img = Mat::zeros(240, hbins*scale+20, CV_8UC3);  for(int i=0; i<sbins; ++i)  {  int intensity = cvRound(hist_sta[i]*(sta_img.rows-10)/max_sta);  rectangle(sta_img, Point(i*scale+10, sta_img.rows-intensity),Point((i+1)*scale-1+10, sta_img.rows-1), Scalar(0,255,0), 1);  }  for(int i=0; i<vbins; ++i)  {  int intensity = cvRound(hist_val[i]*(val_img.rows-10)/max_val);  rectangle(val_img, Point(i*scale+10, val_img.rows-intensity),Point((i+1)*scale-1+10, val_img.rows-1), Scalar(0,0,255), 1);  }  for(int i=0; i<hbins; ++i)  {  int intensity = cvRound(hist_hue[i]*(hue_img.rows-10)/max_hue);  rectangle(hue_img, Point(i*scale+10, hue_img.rows-intensity),Point((i+1)*scale-1+10, hue_img.rows-1), Scalar(255,0,0), 1);  } imshow("Shist", sta_img);    imshow("Vhist", val_img);        imshow("Hhist", hue_img); delete[] hist_sta;delete[] hist_val;delete[] hist_hue;
}int main()
{Mat src=imread("test.jpg"),hsv;if(!src.data){cout<<"error, the image is not built!"<<endl;return -1;}cvtColor(src, hsv, CV_BGR2HSV);imshow("src", src);imshow("hsv", hsv);CalcHistogram h;h.getHistogram(hsv);h.getHistogramImage(hsv);waitKey();return 0;
}

代码运行结果:

hsv图像各通道的颜色分布直方图:

代码总结:
1)由于参考了官方opencvdoc的例程,用到了函数minMaxLoc(hist, 0, &maxVal, 0, 0),但该函数只能求二维数组的最大最小值;
2)调用calcHist函数时,参数channels为0时,参数narrays和dims要相等。
3)若channels不为0时,dims最大不超过channels的个数,当dims小于channels的个数时,参与直方图计算的是channels所有通道还是其中的前dims个,略有疑问。

3、直方图均衡化

从直方图的计算过程中,我们可以体会到直方图的本质其实就是单通道图像的特征统计表示,所以将图像的颜色分布直方图称为灰度直方图也不是没有道理的。同样,直方图均衡化也称为灰度均衡化,其主要想法是:通过某种灰度映射使输入图像转换为每一灰度级上都有近似相同的像素点数的图像,即输出的直方图是均匀的。经过均衡化处理后的图像中,像素将占有尽可能多的灰度级并且分布均匀,使得图像具有较高的对比度,感官上理解就是使图像更加清晰。

直方图均衡化主要用于增强动态范围偏小的图像对比度,丰富图像的灰度级,如:我们在基于Nao机器人的名片邮箱识别中,由于名片的灰度范围过小,所以我们可以在前期的图像预处理阶段使用直方图均衡化增强对比度。直方图均衡化的优点是操作简便。

计算原理:理想情况下,一个完全均衡的直方图中所有的灰度级都有相同数量的像素,即50%的像素的强度低于128,25%的像素强度低于64,归纳地,我们有p%的像素的强度值必须低于或等于255*p%,换个角度就是:强度为 i 的映射值应对应强度值小于 或等于i 的像素所占的比例。则我们通过如下方式构建查找表修改像素就能得到均衡化直方图:

lookup.at<uchar>(i) = static_cast<uchar>(255.0*p[i]); //p[i]是强度值小于 i 的像素所占的比例
  • 1
  • 1

为了更加严谨,还是数学证明一下吧[好吧,不证明不舒服斯基]。

简便起见,先考虑灰度范围为0~1且连续情况,此时图像的归一化直方图即为概率密度函数:

p(x),0≤x≤1

则:

∫1x=0p(x)dx

设转换前后的概率密度函数分别为pr(r),ps(s)转换函数为s=f(r)。
则:

ps(s)=pr(r)drds

要使转换后图像直方图是均匀的,即PS(S)=1,0≤S≤1,则必须有:

pr(r)=dsdr

等式两边积分,得累计分布函数:

s=f(r)=∫r0pr(u)du

对于[0, 255]的情形,只要乘以Dmax即可。即

DB=f(DA)=Dmax∫DA0PDA(u)du

其中DB,DA分别为转换前后的灰度值。
离散形式为:

DB=f(DA)=DmaxA0∑i=0DAHi

其中,Hi为第i级离散灰度的像素个数,A0为图像的面积,即像素总数。

由于离散情形下无法像连续变换得到严格的均匀概率密度函数,所以均衡化的直方图是近似均匀的。OpenCV中直方图均衡化的API函数为:

void equalizeHist(InputArray src, OutputArray dst)
  • 1
  • 1

待续

OpenCV中颜色分布直方图及其应用相关推荐

  1. 第四章:OpenCV中的图像处理

    第四章:OpenCV中的图像处理 本章节你将学习图像的改变色彩空间.提取对象.图像的几何变换.图像的阈值.平滑图像等OpenCV图像处理的基本内容. 更多内容请关注我的GitHub库:TonyStar ...

  2. OpenCV中的图像处理中

    图像金字塔 一般情况下,我们要处理是一副具有固定分辨率的图像.但是有些情况下,我们需要对同一图像的不同分辨率的子图像进行处理.比如,我们要在一幅图像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸 ...

  3. python去除图片复杂背景_[OpenCV-Python] OpenCV 中的图像处理 部分 IV (五)

    部分 IV OpenCV 中的图像处理 22 直方图 22.1 直方图的计算,绘制与分析 目标 • 使用 OpenCV 或 Numpy 函数计算直方图 • 使用 Opencv 或者 Matplotli ...

  4. opencv中直方图和颜色跟踪相关:calcHist, calcBackProject, Meanshift和Camshift

    1. 理解calcHist的应用 1)单通道hist calcHist比较好理解,就是计算 图像的直方图,单通道来说就是 灰度的分布 比如下图是灰度像素的分布,在0-255的灰度图上划分为若干个bin ...

  5. OpenCV中直方图反向投影算法详解与实现

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达本文转自:opencv学堂 一:直方图交叉 OpenCV中直方图反向 ...

  6. 使用OpenCV中的色彩空间进行颜色分割

    在本教程中,我们将学习Computer Vision中使用的流行色彩空间,并将其用于基于颜色的分割. 1975年,匈牙利专利HU170062引入了一种难题,在43,252,003,274,489,85 ...

  7. OpenCV中直方图均衡化

    OpenCV中直方图均衡化 首先知道直方图是个什么鬼?在一幅图像中,直方图所体现的是每个像素值在所有的像素中所占的比例:例值为127的像素点的个数/图像总的像素点的个数\color{#f00}值为12 ...

  8. openCV中基于HSV的颜色判断(红色为例)

    HSV空间中H分量是计量环形渐变分布的颜色的变量,标准的红色H=0(或360),所以类红色应该以0为中值,向两边延展,一般取±30(即0~30和330~360),又openCV中H分量量化成180,所 ...

  9. opencv中 equalizeHist()均衡化直方图

    1.EqualizeHist函数 函数作用: 直方图均衡化,,用于提高图像的质量 2.EqualizeHist函数调用形式 C++: void equalizeHist(InputArray src, ...

最新文章

  1. 1月末周中国五大顶级域名总量增3.4万 美国增3.3万
  2. c语言程序窗口后台持续监测,用c语言实现后台运行的、每隔30s检查一次的、带有日志功能的断网重新连接程序...
  3. Spring Boot 特性 —— SpringApplication
  4. epoll 边沿触发 非阻塞 IO 服务器
  5. 2分钟 Docker 部署 SprinBoot 集成 Flowable 工作流引擎
  6. Centos上虚拟机搭建
  7. 机器学习十大经典算法之KNN最近邻算法
  8. android gradle fail,Android Studio更新后,Android Gradle CompilationFailedException
  9. vivado中bit文件怎么没有生成_Xilinx FPGA bit 文件加密
  10. linux编程之信号
  11. linux系统切换清华源(自带的源下载速度太拉跨)
  12. 2021年奎屯七中高考成绩查询,奎屯高考成绩查询
  13. .NET简谈设计模式之(命令模式)
  14. SQLServer2008 去除换行符
  15. [Kaggle竞赛] IEEE-CIS Fraud Detection
  16. 非度量多维标度_16s分析作图之NMDS非度量多维尺度分析
  17. 在win7上跑基于任少卿作者代码修改的RPN+BF实验
  18. android pc控制工具,电脑控制iPhone 或Android方法?透过这款工具就能实现
  19. 高德地图标识大全_连地震都查得一清二楚!高德地图新功能体验
  20. 赛效:超级简历在线简历助手教您一键制作简历

热门文章

  1. 河南计算机程序大赛,我院成功举办河南省第十一届ACM大学生程序设计竞赛
  2. mysql serial_mysql – 我应该对表id列使用数据类型SERIAL吗?
  3. SSL/TLS协议信息泄露漏洞(CVE-2016-2183)【原理扫描】远程桌面 3389 Windows 2016
  4. 线转hdmi为什么检测不到_为什么苹果MacBook这么保值?
  5. oracle表决盘奇数,Oracle 的OCR,表决盘与ASM SPFILE恢复方法
  6. 用了这么多年 curl,竟然不知道还有这种用法?!
  7. 免费版的 IDEA 为啥不能使用 Tomcat ?
  8. 每日一皮:当我看到Bug背后的一切...我退缩了...
  9. 面试 | HashMap 为什么线程不安全?
  10. 死磕Java并发:深入分析CAS