- 反向投影是一种记录给定图像中的像素点如何适应直方图模型像素分布的方式。
- 简单的讲, 所谓反向投影就是首先计算某一特征的直方图模型,然后使用模型去寻找图像中存在的该特征。
- 例如, 你有一个肤色直方图 ( Hue-Saturation 直方图 ),你可以用它来寻找图像中的肤色区域:
1 反向投影的工作原理
我们使用肤色直方图为例来解释反向投影的工作原理:
1)假设你已经通过下图得到一个肤色直方图(Hue-Saturation), 旁边的直方图就是 模型直方图 ( 代表手掌的皮肤色调).你可以通过掩码操作来抓取手掌所在区域的直方图:
2)下图是另一张手掌图(测试图像) 以及对应的整张图像的直方图:
3)我们要做的就是使用 模型直方图 (代表手掌的皮肤色调) 来检测测试图像中的皮肤区域。以下是检测的步骤
第一步:对测试图像中的每个像素p(i,j) ,获取色调数据并找到该色调(hi,j,si,j) 在直方图中的bin的位置。
第二步:查询 模型直方图 中对应的bin - (hi,j,si,j) - 并读取该bin的数值。
第三步:将此数值储存在新的图像中(BackProjection)。 你也可以先归一化 模型直方图 ,这样测试图像的输出就可以在屏幕显示了。
第四步:通过对测试图像中的每个像素采用以上步骤, 我们得到了下面的 BackProjection 结果图:
第五步:使用统计学的语言, BackProjection 中储存的数值代表了测试图像中该像素属于皮肤区域的 概率 。比如以上图为例, 亮起的区域是皮肤区域的概率更大(事实确实如此),而更暗的区域则表示更低的概率(注意手掌内部和边缘的阴影影响了检测的精度)。
2 反向投影查找原理
查找的方式就是不断的在输入图像中切割跟模板图像大小一致的图像块,并用直方图对比的方式与模板图像进行比较。
假设我们有一张100x100的输入图像,有一张10x10的模板图像,查找的过程是这样的:
(1)从输入图像的左上角(0,0)开始,切割一块(0,0)至(10,10)的临时图像;
(2)生成临时图像的直方图;
(3)用临时图像的直方图和模板图像的直方图对比,对比结果记为c;
(4)直方图对比结果c,就是结果图像(0,0)处的像素值;
(5)切割输入图像从(0,1)至(10,11)的临时图像,对比直方图,并记录到结果图像;
(6)重复(1)~(5)步直到输入图像的右下角。
3 反向投影的应用
用于图像分割或查找图像中感兴趣的对象。它的输出结果是与输入图像大小相同的图像,每一个像素值代表了输入图像上对应点属于目标对象的概率。换言之,输出图像中像素值越高的点越可能代表想要查找的目标。直方图投影经常与camshift(追踪算法)算法一起使用。
4 相关函数
cv2.calcBackProject()计算反向投影
原型:cv2.calcBackProject(images, channels, hist, ranges, scale[, dst=None])
参数:
- images:输入图像,是一个图像集合,可以是包含多通道彩色图像的list或tuple,也可以是多个灰度图组成的list或者tuple;list或tuple形式的输入
- channels:输入通道。根据images确定,指明要用images里的哪个通道号,根据images的形式确定;list或tuple形式的输入;
- hist:输入直方图。模板的直方图,这个直方图的通道要与第二个参数channels是一样的
- ranges:图像元素取值的范围;包含2个元素的list或tuple;均匀bin, ranges只需要最小最大边界;H 范围为[0,180],S 范围[0,256]
- scale:输出结果的缩放因子,默认为1
返回值:
- dst:目标图像,单通道,和images[0]同样的尺寸和depth。包含了以每个输入图像像素点为起点的直方图对比结果;可以将其看做是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图像。
cv2.mixChannels()组合图像的不同通道
5 示例
#! /usr/bin/env python
# -*- coding: utf-8 -*-
"""
反向投影
用于图像分割或查找图像中感兴趣的对象
输入图像为两张三通道图片
"""
import cv2
import numpy as np# 【解决plt显示汉字乱码的临时设置】
#plt.rcParams['font.sans-serif'] = ['SimHei']
#plt.rcParams['axes.unicode_minus'] = False# 【BGR转HSV图像函数】
def bgr2hsv(src):
"""
将原图由BGR色彩空间转换至HSV色彩空间
:param src:三通道图片
:return:返回转换后图片
"""
dst = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
return dst
# 【获取图像特征函数】
def getCharacterPic(roi, target):
"""
分离特征图片
:param roi: 模板,三通道HSV图片
:param target: 待检测图片(目标图片),三通道HSV图片
:return: 无
"""
# 【显示图像】
cv2.imshow('target', target)
cv2.imshow('roi', roi)
# 【将BGR图像转换为HSV图像】
target = cv2.bilateralFilter(target, 13, 70, 50) # 双边滤波 - 保边去噪
roiHsv = bgr2hsv(roi)
targetHsv = bgr2hsv(target)
# 【计算模板roiHsv的直方图】
roiHist = cv2.calcHist([roiHsv], [0, 1], None, [180, 256], [0, 180, 0, 256]) # H 范围为[0,180],S 范围[0,256]
cv2.normalize(roiHist, roiHist, 0, 255, cv2.NORM_MINMAX) # 将roihist归一化至[0, 255]范围
# 【利用反向投影cv2.calcBackProject】
# dst包含了以每个输入图像像素点为起点的直方图比对结果
# 可以将其看成是一个二维的浮点型数组、二维矩阵,或者单通道的浮点型图片
dst = cv2.calcBackProject([targetHsv], [0, 1], roiHist, [0, 180, 0, 256], 1) # 将模板的直方图投影到原图的hsv空间得到dst
cv2.imshow("calcBackProject", dst) # 显示图片
# 【Now convolute with circular disc】
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 构造一个5*5椭圆形的kernel
cv2.filter2D(dst, -1, disc, dst) # -1代表卷积padding,连接分散的点
cv2.imshow("dst after conv", dst) # 显示图片
# 【threshold and binary AND】
ret, thresh = cv2.threshold(dst, 50, 255, 0) # 阈值需要自己调整
# thresh是单通道的浮点型图片,原始图是三通道的,所以需要合并thresh为三通道图方便后续操作
thresh = cv2.merge((thresh, thresh, thresh))
cv2.imshow("thresh", thresh)
res = cv2.bitwise_and(target, thresh)
cv2.imshow('result', res)
cv2.imwrite('res.jpg', res)
cv2.waitKey(0) # 等待用户输入,按任意键即可
cv2.destroyAllWindows()
if __name__ == '__main__':
roi = cv2.imread("C:/Users/xxx/Downloads/roi.png")
target = cv2.imread("C:/Users/xxx/Downloads/target.png") # 原图 - 模板图片从其抠出getCharacterPic(roi, target)
运行结果如下:将图片target中具有roi特征的图像抠出