OpenCV_python3_10

Practical Python and OpenCV,3rd Edition 10


直方图(historams)

那么,直方图到底是什么?直方图表示图像中像素强度(无论是彩色还是灰度)的分布。它可以被可视化为给出强度(像素值)分布的高级直观的图表(graph)(或绘图(plot)) 在本例中,我们将假设RGB颜色空间,因此这些像素值将在0到255的范围内。

绘制直方图时,X轴作为我们的“箱(bins)”。如果我们构造一个有256个bins的直方图,那么我们就可以有效地计算每个像素值出现的次数。相反,如果我们仅使用2个(等间隔)二进制位,那么我们计算一个像素在[0,128]或[128,255]范围内的次数。The number of pixels binned
to the x-axis value is then plotted on the y-axis.

通过简单地检查图像的直方图,您可以大致了解对比度,亮度和强度分布。

using opencv to compute histograms

我们将使用cv2.calcHist函数来构建直方图。 在我们进入任何代码示例之前,让我们快速回顾一下这个函数:

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

images: 我们想要计算直方图的图像,wrap it as a list:[myImage].

channels: 这是索引列表,我们在其中指定要为其计算直方图的通道的索引。要计算灰度图像的直方图,列表将为[0]。要计算所有三个红色,绿色和蓝色通道的直方图,通道列表将为[0,1,2]。

mask:如果提供mask,则仅计算mask像素的直方图。如果我们没有mask或者不想应用mask,我们可以提供None值。

histSize:这是我们在计算直方图时要使用的bins。同样,这是一个列表,我们正在为每个通道计算一个直方图。bins尺寸并非都必须相同。以下是每个通道分配32个bins的示例:[32,32,32]。

ranges:这里我们指定可能的像素值的范围。通常,对于每个通道,这是[0,256],但如果您使用RGB以外的颜色空间(例如HSV),则范围可能不同。

grayscale histograms

from matplotlib import pyplot as plt 
import argparse 
import cv2 

ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
    help="path to the image")
args = vars(ap.parse_args())

image = cv2.imread(args["image"])

image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
cv2.imshow("Original",image)

解释:导入必要的库,显示原始图像并转为灰度图

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

plt.figure()
plt.title("Grayscale Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")
plt.plot(hist)
plt.xlim([0,256])
plt.show()
cv2.waitKey(0)

解释:现在事情变得有趣了。我们计算实际直方图。参考前面的参数解释,这里,我们可以看到我们的第一个参数是灰度图像。灰度图像只有一个通道,因此通道的值为[0]。我们没有mask,因此我们将掩码值设置为None。我们将在直方图中使用256个bin,可能的值范围为0到256。

显示结果:

解释:我们如何解释这个直方图?bins(0-255)绘制在x轴上。并且y轴计算每个bin中的像素数。大多数像素落在大约60到120的范围内。查看直方图的右尾,我们看到200到255范围内的像素非常少。这意味着图像中只有很少的“白色”像素。

color histograms

from __future__ import print_function
from matplotlib import pyplot as plt 
import numpy as np 
import argparse
import cv2 

ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
    help="path to the image")
args = vars(ap.parse_args())

image = cv2.imread(args["image"])
cv2.imshow("Original",image)

#Grab the image channels, initialize the tuple of colors
#and the figure
chans = cv2.split(image)
colors = ("b","g","r")
plt.figure()
plt.title("'Flattened' Color Histogram")
plt.xlabel("Bins")
plt.ylabel("# of Pixels")

#Loop over the image channels
for (chan,color) in zip(chans,colors):
    #Create a histogram for the current channel and plot it
    hist = cv2.calcHist([chan],[0],None,[256],[0,256])
    plt.plot(hist,color=color)
    plt.xlim([0,256])
    plt.show()

我们要做的第一件事是将图像分成三个通道:蓝色,绿色和红色。通常,我们读到这是红色,绿色,蓝色(RGB)。但是,OpenCV以相反的顺序将图像存储为NumPy数组:BGR。这一点很重要 然后我们初始化一个代表颜色的字符串元组。接着我们设置了PyPlot图。我们将在x轴上绘制bins,并在y轴上绘制放置在每个bin中的像素数量。

然后我们进行for循环,在循环里,我们开始循环遍历图像中的每个通道。然后,对于每个通道,我们计算直方图。该代码与计算灰度图像的直方图的代码相同; 但是,我们正在为每个红色,绿色和蓝色通道执行此操作,使我们能够表征像素强度的分布。

我们可以在上图中检查我们的颜色直方图。我们看到在bin 100周围的绿色直方图中存在尖峰。这表示在beach图像中的树木以及绿色植被有一个 darker green value。

