您当前的位置: 首页 >  编程语言

使用 OpenCV 和 NumPy 创建直方图

介绍

不熟悉摄影,我只有有偏见的知识。这就是为什么几年前我买了一台稍贵的数码相机时,我不知道液晶显示屏上显示的图形是什么的原因。但是现在我已经在 OpenCV 中使用图像处理获得了很多乐趣,我知道。这是一个亮度直方图

在 Python 中创建直方图

直方图不是专门用于图像处理的技术。它被广泛用作统计方法之一,并以七大质量控制工具之一而闻名。在 Python 中,OpenCV 和 numpy 具有获取直方图的功能。

在统计界,有关于条形图的个数和宽度的讨论,但如果是图像亮度的直方图,则是“256个类别,从0到255(包括255),宽度为1”。将是正常的

这次的图像是 Rena 小姐,她的 RGB 是适度偏向的。

莲娜.png 开放式CV

在 OpenCV 中cv2.calcHist()利用。只写所需的参数

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

变成。从参数名称images、channels 可以看出,它们指定了列表。元组也是可能的。

争论 图像 输入图像列表。指定为 [image] 和元素 1 的列表。除了np.uint8,np.float32也可以。 channels 颜色通道。为灰度图像指定 [0],为 BGR 指定 [0][1][2]。 蒙版蒙版图像。如果未指定,请使用None。我希望这是可选的。 histSize 间隔数。亦称仓。如果要单独计算 256 个渐变,请使用 [256]。 ranges 要计数的值范围。对图像的所有亮度执行时设置为 [0, 256]。不是 [0, 255]。 输出 hist 直方图。当图像数量为 1 时,返回一个 2D numpy 数组,例如 [[0.000e+00], [0.000e+00], …]。每个元素都是一个 float32。

就这个返回值而言,即使images是一个列表,似乎也不可能一次指定多张图片并获取多个直方图。

样本

为 RGB 图像的每个颜色通道创建直方图的示例。请注意,颜色序列是 BGR,因为它是 OpenCV。

import cv2 
import matplotlib.pyplot as plt

filename = "lenna.png"
image = cv2.imread(filename)
COLORS = ["blue","green","red"]

for i, color in enumerate(COLORS):
    hist = cv2.calcHist([image], [i], None, [256], [0, 256])
    plt.plot(hist, color=color)

plt.show()

结果,确认莉娜图像偏红。

数字货币

在 numpy 中np.histogram()利用。

hist, bin_edges = np.histogram(a, bins=10, range=None, normed=None, weights=None, density=None)

如何使用。

论据(仅部分) 一个输入矩阵。预先进行一维化的情况很多,但实际上在计算过程中一维化。 bins 箱数。可选,默认为 10。它可以是数字、列表或元组(任何东西都可以是字符串)。 范围 Bin 范围。可以省略,初始值为a的最小值到最大值。 输出 直方图。一维 numpy 数组,其中每个元素都是整数。 bin_edges Bin 范围。 样本 1 省略范围

尝试使用指定的bins 和未指定的range 进行绘图。

for i, color in enumerate(COLORS):
    hist, bin_edges = np.histogram(image[:, :, i], bins=256)
    plt.plot(hist, color=color)
仅指定 np.histogram() bins cv2.calcHist()

注意红色。 Lena图像整体呈现强烈的红色,应该没有亮度小于等于50的点,但是在这个图中,正常图中50到255的范围已经扩大到0到25​​5。这是“如果省略范围,则从最小值到最大值”的结果。图被切碎的原因是当将“从最小值到最大值”扩展到“从0到255的256个类”时,会出现“从100.1到100.9”这样的不完整范围。这被认为是因为由于亮度是整数值,因此“从 100.1 到 100.9”范围内的点数为 0。

示例 2 指定范围

接下来,尝试指定bins 和range。

for i, color in enumerate(COLORS):
    hist, bin_edges = np.histogram(image[:, :, i], bins=256, range=(0, 256))
    plt.plot(hist, color=color)
指定 np.histogram() bins 和 range cv2.calcHist()

原以为还有很长的路要走,但我能画出几乎一样的图表。这真的正确吗?

检查正确性

Lena 图像大小为 512x512,像素为 262144。如果您采用直方图,则频率总和应为 262144,无论箱数如何。此外,在此图像中,红色强度 = 255 的像素是

np.sum(image[:, :, 2]==255)    # 112

事实证明,其中有 112 个。

示例 1 范围 =(0, 254)

让我们指定range=(0, 254) 作为一个明显不正确的例子。垃圾箱的数量无关紧要,因此请将其保留为默认值。

hist, bin_edges = np.histogram(image[:, :, 2], range=(0, 254))
len(hist)        # 10 ビン数(デフォ値)
sum(hist)        # 262032 度数の合計

