基于Python的OpenCV图像处理10

May 23,2017   3382 words   13 min


灰度分布直方图

灰度分布直方图是图像处理中基本的概念之一,英文是histogram。一般而言,其x轴是灰度值,取值范围为0 - 255, y轴是图片中某一灰度出现的概率或数目。直方图左边是暗一点的像素的数量,右边是亮一点的像素 的数量,据此即使不看图像也可以判断这幅图像是亮的部分多还是暗的部分多。对比度怎么样等等。 在OpenCV或Numpy中绘制直方图非常简单,在此之前我们需要先了解几个函数调用时的名词:

  • BINS:有时我们不想逐个统计像素出现的次数,想要统计如灰度值在0-15之间像素个数、16-31之间的像素个数等等, 这个时候就要用到BIN的概念了。每一个小组被称为一个BIN,0 - 255逐个统计的时候可以理解为共有256个BIN,每个BIN 只有1个元素。在OpenCV中用histSize表示BINS。

  • DIMS:表示我们手机数据的参数数目。在这里我们只对像素考虑一个灰度值,所以为1。

  • RANGE:要统计的灰度范围,一般为0 - 255,当然如果更高bit量化则范围更大。

计算灰度分布直方图

在OpenCV中,cv2.calcHist()函数用于计算直方图,其原型如下:

cv2.calcHist(images,channels,mask,histSize,ranges[,hist[,accumulate]])

利用该函数得到的是一个256×1维的数组,里面存放的是灰度值对应的个数。

参数解释如下:

  • images:待处理的图像,图像格式为uint8或float32,传入时用[ ]括起来,如[img]

  • channels:对应图像需要统计的通道,若是灰度图则为0,彩色图像B、G、R对应0、1、2,注意同样需要用[ ]括起来,如[0]

  • mask:掩膜图像。如果统计整幅图像就设置为None,否则这里传入设计的掩膜图像。

  • histSize:前面说过了,BIN的数目。同样用[ ]括起来,如[256]

  • ranges:像素量化范围,通常为0 - 255。

示例代码如下:

# coding=utf-8

import numpy as np
import cv2

img = cv2.imread("E:\\522.jpg", 0)

hist = cv2.calcHist([img], [0], None, [256], [0, 255])

print hist.shape

至此我们便得到了一幅图像的灰度分布情况,下面是要把它绘制出来。

绘制直方图

有多种方式可以绘制直方图,包括可以调用OpenCV的函数来绘制,但是比较复杂。所以介绍一种简单方便的方法。 使用Matplotlib来绘制直方图。而它本身也自带直方图统计功能,所以可以有两种选择。一种是使用Matplotlib 统计并绘图,另一种是使用OpenCV统计,用Matplotlib绘图。

Matplotlib统计并绘图

代码如下:

# coding=utf-8
import cv2
from matplotlib import pyplot as plt

# 直接以灰度模式读取图片,不用再转换了
img = cv2.imread("E:\\522.jpg", 0)

# 调用Matplotlib直接统计并绘图
plt.hist(img.ravel(), 256, [0, 255])
plt.show()

运行结果如下: 当然hist函数有非常多的自定义选项参数,这里只用了最简单的几个,如果你想继续美化,可以查看相关帮助。

OpenCV统计、Matplotlib绘图

代码如下:

# coding=utf-8
import cv2
from matplotlib import pyplot as plt

# 读取图片
img = cv2.imread("E:\\522.jpg")

# 新建一个元组,分别对应不同通道的颜色以供遍历
color = ('b', 'g', 'r')

# 对于一个列表或数组,既要遍历索引又要遍历元素时
# 使用内置enumerate函数会更加直接、优美
# 它会将数组或列表组成一个索引序列
# 使我们再获取索引和索引内容的时候更加方便
for i, col in enumerate(color):
    histr = cv2.calcHist([img], [i], None, [256], [0, 255])
    plt.plot(histr, color=col)
    plt.xlim([0, 255])
plt.show()

绘制出的B、G、R三个通道的灰度分布直方图如下所示: 根据直方图我们可以对原图片进行简单分析:

  • 1.首先整幅图片应该以蓝色调为主。
  • 2.蓝色出现在灰度值比较大的地方,说明蓝色偏亮。而红色在灰度比较低的地方出现,说明图片中有些地方呈现暗红色或棕色。
  • 3.在中间部分红绿蓝分量几乎差不多,说明呈现白色调,不同灰度。 下面我们再看看原图: 对照上面的分析可以发现是差不多的。整幅图片主题是蓝天和大海。而且天空占了图片一半左右,且呈亮蓝色。 比较暗的红色对应岩石的棕色或褐色。而中间的白色调体现在演示被阳光照射到的接近白色的地方(偏灰)以及 海浪的白色。
    可以看出,灰度分布直方图是从统计学的角度对图像进行分析,忽略了各个像素之间的空间关系。是另一种表示图像的方法。
使用掩膜

前面说了也可以对图像使用掩膜从而只统计某一区域的灰度直方图。代码如下:

# coding=utf-8
import numpy as np
import cv2
from matplotlib import pyplot as plt

# 读取图片
img = cv2.imread("E:\\522.jpg", 0)

# 新建一个与原图等大的空白图片(黑色)
mask = np.zeros(img.shape[:2], np.uint8)
# 将图片中200×200范围的像素设为255(白色)
mask[100:300, 300:500] = 255
# 将掩膜与原图进行位操作
masked_img = cv2.bitwise_and(img, img, mask=mask)

# 这时传入的参数不再是None,而是mask了
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 255])

# 绘制灰度分布直方图
plt.plot(hist_mask, color='black')
plt.show()

cv2.imshow("img", masked_img)
cv2.waitKey(0)

效果如下: 此外需要注意的是这里在读取的时候是灰度图像,所以[0]表示直接画出来了。如果是BGR读入的, 那么[0]就不是总灰度了,而是Blue这个通道的灰度。所以要区别各个通道的灰度直方图与将图片 转换为灰度图之后的灰度直方图是不一样的。如下代码演示所示。

# coding=utf-8
import cv2
from matplotlib import pyplot as plt

# 读取图片
img = cv2.imread("E:\\522.jpg")

# 将原图转成灰度图
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 分别计算各个通道以及灰度图的直方图
hist_b = cv2.calcHist([img], [0], None, [256], [0, 255])
hist_g = cv2.calcHist([img], [1], None, [256], [0, 255])
hist_r = cv2.calcHist([img], [2], None, [256], [0, 255])
hist_gray = cv2.calcHist([gray_img], [0], None, [256], [0, 255])

# 绘制灰度分布直方图
plt.subplot(2, 2, 1), plt.plot(hist_b, color='blue'), plt.title("Blue")
plt.subplot(2, 2, 2), plt.plot(hist_g, color='green'), plt.title("Green")
plt.subplot(2, 2, 3), plt.plot(hist_r, color='red'), plt.title("Red")
plt.subplot(2, 2, 4), plt.plot(hist_gray, color='black'), plt.title("Gray")
plt.show()

结果如下:

本文作者原创,未经许可不得转载,谢谢配合

返回顶部