啥叫直方图
直方图简单来说就是图像中每个像素值的个数统计,比如一副灰度图中像素值为0的有多少个,1的有多少个...
在计算直方图之前,先了解几个术语:
- dims:要计算的通道数,对于灰度图dims=1,普通彩色图dims=3
- range:要计算的像素值范围,一般为[0,255]
- bins:子区段数目,如果我们统计0~255每个像素值,bins=256;如果划分区间,比如0~15,16~31...240~255这样16个区间,bins=16
计算直方图
OpenCV和Numpy中提供了计算直方图的函数,我们对比一下他们的性能
OpenCV中直方图计算
在OpenCV中使用 cv2.calcHist(images,channels,mask,histSize,ranges) 计算,其中:
- 参数1:要计算的原图,以方括号的传入,如:[img]
- 参数2:类似前面提到的dims,灰度图写[0]就行,彩色图B、G、R分别传入[0]、[1]、[2]
- 参数3:要计算的区域,计算整幅图的话,写None
- 参数4:前面提到的bins
- 参数5:前面提到的range
import cv2 import numpy as np import matplotlib.pyplot as plt
start = cv2.getTickCount() img = cv2.imread('hist.jpg', 0) hist = cv2.calcHist([img], [0], None, [256], [0, 256]) end = cv2.getTickCount() print((end - start) / cv2.getTickFrequency())
这里我使用到了评估代码运行时间,且并没有给出最初最后print()函数输出的结果,其实这个结果一直在变化。这里简单的介绍一下获取代码运行时间的两种方式
第一种:
import cv2 start = cv2.getTickCount() # 这里写测试代码... end = cv2.getTickCount() print((end - start) / cv2.getTickFrequency())
这段代码就是用来测量程序运行时间(单位:s),其中cv2.getTickCount()函数得到电脑启动以来的时钟周期数,cv2.getTickFrequency()返回你电脑的主频,前后相减再除以主频就是你代码的运行时间。我这里就是使用的第一种
第二种:使用Python中的time模块计时:
import time start = time.clock() # 这里写测试代码... end = time.clock() print(end - start)
Numpy中直方图计算
也可用Numpy的函数计算,使用 ravel() 函数 将二维矩阵展平变成一维数组,
hist, bins = np.histogram(img.ravel(), 256, [0, 256])
还有一种更高效的的方式:
hist = np.bincount(img.ravel(), minlength=256)
绘制直方图
其实Matplotlib自带了一个计算并绘制直方图的功能,不需要用到上面的函数
plt.hist(img.ravel(), 256, [0, 256])
plt.show()
从直方图上可以看出图片的像素点集中子150附近,图片偏灰白,效果不好。接下来我们来看看如何来改善它。
当然,我们也可以用前面计算出来的结果绘制:
plt.plot(hist)
plt.show()
直方图的均衡化
一幅效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度的。
OpenCV中用cv2.equalizeHist()实现均衡化,我们把两张图片并排显示一下,对比一下:
equ = cv2.equalizeHist(img) cv2.imshow('equalization', np.hstack((img, equ))) # 并排显示 cv2.waitKey(0)
均衡后的直方图:
plt.hist(equ.ravel(), 256, [0, 256])
plt.show()
可以看出均衡后的直方图明显好于原图。均衡后的图片的亮度和对比度效果明显好于原图
自适应均衡化
直方图均衡化是应用于整幅图片的,那是不是所有图片都适合?会不会出现什么问题?看下图:
很明显,因为全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。
自适应均衡化就是解决这一问题的:它在每一个小区域内(默认8x8)进行直方图均衡化。当然,如果有噪点的话,噪点会被放大,需要对小区域的对比度进行了限制,所以这个算法全称叫:对比度受限的自适应直方图均衡化 CLAHE (Contrast Limited Adaptive Histogram Equalization)
# 自适应均衡化,参数可选 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) cl1 = clahe.apply(img) cv2.imshow('equalization', np.hstack((equ, cl1))) # 并排显示 cv2.waitKey(0)
效果比均衡化效果要好,至少脸部的细节保留了
使用掩膜
要统计图像某个局部区域的直方图只需要构建一副掩膜图像。将要统计的部分设置为白色,其余部分为黑色,就构成了一副掩膜图像。然后把这个掩膜图像传给函数就可以了。
img = cv2.imread('home.jpg', 0) # 创建一个掩膜 mask = np.zeros(img.shape[:2], np.uint8) # 取彩色通道的长宽 mask[0 : 200, 0 : 200] = 255 masked_img = cv2.bitwise_and(img, img, mask = mask) Original_Hist = cv2.calcHist([img], [0], None, [256], [0, 256]) masked_img_Hist = cv2.calcHist([img], [0], mask, [256], [0, 256]) plt.subplot(231), plt.imshow(img, 'gray') plt.subplot(232), plt.imshow(mask, 'gray') plt.subplot(233), plt.imshow(masked_img, 'gray') plt.subplot(234), plt.plot(Original_Hist) plt.subplot(235), plt.plot(masked_img_Hist) plt.subplot(236), plt.plot(Original_Hist), plt.plot(masked_img_Hist) plt.show()
其中在混合的那副直方图中,蓝色为原图的直方图,橙色为进行掩膜之后的直方图。