这里出现的 262032 是总像素数 262144 减去亮度为 255、112 的像素数。这意味着不计算亮度为 255 的像素。

示例 2 bins=256, range=(0, 255)

接下来,让我们指定bins=256, range=(0, 255)。

hist, bin_edges = np.histogram(image[:, :, 2], bins=256, range=(0, 255))
len(hist)        # 256 ビン数
sum(hist)        # 262144 度数の合計
len(bin_edges)   # 257 bin_edgesの数
bin_edges
[  0.           0.99609375   1.9921875    2.98828125   3.984375
   4.98046875   5.9765625    6.97265625   7.96875      8.96484375
    中略
 249.0234375  250.01953125 251.015625   252.01171875 253.0078125
 254.00390625 255.        ]

可以看出,频率之和等于像素数,所有像素都被计算在内。

顺便说一句, bin_edges 有 (length(hist)+1) 元素。0 开头,255 结尾。最后一个是第 257 个,而不是第 256 个。虽然它被 256 个 bin 分隔,但亮度 255 属于它之外的第 257 个簇。情况不妙。另外,我不喜欢每个类都是一个不可分割的数字,而不是一个精确的间隔。

示例 3 bins=256, range=(0, 256)

接下来,让我们指定bins=256, range=(0, 256)。

hist, bin_edges = np.histogram(image[:, :, 2], bins=256, range=(0, 256))
len(hist)        # 256 ビン数
sum(hist)        # 262144 度数の合計
len(bin_edges)   # 257 bin_edgesの数
bin_edges
[  0.   1.   2.   3.   4.   5.   6.   7.   8.   9.  10.  11.  12.  13.
  14.  15.  16.  17.  18.  19.  20.  21.  22.  23.  24.  25.  26.  27.
    中略
 238. 239. 240. 241. 242. 243. 244. 245. 246. 247. 248. 249. 250. 251.
 252. 253. 254. 255. 256.]

现在值 255 最终属于第 256 个集群。我们还确认每个类的宽度为 1。

这周围***关注一下

比较 OpenCV 直方图和 Numpy 直方图

cv2.calcHist() 没有在 np.histogram() 中的bin_edges 的输出。让我们看看这两个直方图是否完美匹配。输出的 dtype 和 shape 再次表示如下。

cv2.calcHist() np.histogram() → 统一格式 类型 dtype('float32') dtype('int64') → 使它成为 dtype('float32') 形状 (256, 1) (256,) → 设置为 (256,)

让我们统一这个区域,检查内容是否完全匹配。还要检查处理时间。似乎有一种更复杂的方式来编写处理时间测量部分,但它离题了,所以我在这里写得很扎实。

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

COLORS = ["blue","green","red"]
filename = "lenna.png"
image = cv2.imread(filename)

for i, color in enumerate(COLORS):
    print("-" * 20)
    print(color)

    # cv2.calcHist() リストでなくタプルで指示してみる 良い子は真似するな
    start_time = time.time()
    hist_cv = cv2.calcHist((image,), (i,), None, histSize=(256,), ranges=(0, 256))
    end_time = time.time()
    print("cv2.calcHist()", end_time - start_time)

    # np.histogram() rangeをリストで指示してみる
    start_time = time.time()
    hist_np, bin_edges = np.histogram(image[:, :, i], bins=256, range=[0, 256])
    end_time = time.time()
    print("np.histogram()", end_time - start_time)

    hist_cv_sameformat = hist_cv.flatten()                      # (256, 1) -> (256,)
    hist_np_sameformat = hist_np.astype(np.float32)             # int64 -> float32
    if np.array_equal(hist_cv_sameformat, hist_np_sameformat):  # 本当はarray_equalはdtypeを揃える必要はない
        print("完全一致")    
    else:
        print("不一致あり")   

这是结果。

--------------------
blue
cv2.calcHist() 0.0020308494567871094
np.histogram() 0.005982637405395508
完全一致
--------------------
green
cv2.calcHist() 0.000997781753540039
np.histogram() 0.003998756408691406
完全一致
--------------------
red
cv2.calcHist() 0.0010008811950683594
np.histogram() 0.003998994827270508
完全一致

我能够使用 OpenCV 和 numpy 获得相同的结果。我还发现 OpenCV 要快得多。发现参数,无论其名称如何,都以几乎相同的方式使用。如果一个是 (0, 255) 而另一个是 [0, 256] 或类似的东西,你甚至不能看它。

在最后

我打算省略 np.histogram() 的bin_edges 的解释,但通过提及这一点,我现在可以理解范围不合适时的行为。这是你无法学习的东西,除非你自己做。

原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308627088.html