0.前言
在看《opencv2计算机视觉编程手册》的第四章时,看到了书中利用opencv提供的meanshift算法实现指定区域的跟踪,感觉很神奇,就相对深入的了解了下。不过这里没有直接上来讲meanshift,而是opencv的calcBackProject()函数。为啥呢,因为书中的例程首先利用它计算反投影矩阵用作meanshift算法的输入。
1.反投影直方图及原理
直方图是图像的一个重要内容,可以作为图像的一个描述特征(当该图像有明显的纹理时效果更佳)。这里我们有图像B和图像A且图像B中包含图像A或类似于图像A的区域,那么如何确定它在B中的确切位置?一种做法就是,计算图像A的直方图,且假定它能够有效地代表A(不是适用于所有情况),之后遍历图像B,使用B中每一点像素对应A的直方图中的统计值来替换原像素的值。如此一来,图像B就变成 了相对于A中各个成分的一个分布图,每一点的值越高,就代表他它属于图像A的可能性越大。
2.反投影的代码实现
知道原理后,就可以用代码实现它了,自己写了一个demo来简单的验证,代码如下。
#include <iostream>
#include <vector>
#include <core/core.hpp>
#include <imgproc/imgproc.hpp>
#include <highgui/highgui.hpp>
#include <opencv2/nonfree/features2d.hpp>
#include <features2d/features2d.hpp>
#include <legacy/legacy.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("beach.jpg", 0);
Mat image_show;
image.copyTo(image_show);
rectangle(image_show, Rect(360, 55, 40, 50), Scalar(0));
imshow("image", image_show);
// 选取感兴趣区域
Mat ROI = image(Rect(360, 55, 40, 50));
int histSize[1];
float hranges[2];
const float* ranges[1];
int channels[1];
histSize[0] = 256;
hranges[0] = 0.f;
hranges[1] = 255.f;
ranges[0] = hranges;
channels[0] = 0;
// 计算感兴趣区域的灰度直方图
MatND hist;
calcHist(&ROI, 1, channels, Mat(), hist, 1, histSize, ranges);
normalize(hist, hist, 1.0);
/*Mat result;
calcBackProject(&image, 1, channels, hist, result, ranges, 255.0);
imshow("result", result);*/
double min, max;
minMaxLoc(hist, &min, &max);
double scale = 255.f/(max - min);
Mat result_test(image.size(), CV_8UC1);
for(int i = 0; i < result_test.rows; i++)
{
for(int j = 0; j < result_test.cols; j++)
{
int gray = image.at<unsigned char>(i,j);
// 使用直方图中的统计值相对大小代替原图的灰度值
result_test.at<unsigned char>(i,j) = (int)((hist.at<float>(gray) - min)*scale);
}
}
imshow("result_test", result_test);
waitKey();
return 0;
}
运行效果如下所示,图1中黑色边框是我们的感兴趣区域,图2则是代表感兴趣区域可能出现的可能性表现(颜色越深,可能性越小)。结果我们看到有很多高可能性的区域,部分原因就是因为黑色边框选择的区域在原图中不具有什么代表性。
图1
图2
3.calcBackProject()函数
当然上一部分中的反投影计算部分局限性很大,毕竟图像的类型、深度、通道数有很多。但是opencv毕竟是大牛们的作品,opencv提供的calcBackProject()函数能够实现多种类型图像的反投影计算,原理和第二部分大致相同。具体使用我就不提了,书上和网上有很多例程,贴一张用calcBackProject()计算得到的反投影效果,见图3。可以看到和图2相比,区别就在于灰度的分布范围,这个是可以人为控制的。
图3
4.提升反投影效果
图2或者图3中的效果并不理想,我们拿到后很难确定感兴趣区域的具体位置。部分原因是我们把彩色图转为了灰度图进行的后续计算,丢失了一些色彩信息。针对这个,书中提了一种改进方法,针对彩色图,转换到HSV空间,在计算直方图和反投影时,忽略低饱和度(S)的像素。书中提供了检测猴子脸部的例子,自己比较了下该方法和原始方法的效果差异,是有比较明显的提升。图4中寻找左边猴子脸部在第二张图片的位置,"result"和"result_hsv"分别是使用基本方法(原图转为灰度图后直接计算反投影)和使用书中例程计算得到的结果,对比可以发现后者效果明显好很多,后续使用它作为输入进行位置的精确计算时难度会下降很多。
5.其他
至此,我们只得到了猴子脸部的一张“分布概率”图,并不知道它的确切位置。那么如何得到最后的位置呢?当然我可以遍历这样图得到最大值的点,然后把该点作为结果。方法简单粗暴,但是复杂度并不好。然后meanshift算法就出现了,它就可以实现结果的快速迭代优化而不是傻兮兮的遍历。终于讲到这了。。不过有关这方面的内容,我要放到下一篇文章了点击打开链接