直方图均衡化在图像处理领域中运用非常广泛,而且非常简单易实现。
首先我们了解一下什么是图像的直方图:
设图像的灰度范围为[a,b],r为此灰度范围内的任一灰度级,p(r)为这幅图像中灰度级为r的像素出现的频率,可以看出p(r)是r的函数。该函数的图形称为这幅图像的直方图。
p(r)=灰度为r的像素数/图像上的总像素数
可以很清楚地看出,灰度直方图抛弃了原灰度图像的空间位置信息,反映了某一像素值在灰度图中出现的频率或者概率信息。如上图所示,可以很清楚地看出0-100灰度值所出现的频率非常大,且越趋近于0概率越高;而灰度值大于200后,出现频率大大降低。由此可以判断出,该图像整体较暗,细节部分不够突出。
于是这里我们引入---直方图均衡化的方法,希望按照一定的变换公式,将原图映射到新图,使得新图在原图的基础上,直方图分布更加均匀,这样图像的明暗分布更加均匀,给人的视觉效果就是对比度好,细节清晰。
那么问题的关键是,如何确定这个变换公式呢?
均衡化方法中,使用直方图的累积分布函数作为变换公式:
其中,
实际上就是用某灰度级的累积概率来代替其原出现概率,得到映射后新的灰度值(累积概率乘以最大灰度值)。
举个例子来说:
rk代表原图的八个灰度级;nk代表每个灰度级出现的频数;Prk代表每个灰度级出现的概率;Sk代表累积概率;Ps代表新图中rk所对应的出现概率。
于是,很容易得到,例如:
原图rk=0,累积概率Sk=1/7,于是其对应的新图灰度值为1*1/7=1/7。
经过意义映射之后,可以发现,新图中灰度级为0的出现概率为0,于是在rk=0时Ps=0;新图中灰度级为1/7的出现概率为0.19,于是在rk=1/7时Ps=0.19···
以此类推,很容易换算出新图的灰度级。
了解了直方图均衡化的原理,那么编程实际上就是小菜一碟了。无论是使用Matlab还是OpenCV,直方图均衡化都很好实现。这里要提一点的就是Matlab相对来说更加地方便,对于直方图的获取与绘制,都有现成的函数可供调用,而OpenCV相对来说麻烦一点,需要自己写绘制函数。
另外,对输入进来的图像,首先要进行通道数的转换,比如说传入进来的是.jpg格式的图像,那么无论从外表上看它是不是一个灰度图像,读进来的都是三通道图像,所以,往往第一步是需要将多通道图像转换成单通道图像。
核心函数(来源于网络,我是大自然的搬运工)
1.直方图均衡化
void MyEqualizeHist_color(IplImage* src, int color) { if (color == 1) {//多通道 IplImage* imgChannel[4] = { 0, 0, 0, 0 }; IplImage* dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3); int i; for (i = 0; i < src->nChannels; i++) { imgChannel[i] = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); //要求单通道图像才能直方图均衡化 } //通道分离 cvSplit(src, imgChannel[0], imgChannel[1], imgChannel[2], imgChannel[3]);//BGRA for (i = 0; i < dst->nChannels; i++) { //直方图均衡化,原始图像和目标图像必须是单通道 cvEqualizeHist(imgChannel[i], imgChannel[i]); } //通道组合 cvMerge(imgChannel[0], imgChannel[1], imgChannel[2], imgChannel[3], dst); cvNamedWindow("src", 1); cvShowImage("src", src); cvNamedWindow("Equalize", 1); cvShowImage("Equalize", dst); } else {//单通道 IplImage* dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvEqualizeHist(src, dst); cvNamedWindow("src", 1); cvShowImage("src", src); cvNamedWindow("Equalize", 1); cvShowImage("Equalize", dst); } }
2.绘制灰度直方图(单通道灰度图像)
IplImage* src = cvLoadImage("orange.JPG");//读取图像 IplImage* gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvCvtColor(src, gray, CV_BGR2GRAY);//彩色图像灰度化 CvHistogram *pcvHistogram = CreateGrayImageHist(&gray);//创建一个灰度直方图 int nHistImageWidth = 255; //0到255个灰度级 int nHistImageHeight = 200; //直方图图像高度 int nScale = 2; IplImage* pHistImage = CreateHisogramImage(nHistImageWidth, nScale,nHistImageHeight, pcvHistogram);//得到灰度直方图 cvNamedWindow("Hist", 1); cvShowImage("Hist", pHistImage); /**** FillWhite是一个填充函数,将pImage填充成白色 *****/ void FillWhite(IplImage *pImage) { cvRectangle(pImage, cvPoint(0, 0), cvPoint(pImage->width, pImage->height), CV_RGB(255, 255, 255), CV_FILLED); } /**** 创建灰度图像的直方图 *****/ CvHistogram* CreateGrayImageHist(IplImage **ppImage) { int nHistSize = 256;//灰度数 float fRange[] = { 0, 255 }; //灰度级的范围 float *pfRanges[] = { fRange }; CvHistogram *pcvHistogram = cvCreateHist(1, &nHistSize, CV_HIST_ARRAY, pfRanges);//创建直方图 cvCalcHist(ppImage, pcvHistogram);//计算ppImage的直方图 return pcvHistogram; } /**** 根据直方图创建直方图图像、 *****/ IplImage * CreateHisogramImage(int nImageWidth, int nScale, int nImageHeight, CvHistogram *pcvHistogram) { IplImage *pHistImage = cvCreateImage(cvSize(nImageWidth * nScale, nImageHeight), IPL_DEPTH_8U, 1); FillWhite(pHistImage); //统计直方图中的最大直方块 float fMaxHistValue = 0; cvGetMinMaxHistValue(pcvHistogram, NULL, &fMaxHistValue, NULL, NULL); //分别将每个直方块的值绘制到图中 int i; for (i = 0; i < nImageWidth; i++) { float fHistValue = cvQueryHistValue_1D(pcvHistogram, i); //像素为i的直方块大小 int nRealHeight = cvRound((fHistValue / fMaxHistValue) * nImageHeight); //要绘制的高度 cvRectangle(pHistImage, cvPoint(i * nScale, nImageHeight - 1), cvPoint((i + 1) * nScale - 1, nImageHeight - nRealHeight), cvScalar(i, 0, 0, 0), CV_FILLED ); } return pHistImage; }