OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
直方图 - 4:直方图反向投影

目标

在本章中,我们将学习关于直方图反向投影。

理论

它是由Michael J. Swain , Dana H. Ballard在他们的论文通过颜色直方图索引中提出的。

用简单的词语来说,它实际上是什么? 它用于图像分割或在图像中查找感兴趣的对象。简单来说,它创建了一个与输入图像大小相同(但单通道)的图像,其中每个像素对应于该像素属于我们对象的概率。更简单地说,输出图像中我们感兴趣的对象将比其余部分更白。嗯,这是一个直观的解释。(我不能让它更简单了)。直方图反向投影与 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 是色调,s 是 (x,y) 处像素的饱和度。之后应用条件 \(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)
# 阈值处理和二进制 AND
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)
计算直方图的反向投影。

下面是我处理过的一个例子。我使用了蓝色矩形内的区域作为样本对象,我想提取整个地面。

image

补充资源

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