目录

0 原理      1 统计直方图      2 绘制直方图      3 使用掩膜


0 原理

什么是直方图呢?通过直方图你可以对整幅图像的灰度分布有一个整体的了解。直方图的 x 轴是灰度值(0 到 255),y 轴是图片中具有同一个灰度值的点的数目。

../../../../../_images/Histogram_Calculation_Theory_Hist0.jpg

直方图其实就是对图像的另一种解释。一下图为例,通过直方图我们可以对图像的对比度,亮度,灰度分布等有一个直观的认识。几乎所有的图像处理 软件都提供了直方图分析功能。

让我们来一起看看这幅图片和它的直方图吧。(要记住,这个直方图是根据灰度图像绘制的,而不是彩色图像)。直方图的左边区域像是了暗一点的像素数量, 右侧显示了亮一点的像素的数量。从这幅图上你可以看到灰暗的区域比两的区域要大,而处于中间部分的像素点很少。

 

1 统计直方图

现在我们知道什么是直方图了,那怎样获得一副图像的直方图呢?OpenCV 和 Numpy 都有内置函数做这件事。在使用这些函数之前我们有必要想了解一下直方图相关的术语。

BINS

上面的直方图显示了每个灰度值对应的像素数。如果像素值为 0到 255,你就需要 256 个数来显示上面的直方图。但是,如果你不需要知道 每一个像素值的像素点数目的,而只希望知道两个像素值之间的像素点数目怎 么办呢?举例来说,我们想知道像素值在 0 到 15 之间的像素点的数目,接着 是 16 到 31,....,240 到 255。我们只需要 16 个值来绘制直方图。OpenCV Tutorials on histograms中例子所演示的内容。 那到底怎么做呢?你只需要把原来的 256 个值等分成 16 小组,取每组的
总和。而这里的每一个小组就被成为 BIN。第一个例子中有 256 个 BIN,第 二个例子中有 16 个 BIN。在 OpenCV 的文档中用 histSize 表示 BINS。

DIMS

表示我们收集数据的参数数目。在本例中,我们对收集到的数据 只考虑一件事:灰度值。所以这里就是 1。

RANGE

就是要统计的灰度值范围,一般来说为 [0,256],也就是说所有的灰度值

1.1 使用 OpenCV 统计直方图

函数 cv2.calcHist 可以帮助我们统计一幅图像 的直方图。我们一起来熟悉一下这个函数和它的参数:

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

  • images:原图像(图像格式为 uint8 或 float32)。当传入函数时应该 用中括号 [] 括起来,例如:[img]。
  • channels:同样需要用中括号括起来,它会告诉函数我们要统计那幅图 像的直方图。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像 的话,传入的参数可以是 [0],[1],[2] 它们分别对应着通道 B,G,R。
  • mask: 掩模图像。要统计整幅图像的直方图就把它设为 None。但是如 果你想统计图像某一部分的直方图的话,你就需要制作一个掩模图像,并 使用它。(后边有例子)
  • histSize:BIN 的数目。也应该用中括号括起来,例如:[256]。 5. ranges: 像素值范围,通常为 [0,256]
  • hist:是一个 256x1 的数组作为返回值,每一个值代表了与次灰度值对应的像素点数目。
  • accumulate:是一个布尔值,用来表示直方图是否叠加。

注:别忘了中括号 [img],[0],None,[256],[0,256],只有 mask 没有中括号,例如

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

1.2 使用 Numpy 统计直方图

Numpy 中的函数 np.histogram() 也可以帮我们统计直方图。例如

#img.ravel() 将图像转成一维数组,这里没有中括号。 
hist,bins = np.histogram(img.ravel(),256,[0,256])

其他:Numpy还有一个函数np.bincount(), 它 的 运 行 速 度 是 np.histgram 的十倍。所以对于一维直方图,我们最好使用这个 函数。使用 np.bincount 时别忘了设置 minlength=256。例如, hist=np.bincount(img.ravel(),minlength=256)

注意:OpenCV 的函数要比 np.histgram() 快 40 倍。所以坚持使用 OpenCV 函数。

 

2 绘制直方图

2.1 使用Matplotlib绘制直方图

Matplotlib中有直方图绘制函数:matplotlib.pyplot.hist() 它可以直接统计并绘制直方图。你应该使用函数calcHist()或np.histogram() 统计直方图。这种方法比使用opencv自带函数绘图简单且直观。

