直方图均衡化 Histogram Equalization
假如图像的灰度分布不均匀,其灰度分布集中在较窄的范围内,使图像的细节不够清晰,对比度较低。通常采用直方图均衡化及直方图规定化两种变换,使图像的灰度范围拉开或使灰度均匀分布,从而增大反差,使图像细节清晰,以达到增强的目的。
直方图均衡化,对图像进行非线性拉伸,重新分配图像的灰度值,使一定范围内图像的灰度值大致相等。这样,原来直方图中间的峰值部分对比度得到增强,而两侧的谷底部分对比度降低,输出图像的直方图是一个较为平坦的直方图。
均衡化算法
直方图的均衡化实际也是一种灰度的变换过程,将当前的灰度分布通过一个变换函数,变换为范围更宽、灰度分布更均匀的图像。也就是将原图像的直方图修改为在整个灰度区间内大致均匀分布,因此扩大了图像的动态范围,增强图像的对比度。通常均衡化选择的变换函数是灰度的累积概率,直方图均衡化算法的步骤:
1、计算原图像的灰度直方图 ,其中为像素总数,为灰度级的像素个数
2、计算原始图像的累积直方图
其中 是目的图像的像素,是源图像灰度为i的累积分布,是图像中最大灰度级(灰度图为255)直接应用该方法得到图像的灰度直方图
3、将灰度直方图进行归一化,计算灰度的累积概率;
创建灰度变化的查找表
应用查找表,将原图像变换为灰度均衡的图像
均衡化过程中,必须要保证两个条件
1、像素无论怎么映射,一定要保证原来的大小关系不变,较亮的区域,依旧是较亮的,较暗依旧暗,只是对比度增大,绝对不能明暗颠倒;
2、如果是八位图像,那么像素映射函数的值域应在0和255之间的,不能越界。
综合以上两个条件,累积分布函数是个好的选择,因为累积分布函数是单调增函数(控制大小关系),并且值域是0到1(控制越界问题),所以直方图均衡化中使用的是累积分布函数。
累积分布函数
累积分布函数具有一些好的性质,那么如何运用累积分布函数使得直方图均衡化?比较概率分布函数和累积分布函数,前者的二维图像是参差不齐的,后者是单调递增的。直方图均衡化过程中,映射方法是
是图像素总和,是当前灰度级的像素个数,是图像中灰度级总数
操作步骤有:
直方图规定化
直方图规定化,就是对原始图像做变换,使得变换后的图像的直方图跟我们规定的一样。
具体步骤如下:
1、首先对原始图像做直方图均衡化,得到每个像素s和累积分布T(s);
2、根据需要的规定化直方图,求累积分布G(Z);
3、显然,如果累积直方图中有0值,那么是不会分配像素值的,因为0乘以255还是零。
4、对于每一个T(s)(假设其像素值为ss),找到在G(Z)中与其差值最小的那个G(z)值(假设对应的像素值为zz),那么规定化后就把ss变换为zz。
直方图规定化流程下图:
1、计算原图像的累积直方图
2、计算规定直方图的累积直方图
3、计算两累积直方图的差值的绝对值
4、根据累积直方图差值建立灰度级的映射
局部直方图处理&直方图统计
Opencv代码
灰度直方图均衡
// HistogramGrayEqualizeHist.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <opencv2/core/core.hpp> //cvGetSize cvCreateImage
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp> //cvResize cvInitMatHeader cvGetMinMaxHistValue cvCvtColor
#include <opencv2/imgproc/imgproc.hpp>
#ifdef _DEBUG
#pragma comment(lib, "opencv_core244d")
#pragma comment(lib, "opencv_highgui244d")
#pragma comment(lib, "opencv_imgproc244d") //cvResize
#else
#pragma comment(lib, "opencv_core244d")
#pragma comment(lib, "opencv_highgui244d")
#pragma comment(lib, "opencv_imgproc244d") //cvResize
#endif
#define cvQueryHistValue_1D(hist,idx0) ((float)cvGetReal1D( (hist)->bins, (idx0)))
using namespace std;
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
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);
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;
}
int main( int argc, char** argv )
{
const char *pstrWindowsSrcTitle = "原图";
const char *pstrWindowsGrayTitle = "灰度图";
const char *pstrWindowsHistTitle = "直方图";
const char *pstrWindowsGrayEqualizeTitle = "灰度图-均衡化后";
const char *pstrWindowsHistEqualizeTitle = "直方图-均衡化后";
// 从文件中加载原图
// IplImage *pSrcImage = cvLoadImage("./images/yangmi.jpg", CV_LOAD_IMAGE_UNCHANGED);
IplImage *pSrcImage = cvLoadImage("./images/beauty.png", CV_LOAD_IMAGE_UNCHANGED);
IplImage *pGrayImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
IplImage *pGrayEqualizeImage = cvCreateImage(cvGetSize(pSrcImage), IPL_DEPTH_8U, 1);
// 灰度图
cvCvtColor(pSrcImage, pGrayImage, CV_BGR2GRAY);
// 直方图图像数据
int nHistImageWidth = 255;
int nHistImageHeight = 150;
int nScale = 2;
// 灰度直方图及直方图图像
CvHistogram *pcvHistogram = CreateGrayImageHist(&pGrayImage);
IplImage *pHistImage = CreateHisogramImage(nHistImageWidth, nScale, nHistImageHeight, pcvHistogram);
// 均衡化
//函数功能:直方图均衡化,该函数能归一化图像亮度和增强对比度
//第一个参数表示输入图像,必须为灰度图(8位,单通道图)
//第二个参数表示输出图像
//该函数采用如下法则对输入图像进行直方图均衡化:
//1:计算输入图像的直方图H。
//2:直方图归一化,因此直方块和为255。
//3:计算直方图积分,H'(i) = Sum(H(j)) (0<=j<=i)。
//4:采用H'作为查询表:dst(x, y) = H'(src(x, y))进行图像变换。
cvEqualizeHist(pGrayImage, pGrayEqualizeImage);
// 均衡化后的灰度直方图及直方图图像
CvHistogram *pcvHistogramEqualize = CreateGrayImageHist(&pGrayEqualizeImage);
IplImage *pHistEqualizeImage = CreateHisogramImage(nHistImageWidth, nScale, nHistImageHeight, pcvHistogramEqualize);
// 显示
cvNamedWindow(pstrWindowsSrcTitle);
cvNamedWindow(pstrWindowsGrayTitle);
cvNamedWindow(pstrWindowsGrayEqualizeTitle);
cvNamedWindow(pstrWindowsHistTitle);
cvNamedWindow(pstrWindowsHistEqualizeTitle);
cvShowImage(pstrWindowsSrcTitle,pSrcImage);
cvShowImage(pstrWindowsGrayTitle,pGrayImage);
cvShowImage(pstrWindowsGrayEqualizeTitle,pGrayEqualizeImage);
cvShowImage(pstrWindowsHistTitle,pHistImage);
cvShowImage(pstrWindowsHistEqualizeTitle,pHistEqualizeImage);
cvWaitKey(0);
//回收资源代码…
cvDestroyWindow(pstrWindowsSrcTitle);
cvDestroyWindow(pstrWindowsGrayTitle);
cvDestroyWindow(pstrWindowsGrayEqualizeTitle);
cvDestroyWindow(pstrWindowsHistTitle);
cvDestroyWindow(pstrWindowsHistEqualizeTitle);
cvReleaseImage(&pSrcImage);
cvReleaseImage(&pGrayImage);
cvReleaseImage(&pGrayEqualizeImage);
cvReleaseImage(&pHistImage);
cvReleaseImage(&pHistEqualizeImage);
return 0;
}
直方图规定化
void hist_specify(const Mat &src, const Mat &dst,Mat &result)
{
Histogram1D hist1D;
MatND src_hist = hist1D.getHistogram(src);
MatND dst_hist = hist1D.getHistogram(dst);
float src_cdf[256] = { 0 };
float dst_cdf[256] = { 0 };
// 源图像和目标图像的大小不一样,要将得到的直方图进行归一化处理
src_hist /= (src.rows * src.cols);
dst_hist /= (dst.rows * dst.cols);
// 计算原始直方图和规定直方图的累积概率
for (int i = 0; i < 256; i++)
{
if (i == 0)
{
src_cdf[i] = src_hist.at<float>(i);
dst_cdf[i] = dst_hist.at<float>(i);
}
else
{
src_cdf[i] = src_cdf[i - 1] + src_hist.at<float>(i);
dst_cdf[i] = dst_cdf[i - 1] + dst_hist.at<float>(i);
}
}
// 累积概率的差值
float diff_cdf[256][256];
for (int i = 0; i < 256; i++)
for (int j = 0; j < 256; j++)
diff_cdf[i][j] = fabs(src_cdf[i] - dst_cdf[j]);
// 构建灰度级映射表
Mat lut(1, 256, CV_8U);
for (int i = 0; i < 256; i++)
{
// 查找源灰度级为i的映射灰度
// 和i的累积概率差值最小的规定化灰度
float min = diff_cdf[i][0];
int index = 0;
for (int j = 1; j < 256; j++)
{
if (min > diff_cdf[i][j])
{
min = diff_cdf[i][j];
index = j;
}
}
lut.at<uchar>(i) = static_cast<uchar>(index);
}
// 应用查找表,做直方图规定化
LUT(src, lut, result);
}