讲解直方图均衡化之前,先解释一下图像的统计直方图与累加概率。
1. 统计直方图,就是统计图像中每一个像素值的个数。比如对于8位的图像,每一个像素点的像素值取值范围是0~255,那么其统计直方图就是统计0~255中所有像素值在图像中的个数,比如0像素值有几个点、1像素值有几个点、2像素值有几个点......像素255有几个点,如下图所示:
2. 像素的概率,也就是该像素值的统计直方图值(像素数)除以图像的总像素数,假设像素x的统计直方图值为hist(x),图像的总像素数为size,那么该像素的概率为:
像素值x的累加概率,就是所有小于等于x的像素值的像素数,除以图像的总像素数,可按下式计算:
3. 接下来讲解直方图均衡化的原理。通常认为,图像的统计直方图分布越均匀,图像的质量越好,直方图均衡化可以提升图像的对比度和质量。比如有的图像整体较暗,那么说明其直方图中低像素值分布较多,高像素值分布较少,这时可以使用直方图均衡化来平衡其直方图分布,即减少低像素值,增加高像素值。从直观上看,均衡化的效果就是图像的部分区域相对原来变亮了,所以提升了图像的对比度。
直方图均衡化就是一个所有像素值重映射的过程。比如像素值x的直方图均衡化之后的值可按下式计算,其中n为图像的每一个像素点的位宽,对于灰度图通常n=8,P(x)为像素值x的累加概率。
使用C++实现上述算法:
void my_equalizeHist(Mat src, Mat &dst){ dst = Mat::zeros(src.size(), CV_8UC1); float hist_tmp[256] = {0.0}; for(int i = 0; i < src.rows; i++) { uchar *p = src.ptr(i); for(int j = 0; j < src.cols; j++) { hist_tmp[p[j]] += 1.0; //统计直方图 } } uchar lutt[256] = {0}; float imgsize = 1.0/(src.rows*src.cols); hist_tmp[0] = hist_tmp[0]*imgsize; lutt[0] = uchar(hist_tmp[0]*255 + 0.5); for(int i = 1; i 256; i++) { hist_tmp[i] = hist_tmp[i-1] + hist_tmp[i]*imgsize; lutt[i] = uchar(hist_tmp[i]*255 + 0.5); //计算查找表,加0.5是为了四舍五入取整 } dst = Mat::zeros(src.size(), CV_8UC1); for(int i = 0; i < dst.rows; i++) { uchar *p_s = src.ptr(i); uchar *p_d = dst.ptr(i); for(int j = 0; j < dst.cols; j++) { p_d [j] = lutt[p_s [j]]; //像素重映射 } }}
运行上述代码,对Lena图像进行直方图均衡化,结果如下,可以看到直方图均衡化之后,图像对比度增强了,统计直方图分布也更加均匀。
原图
直方图均衡化之后图像
原图的统计直方图
直方图均衡化之后的统计直方图
4. 观察上方H(x)的计算公式,P(x)的取值范围是0~1,H(x)与P(x)具有线性关系。为了进一步提升图像对比度,可以对P(x)作一个非线性的S型变换T(P(x)),并保证变换之后T(P(x))的取值范围还在0~1之间。本文分别构造了T1和T2这两个非线性函数用于非线性变换,同时我们可以把原本H(x)与P(x)的线性关系看成函数T0,于是有以下三种变换:
画出以上三种变换函数在0~1区间的曲线如下图所示,可以看到T1和T2的曲线都是S型。
从而H(x)与P(x)有三种映射关系:
增加非线性变换的代码实现与上述代码大同小异:
#define PI 3.14159inline float T1(float x){ return (0.5*(sin(PI*x-PI/2)+1));}inline float T2(float x){ float xx = 3 - x*6; return (1.0/(1+exp(xx)));}void my_equalizeHist(Mat src, Mat &dst){ dst = Mat::zeros(src.size(), CV_8UC1); float hist_tmp[256] = {0.0}; for(int i = 0; i < src.rows; i++) { uchar *p = src.ptr(i); for(int j = 0; j < src.cols; j++) { hist_tmp[p[j]] += 1.0; //统计直方图 } } uchar lutt[256] = {0}; float imgsize = 1.0/(src.rows*src.cols); hist_tmp[0] = hist_tmp[0]*imgsize; //lutt[0] = uchar(hist_tmp[0]*255 + 0.5); //T0 //lutt[0] = uchar(T1(hist_tmp[0])*255 + 0.5); //T1 lutt[0] = uchar(T2(hist_tmp[0])*255 + 0.5); //T2 for(int i = 1; i < 256; i++) //计算累加概率 { hist_tmp[i] = hist_tmp[i-1] + hist_tmp[i]*imgsize; //计算查找表,加0.5是为了四舍五入取整 //lutt[i] = uchar(hist_tmp[i]*255 + 0.5); //T0 //lutt[i] = uchar(T1(hist_tmp[i])*255 + 0.5); //T1 lutt[i] = uchar(T2(hist_tmp[i])*255 + 0.5); //T2 } dst = Mat::zeros(src.size(), CV_8UC1); for(int i = 0; i < dst.rows; i++) { uchar *p_s = src.ptr(i); uchar *p_d = dst.ptr(i); for(int j = 0; j < dst.cols; j++) { p_d [j] = lutt[p_s [j]]; //像素重映射 } }}
分别选择 T
0、 T 1 和 T
2变换,运行以上代码,得到的结果如下,可以看到使用 T 1 和 T
2变换得到结果的对比度相对于 T 0有所提高。
T0
T1
T2
5. 上述直方图均衡算法还存在一个问题,就是当图像的0像素值占很大比例时,从起始的0像素值的累加概率就很大了,导致后面的1~255像素值的累加概率均变得很大,从而造成直方图均衡化之后的像素值都偏大。比如下图,可以看到直方图均衡化之后图像变白了,严重失真。
原图
直方图均衡化之后的图像
为解决上述问题,我们可以把0像素值排除在外,也即0像素值不做处理,从像素值1开始计算累加概率,同时计算累加概率时使用到的图像总像素数应减去0像素值的像素数。代码如下:
void my_equalizeHist(Mat src, Mat &dst){ dst = Mat::zeros(src.size(), CV_8UC1); float hist_tmp[256] = {0.0}; for(int i = 0; i < src.rows; i++) { uchar *p = src.ptr(i); for(int j = 0; j < src.cols; j++) { hist_tmp[p[j]] += 1.0; //统计直方图 } } uchar lutt[256] = {0}; //总像素数减去0像素值的像素数 float imgsize = 1.0/(src.rows*src.cols - hist_tmp[0]); hist_tmp[1] = hist_tmp[1]*imgsize; //从像素值1开始计算累加概率 //lutt[1] = uchar(hist_tmp[1]*255 + 0.5); //lutt[1] = uchar(T1(hist_tmp[1])*255 + 0.5); lutt[1] = uchar(T2(hist_tmp[1])*255 + 0.5); dst = Mat::zeros(src.size(), CV_8UC1); for(int i = 2; i < 256; i++) //计算累加概率 { hist_tmp[i] = hist_tmp[i-1] + hist_tmp[i]*imgsize; //lutt[i] = uchar(hist_tmp[i]*255 + 0.5); //lutt[i] = uchar(T1(hist_tmp[i])*255 + 0.5); lutt[i] = uchar(T2(hist_tmp[i])*255 + 0.5); } dst = Mat::zeros(src.size(), CV_8UC1); for(int i = 0; i < dst.rows; i++) { uchar *p_s = src.ptr(i); uchar *p_d = dst.ptr(i); for(int j = 0; j < dst.cols; j++) { p_d [j] = lutt[p_s [j]]; //像素重映射 } }}
运行上述代码,得到结果,可以看到直方图均衡化之后,像素值不会再有整体偏高的问题。