我们还看到170到225范围内有很多蓝色像素。考虑到这些像素要lighter得多,我们知道它们来自我们海滩图像中的蓝天。同样地,我们看到25到50范围内的蓝色像素范围要小得多——这些像素更暗,因此是图像左下角的海洋像素。

到目前为止,我们一次只计算了一个通道的直方图。 现在我们继续进行多维直方图,并一次考虑两个通道。我喜欢用单词AND来解释多维直方图。

例如,我们可以提出一个问题,例如“有多少像素的红色值为10,蓝色值为30?”。有多少像素的绿色值为200,红色值为130?通过使用连接AND,我们能够构建多维直方图。

#Let's move on to 2D histograms -- I am reducing the
#number of bins in the histogram from 256 to 32 so we
#can better visualize the results
fig = plt.figure()

#Plot a 2D color histogram for green and blue
ax = fig.add_subplot(131)
hist = cv2.calcHist([chans[1],chans[0]],[0,1],None,
        [32,32],[0,256,0,256])
p = ax.imshow(hist,interpolation="nearest")
ax.set_title("2D Color Histogram for G and B")
plt.colorbar(p)

#Plot a 2D color histogram for green and red
ax = fig.add_subplot(132)
hist = cv2.calcHist([chans[1],chans[2]],[0,1],None,
        [32,32],[0,256,0,256])
p = ax.imshow(hist,interpolation="nearest")
ax.set_title("2D Color Histogram for G and R")
plt.colorbar(p)

#Plot a 2D color histogram for blue and red
ax = fig.add_subplot(133)
hist = cv2.calcHist([chans[0],chans[2]],[0,1],None,
        [32,32],[0,256,0,256])
p = ax.imshow(hist,interpolation="nearest")
ax.set_title("2D Color Histogram for B and R")
plt.colorbar(p)

print("2D histogram shape : {}, with {} values".format(
    hist.shape,hist.flatten().shape[0]))

现在我们正在使用多维直方图,我们需要记住我们正在使用的bins的数量。在前面的例子中,我使用了256个bins来进行演示。但是,如果我们在2D直方图中为每个维度使用256个二进制位,则我们得到的直方图将具有256×256=65,536个单独的像素计数。这不仅浪费资源,而且不实用。 在计算多维直方图时,大多数应用程序使用8到64个区间。上面使用32个bins而不是256个bins。

通过检查cv2.calcHist函数的第一个参数,可以看出此代码中最重要的内容。在这里,我们看到我们传递了两个通道的列表:绿色和蓝色通道。

那么,如何在OpenCV中存储2D直方图?它实际上是一个2D NumPy数组。 由于我为每个通道使用了32个bin,我现在有一个32×32的直方图。

我们如何可视化2D直方图?如下图所示,其中我们看到三个图。第一个是绿色和蓝色通道的2D颜色直方图,第二个是绿色和红色,第三个是蓝色和红色。蓝色阴影表示低像素计数,而红色阴影表示大像素计数(即,2D直方图中的峰值)。我们倾向于在绿色和蓝色直方图中看到许多峰,其中x=22且y=12.这对应于植被和树木的绿色像素以及天空和海洋的蓝色。

print输出:

2D histogram shape : (32, 32), with 1024 values

使用2D直方图一次考虑两个通道。但是,如果我们想要考虑所有三个RGB通道呢?

# Our 2D histogram could only take into account 2 out
# of the 3 channels in the image so now let's build a
# 3D color histogram (utilizing all channels) with 8 bins
# in each direction -- we can't plot the 3D histogram, but
# the theory is exactly like that of a 2D histogram, so
# we'll just show the shape of the histogram
hist = cv2.calcHist([image], [0, 1, 2],
    None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
print("3D histogram shape: {}, with {} values".format(
    hist.shape, hist.flatten().shape[0]))

# Show our plots
plt.show()

print输出:

3D histogram shape: (8, 8, 8), with 512 values

这里的代码非常简单——它只是上面代码的扩展。我们现在为每个RGB通道计算8×8×8直方图。我们无法想象这个直方图,但我们可以看到形状确实是(8,8,8),有512个值。

histogram equalization(直方图均衡化)

直方图均衡化通过“拉伸”像素的分布来改善图像的对比度。考虑一个在直方图中心有一个大峰值的。应用直方图均衡会将峰值拉伸到图像的角落,从而改善图像的整体对比度。直方图均衡应用于灰度图像。

当图像包含前景和背景都是dark或两者都是light时,此方法很有用。它往往会在照片中产生不切实际的影响; 但是,在增强医学或卫星图像的对比度时通常很有用。

新建一个equalize.py文本

import numpy as np 
import argparse
import cv2 

ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
    help="path to the image")
