图像直方图

图像直方图 是 图像的 一种统计特征,可以图像质量检测,也可以用于 图像相似度检测;

图像直方图的形态 可以反应图像的 质量,特别是 对比度,大致如下图

 

通过 直方图修整 可以提升图像质量,直方图修整 可以分为两类,一是 直方图均衡化,二是 直方图规定化,本文主要讲 均衡化 

 

直方图均衡化

直方图均衡化 是 图像增强 的一种重要方法,主要是提高图像 质量、清晰度、对比度;

 

均衡化的知识和方法如下:

直方图均衡化与对比度增强

直方图均衡化(HE)

自适应直方图均衡化(AHE)

限制对比度自适应直方图均衡化(CLAHE)

自适应局部区域伸展(Local Region Stretch)直方图均衡化

 

算法目标

直方图趋势集中 表示 图像像素级大小相近,且像素级过于集中,对比度低;

直方图均衡化 是 将原图像通过某种变换,使得图像直方图为 近似均匀分布 的方法;

 

理想效果

这张更实际:原始图像像素比较集中,较亮的图像把所有像素限制在较高区间,但好的图像会有来自所有像素级的像素;

直方图均衡化 会使 像素级 跨度 更大,增加 像素级 的差别;像素权重分布更分散,增加图像整体的饱和度、对比度;    【像素分布集中时,看起来就是 一片白,或者一片黑 之类】

 

直观作用是 把像素权重大的灰度级进行 分割-拉伸-扩展,把像素权重小的灰度级进行 合并-压缩,进而使得 不同像素权重大致相同;

最终实现提高 图像对比度 的目的;

 

数学原理

专门做图像的可以看看,我这里就只上一张图吧

 

算法步骤 

直接举个例子吧,具体步骤说明见代码

补充说明:

1. nk 第二列,红色方框内 像素较多,保持不变,(再高就要分割了),绿色方框和黄色方框内 像素较低,分别进行合并

2. 新的像素级 为 0-n,把原像素权重 coumsum 后,也是 0-cumsum.max,两者点乘,就得到了新的像素值,妙啊

 

手动实现

下面 用 numpy 实现 基本的直方图均衡化

##### numpy 实现直方图均衡
def hist_cdf(img):
    ### 画像素直方图,以及 直方图的 帕累托图,帕累托图可反应 各个像素区间像素点 的变化 是否 均匀
    # 帕累托图
    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    cdf = hist.cumsum()
    cdf_normalized = cdf * float(hist.max()) / cdf.max()
    plt.plot(cdf_normalized, color='b')
    # 像素直方图
    plt.hist(img.flatten(), 256, [0, 256], color='r')
    plt.xlim([0, 256])
    plt.legend(('cdf', 'histogram'), loc='upper left')
    plt.show()

if 1:
    img = cv.imread('imgs/jh.png', 0)
    # img = cv.imread('imgs/image13.jpg', 0)
    hist_cdf(img)

    ##### 为了思路更清晰,我把代码 拆开,方便描述
    ## 实现代码1
    # 1.确定图像灰度级,这里是 0-255,并计算权重
    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    # 2.累计权重
    cdf = hist.cumsum()     # len(cdf) = 256
    cdf_m = np.ma.masked_equal(cdf, 0)      # 掩盖等于 0 的元素
    # 3.权重占比
    hist_zhanbi = (cdf_m - cdf_m.min()) / (cdf_m.max() - cdf_m.min())   # cdf_m.max()-cdf_m.min()=sum(hist)
    # 4.计算新的像素级
    cdf_m = 255 * hist_zhanbi       # 这里新的像素级还是 0-255
    cdf = np.ma.filled(cdf_m, 0).astype('uint8')
    # 5.在图像上进行新老像素的映射
    # cdf:新的像素级,取值 0-255,长度 256,
    # 原来的 hist 下标是 0-255,其实就下标是每个老的像素级,原来 hist 的值 变成了新的像素值后,就是 cdf,
    # 也就是说 cdf 的下标 和 值的对应 就是 新老像素的对应
    img2 = cdf[img]
    hist_cdf(img2)

    plt.subplot(211); plt.imshow(img)
    plt.subplot(212); plt.imshow(img2)
    plt.show()


    ## 实现代码2
    img = cv.imread('imgs/jh.png', 0)
    # 变换前的直方图
    plt.hist(img.flatten(), 256, [0, 256], color='r')
    plt.xlim([0, 256])
    plt.title("before Histogram Equalization")
    plt.show()
    # 累积分布函数计算
    hist, bins = np.histogram(img.flatten(), 256, [0, 256])
    cdf = hist.cumsum()
    pixel_value = (cdf * 255.0 / cdf.max()).astype(np.uint8)  # 变换后的像素值
    img_conv = np.interp(img.flatten(), bins[:-1], pixel_value)  # 将原始图像的像素与变换后的图像一一对应
    img_conv = img_conv.reshape(img.shape)
    # 均衡后的直方图
    plt.hist(img_conv.flatten(), 256, [0, 256], color='r')
    plt.xlim([0, 256])
    plt.title("after Histogram Equalization")
    plt.show()

