OpenCV 4.11.0
开源计算机视觉库
加载中…
搜索中…
未找到匹配项
直方图 - 4:直方图反投影

目标

本章我们将学习直方图反投影。

理论

该方法由Michael J. Swain 和 Dana H. Ballard 在他们的论文Indexing via color histograms中提出。

简单来说是什么? 它用于图像分割或查找图像中感兴趣的对象。简单来说,它会创建一个与输入图像大小相同(但只有一个通道)的图像,其中每个像素对应于该像素属于我们目标对象的概率。更简单地说,输出图像中我们感兴趣的目标对象会比其余部分更亮(白色)。嗯,这是一个直观的解释。(我无法再更简单地解释了)。直方图反投影与camshift算法等一起使用。

我们怎么做? 我们创建一个包含我们感兴趣的对象(在我们的例子中是地面,排除玩家和其他东西)的图像的直方图。为了获得更好的结果,对象应尽可能填充图像。并且优选使用颜色直方图而不是灰度直方图,因为对象的颜色比其灰度强度更能定义对象。然后,我们将此直方图“反投影”到我们需要查找对象的测试图像上,换句话说,我们计算每个像素属于地面的概率并显示它。对结果输出进行适当的阈值处理后,我们就可以得到单独的地面部分。

NumPy算法

  1. 首先,我们需要计算我们需要查找的对象(设为“M”)和我们将要搜索的图像(设为“I”)的颜色直方图。
    import numpy as np
    import cv2 as cvfrom matplotlib import pyplot as plt
    # roi是我们需要查找的对象或对象区域
    roi = cv.imread('rose_red.png')
    assert roi is not None, "文件无法读取,请使用os.path.exists()检查"
    hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
    # target是我们搜索的图像
    target = cv.imread('rose.png')
    assert target is not None, "文件无法读取,请使用os.path.exists()检查"
    hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
    # 使用calcHist查找直方图。也可以使用np.histogram2d
    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] )
    CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
    从文件中加载图像。
    void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
    将图像从一个颜色空间转换为另一个颜色空间。
    void calcHist(const Mat *images, int nimages, const int *channels, InputArray mask, OutputArray hist, int dims, const int *histSize, const float **ranges, bool uniform=true, bool accumulate=false)
    计算一组数组的直方图。
  2. 找到比率 \(R = \frac{M}{I}\)。然后反投影R,即使用R作为调色板并创建一个新图像,每个像素都是其作为目标的相应概率。即B(x,y) = R[h(x,y),s(x,y)],其中h是(x,y)处像素的色相,s是饱和度。之后应用条件\(B(x,y) = min[B(x,y), 1]\)。
    h,s,v = cv.split(hsvt)
    B = R[h.ravel(),s.ravel()]
    B = np.minimum(B,1)
    B = B.reshape(hsvt.shape[:2])
    void split(const Mat &src, Mat *mvbegin)
    将多通道数组分成多个单通道数组。
  3. 现在用圆盘进行卷积,\(B = D \ast 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)
    void normalize(InputArray src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
    归一化数组的范数或值范围。
    void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT)
    用核卷积图像。
    Mat getStructuringElement(int shape, Size ksize, Point anchor=Point(-1,-1))
    返回指定大小和形状的用于形态学运算的结构元素。
  4. 现在,最大强度的位置就给了我们对象的位置。如果我们期望图像中有一个区域,则对合适的值进行阈值处理会得到一个不错的结果。
    ret,thresh = cv.threshold(B,50,255,0)
    double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
    对每个数组元素应用固定级别的阈值。
    就是这样!!

OpenCV中的反投影

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

import numpy as np
import cv2 as cv
roi = cv.imread('rose_red.png')
assert roi is not None, "文件无法读取,请使用os.path.exists()检查"
hsv = cv.cvtColor(roi,cv.COLOR_BGR2HSV)
target = cv.imread('rose.png')
assert target is not None, "文件无法读取,请使用os.path.exists()检查"
hsvt = cv.cvtColor(target,cv.COLOR_BGR2HSV)
# 计算对象直方图
roihist = cv.calcHist([hsv],[0, 1], None, [180, 256], [0, 180, 0, 256] )
# 归一化直方图并应用反投影
cv.normalize(roihist,roihist,0,255,cv.NORM_MINMAX)
dst = cv.calcBackProject([hsvt],[0,1],roihist,[0,180,0,256],1)
# 现在用圆盘进行卷积
disc = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5))
cv.filter2D(dst,-1,disc,dst)
阈值和按位与
ret,thresh = cv.threshold(dst,50,255,0)
thresh = cv.merge((thresh,thresh,thresh))
res = cv.bitwise_and(target,thresh)
res = np.vstack((target,thresh,res))
cv.imwrite('res.jpg',res)
void bitwise_and(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray())
计算两个数组的按位与运算 (dst = src1 & src2) 计算每个元素的按位……
void merge(const Mat *mv, size_t count, OutputArray dst)
将多个单通道数组合并成一个多通道数组。
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
将图像保存到指定文件。
void calcBackProject(const Mat *images, int nimages, const int *channels, InputArray hist, OutputArray backProject, const float **ranges, double scale=1, bool uniform=true)
计算直方图的反向投影。

下面是一个我使用的例子。我使用蓝色矩形内的区域作为样本对象,并希望提取完整的背景。

图像

附加资源

  1. “基于颜色直方图的索引”,Swain, Michael J.,第三届国际计算机视觉会议,1990年。