【笔记】使用Opencv中均值漂移meanShift跟踪移动目标

Mean Shift均值漂移算法是无参密度估计理论的一种,无参密度估计不需要事先知道对象的任何先验知识,完全依靠训练数据进行估计,并且可以用于任意形状的密度估计,在某一连续点处的密度函数值可由该点邻域中的若干样本点估计得出。
Mean shift将特征空间视为先验概率密度函数,那么输入就被视为是一组满足某种概率分布的样本点,这样一来,特征空间中数据最密集的地方,对应于概率密度最大的地方,且概率密度的质心就可以被视为是概率密度函数的局部最优值,也就是要求的聚类中心。对于每一个样本点,计算以它为中心的某个范围内所有样本点的均值,作为新的中心(这就是shift既中心的移动),移动直至收敛。这样每一轮迭代,中心都会向数据更密集的地方移动,直到最后稳定收敛到样本的“质心”。

可以直观理解为:在样本空间中,任选一个点,然后以这个点为圆心,划定一个圆形的区域。在此区域内的所有点以圆心为起点,产生N个向量,然后把这些向量都相加,再以向量的终点为圆心,划定同样半径的圆形区域,执行同样操作,如此迭代,直到收敛。


均值漂移的“漂移”过程如下:

先把圆心移动到当前圆区域内的“质心”上:

 

再以质心为圆心的圆区域内寻找当前圆的质心,并移动到新的“质心”上:

 

如此迭代移动,直到最后满足了迭代条件,质心稳定在质量最密处:

 

Opencv中均值漂移算法由meanShift函数实现:

 

int meanShift( InputArray probImage, CV_OUT CV_IN_OUT Rect& window,
                            TermCriteria criteria );

第一个参数probImage,是直方图的反向投影矩阵;

第二个参数window,初始化的搜索窗口,同时也是输出的目标窗口;

第三个参数criteria,终止迭代条件,可以设置为满足一定迭代次数后终止,也可以设置为目标和初始搜索位置之间差落在某一区间终止,也可以设置为两者的组合;

meanShift函数返回int型变量,代表算法迭代的次数。


以下程序实现了在一个视频中跟踪移动目标,大致步骤如下:

    1. 在视频播放过程中,通过鼠标框选需要跟踪的目标target

    2. 计算目标图像target的HSV中H、S分量的直方图targetHist

    3. 用targetHist反向投影计算原图像中的目标的概率分布

    4. 用meanShift通过迭代获取目标的新的位置window

    5. 以新的位置window执行步骤2
 

#include "core/core.hpp"    
#include "highgui/highgui.hpp"    
#include "imgproc/imgproc.hpp"
#include "video/tracking.hpp"
#include<iostream>    
 
using namespace cv;    
using namespace std;    
 
Mat image;  
Mat rectImage;
Mat imageCopy; //绘制矩形框时用来拷贝原图的图像  
bool leftButtonDownFlag=false; //左键单击后视频暂停播放的标志位  
Point originalPoint; //矩形框起点  
Point processPoint; //矩形框终点  
 
Mat targetImageHSV;
int histSize=200;  
float histR[]={0,255};  
const float *histRange=histR;  
int channels[]={0,1}; 
Mat dstHist;
Rect rect;
vector<Point> pt; //保存目标轨迹
void onMouse(int event,int x,int y,int flags ,void* ustc); //鼠标回调函数  
 
int main(int argc,char*argv[])    
{    
	VideoCapture video(argv[1]);  
	double fps=video.get(CV_CAP_PROP_FPS); //获取视频帧率  
	double pauseTime=1000/fps; //两幅画面中间间隔  
	namedWindow("跟踪木头人",0);    
	setMouseCallback("跟踪木头人",onMouse);  
	while(true)  
	{  
		if(!leftButtonDownFlag) //判定鼠标左键没有按下,采取播放视频,否则暂停  
		{  
			video>>image;  
		}  
		if(!image.data||waitKey(pauseTime)==27)  //图像为空或Esc键按下退出播放  
		{  
			break;  
		} 
		if(originalPoint!=processPoint&&!leftButtonDownFlag)  
		{ 
			Mat imageHSV;
			Mat calcBackImage;
			cvtColor(image,imageHSV,CV_RGB2HSV);
			calcBackProject(&imageHSV,2,channels,dstHist,calcBackImage,&histRange);  //反向投影
			TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.001);  
			meanShift(calcBackImage, rect, criteria);   
			Mat imageROI=imageHSV(rect);   //更新模板			
			targetImageHSV=imageHSV(rect);
			calcHist(&imageROI, 2, channels, Mat(), dstHist, 1, &histSize, &histRange);  
			normalize(dstHist, dstHist, 0.0, 1.0, NORM_MINMAX);   //归一化
			rectangle(image, rect, Scalar(255, 0, 0),3);  	//目标绘制	
			pt.push_back(Point(rect.x+rect.width/2,rect.y+rect.height/2));
			for(int i=0;i<pt.size()-1;i++)
			{
				line(image,pt[i],pt[i+1],Scalar(0,255,0),2.5);
			}
		}  
		imshow("跟踪木头人",image); 
		waitKey(100);
	}
	return 0;  
}    
 