效果图

小结

意义

1. 提高图像质量、清晰度、对比度

2. 不管图像是 过亮 还是 过暗,经过直方图均衡后,其 亮度大致是相同的,也就是它可以使所有图像具有相同的光照条件

  // 这在很多情况下都很有用。例如,在人脸识别中,在对人脸数据进行训练之前,对人脸图像进行直方图均衡化处理,使其具有相同的光照条件。

优势:不需要额外参数,整个过程是自动的;

缺点:拉伸后有些灰度级可能不被映射到,造成图像观感上的颗粒感;

 

opencv 用法

opencv 直方图均衡化 只能 处理 单通道图像

img = cv.imread('imgs/jh.png', 0)       # 单通道
equ = cv.equalizeHist(img)
print(equ.shape)
res = np.hstack((img, equ)) # stacking images side-by-side
cv.imshow('res', res)
cv.waitKey(0)


img = cv.imread('imgs/jh.png')
b, g, r = cv.split(img)
equ = cv.equalizeHist(b)        # 单通道
print(equ.shape)
res = np.hstack((b, equ)) # stacking images side-by-side
cv.imshow('res2', res)
cv.waitKey(0)

 

彩色图像处理

1.分别对 R G B 进行 均衡,然后在合并,但这样做 可能导致 图像色彩失真

2.把 BGR 转换成 HSV 色彩空间,然后对 V 通道进行 均衡,这样可 保证图像色彩不失真  【HSV 分别是 色调、饱和度、亮度】

img = cv.imread('imgs/jh.png')
img2 = cv.cvtColor(img, code=cv.COLOR_BGR2HSV)
print(img2.shape)   # (201, 214, 3)     色调 饱和度 亮度
s, b, l = cv.split(img2)
cv.imshow('hsvs', s)
cv.imshow('hsvb', b)
cv.imshow('hsvl', l)
cv.waitKey(0)

# 对 V 通道进行均衡
l_repair = cv.equalizeHist(l)
res = np.hstack((l, l_repair))
cv.imshow('hsv', res)
cv.waitKey(0)

 

限制对比度 自适应 直方图均衡

直方图均衡化考虑的是 图像的整体对比度。在许多情况下,这不是一个好主意。例如下图

直方图均衡后,背景对比度确实得到了改善。但是图像中雕像的脸。由于亮度过高,我们在那里丢失了大多数信息。

这是因为它的直方图不像我们在前面的案例中所看到的那样局限于特定区域。

 

因此,为了解决这个问题,使用了**自适应直方图均衡**。

在这种情况下,图像被分成称为“tiles”的小块(在OpenCV中,tileSize默认为8x8)。

然后,像往常一样对这些块中的每一个进行直方图均衡。因此,在较小的区域中,直方图将限制在一个较小的区域中(除非存在噪声)。

如果有噪音,它将被放大。

为了避免这种情况,应用了对比度限制。如果任何直方图bin超出指定的对比度限制(在OpenCV中默认为40),

则在应用直方图均衡之前,将这些像素裁剪并均匀地分布到其他bin。均衡后,要消除图块边界中的伪影,请应用双线性插值

 

opencv 用法

img = cv.imread('imgs/hist2.png', 0)
plt.hist(img.flatten(), 256, (0, 256))
plt.show()

## 直方图均衡化
img3 = cv.equalizeHist(img)
print(img3.shape)
cv.imshow('res', img3)
cv.waitKey(0)
## 限制对比度 的 自适应 直方图均衡
img4 = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
img4 = img4.apply(img)
print(img4.shape)

img5 = np.hstack([img3, img4])
cv.imshow('res', img5)
cv.waitKey(0)

对比图

 

 

 

 

 

参考资料:

https://zhuanlan.zhihu.com/p/54771264  直方图均衡化原理   简单直接

https://zhuanlan.zhihu.com/p/312220402  简单看看

https://zhuanlan.zhihu.com/p/172049969  理论很详细、很完整

https://zhuanlan.zhihu.com/p/32857009

https://zhuanlan.zhihu.com/p/382430357

https://zhuanlan.zhihu.com/p/409574671

https://zhuanlan.zhihu.com/p/82606523

https://zhuanlan.zhihu.com/p/44918476  多种 直方图均衡化算法的 原理,

https://blog.csdn.net/dugudaibo/article/details/84450615  opencv-python 绘制图像直方图及直方图均衡化