本文只讲数字图像处理,即离散化后的公式,至于积分形式的分析,网上很多。
具体的数学推导可以参考这篇博客直方图均衡化。
如果想自己代码实现直方图均衡化,相关源代码可以参考(不使用api)使用C++实现彩色图像直方图均衡化的三种方法。
如果要直接使用opencv
的api,直接一个函数搞定,用法如下:
void equalizeHist(InputArray src,OutputArray dst)
在实际均衡化过程中,核心步骤如下:
说明:是灰度值,取值[0~255]
,也取值为[0~255]
,是一个像素(原始的),是直方图均衡化之后的值。
- 1.计算灰度图的直方图;
- 2.遍历灰度直方图,对灰度值从 0~255 累计求和(图像计算中,积分可以用求和来近似替代),对求和结果除以图像的像素总数,然后乘以 得到函数结果 得到函数结果 。
如果写成迭代的式子,就是下面这样:
不过这种方式会产生小数,然后又取整,每次像素值都取整,有精度损失,虽然影响不大,但是更好的方式是对该像素和该像素之前的像素数量求和,,
这个公式就是,当 时,灰度值为80的像素在直方图均衡化之后的灰度值为。 - 3.将每一个灰度值累积求和的结果存储在一个大小为 256 的
int
数组中,存储的位置为 ; - 4.遍历输入图像每一个像素,记点值为,修改其像素值使等于。
- 5.均衡化结束。
例子:
假设一幅图中的像素范围是 [10~160],有100个像素取值(因为有些像素值可能并没有,例如,一张图中像素值为75的像素一个都没有),直方图均衡化的做法就是:
其实累计求和就是下面这样,假设灰度图片总共有total=2550个像素:
origin_pixel_value | 0 | 1 | … | 10 | 11 | 12 | 13 | … | 160 | … | 255 |
num | 0 | 0 | 0 | 250 | 500 | 10 | 800 | … | 100 | 0 | 0 |
calculate: 例:, | 0 | v11 | v12 | v13 | v160 | v255 | |||||
final_pixel_value | 0 | 0 | 0 | 25 | 75 | 76 | 156 | 255 | 255 | 255 |
由上表可知,本来像素范围在[10~160]
,直方图均衡化之后,像素范围变为了[25~255]
,动态范围大大增加;
- 原来的低亮度像素值为
10
的变为了25
,11
的变为了75
,12
的变为了76
,13
变为了156
,160
变成了255
。可以看出该像素对应的灰度值在整幅图中的个数越多,变换后的灰度值就越大。 - 本来像素
12
和像素13
只相差1
,直方图均衡化之后,变为了76
和156
,对比度显著变大,之所以会这样,就是因为像素13
比像素12
的灰度值个数多,并且多的不是一个量级。 - 但是由于直方图均衡化是递增的,对于原来像素
11
和像素12
只相差1
,直方图均衡化之后,变为了75
和76
,所以即使二者像素个数相差几百个,对比度也没有增大。 - 因此灰度值更高时,而像素数量骤减的话,并不会拉升对比度。只对灰度值更高,像素数量陡增时,才会拉升对比度。
假设该图像有total=25500个像素,即像素个数扩大了10倍,本例把像素值为160的像素增大到了3000个:
origin_pixel_value | 0 | 1 | … | 10 | 11 | 12 | 13 | … | 160 | … | 255 |
num | 0 | 0 | 0 | 250 | 500 | 10 | 800 | … | 3000 | 0 | 0 |
calculate: 例:, | 0 | v11 | v12 | v13 | v160 | v255 | |||||
calculate_pixel_value | 0 | 0 | 0 | 2.5 | 7.5 | 7.6 | 15.6 | 255 | 255 | 255 | |
final_pixel_value | 0 | 0 | 0 | 3 | 8 | 8 | 16 | 255 | 255 | 255 |
由上表可知,本来像素范围在 [10~160]
,直方图均衡化之后,像素范围变为了 [3~255]
,动态范围大大增加;
原来的低亮度像素值为10
的变为了3
,11
的变为了85
,12
的变为了8
, 13
变为了16
,160
变成了255
。可以看出该像素对应的灰度值在整幅图中的个数越多,变换后的灰度值就越大。
图像中像素灰度值数量最多的变亮了,那么整幅图看起来就更亮,对比度更高,更清晰了。