TLD(Tracking-Learning-Detection)学习与源码理解之(跟踪器)
来源:互联网 发布:mac virtualbox usb 编辑:程序博客网 时间:2024/05/22 00:30
目标跟踪的一般思想是跟踪目标中关键点。TLD也是跟踪点(但不是跟踪SIFT之类的关键点)。点跟踪采用的是光流法,具体来说是Pyramidal Lucas-Kanade tracker,这个以后机会再介绍,推荐阅读《Learning OpenCV》第10章的Lucas-Kanade Method部分,这里只介绍OpenCV的实现函数,跳过原理和实现细节。
首先看跟踪点的函数,calcOpticalFlowPyrLK,它的作用是找到上一帧中的跟踪点在当前帧的位置。调用形式如下:
calcOpticalFlowPyrLK( img_last,img_curr, points_last , points_curr, status, errs)
参数意思应该很直白了吧,补充一下status为1,表示对应点找到了,为0就是没找到,errs自然是误差。注意:可以是单个点,也可以是点集,如果是点集,那么对应的status和errs就都是vector啦。
下面说说怎么跟踪目标,TLD采用的是基于作者自己提出的Median-Flow tracker,此外增加了跟踪失败检测。
通过Forward-Backward error来筛选要跟踪的点
前面提到TLD跟踪的不是关键点,它跟踪的是更简单的点:能稳定存在的点,那哪些点是稳定的呢?Median-Flow tracker的基本思想是,看反向跟踪后的残差,用所有点的残差中值作为稳定点的筛选条件。如上图中的黄色点就因为残差太大,被pass掉了,既然稳定点是可以筛选出来的,那么就不必煞费苦心的寻找那些关键点,可以直接将所有的点都作为初始跟踪点,好吧所,有的点毕竟还是太多了,于是作者是选取网格交叉点作为初始跟踪点(见下图框框中黄色的点点)。
Median Flow tracker 的流程图
下面正式介绍作者的跟踪函数TLD::track,调用形式如下:
track(img1,img2,points1,points2)
img1是上一帧的图像,img2是当前帧的图像,points1,points2都是这个函数的输出函数,points1是将上一次跟踪到的目标区域lastbox划分成网格后,所得到的网格交点,即上图左边的黄色点,而points2是points1中能稳定出现在当前帧出的点,即右图中的点。
下面结合上面的流程图,并补充TLD需要增加的环节,来介绍track。
TLD::track函数
- void TLD::track(const Mat& img1, const Mat& img2,vector<Point2f>& points1,vector<Point2f>& points2){
- //【5.4】
- // 1.Generate points
- bbPoints(points1,lastbox);
- if (points1.size()<1){//问题:何时会出现这种情况??
- printf("BB= %d %d %d %d, Points not generated\n",lastbox.x,lastbox.y,lastbox.width,lastbox.height);
- tvalid=false;
- tracked=false;
- return;
- }
- vector<Point2f> points = points1;
- //Frame-to-frame tracking with forward-backward error cheking
- // 2. 推断上一帧的points,在当前帧的位置,points->points2
- // 注意:只有通过筛选的point对 还保留在points,points2
- tracked = tracker.trackf2f(img1,img2,points,points2);
- if (tracked){//只要有一个点跟到了,就算跟到了……,是不是应该严格一点呢??
- // 3. Bounding box prediction
- bbPredict(points,points2,lastbox,tbb);//此时,lastbox,还是依据上一帧预测的目标在当前帧的位置
- // 4. Failure detection,检测 getFB()>10 || 完全出轨
- if (tracker.getFB()>10 || tbb.x>img2.cols || tbb.y>img2.rows || tbb.br().x < 1 || tbb.br().y <1){//br() bottom right坐标
- tvalid =false; //too unstable prediction or bounding box out of image
- tracked = false;
- printf("Too unstable predictions FB error=%f\n",tracker.getFB());
- return;
- }
- // 5. Estimate Confidence and Validity
- Mat pattern;
- Scalar mean, stdev;
- BoundingBox bb;
- bb.x = max(tbb.x,0);
- bb.y = max(tbb.y,0);
- bb.width = min(min(img2.cols-tbb.x,tbb.width),min(tbb.width,tbb.br().x));//问题:我觉得后面的min没必要呀??
- bb.height = min(min(img2.rows-tbb.y,tbb.height),min(tbb.height,tbb.br().y));
- getPattern(img2(bb),pattern,mean,stdev);
- vector<int> isin;
- float dummy;
- classifier.NNConf(pattern,isin,dummy,tconf); //1.tconf是用Conservative Similarity
- tvalid = lastvalid;
- if (tconf>classifier.thr_nn_valid){//thr_nn_valid
- tvalid =true;//2.判定轨迹是否有效,从而决定是否要增加正样本,标志位tvalid【5.6.2 P-Expert】
- }
- }
- else
- printf("No points tracked\n");
- }
1.Initialize points to grid
将bb切成10*10的网格,将网格交点存在points,函数为TLD::bbPoints。
- //将bb切成10*10的网格,将网格交点存在points
- void TLD::bbPoints(vector<cv::Point2f>& points,const BoundingBox& bb){
- int max_pts=10;
- int margin_h=0;//留白没有用到
- int margin_v=0;
- int stepx = ceil((bb.width-2*margin_h)/max_pts);//向上取整
- int stepy = ceil((bb.height-2*margin_v)/max_pts);
- for (int y=bb.y+margin_v;y<bb.y+bb.height-margin_v;y+=stepy){
- for (int x=bb.x+margin_h;x<bb.x+bb.width-margin_h;x+=stepx){
- points.push_back(Point2f(x,y));//最多有11*11=121个点
- }
- }
- }
2.Track points
3.Estimate tracking error
4.Filter out outliers
这三步都在函数trackf2f 中,调用层次关系tld.processFrame->track->[tracked = tracker.trackf2f(img1,img2,points,points2)]
- //points1->points2,由于调用了filterPts,所以只有通过筛选的point对还保留在points1,points2
- bool LKTracker::trackf2f(const Mat& img1, const Mat& img2,vector<Point2f> &points1, vector<cv::Point2f> &points2){
- //TODO!:implement c function cvCalcOpticalFlowPyrLK() or Faster tracking function
- //1. Track points,Forward-Backward tracking
- calcOpticalFlowPyrLK( img1,img2, points1, points2, status,similarity, window_size, level, term_criteria, lambda, 0);
- calcOpticalFlowPyrLK( img2,img1, points2, pointsFB, FB_status,FB_error, window_size, level, term_criteria, lambda, 0);
- //2. Estimate tracking error,Compute the real FB-error
- for( int i= 0; i<points1.size(); ++i ){
- FB_error[i] = norm(pointsFB[i]-points1[i]);//残差为欧氏距离【ICPR 2】
- }
- //3.Filter out outliers
- //Filter out points with FB_error[i] > median(FB_error) && points with sim_error[i] > median(sim_error)
- normCrossCorrelation(img1,img2,points1,points2);
- return filterPts(points1,points2);
- }
其中normCrossCorrelation(img1,img2,points1,points2)是对光流法跟踪的结果不放心,因此希望通过对比前后两点周围的小块的相似性,来进一步去掉不稳定的点。这次的相似性不是相关系数,而是normalized cross-correlation (NCC):
这个比较复杂,建议看wiki的公式,其实还是前面提到的相关系数,只不过计算的时候需要自己减去均值。
- void LKTracker::normCrossCorrelation(const Mat& img1,const Mat& img2, vector<Point2f>& points1, vector<Point2f>& points2) {
- Mat rec0(10,10,CV_8U);
- Mat rec1(10,10,CV_8U);
- Mat res(1,1,CV_32F);
- for (int i = 0; i < points1.size(); i++) {
- if (status[i] == 1) {//跟踪到了
- getRectSubPix( img1, Size(10,10), points1[i],rec0 );//以points1[i]为中心,提取10*10的小块
- getRectSubPix( img2, Size(10,10), points2[i],rec1);
- matchTemplate( rec0,rec1, res, CV_TM_CCOEFF_NORMED);//Cross Correlation
- similarity[i] = ((float *)(res.data))[0];
- } else {
- similarity[i] = 0.0;
- }
- }
- rec0.release();
- rec1.release();
- res.release();
- }
该计算的都计算好了,终于可以筛选了,filterPts(points1,points2)
- //Filter out points with FB_error[i] > median(FB_error) && points with sim_error[i] > median(sim_error)
- bool LKTracker::filterPts(vector<Point2f>& points1,vector<Point2f>& points2){
- //Get Error Medians
- simmed = median(similarity);//NCC中值
- size_t i, k;
- for( i=k = 0; i<points2.size(); ++i ){//前向筛选,没跟踪到的不要
- if( !status[i])
- continue;
- if(similarity[i]> simmed){//normalized crosscorrelation (NCC)筛选,比对前后两点周围的小块
- points1[k] = points1[i];
- points2[k] = points2[i];
- FB_error[k] = FB_error[i];
- k++;
- }
- }
- if (k==0)
- return false;
- points1.resize(k);
- points2.resize(k);
- FB_error.resize(k);
- fbmed = median(FB_error);//残差中值
- for( i=k = 0; i<points2.size(); ++i ){//后向筛选,找到了,但是偏离太多
- if( !status[i])
- continue;
- if(FB_error[i] <= fbmed){
- points1[k] = points1[i];
- points2[k] = points2[i];
- k++;
- }
- }
- points1.resize(k);
- points2.resize(k);
- if (k>0)
- return true;
- else
- return false;
- }
5.Update bounding box
bbPredict(points,points2,lastbox,tbb), points和points2是前面筛选完之后的点对,现在要依据points,points2来估计bb1的位移和尺度变化,这两个信息都有了,自然可以决定lastbox在当前帧的位置tbb。
位移估计
位移估计的方法是用所有点对x,y位移的中值作为位移的估计,如上图。尺度的估计的方法是用所有点对(同一帧)的伸缩比的中值作为尺度伸缩的估计,假设只有一堆点,尺度伸缩值的估计方式如下图:
尺度估计
- //依据points1,points2估计bb1的位移和尺度变化,这两个信息都有了,自然可以决定其范围bb2
- void TLD::bbPredict(const vector<cv::Point2f>& points1,const vector<cv::Point2f>& points2,
- const BoundingBox& bb1,BoundingBox& bb2) {
- int npoints = (int)points1.size();
- vector<float> xoff(npoints);
- vector<float> yoff(npoints);
- printf("tracked points : %d\n",npoints);
- // 用位移的中值,作为目标位移的估计
- for (int i=0;i<npoints;i++){
- xoff[i]=points2[i].x-points1[i].x;
- yoff[i]=points2[i].y-points1[i].y;
- }
- float dx = median(xoff);//
- float dy = median(yoff);
- float s;
- // 用点对之间的距离的伸缩比例的中值,作为目标尺度变化的估计
- if (npoints>1){
- vector<float> d;
- d.reserve(npoints*(npoints-1)/2);
- for (int i=0;i<npoints;i++){
- for (int j=i+1;j<npoints;j++){
- d.push_back(norm(points2[i]-points2[j])/norm(points1[i]-points1[j]));
- }
- }
- s = median(d);//
- }
- else {
- s = 1.0;
- }
- float s1 = 0.5*(s-1)*bb1.width;// top-left 坐标的偏移(s1,s2)
- float s2 = 0.5*(s-1)*bb1.height;
- printf("s= %f s1= %f s2= %f \n",s,s1,s2);
- bb2.x = round( bb1.x + dx -s1);
- bb2.y = round( bb1.y + dy -s2);
- bb2.width = round(bb1.width*s);
- bb2.height = round(bb1.height*s);
- printf("predicted bb: %d %d %d %d\n",bb2.x,bb2.y,bb2.br().x,bb2.br().y);
- }
6.Failure detection
这一步很简单,原文是说A failure of the tracker is declared if pixels,其中是残差的中值,残差即反向跟踪和原始跟踪点的距离。不过程序里面还要防止目标飞到图像外面去了。
- if (tracker.getFB()>10 || tbb.x>img2.cols || tbb.y>img2.rows || tbb.br().x < 1 || tbb.br().y <1){//br() bottom right坐标
- tvalid =false; //too unstable prediction or bounding box out of image
- tracked = false;
- printf("Too unstable predictions FB error=%f\n",tracker.getFB());
- return;
- }
7.Estimate Confidence and Validity
- Mat pattern;
- Scalar mean, stdev;
- BoundingBox bb;
- bb.x = max(tbb.x,0);
- bb.y = max(tbb.y,0);
- bb.width = min(min(img2.cols-tbb.x,tbb.width),min(tbb.width,tbb.br().x));// bb.height = min(min(img2.rows-tbb.y,tbb.height),min(tbb.height,tbb.br().y));
- getPattern(img2(bb),pattern,mean,stdev);
- vector<int> isin;
- float dummy;
- classifier.NNConf(pattern,isin,dummy,tconf); //1.tconf是用Conservative Similarity
- tvalid = lastvalid;
- if (tconf>classifier.thr_nn_valid){//thr_nn_valid
- tvalid =true;//2.判定轨迹是否有效,从而决定是否要增加正样本,标志位tvalid【5.6.2 P-Expert】
- }
注释很清楚了,大家可以先忽略判定轨迹是否有效这一部分,只要知道它是用最近邻分类器的Conservative Similarity【5.2】作为跟踪目标的得分即可,后面要用这个分数和检测器进行比较。
- TLD(Tracking-Learning-Detection)学习与源码理解之(跟踪器)
- TLD(Tracking-Learning-Detection)学习与源码理解之
- TLD(Tracking-Learning-Detection)学习与源码理解之
- TLD(Tracking-Learning-Detection)学习与源码理解之(学习器)
- 【Android】TLD(Tracking-Learning-Detection)学习与源码理解
- TLD参考--TLD(Tracking-Learning-Detection)学习与源码理解之(一)
- TLD(Tracking-Learning-Detection)学习与源码理解之(分类器)
- TLD(Tracking-Learning-Detection)学习与源码理解之(三) (转自zouxy09)
- TLD(Tracking-Learning-Detection)学习与源码理解之(一)
- TLD(Tracking-Learning-Detection)学习与源码理解之(二)
- TLD(Tracking-Learning-Detection)学习与源码理解之(三)
- TLD(Tracking-Learning-Detection)学习与源码理解之(四)
- TLD(Tracking-Learning-Detection)学习与源码理解之(五)
- TLD(Tracking-Learning-Detection)学习与源码理解之(六)
- TLD(Tracking-Learning-Detection)学习与源码理解之(七)
- TLD(Tracking-Learning-Detection)学习与源码理解之(一)
- TLD(Tracking-Learning-Detection)学习与源码理解之(二)
- TLD(Tracking-Learning-Detection)学习与源码理解之(三)
- SWT_JFace_RCP学习相关网页
- 脏读、不可重复读 共享锁、悲观锁 和 事务五种隔离级别
- BZOJ 2708 [Violet 1]木偶 DP
- TLD(Tracking-Learning-Detection)学习与源码理解之(main)
- 在目标串中寻找模版串出现的次数w在t中出现的次数
- TLD(Tracking-Learning-Detection)学习与源码理解之(跟踪器)
- cocos2d-x源码中的一个BUG
- 树莓派做 wifi 热点
- MySQL数据库事务隔离级别(Transaction Isolation Level)
- spring官网翻译
- TLD(Tracking-Learning-Detection)学习与源码理解之(综合模块)
- HDU - 5407(规律)
- codeforces 571B--Minimization(贪心+dp)
- 枚举