一、反投影响直方图查找物体
直方图反向投影可以用来做图像分割,或者在图像中找寻我们感兴趣的部分。简单来说,它会输出与输入图像同样大小的图像,其中的每一个像素值代表了输入图像上对应点属于我们感兴趣的目标图像的概率。输出图像中像素值越高(越白)的点就越可能代表我们要搜索的目标。
比如下面的图片,我们希望查找其中的车牌号部分(注:本文中所有车牌图片均来自百度):
其中,我们感兴趣的部分是:
OpenCV提供了calcBackProject函数用于直方图反向投影,它的参数与计算直方图的函数calcHist基本相同。
void calcBackProject(const Mat* arrays, int narrays, const int* channels, InputArray hist, OutputArray backProject, const float** ranges, double scale=1, bool uniform=true )
计算直方图的反向投影,一般有以下几个步骤:
1、首先要定义你要查找的区域,即感兴趣区域
2、提取感兴趣区域的直方图
3、归一化感兴趣区域直方图
4、使用calcBackProject计算反向投影
注:最好在计算之前先对输入图像进行降维操作,那样得出的结果会更好。
#include
#include
#include
using namespace cv;
void colorReduce(Mat &image,int div);
int main()
{
Mat image = imread("1.jpg",CV_LOAD_IMAGE_UNCHANGED);
namedWindow("im1");
imshow("im1",image);
colorReduce(image,32);
Mat imageROI = imread("roi.jpg",CV_LOAD_IMAGE_UNCHANGED);
namedWindow("im2");
imshow("im2",imageROI);
const float hranges[2] = {0.0,255.0};
const float *ranges[3] = {hranges,hranges,hranges};
const int channels[3] = {0,1,2};
const int histSize[3]={256,256,256};
MatND hist;
calcHist(&imageROI,1,channels,Mat(),hist,3,histSize,ranges);
normalize(hist,hist,1.0);
Mat result;
calcBackProject(&image,1,channels,hist,result,ranges,255.0);
threshold(result,result,12.75,255,THRESH_BINARY);
namedWindow("im3");
imshow("im3",result);
waitKey(0);
}
void colorReduce(Mat &image,int div)
{
int nl = image.rows;
int nc = image.cols*image.channels();
for(int j=0;j(j);
for(int i=0;i2;
}
}
}
结果:
如果用上面同样的感兴趣区域去检测其它的车牌会是什么效果呢?
二、使用均值漂移(Mean Shift)算法查找物体
MeanShift算法的原理很简单。假设我们有一堆点(比如直方图反向投影得到的点)和一个圆形窗口,算法的任务就是将这个窗口移动到最大灰度密度处(也就是点最多的地方),如下 图所示:
初始窗口是蓝色的C1,它的圆心是蓝色方框C1_o,而窗口中所有点的质心却是C1_r,很明显圆心和质心不重合,因此移动圆心到质心位置处,这样就得到了一个新的窗口,此时又可以找到新窗口中所有点的质心,在大多数情况下,新圆心和质心还是不重合的,所以重复上述操作,不断的将新窗口的中心移动到新的质心,直到窗口的中心和质心重合为止(或者误差达到能容忍的地步),最终窗口会落在像素值最大的地方。OpenCV的文档中定义了两种迭代的终止条件:迭代的最大次数和偏移值(窗口中心和质心间的偏差低于该值时认为收敛)
使用MeanShift主要以下几个步骤
1、一张已经识别出感兴趣区域的图像
2、将感兴趣区域转换到HSV颜色空间并计算其色调直方图
3、将新输入图像转换到HSV颜色空间并计算其反向投影
4、使用OpenCV提供的meanShift查找出新的感兴趣区域位置
示例
#pragma comment(lib, "opencv_video220d.lib")
#include
#include
#include
using namespace cv;
const float hranges[2] = {0.0,180.0};
const int channels[1] = {0};
const int histSize[1] = {256};
const float* ranges[1] = {hranges};
int main()
{
Mat image = imread("baboon1.jpg",CV_LOAD_IMAGE_UNCHANGED);
namedWindow("im1");
imshow("im1",image);
Mat imageROI = image(Rect(110,260,35,40));
Mat hsv;
cvtColor(imageROI,hsv,CV_BGR2HSV);
Mat mask;
vector v;
split(hsv,v);
MatND hist;
calcHist(&hsv,1,channels,mask,hist,1,histSize,ranges);
Mat inputImg = imread("baboon3.jpg");
cvtColor(inputImg,hsv,CV_BGR2HSV);
v.clear();
split(hsv,v);
threshold(v[1],v[1],65,255,THRESH_BINARY);
Mat result;
normalize(hist,hist,1.0);
calcBackProject(&hsv,1,channels,hist,result,ranges,255.0);
threshold(result,result,0.2*255,255,THRESH_BINARY);
bitwise_and(result,v[1],result);
Rect rect(110,260,35,40);
rectangle(inputImg,rect,Scalar(0,0,255));
TermCriteria criteria(TermCriteria::MAX_ITER,10,0.01);
cv::meanShift(result,rect,criteria);
cv::rectangle(inputImg, rect, cv::Scalar(0,255,0));
namedWindow("im5");
imshow("im5",inputImg);
waitKey(0);
}
将图像转换到HSV空间后,色调分量位于图像的第一个通道,值的范围是0-180,为了提取色调图像,使用split函数将HSV图像侵害为三个单通道图像。当考虑色调分量时,饱和度分量也是很重要的。因为如果饱和度偏低,通常色调信息也会不稳定或者不可靠,这是因为在低饱和度的颜色中,红绿蓝三个分量的差别不大,这会导致难以精确的确定颜色,因此在MeanShift算法忽略低饱和度的色调分量(使用bitwise_and)。