OpenCV Tutorials 09 - 图像直方图 - 2

305 阅读8分钟

图像直方图 - 2

import cv2 as cv
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
def cv_show(name, img):
    cv.imshow(name, img)
    cv.waitKey(0)
    cv.destroyAllWindows()
def compare(imgs):
  #  for i in range(len(imgs)):
 #       imgs[i][:,-3:-1,:] = [255,255,255]
    res = np.hstack(imgs)
    cv_show('Compare', res)

一、2D直方图

本小节,我们主要来学习如何得到并绘制2D直方图。

在第一篇文章中,我们计算并绘制了一维直方图。 之所以称为一维,是因为我们只考虑一个特征,即像素的灰度强度值。 但是在二维直方图中,您需要考虑两个特征。 通常它用于查找两个特征是色调的颜色直方图

已经有一个 python 示例 (samples/python/color_histogram.py) 用于查找颜色直方图。 我们将尝试了解如何创建这样的颜色直方图,这将有助于理解直方图反投影等其他主题。

1. OpenCV中的2D直方图

它非常简单,使用相同的函数 cv.calcHist() 进行计算。 对于颜色直方图,我们需要将图像从 BGR 转换为 HSV。 (请记住,对于一维直方图,我们从 BGR 转换为灰度)。 对于二维直方图,其参数将修改如下:

  1. channels = [0,1] 因为我们需要同时处理 H 和 S 平面。
  2. bins = [180,256] H 平面(角度)为 180,S (强度)平面为 256。
  3. range = [0,180,0,256] 色调值介于 0 到 180 之间
img = cv.imread('lena.png')
# 对原BGR图进行色域转换
hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
hist = cv.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
plt.hist(hist)
plt.show()

download.png

2. Numpy中的2D直方图

Numpy还为此提供了一个特定功能:np.histogram2d()。 (请记住,对于1D直方图,我们使用了NP.histogram())。

img = cv.imread('lena.png')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
# 注意此处的分层方法
h,s,v = cv.split(hsv)
hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])
plt.hist(hist)
plt.show()

download.png

第一个参数是 H 平面,第二个是 S 平面,第三个是每个 bin 的数量,第四个是它们的范围。 现在我们可以学习如何绘制这个颜色直方图。

3. 绘制2D直方图

a.使用cv.imshow()

我们得到的结果是一个大小为 180x256 的二维数组。 所以我们可以像往常一样展示它们,使用 cv.imshow() 函数。 这将是一个灰度图像,它不会给出太多的颜色,除非你知道不同颜色的色调值。

b.使用Matplotlib

我们可以使用 matplotlib.pyplot.imshow() 函数来绘制具有不同颜色图的 2D 直方图。 它让我们更好地了解不同的像素密度。 但这也不能让我们一眼就知道是什么颜色,除非你知道不同颜色的色调值。 我还是更喜欢这种方法。 它简单而且更好。

  • 注意: 在使用这个函数时,请记住,插值标志应该是最接近的,以获得更好的结果。
img = cv.imread('lena.png')
hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist = cv.calcHist( [hsv], [0, 1], None, [180, 256], [0, 180, 0, 256] )
plt.subplot(121),plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
# interpolation 是插值方法,这里选择的是最近邻(可能会产生锯齿效应)
plt.subplot(122),plt.imshow(hist,interpolation = 'nearest')
plt.show()

download.png

上面是输入图像及其颜色直方图。 X 轴显示 S 值,Y 轴显示色调。

c.OpenCV的简例

在 OpenCV-Python2 示例 (samples/python/color_histogram.py) 中有一个颜色直方图示例代码。 如果你运行代码,你可以看到直方图也显示了相应的颜色。 或者只是它输出一个颜色编码的直方图。 它的结果非常好(尽管您需要添加额外的一行)。 在该代码中,作者在 HSV 中创建了一个颜色图。 然后将其转换为BGR。 生成的直方图图像与此颜色图相乘。 他还使用了一些预处理步骤来去除小的孤立像素,从而得到一个很好的直方图。 我把它留给读者来运行代码、分析它并拥有自己的 hack 方法。 下面是与上面相同图像的代码输出:

# 未找出示例代码

download.jpg

您可以在直方图中清楚地看到存在哪些颜色,有蓝色,有黄色,还有一些由于棋盘而导致的白色。 很好 !!!

二、直方图反投影

