直方图均衡化(Histogram Equalization)是一种增强图像对比度(Image Contrast)的方法,其主要思想是将一副图像的直方图分布变成近似均匀分布,从而增强图像的对比度。直方图均衡化是指将一幅图像的灰度直方图变平,使变换后的图像中每个灰度值的分布概率都相同。在对图像做进一步处理之前,直方图均衡化通常是对图像灰度值进行归一化的一个非常好的方法,并且可以增强图像的对比度。
直方图均衡化虽然只是数字图像处理(Digital Image Processing)里面的基本方法,但是其作用很强大,是一种很经典的算法。
均匀分布
在概率论和统计学中,均匀分布也叫矩形分布,它是对称概率分布,在相同长度间隔的分布概率是等可能的。 均匀分布由两个参数a和b定义,它们是数轴上的最小值和最大值,通常缩写为U(a,b)。
在这种情况下,直方图均衡化的变换函数是图像中像素值的累积分布函数(cumulative distribution function,简写为 cdf,将像素值的范围映射到目标范围的归一化操作)
- 直方图均衡化与对比度增强
- 直方图均衡化(HE)原理
- 自适应直方图均衡化(AHE)原理
- 限制对比度自适应直方图均衡化(CLAHE)原理和实现
- 自适应局部区域伸展(Local Region Stretch)直方图均衡化原理和实现
直方图均衡化与对比度增强
图 1 是一张皮卡丘的图片(图片来自:baidu),图片是一张959 * 959的彩图。可以看出很多线条都是雾蒙蒙的看不清楚,整张图片偏暗,并且皮卡丘与背景(其他小精灵)区别不是很明显。将其直方图绘制出来之后得到图 2,可以看出其灰度绝大多数分布在0-50之间,而直方图均衡化要做的就是让直方图尽可能地均匀分布在0~255内。
图一:五彩斑斓的暗黑皮卡丘
图二:图一对应的直方图
from PIL import Image
from pylab import *
#绘制直方图的代码
pil_im1 = Image.open(r'D:\123456\lrs_eq_img.jpg')
im = array(pil_im1.convert('L'))
figure()
hist(im.flatten(), 128) # 对数据进行压平处理
show()
或者
def draw_histogram_(self, hists):
### draw a bar picture of the given histogram
plt.figure()
plt.bar(range(len(hists)), hists)
plt.show()
下面直接使用Python库PIL中的ImageOps来完成直方图均衡化的过程,来看看结果图三如何。代码简单,一行即可:
eq_img = ImageOps.equalize(img)
图三:原图用库函数进行均衡化
图四:图三的直方图分布(可见强行凭空捏造无中生有了很多数据,然后强行均衡化)
图五:均匀分布标准图(所有均衡化都是使直方图向这个图拟合的过程)
直方图均衡化(HE)原理
效果图
效果图在上面用作对比了。
实现代码
def histogram_equalization(self, img_arr, level = 256, **args):
# calculate hists
hists = self.calc_histogram_(img_arr, level)
# equalization
(m, n) = img_arr.shape
hists_cdf = self.calc_histogram_cdf_(hists, m, n, level) # calculate CDF
arr = np.zeros_like(img_arr)
arr = hists_cdf[img_arr] # mapping
return arr
def calc_histogram_cdf_(self, hists, block_m, block_n, level = 256):
hists_cumsum = np.cumsum(np.array(hists))
const_a = (level - 1) / (block_m * block_n)
hists_cdf = (const_a * hists_cumsum).astype("uint8")
return hists_cdf
自适应直方图均衡化(AHE)原理
效果图
图六:
图七:
实现代码
限制对比度自适应直方图均衡化(CLAHE)原理和实现
效果图
图八:
图九:
自适应局部区域伸展(Local Region Stretch)直方图均衡化原理和实现
效果图
图十:线条颜色比原图更亮,而且彩色部分更加逼真。
图十一:易见最后一张图的直方图是原图对均衡化的最优拟合
上面提到的AHE和CLAHE都是基于块状区域进行直方图均衡化的,但是能不能根据灰度级 区域 近似的区域进行均衡化呢?比如对图像中灰度级[min, max]范围里面的所有像素点进行均衡化,使得像素点的直方图尽量在[min, max]上均匀分布。在论文Adaptive Contrast Enhancement Using Local Region Stretching中,根据亮度(Brightness)对图像进行分割成几个区域,然后分别做直方图均衡化。下面根据论文的主要思想,引入下面的方法:统计图像直方图,按照灰度级划分为三个灰度区间,使得三个区间的像素点数量近似相等,这样就分别在[0, level1), [level1, level2), [level2, 255]三个灰度区间做直方图均衡化,最后合并。
原注释已经很清楚了,我就不必画蛇添足了。
def bright_wise_histequal(self, img_arr, level = 256, **args):
### split the image to three level accoding brightness, equalize histogram dividely
### @params img_arr : numpy.array uint8 type, 2-dim
### @params level : gray scale
### @return arr : the equalized image array
def special_histogram(img_arr, min_v, max_v):
### calculate a special histogram with max, min value
### @params img_arr : 1-dim numpy.array
### @params min_v : min gray scale
### @params max_v : max gray scale
### @return hists : list type, length = max_v - min_v + 1
hists = [0 for _ in range(max_v - min_v + 1)]
for v in img_arr:
hists[v - min_v] += 1
return hists
def special_histogram_cdf(hists, min_v, max_v):
### calculate a special histogram cdf with max, min value
### @params hists : list type
### @params min_v : min gray scale
### @params max_v : max gray scale
### @return hists_cdf : numpy.array
hists_cumsum = np.cumsum(np.array(hists))
hists_cdf = (max_v - min_v) / hists_cumsum[-1] * hists_cumsum + min_v
hists_cdf = hists_cdf.astype("uint8")
return hists_cdf
def pseudo_variance(arr):
### caluculate a type of variance
### @params arr : 1-dim numpy.array
arr_abs = np.abs(arr - np.mean(arr))
return np.mean(arr_abs)
# search two grayscale level, which can split the image into three parts having approximately same number of pixels
(m, n) = img_arr.shape
hists = self.calc_histogram_(img_arr)
hists_arr = np.cumsum(np.array(hists))
hists_ratio = hists_arr / hists_arr[-1]
scale1 = None
scale2 = None
for i in range(len(hists_ratio)):
if hists_ratio[i] >= 0.333 and scale1 == None:
scale1 = i
if hists_ratio[i] >= 0.667 and scale2 == None:
scale2 = i
break
# split images
dark_index = (img_arr <= scale1)
mid_index = (img_arr > scale1) & (img_arr <= scale2)
bright_index = (img_arr > scale2)
# variance
dark_variance = pseudo_variance(img_arr[dark_index])
mid_variance = pseudo_variance(img_arr[mid_index])
bright_variance = pseudo_variance(img_arr[bright_index])
# build three level images
dark_img_arr = np.zeros_like(img_arr)
mid_img_arr = np.zeros_like(img_arr)
bright_img_arr = np.zeros_like(img_arr)
# histogram equalization individually
dark_hists = special_histogram(img_arr[dark_index], 0, scale1)
dark_cdf = special_histogram_cdf(dark_hists, 0, scale1)
mid_hists = special_histogram(img_arr[mid_index], scale1, scale2)
mid_cdf = special_histogram_cdf(mid_hists, scale1, scale2)
bright_hists = special_histogram(img_arr[bright_index], scale2, level - 1)
bright_cdf = special_histogram_cdf(bright_hists, scale2, level - 1)
def plot_hists(arr):
hists = [0 for i in range(256)]
for a in arr:
hists[a] += 1
self.draw_histogram_(hists)
# mapping
dark_img_arr[dark_index] = dark_cdf[img_arr[dark_index]]
mid_img_arr[mid_index] = mid_cdf[img_arr[mid_index] - scale1]
bright_img_arr[bright_index] = bright_cdf[img_arr[bright_index] - scale2]
# weighted sum
#fractor = dark_variance + mid_variance + bright_variance
#arr = (dark_variance * dark_img_arr + mid_variance * mid_img_arr + bright_variance * bright_img_arr)/fractor
arr = dark_img_arr + mid_img_arr + bright_img_arr
arr = arr.astype("uint8")
return arr
代码参考
Github: lxcnju/histogram_equalization