图像直方图 - 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 转换为灰度)。 对于二维直方图,其参数将修改如下:
- channels = [0,1] 因为我们需要同时处理 H 和 S 平面。
- bins = [180,256] H 平面(角度)为 180,S (强度)平面为 256。
- 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()
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()
第一个参数是 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()
上面是输入图像及其颜色直方图。 X 轴显示 S 值,Y 轴显示色调。
c.OpenCV的简例
在 OpenCV-Python2 示例 (samples/python/color_histogram.py) 中有一个颜色直方图示例代码。 如果你运行代码,你可以看到直方图也显示了相应的颜色。 或者只是它输出一个颜色编码的直方图。 它的结果非常好(尽管您需要添加额外的一行)。 在该代码中,作者在 HSV 中创建了一个颜色图。 然后将其转换为BGR。 生成的直方图图像与此颜色图相乘。 他还使用了一些预处理步骤来去除小的孤立像素,从而得到一个很好的直方图。 我把它留给读者来运行代码、分析它并拥有自己的 hack 方法。 下面是与上面相同图像的代码输出:
# 未找出示例代码
您可以在直方图中清楚地看到存在哪些颜色,有蓝色,有黄色,还有一些由于棋盘而导致的白色。 很好 !!!
二、直方图反投影
1. 概念
- 它是由 Michael J. Swain 和 Dana H. Ballard 在他们的论文 Indexing via color histograms 中提出的。
- 用简单的话来说实际上是什么?它用于图像分割或在图像中查找感兴趣的对象。简而言之,它创建了与我们的输入图像大小相同(但单通道)的图像,其中每个像素对应于该像素属于我们对象的概率。用更简单的话来说,与剩余部分相比,输出图像将使我们感兴趣的对象更白。嗯,这是一个直观的解释(提取ROI) 。直方图反投影与 camshift 算法等一起使用。
- 我们如何做到这一点?我们创建包含我们感兴趣的对象(在我们的例子中是地面,滤除玩家和其他事物)的图像的直方图。物体应尽可能填充图像以获得更好的效果。并且颜色直方图优于灰度直方图,因为对象的颜色比其灰度强度是定义对象的更好方法。然后,我们将这个直方图“反向投影”到我们需要找到对象的测试图像上,换句话说,我们计算每个像素属于地面的概率并显示它。正确阈值的结果输出为我们提供了基础。
2. Numpy中的算法
- 首先,我们需要计算我们需要找到的对象(让它是'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] )
- 找到比率 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])
- 现在对圆盘应用卷积,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])
可以看出Numpy反向直方图的结果和OpenCV得到的结果有很大区别。
3. OpenCV中的反向投影
OpenCV 提供了一个内置函数 cv.calcBackProject()。 它的参数与 cv.calcHist() 函数几乎相同。 它的参数之一是直方图,它是对象的直方图,我们必须找到它。 此外,在传递给 backproject 函数之前,应该对对象直方图进行归一化。 它返回概率图像。 然后我们将图像与圆内核卷积并应用阈值。 下面是我的代码和输出:
- 用法如下:calcBackProject(images, channels, hist, ranges, scale[, dst]) -> dst
参数说明:
-
images参数表示输入图像(是HSV图像)。传入时应该用中括号[ ]括起来。
-
channels参数表示用于计算反向投影的通道列表,通道数必须与直方图维度相匹配。
-
hist参数表示输入的模板图像直方图。
-
ranges参数表示直方图中每个维度bin的取值范围 (即每个维度有多少个bin)。
-
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)