1. 概念

  1. 它是由 Michael J. Swain 和 Dana H. Ballard 在他们的论文 Indexing via color histograms 中提出的。
  2. 用简单的话来说实际上是什么?它用于图像分割或在图像中查找感兴趣的对象。简而言之,它创建了与我们的输入图像大小相同(但单通道)的图像,其中每个像素对应于该像素属于我们对象的概率。用更简单的话来说,与剩余部分相比,输出图像将使我们感兴趣的对象更白。嗯,这是一个直观的解释(提取ROI) 。直方图反投影与 camshift 算法等一起使用。
  3. 我们如何做到这一点?我们创建包含我们感兴趣的对象(在我们的例子中是地面,滤除玩家和其他事物)的图像的直方图。物体应尽可能填充图像以获得更好的效果。并且颜色直方图优于灰度直方图,因为对象的颜色比其灰度强度是定义对象的更好方法。然后,我们将这个直方图“反向投影”到我们需要找到对象的测试图像上,换句话说,我们计算每个像素属于地面的概率并显示它。正确阈值的结果输出为我们提供了基础。

2. Numpy中的算法

  1. 首先,我们需要计算我们需要找到的对象(让它是'M')和我们要搜索的图像(让它是'I')的颜色直方图
# roi使我们需要寻找的对象
roi = cv.imread('xy_back.png')
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
# target是roi委身的图像!!!,不要弄反了
target = cv.imread('xy.png')
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# Find the histograms using calcHist. Can be done with np.histogram2d also
M = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
I = cv.calcHist([hsvt],[0, 1], None, [180, 256], [0, 180, 0, 256] )
  1. 找到比率 R=M/I。 然后反向投影 R,即使用 R 作为调色板并创建一个新图像,其中每个像素作为其对应的目标概率。 即 B(x,y) = R[h(x,y),s(x,y)] 其中 h 是色调,s 是 (x,y) 处像素的饱和度。 之后应用条件 B(x,y)=min[B(x,y),1]。
M.shape
(180, 256)
h,s,v = cv.split(hsvt)
# 注意此处的阵列乘法,0不能做分母,所以需要加上一个很小的偏移量
R = M/(I + 0.00000001)
# M 和 I的第一维都是颜色H,第二位都是V饱和度
B = R[h.ravel(),s.ravel()]
B = np.minimum(B,1)
B = B.reshape(hsvt.shape[:2])
  1. 现在对圆盘应用卷积,B=D∗B,其中 D 是椭圆内核。
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
# 前三个参数分别为:原图像、返回图像的通道数,滤波
cv.filter2D(B,-1,disc,B)
B = np.uint8(B)
# 泛化
cv.normalize(B,B,0,255,cv.NORM_MINMAX)
B.shape
(673, 684)

4. 现在最大强度的位置给了我们物体的位置。 如果我们期望图像中有一个区域,则对合适的值进行阈值处理会得到很好的结果。

ret,thresh = cv.threshold(B,50,255,0)
res = cv.bitwise_and(target,target,mask = thresh)
compare([target, cv.merge([thresh,thresh,thresh]), res])

download.png

可以看出Numpy反向直方图的结果和OpenCV得到的结果有很大区别。

3. OpenCV中的反向投影

OpenCV 提供了一个内置函数 cv.calcBackProject()。 它的参数与 cv.calcHist() 函数几乎相同。 它的参数之一是直方图,它是对象的直方图,我们必须找到它。 此外,在传递给 backproject 函数之前,应该对对象直方图进行归一化。 它返回概率图像。 然后我们将图像与圆内核卷积并应用阈值。 下面是我的代码和输出:

  • 用法如下:calcBackProject(images, channels, hist, ranges, scale[, dst]) -> dst

参数说明:

  1. images参数表示输入图像(是HSV图像)。传入时应该用中括号[ ]括起来。

  2. channels参数表示用于计算反向投影的通道列表,通道数必须与直方图维度相匹配。

  3. hist参数表示输入的模板图像直方图。

  4. ranges参数表示直方图中每个维度bin的取值范围 (即每个维度有多少个bin)。

  5. scale参数表示可选输出反向投影的比例因子,一般取1。

import numpy as np
import cv2 as cv
# 读取ROI和Target图像并进行对应的色域转换
roi = cv.imread('xy_back.png')
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
target = cv.imread('xy.png')
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)

# 计算ROI的H,S通道的直方图
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )

# 将ROI的H,S直方图归一化
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
# 第二三个参数的维度必须匹配,得到的是一副二值图
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)

# 构造滤波器并将其应用在结果图像上
dis_c = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,dis_c,dst)


# 把结果图像转化为灰度图之后进行阈值处理
ret,thresh = cv.threshold(dst,50,255,0)
# 由于 thresh 自身是 一维 不能直接和三维的结果图像进行按位与,那么就要使用merge对thresh升维
thresh = cv.merge((thresh,thresh,thresh))
# 按位与滤除所有与ROI无关的像素点
res = cv.bitwise_and(target,thresh)
res = np.hstack((target,thresh,res))
cv_show('Test', res)

download.png