OpenCV 学习笔记(直方图反向投影 BackProject)

OpenCV 学习笔记(直方图反向投影 BackProject)

上个笔记简单讲了讲如何计算直方图。这个笔记就来讲讲直方图的一种简单应用。直方图反向投影,是一种从图像中提取某个特定区域的方法。直方图可以理解为图像中各种颜色的分布情况,如果归一化了,就可以认为是一种概率分布。

如果我们知道某个物体或者某个特征的直方图,也就是知道了这个物体各种颜色的分布概率。根据这个概率,我们可以根据图像中每一个点的颜色,都给一个 对应的概率。概率高的地方就更可能是存在这种物体的地方。概率为 0 的地方可以认为不存在这种物体。

在 OpenCV 中有个专门的函数用来做直方图反向投影:

CV_EXPORTS void calcBackProject( const Mat* images, int nimages,
                                 const int* channels, InputArray hist,
                                 OutputArray backProject, const float** ranges,
                                 double scale = 1, bool uniform = true );

/** @overload */
CV_EXPORTS void calcBackProject( const Mat* images, int nimages,
                                 const int* channels, const SparseMat& hist,
                                 OutputArray backProject, const float** ranges,
                                 double scale = 1, bool uniform = true );

/** @overload */
CV_EXPORTS_W void calcBackProject( InputArrayOfArrays images, const std::vector<int>& channels,
                                   InputArray hist, OutputArray dst,
                                   const std::vector<float>& ranges,
                                   double scale );

calcBackProject 有这三种函数原型,我们常用到前两种。这三个函数都不太好用,我们还是可以封装一下:

class ContentFinder
{
  public:
    ContentFinder();

    // Sets the threshold on histogram values [0,1]
    void setThreshold(float t);

    // Gets the threshold
    float getThreshold();

    // Sets the reference histogram
    void setHistogram(const cv::Mat& h);

    // Sets the reference histogram
    void setHistogram(const cv::SparseMat& h);

    // Simplified version in which
    // all channels used, with range [0,256[
    cv::Mat find(const cv::Mat& image);

    // Finds the pixels belonging to the histogram
    cv::Mat find(const cv::Mat& image, float minValue, float maxValue, int *channels);
private:

  // histogram parameters
  float hranges[2];
  const float * ranges[3];
  int channels[3];

  float threshold;           // decision threshold
  cv::Mat histogram;         // histogram can be sparse
  cv::SparseMat shistogram;  // or not
  bool isSparse;
};

ContentFinder::ContentFinder() : threshold(0.1f), isSparse(false)
{
    // in this class,
    // all channels have the same range
    ranges[0]= hranges;
    ranges[1]= hranges;
    ranges[2]= hranges;
}

void ContentFinder::setThreshold(float t)
{
    threshold = t;
}

// Gets the threshold
float ContentFinder::getThreshold()
{
    return threshold;
}

// Sets the reference histogram
void ContentFinder::setHistogram(const cv::Mat& h)
{
    isSparse = false;
    cv::normalize(h,histogram,1.0);
}

void ContentFinder::setHistogram(const cv::SparseMat& h)
{
    isSparse = true;
    cv::normalize(h, shistogram, 1.0, cv::NORM_L2);
}

// Simplified version in which
// all channels used, with range [0,256[
cv::Mat ContentFinder::find(const cv::Mat& image)
{
    cv::Mat result;

    hranges[0] = 0.0;	// default range [0,256[
    hranges[1] = 256.0;
    channels[0] = 0;		// the three channels
    channels[1] = 1;
    channels[2] = 2;

    return find(image, hranges[0], hranges[1], channels);
}

cv::Mat ContentFinder::find(const cv::Mat& image, float minValue, float maxValue, int *channels)
{
    cv::Mat result;
    hranges[0] = minValue;
    hranges[1] = maxValue;
    if (isSparse)
    { // call the right function based on histogram type

       for (int i = 0; i < shistogram.dims(); i++)
       {
          this->channels[i] = channels[i];
       }

       cv::calcBackProject(&image,
                  1,            // we only use one image at a time
                  channels,     // vector specifying what histogram dimensions belong to what image channels
                  shistogram,   // the histogram we are using
                  result,       // the resulting back projection image
                  ranges,       // the range of values, for each dimension
                  255.0         // the scaling factor is chosen such that a histogram value of 1 maps to 255
       );

    }
    else
    {
       for (int i = 0; i < histogram.dims; i++)
       {
          this->channels[i] = channels[i];
       }

       cv::calcBackProject(&image,
                  1,            // we only use one image at a time
                  channels,     // vector specifying what histogram dimensions belong to what image channels
                  histogram,    // the histogram we are using
                  result,       // the resulting back projection image
                  ranges,       // the range of values, for each dimension
                  255.0         // the scaling factor is chosen such that a histogram value of 1 maps to 255
       );
    }

    // Threshold back projection to obtain a binary image
    if (threshold > 0.0)
    {
        cv::threshold(result, result, 255.0 * threshold, 255.0, cv::THRESH_BINARY);
    }
    return result;
}

下面是个例子:

    cv::Mat image = cv::imread("D:\\向日葵.jpg");
    //cv::cvtColor(image, image, cv::COLOR_BGR2GRAY);
    cv::Mat imageROI = image(cv::Rect(130, 250, 75, 75));
    ColorHistogram hist;
    hist.setSize(16);
    cv::Mat h = hist.getHistogram(imageROI);
    cv::normalize(h, h, 1);
    cv::imshow("pic", image);
    ContentFinder finder;
    finder.setHistogram(h);
    finder.setThreshold(0.01);
    cv::Mat mask = finder.find(image);

    cv::Mat img ;
    image.copyTo(img, mask);

    cv::imshow("img", img);

下面是运行结果,可以看到将原图中几个向日葵所在区域都提取出来了。提取的效果还不错。当然提取之后应该用个形态学的闭运算,把里面的小空洞给填上,结果会更好。这里就不演示了。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/liyuanbhu/article/details/119875235