//*******************************************************************//    
//鼠标回调函数    
void onMouse(int event,int x,int y,int flags,void *ustc)    
{   
	if(event==CV_EVENT_LBUTTONDOWN)    
	{    
		leftButtonDownFlag=true; //标志位  
		originalPoint=Point(x,y);  //设置左键按下点的矩形起点  
		processPoint=originalPoint;  
	}    
	if(event==CV_EVENT_MOUSEMOVE&&leftButtonDownFlag)    
	{    
		imageCopy=image.clone();  
		processPoint=Point(x,y);  
		if(originalPoint!=processPoint)  
		{  
			//在复制的图像上绘制矩形  
			rectangle(imageCopy,originalPoint,processPoint,Scalar(255,0,0),2);  
		}  
		imshow("跟踪木头人",imageCopy);  
	}    
	if(event==CV_EVENT_LBUTTONUP)    
	{    
		leftButtonDownFlag=false;  
		rect=Rect(originalPoint,processPoint);		
		rectImage=image(rect); //子图像显示  
		imshow("Sub Image",rectImage); 		
		cvtColor(rectImage,targetImageHSV,CV_RGB2HSV);
		imshow("targetImageHSV",targetImageHSV);
		calcHist(&targetImageHSV,2,channels,Mat(),dstHist,1,&histSize,&histRange,true,false);  		
		normalize(dstHist,dstHist,0,255,CV_MINMAX);
		imshow("dstHist",dstHist);
	}      
}   

跟踪结果1:

2:

 

3:

 

 4:

蓝色方框是跟踪的目标,绿色线条是目标中心走过的轨迹。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义Meanshift跟踪算法的实现步骤如下: 1. 获取视频帧,并选择要跟踪目标区域。 2. 将目标区域转换为HSV色彩空间,并计算出该区域的直方图。 3. 对于每个后续帧,首先将其转换为HSV色彩空间,然后使用反向投影算法将其与目标直方图进行比较。 4. 对于每个像素,计算该像素的概率,即它属于目标区域的概率。 5. 使用MeanShift算法来计算下一个目标位置。在此算法,计算出目标区域的质心,并将其用作新的目标位置。重复该过程,直到质心不再移动。 6. 将新的目标位置用矩形框标记在视频帧上,并将其显示出来。 下面是C++代码实现: ```c++ #include <iostream> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; int main(int argc, char** argv) { VideoCapture cap(0); if (!cap.isOpened()) { cout << "Error opening video stream or file" << endl; return -1; } // 选择目标区域 Rect trackWindow(0, 0, 0, 0); bool init = false; Mat frame, hsv, mask, hist, backproj; // 设置终止条件 TermCriteria termcrit(TermCriteria::EPS | TermCriteria::COUNT, 10, 1); // 设置HSV颜色范围 int hmin = 0, smin = 0, vmin = 0; int hmax = 180, smax = 255, vmax = 255; while (true) { cap >> frame; if (frame.empty()) break; // 将帧转换为HSV颜色空间 cvtColor(frame, hsv, COLOR_BGR2HSV); // 如果已经选择了初始目标区域,则执行跟踪 if (init) { // 计算反向投影 calcBackProject(&hsv, 1, 0, hist, backproj, &ranges); // 应用CAMShift算法来计算新的目标位置 meanShift(backproj, trackWindow, termcrit); // 绘制矩形框 rectangle(frame, trackWindow, Scalar(0, 0, 255), 3); } // 显示图像 imshow("Frame", frame); // 按下空格键来选择目标区域 if (waitKey(1) == ' ') { init = false; trackWindow = selectROI("Frame", frame, false, false); if (trackWindow.area() > 0) { Mat roi(hsv, trackWindow); calcHist(&roi, 1, 0, mask, hist, 1, &histSize, &ranges); normalize(hist, hist, 0, 255, NORM_MINMAX); init = true; } } } cap.release(); destroyAllWindows(); return 0; } ``` 需要注意的是,这个算法的效果可能不如OpenCVMeanShift算法,因为OpenCV的算法使用了更复杂的技术来提高跟踪的准确性。但是,通过自定义算法,可以更好地理解MeanShift算法的原理和实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值