在分析图像、物体和视频信息的时候,我们经常用直方图来表达我们关注的信息。直方图可以用来表达很多不同的信息,例如物体的颜色分布,物体的边缘梯度模板或是以概率分布的形式表达当前对物体位置的估计。本文记录 OpenCV 中的直方图相关操作。
直方图在计算机视觉中应用广泛。例如,通过判断帧与帧之间边缘和颜色的统计量是否出现巨大变化,来检测视频中场景的变换。通过使用兴趣点邻域内的特征组成的直方图,来辨识兴趣点。若将边缘、颜色、角点等等的直方图作为特征,可以使用分类器来进行目标识别。提取视频中的颜色或边缘直方图序列,可以用来判断视频是否拷贝自网络。这样的应用数不胜数,直方图可以说是计算机视觉领域中的经典工具之一。
当构造直方图时,我们首先需要将信息放入在各个区间。然而,一旦完成这些,我们有时还会希望将直方图变为归一化的形式,这时每个区间恰好表示的是该区间内的信息占总体的百分比。
规范化数组的范围或值范围。 官方文档
cv2.normalize(
src, # 输入数组。
dst[, # 与 src 大小相同的输出数组。
alpha[, # 归一范数目标值 / 最大最小规范化中的下限
beta[, # 范数归一化时该值无效 / 最大最小规范化中的上限
norm_type[, # 归一化方法
dtype[, # 当为负数时,输出数组的类型与 src 相同;否则,它具有与 src 相同的通道数和深度
mask]]]]] # 数据蒙版(可选)
) ->
dst
其中 P
值与 norm_type
对应关系为:
norm_type | P 值 |
---|---|
cv2.NORM_INF | inf |
cv2.NORM_L1 | 1 |
cv2.NORM_L2 | 2 |
norm_type
为 cv2.NORM_MINMAX
时:mask
参数指定要规范化的子数组。这意味着在子数组上计算范数或 min-n-max,然后修改该子数组以进行归一化。
img = mt.cv_rgb_imread('img.jpg', gray=True)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
res = cv2.normalize(hist, None, alpha=1, norm_type=cv2.NORM_L2)
PIS(hist[:, 0], res[:, 0])
-->
np.sum(res**2)
1.0
img = mt.cv_rgb_imread('img.jpg', gray=True)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
res = cv2.normalize(hist, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX)
thre, res_hist = cv2.threshold(res, 50, 0, cv2.THRESH_TOZERO)
PIS(hist[:, 0], res_hist[:, 0])
有时你希望能找出所有元素个数高于某个给定阈值的区间,有时你只是希望能找出有最多元素的区间。这种情况多发生在使用直方图来表示概率分布的时候。这时你可以选择使用cv2.minMaxLoc()
。
查找数组中的全局最小值和最大值。 官方文档
cv2.minMaxLoc(
src[, # 单通道二维数组
mask] # 可选参数,用于筛选一个子数组
) ->
minVal, # 最小值
maxVal, # 最大值
minLoc, # 最小值下标
maxLoc # 最大值下标
img = mt.cv_rgb_imread('img.jpg', gray=True)
hist = cv2.calcHist([img], [0], None, [256], [0, 255])
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(hist)
-->
min_val, max_val, min_loc, max_loc
(0.0, 68891.0, (0, 0), (0, 20))
不同直方图可以做距离度量,得到直方图之间的相似性。
cv2.compareHist(
H1, # 直方图1
H2, # 直方图2,尺寸和 H1 相同
method # 比较方法
) ->
retval # 距离
hist1 = np.random.random([80]).astype('float32')
hist2 = np.random.random([80]).astype('float32')
dis = cv2.compareHist(hist1, hist2, method=cv2.HISTCMP_KL_DIV)
光照的变化会使颜色值产生大量的偏移,虽然这种偏移倾向于并不改变颜色直方图的形状,但目前我们见到的距离度量方法都对直方图颜色位置的移动束手无策。核心的困难是对于两个形状相同、但只是相对平移的两个直方图,距离度量会给出一个很大的值。我们希望能找到一个对这种平移不敏感的距离度量方法。
计算两个加权点配置之间的“最小工作/推土机”距离。 官方文档
cv2.EMD(
signature1,
signature2,
distType,
[cost,
[lowerBound,
[flow]]]
) - >
retval, lowerBound, flow
hist1 = np.array([0,0,1,2,3,4,5,6,0,0], dtype='float32')
hist2 = np.array([1,2,3,4,5,6,0,0,0,0], dtype='float32')
cost = np.ones([len(hist1), len(hist1)], dtype='float32')
retval, lowerBound, flow = cv2.EMD(hist1, hist2, cv2.DIST_USER, cost)
-->
retval
1.0
flow
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 2., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 3., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 4., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 5., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 6., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
hist1 = np.array([0,0,1,2,3,4,5,6,0,0], dtype='float32')
hist2 = np.array([1,2,3,4,5,6,0,0,0,0], dtype='float32')
signature_1 = np.concatenate([[hist1], [np.arange(len(hist1))]]).astype('float32').T
signature_2 = np.concatenate([[hist2], [np.arange(len(hist2))]]).astype('float32').T
retval, lowerBound, flow = cv2.EMD(signature_1, signature_2, cv2.DIST_L1)
-->
flow
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 2., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 3., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 4., 0., 0., 0., 0.],
[0., 0., 0., 3., 0., 2., 0., 0., 0., 0.],
[0., 0., 0., 1., 5., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
反向投影(Back Projection)是计算像素和直方图模型中像素吻合度的一种方法。
计算直方图的反投影 官方文档
cv2.clacHist()
类似,反向投影从输入图像的指定通道中计算出一个向量,但不同于前者在向直方图中记录累计值,反向投影从输入的直方图中读取当前像素对应的计数值作为结果。从统计学的视角来看,如果将输入的直方图视为某个物体上特定的向量(颜色)的一个(先验)概率分布,那么反向投影就是计算图片上某个特定部分来自该先验分布(即属于物体的一部分)的概率。cv2.calcBackProject(
images, # 源数据数组。
它们都应该具有相同的深度、CV_8U、CV_16U 或 CV_32F,以及相同的大小。它们中的每一个都可以有任意数量的通道。
channels, # 用于计算反投影的通道列表。
通道的数量必须与直方图的维度相匹配。
第一个数组通道从 0 到 images[0].channels()-1 进行计数,
第二个数组通道从 images[0].channels() 到 images[0].channels() + images[1].channels(),依此类推。
hist, # 直方图。
ranges, # 每个维度中直方图 bin 边界的数组数组。
scale[, # 输出反投影的可选比例因子。
dst]
) ->
dst
# 读取图片
sample = cv2.imread("sample.jpeg")
target = cv2.imread("target.jpeg")
# 转换为HSV格式
roi_hsv = cv2.cvtColor(sample, cv2.COLOR_BGR2HSV)
target_hsv = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)
# 计算图像直方图
roiHist = cv2.calcHist([roi_hsv], [0, 1], None, [64, 64], [0, 180, 0, 256])
# 图像归一化处理
cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX)
# 获取直方图的反向投影
dst = cv2.calcBackProject([target_hsv], [0, 1], roiHist, [0, 180, 0, 256], 1)
PIS(dst, cmap='gray')