OpenCV-Python教程:直方图比对、直方图反投影(compareHist,calcBackProject)

原文链接:http://www.juzicode.com/opencv-python-comparehist-calcbackproject

返回Opencv-Python教程

1、直方图比对compareHist

通过compareHist()可以从直方图的角度对比2幅图像的相关性,比较的对象可以是1D或2D直方图。

接口形式:

cv2.compareHist(H1, H2, method) ->retval
  • 参数含义:
  • H1:输入图像直方图;
  • H2:输入图像直方图,和H1相同的尺寸;
  • method:比较方法;

method包含6种方法,整数数值从0~5:

enum  	cv::HistCompMethods {
  cv::HISTCMP_CORREL = 0,
  cv::HISTCMP_CHISQR = 1,
  cv::HISTCMP_INTERSECT = 2,
  cv::HISTCMP_BHATTACHARYYA = 3,
  cv::HISTCMP_HELLINGER = HISTCMP_BHATTACHARYYA,
  cv::HISTCMP_CHISQR_ALT = 4,
  cv::HISTCMP_KL_DIV = 5
}

下面这个例子对读出的图像分别经过平滑、像素值减去一个数、加上一个数的操作,分别计算着4幅图像的直方图,并和源图像做直方图比对,就得到源图像和源图像、平滑图像、加数图像、减数图像的4个比对结果:

import numpy as np
import matplotlib.pyplot as plt
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')

img_src = cv2.imread('..\\samples\\data\\lena.jpg',0) 
img_comp1 = cv2.blur(img_src,(13,13))
img_comp2 = cv2.subtract(img_src,35)
img_comp3 = cv2.add(img_src,35)
#计算直方图
histSize = 256
histRange = (0, 256) 
hist_src = cv2.calcHist([img_src], [0], None, [histSize], histRange) 
hist_comp1 = cv2.calcHist([img_comp1], [0], None, [histSize], histRange)  
hist_comp2 = cv2.calcHist([img_comp2], [0], None, [histSize], histRange)  
hist_comp3 = cv2.calcHist([img_comp3], [0], None, [histSize], histRange)  
#直方图比较
for method in range(6):
    src_src = cv2.compareHist(hist_src, hist_src, method)
    src_comp1 = cv2.compareHist(hist_src, hist_comp1, method)
    src_comp2 = cv2.compareHist(hist_src, hist_comp2, method)
    src_comp3 = cv2.compareHist(hist_src, hist_comp3, method)
    print('method=%d src blur sub add'%method,src_src,src_comp1,src_comp2,src_comp3)

#显示图像
fig,ax = plt.subplots(2,2)
ax[0,0].set_title('hist_src')
ax[0,0].plot(hist_src) 
ax[0,1].set_title('blur')
ax[0,1].plot(hist_comp1)
ax[1,0].set_title('sub')
ax[1,0].plot(hist_comp2)
ax[1,1].set_title('add')
ax[1,1].plot(hist_comp3)
#ax[0,0].axis('off');ax[0,1].axis('off');ax[1,0].axis('off');ax[1,1].axis('off')#关闭坐标轴显示
plt.show()  

运行结果:

从显示的直方图可以看到,平滑处理后图像的直方图和源图像差不多一致,加数或减数会导致直方图左右或右移,可以推测平滑处理的直方图比对结果和源图像会比较接近,而加数或减数图像的直方图比对结果会相差更大,下面打印的比较结果数值可以验证该结论:

VX公众号: 桔子code / juzicode.com
cv2.__version__: 4.5.3
method=0 src blur sub add 1.0 0.9285279683865829 0.35037265936214546 0.3512659314459189
method=1 src blur sub add 0.0 23850.06026419833 7146491.858903675 5844706.253864458
method=2 src blur sub add 262144.0 236732.0 167915.0 167915.0
method=3 src blur sub add 0.0 0.15727062402652872 0.4038869008245676 0.4038869008245676
method=4 src blur sub add 0.0 37400.61455341503 217214.77007193133 217214.77007193133
method=5 src blur sub add 0.0 79978.49978387816 462272.6122239813 1060797.6089261884

method=0和method=2表示的HISTCMP_CORREL和HISTCMP_INTERSECT比对,得到的比对值越大表示2幅图像越接近;method的其他值表示的比对方法得到的值越小表示2幅图像越接近。

下图是前面例子中得到的4张图像,平滑处理的图像(blur)模糊不清,人眼看起来和源图像(src)要显得“更不像”一些,而加减数得到的图像(sub,add)和源图像除了亮度几乎没有差异。但是通过compareHist()计算得到的对比结果却认为平滑处理的图像和源图像要“更像”一些,加减数得到的图像却“更不像”一些,这点和常识是有些相悖的。

得出这个“悖论”的原因是compareHist()比对的是直方图,也就是像素值分布的比对,而人眼观察到的清晰度、梯度变化等内容在直方图里是体现不出来的。下面我们再通过一个更极端的例子来看看直方图比对的局限性,我们将前面代码中的img_comp3图像改成源图像的左右半边对调后得到的新图像:

img_src = cv2.imread('..\\samples\\data\\lena.jpg',0) 
img_comp1 = cv2.blur(img_src,(13,13))
img_comp2 = cv2.subtract(img_src,35)
img_comp3 = img_src.copy() 
img_comp3[:,:256]=img_src[:,256:] #左右半边对调
img_comp3[:,256:]=img_src[:,:256] #左右半边对调

