Opencv-Python学习笔记(十一):图像直方图

本篇博客记录学习OpenCV-Python图像直方图的相关知识。

  • 使用OpenCV和Numpy函数查找直方图,使用Matplotlib函数绘制直方图
  • 您要学习的函数有:cv2.calcHist()np.histogram()等。

原理

什么是直方图呢?通过直方图我们可以对整幅图像的灰度分布有一个整体的了解。直方图的 x 轴是灰度值( 0 到 255)y 轴是图片中具有同一个灰度值的点的数目
直方图其实就是对图像的另一种解释。以下图为例,通过直方图我们可以对图像的对比度,亮度,灰度分布等有一个直观的认识。几乎所有的图像处理软件都提供了直方图分析功能。

统计、绘制直方图

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

有两种方法来绘制直方图:
1. Short Way(简单方法):使用 Matplotlib 中的绘图函数。
2. Long Way(复杂方法):使用 OpenCV 绘图函数。

使用 Matplotlib Matplotlib 中有直方图绘制函数: matplotlib.pyplot.hist()它可以直接统计并绘制直方图。

接下来我们以下图为例进行操作。

                                                               

# -*- coding: utf-8 -*-
# @Time    : 2019/11/10 19:33
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 直方图.py
# @Software: PyCharm
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


def plot_demo(image):
    plt.hist(image.ravel(), 256, [0, 256])  # image.ravel()是将多维数组降为一维数组,256为bins数量,[0, 256]为范围
    plt.show()


def image_hist(image):
    color = ('blue', 'green', 'red')
    for i, color in enumerate(color):
        # 计算出直方图,calcHist(images, channels, mask, histSize(有多少个bin), ranges[, hist[, accumulate]]) -> hist
        # hist 是一个 256x1 的数组,每一个值代表了与该灰度值对应的像素点数目。
        hist = cv.calcHist(image, [i], None, [256], [0, 256])
        print(hist.shape)
        plt.plot(hist, color=color)
        plt.xlim([0, 256])
    plt.show()


if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/Mimi256.jpg")  # 读入图片放进src中
    cv.namedWindow("input image")  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    plot_demo(src)
    image_hist(src)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

运行结果图:

使用掩模

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

import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


def mask_process(image):
    image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
    mask = np.zeros(image.shape[:2], np.uint8)
    mask[0:150, 60:200] = 255
    # cv.imshow("mask", mask)
    masked_img = cv.bitwise_and(image, image, mask=mask)
    # cv.imshow("masked_image", masked_img)
    hist_full = cv.calcHist([image], [0], None, [256], [0, 256])
    hist_mask = cv.calcHist([image], [0], mask, [256], [0, 256])
    plt.subplot(221), plt.imshow(image, 'gray')
    plt.subplot(222), plt.imshow(mask, 'gray')
    plt.subplot(223), plt.imshow(masked_img, 'gray')
    plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
    plt.xlim([0, 256])
    plt.show()


if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/Mimi256.jpg")  # 读入图片放进src中
    cv.namedWindow("input image")  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    mask_process(src)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

效果图:

直方图均衡化

原理

想象一下如果一副图像中的大多是像素点的像素值都集中在一个像素值范围之内会怎样呢?例如,如果一幅图片整体很亮,那所有的像素值应该都会很高。但是一副高质量的图像的像素值分布应该很广泛。所以应该把它的直方图做一个横向拉伸(如下图),这就是直方图均衡化要做的事情。通常情况下这种操作会改善图像的对比度。


                                                              

# -*- coding: utf-8 -*-
# @Time    : 2019/11/10 19:33
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 直方图.py
# @Software: PyCharm
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


def equalHist_demo(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    # 全局直方图均衡化,用于增强图像对比度,即黑的更黑,白的更白
    dst = cv.equalizeHist(gray)
    cv.imshow("equalHist_demo", dst)
  

if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/rice.png")  # 读入图片放进src中
    cv.namedWindow("input image")  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    equalHist_demo(src)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

我们可以看出对比度明显增强。

但是有时候我们这样整体处理一幅图像会丢失一些信息,这个时候我们就需要自适应的直方图均衡化。这种情况下,整幅图像会被分成很多小块,这些小块被称为“tiles”(在 OpenCV 中 tiles 的大小默认是 8x8),然后再对每一个小块分别进行直方图均衡化(跟前面类似)。所以在每一个的区域中,直方图会集中在某一个小的区域中(除非有噪声干扰)。如果有噪声的话,噪声会被放大。为了避免这种情况的出现要使用对比度限制。对于每个小块来说,如果直方图中的 bin 超过对比度的上限的话,就把其中的像素点均匀分散到其他 bins 中,然后在进行直方图均衡化。最后,为了去除每一个小块之间“人造的”(由于算法造成)边界,再使用双线性差值,对小块进行缝合。

# -*- coding: utf-8 -*-
# @Time    : 2019/11/10 19:33
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 直方图.py
# @Software: PyCharm
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


def equalHist_demo(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    # 局部直方图均衡化
    clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    clahe_dst = clahe.apply(gray)
    cv.imshow("clahe", clahe_dst)
  

if __name__ == '__main__':
    src = cv.imread("F:/Pycharm/opencv_exercises-master/images/rice.png")  # 读入图片放进src中
    cv.namedWindow("input image")  # 创建窗口
    cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    equalHist_demo(src)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

结果如图所示:

直方图比较

建议去看看贾志刚老师的课程。我也不是很懂,emmmm  直接上代码看一下效果吧。

# -*- coding: utf-8 -*-
# @Time    : 2019/11/10 19:33
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 直方图.py
# @Software: PyCharm
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


# 创建直方图
def create_rgb_demo(image):
    h, w, c = image.shape
    rgbHist = np.zeros([16*16*16, 1], np.float32)
    bsize = 256 / 16
    for row in range(h):
        for col in range(w):
            b = image[row, col, 0]
            g = image[row, col, 1]
            r = image[row, col, 2]
            index = np.int(b/bsize)*16*16 + np.int(g/bsize)*16 + np.int(r/bsize)
            rgbHist[np.int(index), 0] = rgbHist[np.int(index), 0] + 1
    return rgbHist


# 利用直方图比较相似性,用巴氏和相关性比较好
def hist_compare(image_1, image_2):
    hist1 = create_rgb_demo(image1)
    hist2 = create_rgb_demo(image2)
    match1 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_BHATTACHARYYA)
    match2 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_CORREL)
    match3 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_CHISQR)
    print("巴式距离:%s, 相关性:%s, 卡方:%s" % (match1, match2, match3))


