直方图均衡化属于灰度变换的一种,是直接对图像的各个像素进行操作。直方图均衡化能够增强图像的对比度,简单来说,直方图均衡化就是将集中在某一块区域的灰度值通过函数变换映射到新的像素区域。
直方图均衡化前:
直方图均衡化后:
一、原理
通过公式推导可以证明,通过一个严格单调递增(有相等也不可以)的函数对待处理的图像做灰度变换,得到的新的灰度值会满足均匀分布。只要这个变换满足严格的单调递增,离散值也能有同样的效果。这里的证明过程涉及到积分、求导以及概率统计,因此不做说明,只需知道这个意思即可。
二、直方图均衡化的实现步骤
s表示新图像,k∈[0,L-1]表示灰度值的范围,s_k表示新的灰度值,n_k表示当前灰度值在原图像中的个数,MN表示图像中的像素总数。
s k = ( L − 1 ) ∑ j = 0 k n k M N s_k=(L-1)\sum_{j=0}^{k}\frac{n_k}{MN} sk=(L−1)j=0∑kMNnk
根据该公式可得,实现直方图均衡化的步骤为:
1.新建一个大小为256的int型数组count,下标范围对应0~255,数组初始化为0;
2.遍历图像中的像素点,并记录像素值的数量,比如遇到像素值为25的像素点,则对应count数组的count[25]就+1,以此类推。在遍历的同时要统计图像中的像素总数。
3.计算当前像素值包括该像素值之前所有的像素值出现的概率。比如0出现了2次,1出现了3次,那么在计算像素值为1的概率的时候要用2+3也就是5来计算。
4.得到概率之后再乘以像素范围255。
5.经过3和4的处理我们就得到了一个新的count数组,其中下标对应的是原来的像素值,而数组中对应的值是经过变换后的新的像素值。
6.利用这个count数组我们就可以对图像做直方图均衡化了,遍历数组中的所有像素值,然后将对应的像素值作为下标,利用数组中新的像素值作为替换。
三、具体实现代码
Mat srcImage2 = srcImage.clone();
Mat srcImage3 = srcImage.clone();
Mat srcImage4 = srcImage.clone();
int count[256];//用来保存像素值的数组
int sum = 0;//记录像素的总个数
for (int i = 0; i < 256; i++) { //数组初始化赋值为0
count[i] = 0;
}
int temp = 0;//暂存当前像素值
//遍历图像中的像素,这里举的例子是灰度图,如果是RGB图像需要遍历三个通道
for (int i = 0; i < rowNumber; i++) //行循环
{
uchar* data = srcImage.ptr<uchar>(i); //获取第i行的首地址 指针指向首地址
for (int j = 0; j < colNumber; j++) //列循环
{
temp = (int)data[j];//取出当前的像素值
count[temp]++;//对应数组中的值加一
sum++;//总数增加
}
}
temp = 0;
// 得到每个像素映射后的值
for (int i = 0; i < 256; i++) {
temp = temp + count[i];//累积求当前点之前出现的次数
count[i] = temp * 255 / sum;
}
//修改图像中的像素值,实现直方图均衡化
for (int i = 0; i < rowNumber; i++) //行循环
{
uchar* data = srcImage3.ptr<uchar>(i); //获取第i行的首地址 指针指向首地址
for (int j = 0; j < colNumber; j++) //列循环
{
temp = (int)data[j];//获取当前像素值
srcImage3.at<uchar>(i, j) = count[temp];
}
}
如果用OpenCV的自带函数实现则写法如下
equalizeHist(srcImage, srcImage4);
//函数对应的参数含义
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
四、效果对比
原图:
我实现的效果:
OpenCV自带的算法实现效果: