直方图均衡化
今天学习了下直方图均衡化的原理。
先来看看维基百科上的一个例子:
从这张未经处理的灰度图可以看出,其灰度集中在非常小的一个范围内。这就导致了图片的强弱对比不强烈。直方图均衡化的目的,就是使得灰度分布尽可能平均地“填满”整个灰度空间(0~255),就是做到直方图的上界是一条平行于X轴的直线(理想化)。
有了这个目标之后,开始推导。
因为不懂得怎么在博客中输入数学公式(也懒得),可以看看度娘给的奶:http://wenku.baidu.com/view/6c55ecbdc77da26925c5b090
这个推导过程从连续函数出发。
第一步:建立一个从灰度r到s的映射s=T(r)
第二步:将均衡化后的直方图的分布HB 表示成原图灰度分布HA 的函数 --> HB(s)=HA(r)/T(r),原理见下。
第三步:理想化的灰度分母应为常数,得T(r)=k*HA(r),不妨另:k=C/Ao .(Ao为所有像素数)
第四步:两边同时积分(0->L),由密度函数的归一化条件得 : s=T(L)=C,因为s取值也是0->L,所以C=L。
第五步:把连续函数写成离散形式以适应计算机的处理。
(写出来大概就是这样,自己也能理解,但是总是感觉怪怪的)
可以看出,直方图均衡化只是将直方图进行了伸缩变换,或者说是一个搬移。将变化律低的部分挤压,将变化率高的部分拉伸,得到一个两边变化陡峭中间平滑的灰度分布(单峰的话就很明显)。
可以看看维基百科上直方图均衡化之后的例子:
通过观察可以发现,由于中间部分灰度被拉伸了,灰度的取值变得不是很“连续”。这个就是直方图均衡化的一个缺点吧。(其实是离散的问题)
———————————————————————————————————————————————————————————————————————————————
闲着无聊搞了个最简单的直方图均衡化小程序,大家可以自己拿去试一下。接下来的计划是做自适应直方图均衡化和对比对限制的直方图均衡化。
我会参考这里:http://www.cnblogs.com/Imageshop/archive/2013/04/07/3006334.html (同时推荐此博客哈)
代码:
#include <iostream> #include <string> #include <io.h> #include <opencv2/opencv.hpp> #include<math.h> #include<cv.h> using namespace std; using namespace cv; void main(int argc, char** argv) { //变量定义 //图片类 IplImage *src , *dst; IplImage *histframesrc , *histframedst; CvSize size; //直方图类 CvHistogram *histo_src , *histo_dst; int scale; float histmin,histmax; int bins=256; float range[]={0,255}; float *ranges[]={range}; //杂家 int i,j,k,m,n; //循环变量……而已 float s_r[256]; //S(r)映射函数 //--------------------------------------------------------------- src=cvLoadImage("11.jpg",0); //图片变量初始化 cvShowImage("src",src); size=cvGetSize(src); dst=cvCreateImage(size,8,1); histo_src=cvCreateHist(1,&bins,CV_HIST_ARRAY,ranges); //直方图初始化 histo_dst=cvCreateHist(1,&bins,CV_HIST_ARRAY,ranges); cvCalcHist(&src,histo_src,0,0); //计算直方图 cvNormalizeHist(histo_src,1); //归一 //累加,求S(r)映射 s_r[0]=cvQueryHistValue_1D(histo_src,0); for(i=1;i<=255;i++) { s_r[i]=s_r[i-1]+cvQueryHistValue_1D(histo_src,i); } //当然要把是S(r)归一到255啦 for(i=0;i<=255;i++) { s_r[i]=cvRound(s_r[i]*255); } //遍历图像并由sr关系进行直方图均衡化啦 CvScalar s; for(m=0;m<size.height;m++) { for(n=0;n<size.width;n++) { s=cvGet2D(src,m,n); i=s.val[0]; i=s_r[i]; s.val[0]=i; cvSet2D(dst,m,n,s); } } //计算dst直方图 cvCalcHist(&dst,histo_dst,0,0); cvNormalizeHist(histo_dst,1); //SHOWOFF一下啦 cvShowImage("dst",dst); scale=1; //画出src和dst的直方图 histframesrc=cvCreateImage( cvSize(bins*scale,256),8,1); histframedst=cvCreateImage( cvSize(bins*scale,256),8,1); //src的 cvGetMinMaxHistValue(histo_src , &histmin , &histmax , 0 , 0 ); for(int i = 0; i < bins; i++) { /** 获得直方图中的统计次数,计算显示在图像中的高度 */ float bin_val = cvGetReal1D(histo_src->bins,i); bin_val=bin_val*255/histmax; s.val[0]=cvRound(bin_val); cvRectangle(histframesrc,cvPoint(i*scale,256),cvPoint((i+1)*scale,256-bin_val),s,-1,8,0); } cvShowImage("src's hisogram",histframesrc); //dst的 cvGetMinMaxHistValue(histo_dst , &histmin , &histmax , 0 , 0 ); for(int i = 0; i < bins; i++) { /** 获得直方图中的统计次数,计算显示在图像中的高度 */ float bin_val = cvGetReal1D(histo_dst->bins,i); bin_val=bin_val*255/histmax; cout<<bin_val<<"\t"; s.val[0]=cvRound(bin_val); cvRectangle(histframedst,cvPoint(i*scale,256),cvPoint((i+1)*scale,256-bin_val),s,-1,8,0); } cvShowImage("dst's hisogram",histframedst); //输出sr试一下 for(i=0;i<=255;i++) cout<<s_r[i]<<"\n"; for(;;)if(cvWaitKey(0)==27)break; }
结果:(直方图变得平缓了但是中间部分变得稀疏了)