全局直方图均衡化的作用
全局直方图均衡化主要应用在图像增益之中,用于提升图像的对比度,简单来说就是让图像亮的地方变暗一点,暗的地方变亮一些,整体提升图像的动态范围.
上面的话听起来可能不是那么直观,下面放两张图进行一下对比应该会好一些
PS:此处直方图就是对图像的灰阶/亮度信息进行统计记录每个亮度等级的数量.
这是原图像未经过处理,左边是目前的亮度直方图 ,可以看到亮度范围比较集中在中间区域.
这是经过全局直方图均衡化之后的图像,经过参与处理之后的图像对比度明显提高.同样的通过亮度直方图可以看出目前的图像动态范围较之前有所提升.
下面进入具体的理论部分
首先看一下公式
或者是这个
当然这两个公式其实差不多啦,表达的都是图像的累计分布函数,乍一看确实有点摸不着头脑,不知道这都是些啥?具体意义是什么?这些我们先放到一边,先来看几个问题.
1.什么是累计分布函数?
严格的数学定义和性质很容易查找到,我这里还是引用一下
累积分布函数(Cumulative Distribution Function),又叫分布函数,是概率密度函数的积分,能完整描述一个实随机变量X的概率分布。一般以大写CDF标记,,与概率密度函数probability density function(小写pdf)相对。
累积分布函数表示:对离散变量而言,所有小于等于a的值出现概率的和.
如果是在图像处理中,密度函数的含义也就是图像中的亮度/灰阶所占图像总像素的多少,而累计分布函数则是当前亮度等级对于图像整体出现的累计概率,根据CDF的定义这是很明显的一件事.
举个例子:有如下2*3单通道8位(0-255)图像,像素值如图所示
100 | 150 |
150 | 160 |
40 | 100 |
则密度函数的值为:
像素值 | 密度/出现的概率h(I) | 累计概率( C(I) ) |
40 | 出现1次:1/6 | 1/6 |
100 | 出现2次:2/6 = 1/3 | 1/6 + 1/3= 3/6 |
150 | 出现2次:2/6 = 1/3 | 1/6 + 1/3 +1/3 = 5/6 |
160 | 出现1次: 1/6 = 1/6 | 1/6 + 1/3 +1/3+1/6 = 1 |
2.为什么使用累计分布函数可以完成全局直方图均衡化?
直方图均衡化的目标是提升图像的动态范围从而进行图像增益,我们其实是通过累计分布函数建立一种映射关系,我们希望亮度值可以更加均匀的散布在整个亮度范围之内对于8位图像来说是(0-255),同样的图像的累计分布函数很好的反应了当前图像亮度的分布规律,我们只需要将其映射至[0,255]的整个空间之内便可以达到我们想要的效果.
还是举个例子:
如上面的表格所示
像素值 | 密度概率h(I) | 累计概率 C(I) | 最终值(取整) |
40 | 出现1次:1/6 | 1/6 | 1/6*255 = 42.49=42 |
100 | 出现2次:2/6 = 1/3 | 1/6 + 1/3= 3/6 | 3/6*255 =128 |
150 | 出现2次:2/6 = 1/3 | 1/6 + 1/3 +1/3 = 5/6 | 5/6*255 = 213 |
160 | 出现1次: 1/6 = 1/6 | 1/6 + 1/3 +1/3+1/6 = 1 | 1*255 = 255 |
最终图像
128 | 213 |
213 | 255 |
42 | 128 |
可以看到图像的整体动态范围由原先的 160 - 40 = 120 变为了 255 - 42 = 213 范围有所提升,从直方图结果来看整体分布也更加均匀 ,
再来看一下之前忽略的函数部分:
其中c(I)也就是累计概率函数,1/N*h(I)就是密度概率
我们要做的就是如下几步:
1.获取图像的灰度信息.
2.获取像素值的密度概率
3.根据密度概率求出累计概率
4.将累计概率*255完成最终映射
至此原理部分结束.
程序部分
#include<opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std ;
Mat src,gray,dst;
void Test(Mat &img);
int main(int argc, char *argv[])
{
src =imread("/home/qinzihang/opencv-4.2.0/samples/data/lena.jpg");
resize(src, src, Size(200,200));
cvtColor(src, src,COLOR_BGR2GRAY);
Test(src);
imshow("dst", src);
waitKey(0);
}
void Test(Mat &img)
{
float gray_l[256] = {0};
float HI[256] = {0};//图像亮度密度1/N*h(I)
float CDF[256] = {0};//累积分布函数
int rows=img.rows;
int cols=img.cols;
Mat draw_mat = Mat::zeros(600,600,CV_8U);
cout << rows << endl;//x
for(int i=0; i<rows; i++)
{
for(int j=0; j<cols; j++)
{
uchar t;
if(img.channels()==1)
{
t=img.at<uchar>(i,j);
gray_l[t]++;//获得图像亮度信息
}
}
}
for(int i = 0; i <256; i++)
{
line(draw_mat,Point(i+150,600-1),Point(i+150, 600-gray_l[i]),254);
imshow("draw", draw_mat);
HI[i] = (gray_l[i]/(img.cols*img.rows*1.0f));//获得密度概率
cout << HI[i] << endl;
}
for(int i = 0; i < 255; i++)
{
if(i == 0)
{
CDF[0] = HI[ 0];
}
else
{
CDF[i] = (CDF[i-1] +HI[i]);//C(I) = C(I - 1 ) +h(I)
cout << CDF[i] << endl;
}
}
for(int i=0; i<rows; i++)
{
for(int j=0; j<cols; j++)
{
uchar t;
if(img.channels()==1)
{
t=img.at<uchar>(i,j);
img.at<uchar>(i,j) = 255*CDF[t];//完成图像重映射至0-255
}
} draw_mat = Mat::zeros(600,600,CV_8U);
}
imshow("yes",img);
}
本文仅仅是我的一些浅薄的看法和对这个算法的一些思考,整个过程不是非常的严谨,希望看了之后多少对初学者理解全局直方图均衡化这个算法有一些帮助,文章中的错误也希望大家可以指出.