这个时候图像img_comp3(下图中的swap)和源图像(下图中的src)从直观上看起来差别就非常大了,但是他们的直方图却是一样的:

通过compareHist()计算的比对值显示,交换左右半边得到的新图像(swap)和源图像(src)是一样的图像:

method=0 src blur sub swap 1.0 0.9285279683865829 0.35037265936214546 1.0
method=1 src blur sub swap 0.0 23850.06026419833 7146491.858903675 0.0
method=2 src blur sub swap 262144.0 236732.0 167915.0 262144.0
method=3 src blur sub swap 0.0 0.15727062402652872 0.4038869008245676 0.0
method=4 src blur sub swap 0.0 37400.61455341503 217214.77007193133 0.0
method=5 src blur sub swap 0.0 79978.49978387816 462272.6122239813 0.0u

compareHist()除了可以比对一维直方图,还可以比对2D直方图,这时compareHist()入参的H1和H2要求都是2D直方图:

#计算直方图
histSize = (256,256)
histRange = (0, 256,0, 256) 
hist_src = cv2.calcHist([img_src], [0,1], None, histSize, histRange) 
hist_comp1 = cv2.calcHist([img_comp1], [0,1], None, histSize, histRange)  
hist_comp2 = cv2.calcHist([img_comp2], [0,1], None, histSize, histRange)  
hist_comp3 = cv2.calcHist([img_comp3], [0,1], None, histSize, histRange)  
#直方图比较
for method in range(6):
    src_src = cv2.compareHist(hist_src, hist_src, method)
    src_comp1 = cv2.compareHist(hist_src, hist_comp1, method)
    src_comp2 = cv2.compareHist(hist_src, hist_comp2, method)
    src_comp3 = cv2.compareHist(hist_src, hist_comp3, method)
    print('method=%d src blur sub add'%method,src_src,src_comp1,src_comp2,src_comp3)

2、直方图反投影calcBackProject

反投影用来计算源图像的像素和特征图像直方图中像素分布的匹配程度。它返回一个与输入图像尺寸相同的图像,其中每个像素对应于该像素属于特征图像的概率,概率越高越接近要查找的对象,可以用于图像分割或查找感兴趣的区域。

接口形式:

cv2.calcBackProject(images,channels,hist,ranges,scale[,dst])->dst
  • 参数含义:
  • images:输入图像,是一个图像集合,可以是包含多通道彩色图像的list或tuple,也可以是多个灰度图组成的list或者tuple;list或tuple形式的输入
  • channels:根据images确定,指明要用images里的哪个通道号,根据images的形式确定;list或tuple形式的输入;
  • hist:输入直方图;
  • ranges:图像元素取值的范围;包含2个元素的list或tuple;
  • scale:缩放比例;
  • dst:目标图像,单通道,和images[0]同样的尺寸和depth ;

其中入参images、channels、ranges参数用法同calcHist()

入参hist是特征图像的的直方图,使用它在源图像中查找该特征。

下面这里例子从源图像img_src中找出绿色的草地:

import matplotlib.pyplot as plt
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
plt.rc('font',family='Youyuan',size='9')
plt.rc('axes',unicode_minus='False')
 
img_src = cv2.imread('..\\samples\\picture\\cow.jpeg' ) 
img_roi = cv2.imread('..\\samples\\picture\\cow-roi.jpeg' ) 
#计算特征图像直方图
hsv_src = cv2.cvtColor(img_src,cv2.COLOR_BGR2HSV)
hsv_roi = cv2.cvtColor(img_roi,cv2.COLOR_BGR2HSV)
hist_roi = cv2.calcHist([hsv_roi],[0, 1], None, [180, 256], [0, 180, 0, 256] )
cv2.normalize(hist_roi,hist_roi,0,255,cv2.NORM_MINMAX)
#计算反投影
img_back = cv2.calcBackProject([hsv_src],[0,1],hist_roi,[0,180,0,256],2)
#二值化和叠加
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))
img_dest=cv2.filter2D(img_back,-1,disc)
ret,thresh = cv2.threshold(img_dest,30,255,0)
thresh = cv2.merge((thresh,thresh,thresh))
img_merge = cv2.bitwise_and(img_src,thresh)
#绘图
fig,ax = plt.subplots(1,4)
ax[0].set_title('img_src')
ax[0].imshow(cv2.cvtColor(img_src,cv2.COLOR_BGR2RGB)) 
ax[1].set_title('img_roi')
ax[1].imshow(cv2.cvtColor(img_roi,cv2.COLOR_BGR2RGB)) 
ax[2].set_title('img_back')
ax[2].imshow(img_back,'gray') 
ax[3].set_title('img_merge')
ax[3].imshow(cv2.cvtColor(img_merge,cv2.COLOR_BGR2RGB)) 
plt.show()  

在这个例子里首先手动从源图像img_src中截取部分草地作为特征图像img_roi,然后读入源图像和特征图像转换为HSV色彩空间,计算特征图像的H-S通道的2D直方图,然后利用该直方图作为输入传入calcBackProject()计算匹配程度,得到第3张图img_back,该图像为单通道灰度图像,其中的草地部分表现出更高的灰度级,经过阈值处理后和源图像相与就是绿色的草地部分img_merge。

扩展阅读:

  1. OpenCV-Python教程
  2. OpenCV-Python教程:直方图(calcHist)与绘制

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注