直方图
什么是直方图?一个数字图像是由像素点组成的,每个像素点在计算机里都是以二进制代码存储的,通常都是8bit编码,也就是说一个像素的可能值是00H到FFH,如果是灰度图像,那么每个像素值便代表它的灰度值,如果是RGB三通道图像,每个像素值是一个数组比如[60,40,244] 它代表每个通道的灰度值。直方图用来统计每个灰度值出现的次数。也就是每个灰度值出现的频数,横坐标是像素点的值,比如8bit编码横坐标就是0-255,纵坐标就是相应出现的次数,画出一种柱形图,这个柱形图就是直方图。
下面有若干种方法来画直方图,其实也可以自己实现,用for循环统计每个像素出现的次数。
import cv2
import numpy as np
from matplotlib import pyplot as plt
def demo():
# 生成一个一维数组0-15
vec = np.arange(0,15)
print(vec)
# 把一维数组转为3 * 5的一个矩阵
mat = vec.reshape(3,5)
print(mat)
# 把这个3*5的矩阵再转换为一维数组,在python做直方图的时候需要用到
arr = mat.flatten()
print(arr)
def zhifangtu():
# 灰度图像的直方图
path = "e:/images/1.JPEG"
img = cv2.imread(path,0)
plt.figure("灰度直方图")
arr=img.flatten()
''' hist的参数非常多,但常用的就这五个,只有第一个是必须的,后面四个可选 arr: 需要计算直方图的一维数组 bins: 直方图的柱数,可选项,默认为10 normed: 是否将得到的直方图向量归一化。0:不归一化,统计的是频数,1是归一化,统计的是频率,默认为0 facecolor: 直方图颜色 alpha: 透明度 返回值 : n: 直方图向量,是否归一化由参数设定 bins: 横坐标下标 patches: 返回每个bin里面包含的数据,是一个list '''
n, bins, patches = plt.hist(arr, bins=256, normed=1, facecolor='green', alpha=0.75)
print(patches[0])
plt.show()
def zhifangtu1():
# 彩色图像的直方图
path = "e:/images/1.JPEG"
img = cv2.imread(path)
plt.figure("彩色直方图")
b = img[:,:,0]
g = img[:,:,1]
r = img[:,:,2]
ar = np.array(r).flatten()
plt.hist(ar, bins=256, normed=1, facecolor='r', edgecolor='r')
ag = np.array(g).flatten()
plt.hist(ag, bins=256, normed=1, facecolor='g', edgecolor='g')
ab = np.array(b).flatten()
plt.hist(ab, bins=256, normed=1, facecolor='b', edgecolor='b')
plt.show()
def drawHist():
# 绘制直方图的官方方法
''' def calcHist(images, channels, mask, histSize, ranges, hist=None, accumulate=None): images: 原图像(图像格式为uint8或float32)。当传入函数时应该用中括号[]括起来,例如:[img]。 channels: 同样需要用中括号括起来,它会告诉函数我们要统计那幅图像的直方图。如果输入图像是灰度图,它的值就是[0];如果是彩色图像的话,传入的参数可以是[0],[1],[2]它们分别对应着通道B,G,R。 mask: 掩模图像。要统计整幅图像的直方图就把它设为None。但是如果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并使用它。(后边有例子) histSize: BIN的数目。也应该用中括号括起来,例如:[256]。 ranges: 像素值范围,通常为[0,256] :return: '''
path = "e:/images/1.JPEG"
img = cv2.imread(path,0)
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
# img.ravel() 将图像转成一维数组,这里没有中括号。
# matplotlib展示
plt.hist(img.ravel(), 256, [0, 256]);
plt.show()
img = cv2.imread(path)
color = ('b', 'g', 'r')
# 对一个列表或数组既要遍历索引又要遍历元素时
# enumerate 会将数组或列表组成一个索引序列。
# 使我们再获取索引和索引内容的时候更加方便
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.xlim([0, 256])
plt.show()
def main():
drawHist()
if __name__ == '__main__':
main()
直方图的均衡化
直方图为什么要均衡化,首先举一个例子,如果一个灰度图像,它的像素点都集中在100到130之间,其它的像素点比较少,那么这意味着什么呢?我们知道像素点为0代表纯黑,255代表纯白,在中间的时候是黑到白之间的颜色,换句话说像素点为0代表的黑色和像素点1代表的黑色是非常接近的,人眼基本看不出来。那么回到刚才那个例子中,像素点都集中在100到130之间,那么这个图像的颜色都比较接近,对比度很小,视觉效果不好,如果能够把各个像素点的分布分开的话,那么对比度不就大了嘛,人眼也就能看出差别了。
下面用程序说话
def drawHist():
path = "e:/images/11.png"
img = cv2.imread(path,0)
cv2.imshow("image",img)
cv2.waitKey(0)
cv2.calcHist([img],[0],None,[256],[0,256])
plt.hist(img.ravel(), 256, [0, 256]);
plt.show()
先看一下什么效果:
可以看出,它的像素点基本都在15-90之间,下面把这幅图片的直方图均衡化。
import cv2
import numpy as np
from matplotlib import pyplot as plt
def drawHist():
path = "e:/images/11.png"
img = cv2.imread(path,0)
cv2.imshow("image",img)
cv2.waitKey(0)
cv2.calcHist([img],[0],None,[256],[0,256])
plt.hist(img.ravel(), 256, [0, 256]);
plt.show()
# cv2.equalizeHist() 均衡化函数,返回新图像的二维数组
res = cv2.equalizeHist(img)
plt.hist(res.ravel(),256,[0,256])
plt.show()
cv2.imshow("after",res)
cv2.waitKey(0)
# 保存图片
cv2.imwrite('res.png', res)
def main():
drawHist()
if __name__ == '__main__':
main()
后来的直方图和后来的图片:
这就是所谓的直方图均衡化,增强对比度的效果,但是有人就要问了,直方图均衡化的原理是什么?可以参考
[直方图均衡化的数学原理]