if __name__ == '__main__':
    image1 = cv.imread("F:/Pycharm/opencv_exercises-master/images/rice.png")
    image2 = cv.imread("F:/Pycharm/opencv_exercises-master/images/noise_rice.png")
    cv.imshow("image1", image1)
    cv.imshow("image2", image2)
    hist_compare(image1, image2)
    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口

通过数值,我们可以很容易的判断图像的相似度。

完整工程代码:

# -*- coding: utf-8 -*-
# @Time    : 2019/11/10 19:33
# @Author  : MMagicLoren
# @Email   : [email protected]
# @File    : 直方图.py
# @Software: PyCharm
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np


def plot_demo(image):
    plt.hist(image.ravel(), 256, [0, 256])  # image.ravel()是将多维数组降为一维数组,256为bins数量,[0, 256]为范围
    plt.show()


def image_hist(image):
    color = ('blue', 'green', 'red')
    for i, color in enumerate(color):
        # 计算出直方图,calcHist(images, channels, mask, histSize(有多少个bin), ranges[, hist[, accumulate]]) -> hist
        # hist 是一个 256x1 的数组,每一个值代表了与该灰度值对应的像素点数目。
        hist = cv.calcHist(image, [i], None, [256], [0, 256])
        print(hist.shape)
        plt.plot(hist, color=color)
        plt.xlim([0, 256])
    plt.show()


def mask_process(image):
    image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
    mask = np.zeros(image.shape[:2], np.uint8)
    mask[0:150, 60:200] = 255
    # cv.imshow("mask", mask)
    masked_img = cv.bitwise_and(image, image, mask=mask)
    # cv.imshow("masked_image", masked_img)
    hist_full = cv.calcHist([image], [0], None, [256], [0, 256])
    hist_mask = cv.calcHist([image], [0], mask, [256], [0, 256])
    plt.subplot(221), plt.imshow(image, 'gray')
    plt.subplot(222), plt.imshow(mask, 'gray')
    plt.subplot(223), plt.imshow(masked_img, 'gray')
    plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
    plt.xlim([0, 256])
    plt.show()


def equalHist_demo(image):
    gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
    # 全局直方图均衡化,用于增强图像对比度,即黑的更黑,白的更白
    # dst = cv.equalizeHist(gray)
    # cv.imshow("equalHist_demo", dst)
    # 局部直方图均衡化
    clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    clahe_dst = clahe.apply(gray)
    cv.imshow("clahe", clahe_dst)


# 创建直方图
def create_rgb_demo(image):
    h, w, c = image.shape
    rgbHist = np.zeros([16*16*16, 1], np.float32)
    bsize = 256 / 16
    for row in range(h):
        for col in range(w):
            b = image[row, col, 0]
            g = image[row, col, 1]
            r = image[row, col, 2]
            index = np.int(b/bsize)*16*16 + np.int(g/bsize)*16 + np.int(r/bsize)
            rgbHist[np.int(index), 0] = rgbHist[np.int(index), 0] + 1
    return rgbHist


# 利用直方图比较相似性,用巴氏和相关性比较好
def hist_compare(image_1, image_2):
    hist1 = create_rgb_demo(image1)
    hist2 = create_rgb_demo(image2)
    match1 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_BHATTACHARYYA)
    match2 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_CORREL)
    match3 = cv.compareHist(hist1, hist2, method=cv.HISTCMP_CHISQR)
    print("巴式距离:%s, 相关性:%s, 卡方:%s" % (match1, match2, match3))


if __name__ == '__main__':
    # src = cv.imread("F:/Pycharm/opencv_exercises-master/images/rice.png")  # 读入图片放进src中
    # cv.namedWindow("input image")  # 创建窗口
    # cv.imshow("input image", src)  # 将src图片放入该创建的窗口中
    # plot_demo(src)
    # image_hist(src)
    # mask_process(src)
    # equalHist_demo(src)
    image1 = cv.imread("F:/Pycharm/opencv_exercises-master/images/Crystal256.jpg")
    image2 = cv.imread("F:/Pycharm/opencv_exercises-master/images/Mimi256.jpg")

    # create_rgb_demo(image1)
    cv.imshow("image1", image1)
    cv.imshow("image2", image2)
    hist_compare(image1, image2)

    cv.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
    cv.destroyAllWindows()  # 关闭所有窗口
发布了29 篇原创文章 · 获赞 83 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/l59565455/article/details/103001088