举个例子:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img1 = cv2.imread('test6.jpg', 0)
# 别忘了中括号 [img],[0],None,[256],[0,256],只有 mask 没有中括号
hist1 = cv2.calcHist([img1], [0], None, [256], [0, 256])

img2 = cv2.imread('test6.jpg')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
    histr = cv2.calcHist([img2], [i], None, [256], [0, 256])
    plt.subplot(224), plt.plot(histr, color=col),
    plt.xlim([0, 256]), plt.title('Histogram')
    

plt.subplot(221), plt.imshow(img1, 'gray'), plt.title('Image1')
plt.subplot(222), plt.hist(img1.ravel(), 256, [0, 256]),
plt.title('Histogram'), plt.xlim([0, 256])
plt.subplot(223), plt.imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)), plt.title('Image2')
plt.show()

结果如下:

 

2.2 使用OpenCV绘制直方图

利用OpenCV自带的绘图函数也可以绘制直方图,只不过比上面的方法麻烦,举一个例子

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('test6.jpg')
rows, cols, dpt = img.shape
size = rows * cols
color = ('b', 'g', 'r')
colors = ((255, 0, 0), (0, 255, 0), (0, 0, 255))


for i, c in enumerate(colors):
    histr = cv2.calcHist([img], [i], None, [256], [0, 256])
    histr = histr*cols/size
    lines = []
    for j, k in enumerate(histr):
        x = int(j*rows/255)
        y = cols - int(k*10)
        # cv2.circle(img, (x, y), 2, c, -1)  # 点显示
        ploy = lines.append([x, y])
    pts = np.array([lines], dtype=np.int32)
    cv2.polylines(img, pts, 0, c, 2)
    plt.subplot(122), plt.plot(histr, color=color[i]),
    plt.xlim([0, 256]), plt.title('Histogram')

plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Image2')
plt.show()

结果如下:

可以看出直方图绘制到了图像中,当然你也可以将其单独绘制到另一个窗口中。此外,这个方法还有一个作用就是可以实时显示摄像头的直方图,例如:

import cv2
import numpy as np

cap = cv2.VideoCapture(0)

color = ('b', 'g', 'r')
colors = ((255, 0, 0), (0, 255, 0), (0, 0, 255))

if cap.isOpened() is True:  # 检查摄像头是否正常启动
    while(True):
        frame = cv2.imread('test6.jpg')
        ret, frame = cap.read()
        rows, cols, dpt = frame.shape
        # 尺寸读取出了问题
        rows = 640
        cols = 480
        size = rows * cols
        for i, c in enumerate(colors):
            histr = cv2.calcHist([frame], [i], None, [256], [0, 256])
            histr = histr*cols/size
            lines = []
            for j, k in enumerate(histr):
                x = int(j*rows/255)
                y = cols - int(k*5)
                # cv2.circle(frame, (x, y), 2, c, -1)  # 点显示
                ploy = lines.append([x, y])
            pts = np.array([lines], dtype=np.int32)
            cv2.polylines(frame, pts, 0, c, 2)
        cv2.imshow('frame', frame)
        k = cv2.waitKey(5) & 0xFF
        if k == 27:
            break
    cap.release()
    cv2.destroyAllWindows()
else:
    print('cap is not opened!')

结果如下:

         

                                 正常的直方图                                                                               图像偏红的直方图 

         

                                图像偏蓝的直方图                                                                            图像偏绿的直方图 

3 使用掩膜

要统计图像某个局部区域的直方图只需要构建一副掩模图像。将要统计的部分设置成白色,其余部分为黑色,就构成了一副掩模图像。然后把这个掩模 图像传给函数就可以了。

例如:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('test6.jpg', 0)
# 创建一个掩膜
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:400, 100:400] = 255
masked_img = cv2.bitwise_and(img, img, mask=mask)

hist_full = cv2.calcHist([img], [0], None, [256], [0, 256]) # 无掩膜
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256]) # 有掩膜

plt.subplot(221), plt.imshow(img, 'gray'), plt.title('Original')
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('Mask')
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('Masked')
plt.subplot(224), plt.plot(hist_full, label='original'),
plt.plot(hist_mask, label='masked'), plt.xlim([0, 256]),
plt.legend(loc=1), plt.title('Hostogram')

plt.show()

结果如下:

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