args = vars(ap.parse_args())

# Load the image and convert it to grayscale
image = cv2.imread(args["image"])
image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

# Apply histogram equalization to stretch the constrast
# of our image
eq = cv2.equalizeHist(image)

# Show our images -- notice how the constrast of the second
# image has been stretched
cv2.imshow("Histogram Equalization",np.hstack([image,eq]))
cv2.waitKey(0)

解释:使用单个函数执行直方图均衡:cv2.equalizeHist,它接受单个参数,即我们想要执行直方图均衡的灰度图像。最后几行代码显示我们的直方图均衡图像。

在左边,我们有原始的海滩图像 然后,在右边,我们有直方图均衡的海滩图像。注意图像的对比度是如何彻底改变的,现在跨越[0,255]的整个范围。

histograms and masks

我们现在将构建一个mask并仅计算mask区域的颜色直方图。

新建一个histogram_with_mask.py文件

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

def plot_histogram(image,title,mask = None):
    chans = cv2.split(image)
    colors = ("b","g","r")
    plt.figure()
    plt.title(title)
    plt.xlabel("Bins")
    plt.ylabel("# of Pixels")

    # Grab the image channels, initialize the tuple of colors
    # and the figure
    for (chan,color) in zip(chans,colors):
        hist = cv2.calcHist([chan],[0],mask,[256],[0,256])
        plt.plot(hist,color=color)
        plt.xlim([0,256])

我们定义plot_histogram。此函数接受三个参数:图像,绘图标题和掩码。如果我们没有图像mask,则mask默认为None。

plot_histogram函数的主体只是为图像中的每个通道计算直方图并绘制它。既然我们有一个函数来帮助我们轻松绘制直方图,那么让我们进入大部分代码:

ap = argparse.ArgumentParser()
ap.add_argument('-i',"--image",required=True,
    help="path to the image")
args = vars(ap.parse_args())

image = cv2.imread(args["image"])
cv2.imshow("Original",image)
plot_histogram(image,"Histogram for Original Image")

# Construct a mask for our image -- our mask will be BLACK for
# regions we want to IGNORE and WHITE for regions we want to
# EXAMINE. In this example we will be examining the foliage
# of the image, so we'll draw a white rectangle where the foliage
# is
mask = np.zeros(image.shape[:2],dtype="uint8")
cv2.rectangle(mask,(15,15),(130,100),255,-1)
cv2.imshow("Mask",mask)

masked = cv2.bitwise_and(image,image,mask=mask)
cv2.imshow("Applying the Mask",masked)
cv2.waitKey(0)

结果

现在我们准备为图像构建一个mask。我们将mask定义为NumPy数组,其宽度和高度与海滩图像相同。然后,我们从点(15,15)到点(130,100)绘制一个白色矩形。这个矩形将用作我们的蒙版——在直方图计算中仅将属于蒙版区域的像素进行直方图计算。

为了可视化我们的蒙版,我们对海滩图像应用按位AND,其结果可以上图中看到。注意中间的图像只是一个白色矩形,但是当我们将mask应用到海滩图像时,我们只看到蓝天(最右)。

# Let's compute a histogram for our image, but we'll only include
# pixels in the masked region
plot_histogram(image, "Histogram for Masked Image", mask = mask)

# Show our plots
plt.show()

最后,我们使用plot_histogram函数为我们的蒙版图像计算直方图并显示我们的结果。

结果:

我们可以在上图中看到我们的蒙版直方图。大多数红色像素落在[0,80]范围内,表明红色像素对我们的图像贡献很小。这是有道理的,因为我们的天空是蓝色的。然后存在绿色像素,但是再次朝向RGB光谱的较暗端。最后,我们的蓝色像素落在更亮的范围内,显然是我们的蓝天。

最重要的是,将上图的蒙版颜色直方图与上面没有应用mask的颜色直方图进行比较。注意颜色直方图有多么不同。通过使用mask,我们只能将计算应用于我们感兴趣的图像的特定区域——在这个例子中,我们只是想检查蓝天的分布。

用到的函数

cv2.calcHist

add_subplot

colorbar

cv2.equalizeHist

更多的参考:

PPaO Chapter 7 – Histograms

matplotlib

How-To: 3 Ways to Compare Histograms using OpenCV and Python

The complete guide to building an image search engine with Python and OpenCV

OpenCV Shape Descriptor: Hu Moments Example

Building a Pokedex in Python: Indexing our Sprites using Shape Descriptors (Step 3 of 6)


---------------- The End ----------------
支持一下
Fork me on GitHub ;