opencv动态目标跟踪学习总结

2023-05-16

用opencv实现对视频中动态目标的追踪


第一步,是要建立一个编程环境,然后加载opencv的库路径等等。具体步骤在 http://www.opencv.org.cn/ 的“安装”中
有详细介绍。


第二步,建立一个MFC的对话框程序,做两个按钮,一个“打开视频文件”,一个“运动跟踪处理”。
具体操作:
1 建立MFC对话框程序的框架:File  ->New  -> MFC AppWizard(exe),选取工程路径,并取工程
名“VideoProcesssing”-> Next -> 选择Dialog based后,点Finish,点OK.
2 添加按钮:直接Delete掉界面默认的两个“确定”“取消”按钮。然后添加两个button,分别名为“打开视频”,“运动
跟踪处理”,其ID分别设为IDC_OPEN_VIDEO,IDC_TRACKING.
3 添加消息响应函数:双击按钮“打开视频”,自动生成响应函数名OnOpenVideo,点Ok。然后添加如下代码:

 CFileDialog dlg(true,"*.avi",NULL,NULL,"*.avi|*.avi||");
 if (dlg.DoModal()==IDOK)
 {
  strAviFilePath = dlg.GetPathName();
 }else
 {
  return;
 }

同样,双击“运动跟踪处理”,选择默认的响应函数名,然后添加代码:
 //声明IplImage指针
 IplImage* pFrame = NULL;
 IplImage* pFrImg = NULL;
 IplImage* pBkImg = NULL;
 
 CvMat* pFrameMat = NULL;
 CvMat* pFrMat = NULL;
 CvMat* pBkMat = NULL;
 
 CvCapture* pCapture = NULL;
 
 int nFrmNum = 0;
 
 //打开AVI视频文件
 if(strAviFilePath=="")  //判断文件路径是否为空
 {
  MessageBox("请先选择AVI视频文件!");
  return;
 }else
 {
  if(!(pCapture = cvCaptureFromFile(strAviFilePath)))
  {
   MessageBox("打开AVI视频文件失败!");
   return;
  }
 }
 //创建窗口
 cvNamedWindow("Video", 1);
 cvNamedWindow("Background",1);
 cvNamedWindow("Foreground",1);
 
 //使窗口有序排列,窗口宽330
 cvMoveWindow("Video", 30, 0);
 cvMoveWindow("Background", 360, 0);
 cvMoveWindow("Foreground", 690, 0);
 
 //逐帧读取视频
 while(pFrame = cvQueryFrame( pCapture ))
 {
  nFrmNum++;
  
  //如果是第一帧,需要申请内存,并初始化
  if(nFrmNum == 1)
  {
   pBkImg = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,1); // 存放背景图像(灰度)
   pFrImg = cvCreateImage(cvSize(pFrame->width, pFrame->height), IPL_DEPTH_8U,1); // 存放中间图像(灰度)
   
   pBkMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
   pFrMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
   pFrameMat = cvCreateMat(pFrame->height, pFrame->width, CV_32FC1);
   
   //转化成单通道图像再处理(灰度)
   cvCvtColor(pFrame, pBkImg, CV_BGR2GRAY);
   cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY);
   
   cvConvert(pFrImg, pFrameMat);
   cvConvert(pFrImg, pFrMat);
   cvConvert(pFrImg, pBkMat);
  }
  else
  {
   cvCvtColor(pFrame, pFrImg, CV_BGR2GRAY); //转化成单通道图像再处理(灰度)
   cvConvert(pFrImg, pFrameMat);
   //高斯滤波先,以平滑图像
   //cvSmooth(pFrameMat, pFrameMat, CV_GAUSSIAN, 3, 0, 0);
   
   //当前帧跟背景图相减(求背景差并取绝对值)
   cvAbsDiff(pFrameMat, pBkMat, pFrMat);
   
   //二值化前景图(这里采用特定阈值进行二值化)
   cvThreshold(pFrMat, pFrImg, 60, 255.0, CV_THRESH_BINARY);
   
   //进行形态学滤波,去掉噪音
   cvErode(pFrImg, pFrImg, 0, 1);
   cvDilate(pFrImg, pFrImg, 0, 1);
   
   //滑动平均更新背景(求平均)
   cvRunningAvg(pFrameMat, pBkMat, 0.003, 0);
   //将背景转化为图像格式,用以显示
   cvConvert(pBkMat, pBkImg);
   
   // 保持原图像的旋转方向
   pBkImg->origin = pFrImg->origin = pFrame->origin;
   //显示图像
   cvShowImage("Video", pFrame);
   cvShowImage("Background", pBkImg);
   cvShowImage("Foreground", pFrImg);
   
   //如果有按键事件,则跳出循环
   //此等待也为cvShowImage函数提供时间完成显示
   //等待时间可以根据CPU速度调整
   if( cvWaitKey(200) >= 0 )
    break;
  }
 }
 
 //销毁窗口
 cvDestroyWindow("Video");
 cvDestroyWindow("Background");
 cvDestroyWindow("Foreground");
 
 //释放图像和矩阵
 cvReleaseImage(&pFrImg);
 cvReleaseImage(&pBkImg);
 
 cvReleaseMat(&pFrameMat);
 cvReleaseMat(&pFrMat);
 cvReleaseMat(&pBkMat);
 
 cvReleaseCapture(&pCapture);

4 选fileview选项卡中VideoProcessingDlg.h,在CVideoProcessingDlg类中添加公有类成员:
CString  strAviFilePath;
5 选fileview选项卡中VideoProcessingDlg.cpp,添加opencv头文件
#include "cv.h"
#include "highgui.h"
#include "cxcore.h"
6 点击Project——settings——link——Object/library modules下填入
cxcore.lib cv.lib cvaux.lib highgui.lib cvcam.lib ml.lib
7 编译执行,成功!

========

Opencv学习笔记 目标跟踪

http://blog.csdn.net/crzy_sparrow/article/details/7414851


    如果摄像机是固定的,那么我们可以认为场景(背景)大多数情况下是不变的,而只有前景(被跟踪的目标)会运动,
这样就可以建立背景模型。通过比较当前帧和背景模型,就能轻松地跟踪目标运动情况了。这里,最容易想到的比较方式就

是当前帧减去背景模型了,如opencv2.3.1里的以下函数可计算当前帧与背景之差的绝对值。


cv::absdiff(backgroundImage,currentImage,foreground);

    背景模型的建立最容易想到的就是选取一张没有目标时的图片作为背景模型,但是很多情况下,背景中往往一直会有目
标在运动,比如交通监控的时候可能一直车轮滚滚,而且程序在没有经验的情况下又不会知道哪幅图片是有目标的,哪幅图
片是没有目标的。好吧,我承认我说慌了,建模是我们人去建立的,是可以根据我们的感知去选择建模图片的,程序只是根
据我们人为建立的模型去检测目标而已。但我也说过了,没有目标的图片可能就是很难获得,而且你肯定也觉得把仅仅一张
没有目标的图片作为背景模型实在是不靠普,再怎么说也得取多张背景图片求个均值吧。于是,不难想到,我们可以截取一
段目标出现相对较少的视频段(也可叫做图片序列吧。。。),然后对帧求均值,将均值图片作为背景模型。这时候的背景
模型总该靠谱一点了吧。
    问题又来了,我相机是固定了,但是天气情况总是关于时间的函数吧,白天晚上的背景肯定不一样喽,另外,除了那些
大楼,山,马路等不会移动的物体,背景中的物体位置可能也会随着时间变化,或移动或消失,比如说背景中的某块广告牌
被风刮倒了,某棵树被砍了。。。这个时候我们就应该动态更新背景模型了。对前面的均值背景模型做个修改倒是也能解决
这个问题:就是每隔一段时间重建一个背景模型。这样就会遇到几个问题:1)建模之前,保存的大量图片会占大量存储空
间;2)建模时会消耗比较长的时间,此时无法跟踪目标;3)选取几帧图片建模是个问题。2)不难解决,先建立一个均值
模型,记录均值模型和每一帧图片,然后每来一帧图片,求其前景目标,删除原来的第一帧图片,将当前帧背景图片插入到
建模图片的末尾,更新背景模型即可。解决1)的方法是:先建立一个模型,不再保存建模图片,每来一帧,计算前景目标
,用余下的背景图片直接更新原来的模型(model*N+I)/(N+1)(model为模型图片,N为建模图片数)。不过这样有一个
弊端:就是随着时间的推移,背景发生了大的改变,也就是建模的图片中最后一帧与第一帧已经差别很大了,而最后一帧更
接近与当前的背景,于是我们可以对建模的图片按次序赋以不同的权重,让后面的图片权重更大些。这就是本文要讲的目标
跟踪算法。
    如果model是当前背景模型,cur是当前帧去除背景后的图片,则新的模型为:
modelnew = (1-a)*model+a*cur            ;a为学习率,随着时间的推移,之前的建模图片权重越来越小
opencv2.3.1里
void accumulateWeighted(InputArray src, InputOutputArray dst, double alpha, InputArray mask=noArray() )
函数实现该功能:
其参数介绍如下:
Parameters
src – Input image as 1- or 3-channel, 8-bit or 32-bit floating point.
dst – Accumulator image with the same number of channels as input image, 32-bit or 64-bit floating-point.
alpha – Weight of the input image.
mask – Optional operation mask.
The function calculates the weighted sum of the input image src and the accumulator dst so that dst becomes 


a
running average of a frame sequence:
dst(x, y) ← (1 − alpha) · dst(x, y) + alpha · src(x, y) if mask(x, y) = 0


用该方法实现目标跟踪的opencv代码如下,我这里把第一帧图片作为初始背景模型了:
[cpp] view plain copy
<span style="font-size:18px;">//帧处理基类  
class FrameProcessor{  
    public:  
        virtual void process(Mat &input,Mat &ouput)=0;  
};  
  
class BGFGSegmentor : public FrameProcessor{  
    Mat gray;//当前帧灰度图  
    Mat background;//背景图,格式为32位浮点  
    Mat backImage;//CV_8U格式背景图  
    Mat foreground;//前景图  
    double learningRate;//学习率  
    int threshold;//阈值,滤去扰动  
    public:  
    BGFGSegmentor():threshold(30),learningRate(0.6){}  
    //帧处理函数  
    void process(Mat &frame,Mat &output){  
        //转化为灰度图  
        cvtColor (frame,gray,CV_BGR2GRAY);  
        if(background.empty ())  
            //第一帧  
            gray.convertTo (background,CV_32F);  
            //背景转为CV_8U格式以便求取和当前帧差的绝对值  
            background.convertTo (backImage,CV_8U);  
            //求当前帧与背景的差别  
            absdiff (backImage,gray,foreground);  
            //过滤掉前景中与背景差别不大的扰动点  
           cv:: threshold(foreground,output,threshold,255,THRESH_BINARY_INV);  
           //更新背景,output作为掩码  
            accumulateWeighted (gray,background,learningRate,output);  
        }  
};  
 
结果如下:


用到的视频处理类和main函数如下:
[cpp] view plain copy
<span style="font-size:18px;">class VideoProcessor{  
private:  
    VideoCapture caputure;  
    //写视频流对象  
    VideoWriter writer;  
    //输出文件名  
    string Outputfile;  
  
    int currentIndex;  
    int digits;  
    string extension;  
    FrameProcessor *frameprocessor;  
    //图像处理函数指针  
    void (*process)(Mat &,Mat &);  
    bool callIt;  
    string WindowNameInput;  
    string WindowNameOutput;  
    //延时  
    int delay;  
    long fnumber;  
    //第frameToStop停止  
    long frameToStop;  
    //暂停标志  
    bool stop;  
    //图像序列作为输入视频流  
    vector<string> images;  
    //迭代器  
public:  
    VideoProcessor() : callIt(true),delay(0),fnumber(0),stop(false),digits(0),frameToStop(-1){}  
   //设置图像处理函数  
    void setFrameProcessor(void (*process)(Mat &,Mat &)){  
        frameprocessor = 0;  
        this->process = process;  
        CallProcess ();  
    }  
    //打开视频  
    bool setInput(string filename){  
        fnumber = 0;  
        //若已打开,释放重新打开  
        caputure.release ();  
        return caputure.open (filename);  
    }  
    //设置输入视频播放窗口  
    void displayInput(string wn){  
        WindowNameInput = wn;  
        namedWindow (WindowNameInput);  
    }  
    //设置输出视频播放窗口  
    void displayOutput(string wn){  
        WindowNameOutput = wn;  
        namedWindow (WindowNameOutput);  
    }  
    //销毁窗口  
    void dontDisplay(){  
        destroyWindow (WindowNameInput);  
        destroyWindow (WindowNameOutput);  
        WindowNameInput.clear ();  
        WindowNameOutput.clear ();  
    }  
  
    //启动  
    void run(){  
        Mat frame;  
        Mat output;  
        if(!isOpened())  
            return;  
        stop = false;  
        while(!isStopped()){  
            //读取下一帧  
            if(!readNextFrame(frame))  
                break;  
            if(WindowNameInput.length ()!=0)  
                imshow (WindowNameInput,frame);  
            //处理该帧  
            if(callIt){  
                if(process)  
                    process(frame,output);  
                else if(frameprocessor)  
                    frameprocessor->process (frame,output);  
            }  
            else{  
                output = frame;  
            }  
            if(Outputfile.length ()){  
                    cvtColor (output,output,CV_GRAY2BGR);  
                    writeNextFrame (output);  
              }  
            if(WindowNameOutput.length ()!=0)  
                imshow (WindowNameOutput,output);  
            //按键暂停,继续按键继续  
            if(delay>=0&&waitKey (delay)>=0)  
                waitKey(0);  
            //到达指定暂停键,退出  
            if(frameToStop>=0&&getFrameNumber()==frameToStop)  
                stopIt();  
        }  
    }  
    //暂停键置位  
    void stopIt(){  
        stop = true;  
    }  
    //查询暂停标志位  
    bool isStopped(){  
        return stop;  
    }  
    //返回视频打开标志  
    bool isOpened(){  
       return  caputure.isOpened ()||!images.empty ();  
    }  
    //设置延时  
    void setDelay(int d){  
        delay = d;  
    }  
    //读取下一帧  
    bool readNextFrame(Mat &frame){  
        if(images.size ()==0)  
            return caputure.read (frame);  
        else{  
            if(itImg!=images.end()){  
                frame = imread (*itImg);  
                itImg++;  
                return frame.data?1:0;  
            }  
            else  
                return false;  
        }  
    }  
  
    void CallProcess(){  
        callIt = true;  
    }  
    void  dontCallProcess(){  
        callIt = false;  
    }  
    //设置停止帧  
    void stopAtFrameNo(long frame){  
        frameToStop = frame;  
    }  
   // 获得当前帧的位置  
    long getFrameNumber(){  
        long fnumber = static_cast<long>(caputure.get ((CV_CAP_PROP_POS_FRAMES)));  
        return fnumber;  
    }  
  
     //获得帧大小  
       Size getFrameSize() {  
        if (images.size()==0) {  
            // 从视频流获得帧大小  
            int w= static_cast<int>(caputure.get(CV_CAP_PROP_FRAME_WIDTH));  
            int h= static_cast<int>(caputure.get(CV_CAP_PROP_FRAME_HEIGHT));  
            return Size(w,h);  
            }  
        else {  
                //从图像获得帧大小  
                cv::Mat tmp= cv::imread(images[0]);  
                return (tmp.data)?(tmp.size()):(Size(0,0));  
            }  
          }  
  
   //获取帧率  
    double getFrameRate(){  
        return caputure.get(CV_CAP_PROP_FPS);  
    }  
    vector<string>::const_iterator itImg;  
    bool setInput (const vector<string> &imgs){  
        fnumber = 0;  
        caputure.release ();  
        images = imgs;  
        itImg = images.begin ();  
        return true;  
    }  
  
    void  setFrameProcessor(FrameProcessor *frameprocessor){  
        process = 0;  
        this->frameprocessor = frameprocessor;  
        CallProcess ();  
    }  
  
    //获得编码类型  
    int getCodec(char codec[4]) {  
        if (images.size()!=0)  
            return -1;  
        union { // 数据结构4-char  
            int value;  
            char code[4];  
        } returned;  
        //获得编码值  
        returned.value= static_cast<int>(  
        caputure.get(CV_CAP_PROP_FOURCC));  
        // get the 4 characters  
        codec[0]= returned.code[0];  
        codec[1]= returned.code[1];  
        codec[2]= returned.code[2];  
        codec[3]= returned.code[3];  
        return returned.value;  
    }  
  
  
    bool setOutput(const string &filename,int codec = 0,double framerate = 0.0,bool isColor = true){  
        //设置文件名  
        Outputfile = filename;  
        //清空扩展名  
        extension.clear ();  
        //设置帧率  
        if(framerate ==0.0){  
            framerate = getFrameRate ();  
        }  
        //获取输入原视频的编码方式  
        char c[4];  
        if(codec==0){  
            codec = getCodec(c);  
        }  
        return writer.open(Outputfile,  
                           codec,  
                           framerate,  
                           getFrameSize(),  
                           isColor);  
    }  
  
    //输出视频帧到图片fileme+currentIndex.ext,如filename001.jpg  
    bool setOutput (const string &filename,//路径  
                    const string &ext,//扩展名  
                    int numberOfDigits=3,//数字位数  
                    int startIndex=0 ){//起始索引  
           if(numberOfDigits<0)  
               return false;  
           Outputfile = filename;  
           extension = ext;  
           digits = numberOfDigits;  
           currentIndex = startIndex;  
           return true;  
    }  
  
    //写下一帧  
    void writeNextFrame(Mat &frame){  
        //如果扩展名不为空,写到图片文件中  
        if(extension.length ()){  
            stringstream ss;  
            ss<<Outputfile<<setfill('0')<<setw(digits)<<currentIndex++<<extension;  
            imwrite (ss.str (),frame);  
        }  
        //反之,写到视频文件中  
        else{  
            writer.write (frame);  
        }  
    }  
  
};  
  
    //帧处理函数:canny边缘检测  
    void canny(cv::Mat& img, cv::Mat& out) {  
        //灰度变换  
        if (img.channels()==3)  
            cvtColor(img,out,CV_BGR2GRAY);  
        // canny算子求边缘  
        Canny(out,out,100,200);  
        //颜色反转,看起来更舒服些  
        threshold(out,out,128,255,cv::THRESH_BINARY_INV);  
    }  
  
  
int main(int argc, char *argv[])  
{  
    VideoProcessor processor;  
  //  FeatureTracker tracker;  
    BGFGSegmentor tracker;  
    //打开输入视频  
    processor.setInput ("bike.avi");  
    processor.displayInput ("Current Frame");  
    processor.displayOutput ("Output Frame");  
    //设置每一帧的延时  
    processor.setDelay (1000./processor.getFrameRate ());  
    //设置帧处理函数,可以任意  
    processor.setFrameProcessor (&tracker);  
    //   processor.setOutput ("./bikeout.avi");  
    //    processor.setOutput ("bikeout",".jpg");  
    processor.run ();  
    return 0;  
}  
========

目标跟踪学习笔记 opencv中kalman点跟踪例子


    一些网络资料

     另外博文:http://blog.csdn.net/onezeros/article/details/6318944将opencv中自带的kalman改装成了鼠标跟踪程

序,可以一看。

     这篇博客http://blog.csdn.net/yang_xian521/article/details/7050398 对opencv中本身自带的Kalman例子讲解得

很清楚。


  kalman滤波简单介绍

     Kalman滤波理论主要应用在现实世界中个,并不是理想环境。主要是来跟踪的某一个变量的值,跟踪的依据是首先根

据系统的运动方程来对该值做预测,比如说我们知道一个物体的运动速度,那么下面时刻它的位置按照道理是可以预测出来

的,不过该预测肯定有误差,只能作为跟踪的依据。另一个依据是可以用测量手段来测量那个变量的值,当然该测量也是有

误差的,也只能作为依据,不过这2个依据的权重比例不同。最后kalman滤波就是利用这两个依据进行一些列迭代进行目标

跟踪的。


     在这个理论框架中,有2个公式一定要懂,即:

     第一个方程为系统的运动方程,第二个方程为系统的观测方程,学过自控原理中的现代控制理论的同学应该对这2个公

式很熟悉。具体的相关理论本文就不做介绍了。

     Opencv目标版本中带有kalman这个类,可以使用它来完成一些跟踪目的。 

  下面来看看使用Kalman编程的主要步骤:

  步骤一  :

     Kalman这个类需要初始化下面变量:      

  转移矩阵,测量矩阵,控制向量(没有的话,就是0),过程噪声协方差矩阵,测量噪声协方差矩阵,后验错误协方差矩

阵,前一状态校正后的值,当前观察值。 

  步骤二:

  调用kalman这个类的predict方法得到状态的预测值矩阵,预测状态的计算公式如下:

  predicted state (x'(k)): x'(k)=A*x(k-1)+B*u(k)

  其中x(k-1)为前一状态的校正值,第一个循环中在初始化过程中已经给定了,后面的循环中Kalman这个类内部会计算。

A,B,u(k),也都是给定了的值。这样进过计算就得到了系统状态的预测值x'(k)了。 

  步骤三:

  调用kalman这个类的correct方法得到加入观察值校正后的状态变量值矩阵,其公式为:

  corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))

  其中x'(k)为步骤二算出的结果,z(k)为当前测量值,是我们外部测量后输入的向量。H为Kalman类初始化给定的测量矩

阵。K(k)为Kalman增益,其计算公式为:

  Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)

  计算该增益所依赖的变量要么初始化中给定,要么在kalman理论中通过其它公式可以计算。

  经过步骤三后,我们又重新获得了这一时刻的校正值,后面就不断循环步骤二和步骤三即可完成Kalman滤波过程。

  实验部分

  本次实验来源于opencv自带sample中的例子,该例子是用kalman来完成一个一维的跟踪,即跟踪一个不断变化的角度。

在界面中表现为一个点在圆周上匀速跑,然后跟踪该点。看起来跟踪点是个二维的,其实转换成角度就是一维的了。

  该代码中,有这么几句
  KalmanFilter KF(2, 1, 0);
  ...
  KF.transitionMatrix = *(Mat_<float>(2, 2) << 1, 1, 0, 1);
  ...


  一直在想Mat_<float>(2, 2) << 1是什么意思呢?
  如果是用:Mat_<float> A(2, 2);则这表示的是定义一个矩阵A。

  但是Mat_<float>(2, 2)感觉又不是定义,貌似也不是数,既然不是数怎能左移呢?后面发现自己想错了.

  Mat_<float>(2, 2) << 1, 1, 0, 1是一个整体,即往Mat_<float>(2, 2)的矩阵中赋值1,1,0,1;说白了就是给Mat矩阵赋

初值,因为初值没什么规律,所以我们不能用zeros,ones等手段来赋值。比如运行下面语句时:

  Mat a;

  a = (Mat_<float>(2, 2) << 1, 1, 0, 1);

   cout<<a<<endl;

   其结果就为

  [1,1;
  0,1]

   实验结果如下:

  跟踪中某一时刻图1:

  跟踪中某一时刻图2:

  其中红色的短线条为目标点真实位置和目标点的测量位置的连线,黄色的短线为目标点真实位置和预测位置的连线,所

以2中颜色相交中间那个点坐标为目标的真实坐标。

实验主要部分代码和注释(附录有工程code下载链接地址):

Kalman.h:

#ifndef KALMAN_H
#define KALMAN_H

#include <QDialog>
#include <QTimer>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/video/video.hpp>

using namespace cv;

namespace Ui {
class kalman;
}

class kalman : public QDialog
{
    Q_OBJECT
    
public:
    explicit kalman(QWidget *parent = 0);
    ~kalman();
    
private slots:

    void on_startButton_clicked();

    void kalman_process();

    void on_closeButton_clicked();

private:
    Ui::kalman *ui;
    Mat img, state, processNoise, measurement;
    KalmanFilter KF;
    QTimer *timer;
    //给定圆心和周长,和圆周上的角度,求圆周上的坐标
    static inline Point calcPoint(Point2f center, double R, double angle)
    {
        //sin前面有个负号那是因为图片的顶点坐标在左上角
        return center + Point2f((float)cos(angle), (float)-sin(angle))*(float)R;
    }

};

#endif // KALMAN_H

Kalman.cpp:

#include "kalman.h"
#include "ui_kalman.h"
#include <iostream>

using namespace std;

kalman::kalman(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::kalman)
{
    //在构造函数中定义变量不行?
    img.create(500, 500, CV_8UC3);
    cout<<img.rows<<endl;
    //状态维数2,测量维数1,没有控制量
    KF.init(2, 1, 0);
    //状态值,(角度,角速度)
    state.create(2, 1, CV_32F);
    processNoise.create(2, 1, CV_32F);
    measurement = Mat::zeros(1, 1, CV_32F);
    timer   = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(kalman_process()));

    ui->setupUi(this);
    //这句必须放在ui->setupUi(this)后面,因为只有这样才能访问ui->textBrowser
    ui->textBrowser->setFixedSize(500, 500);
}

kalman::~kalman()
{
    delete ui;
}

void kalman::on_startButton_clicked()
{
    /*
      使用kalma步骤一
      下面语句到for前都是kalman的初始化过程,一般在使用kalman这个类时需要初始化的值有:
      转移矩阵,测量矩阵,过程噪声协方差,测量噪声协方差,后验错误协方差矩阵,
      前一状态校正后的值,当前观察值
    */

    //产生均值为0,标准差0.1的二维高斯列向量
    randn( state, Scalar::all(0), Scalar::all(0.1) );
    //transitionMatrix为类KalmanFilter中的一个变量,Mat型,是Kalman模型中的状态转移矩阵
    //转移矩阵为[1,1;0,1],2*2维的
    KF.transitionMatrix = *(Mat_<float>(2, 2) << 1, 1, 0, 1);

    //函数setIdentity是给参数矩阵对角线赋相同值,默认对角线值值为1
    setIdentity(KF.measurementMatrix);
    //系统过程噪声方差矩阵
    setIdentity(KF.processNoiseCov, Scalar::all(1e-5));
    //测量过程噪声方差矩阵
    setIdentity(KF.measurementNoiseCov, Scalar::all(1e-1));
    //后验错误估计协方差矩阵
    setIdentity(KF.errorCovPost, Scalar::all(1));
    //statePost为校正状态,其本质就是前一时刻的状态
    randn(KF.statePost, Scalar::all(0), Scalar::all(0.1));
    //设置定时器时间,默认情况下其该定时器可无限定时,即其SingleShot为false
    //如果将其设置为true,则该定时器只能定时一次
    //因此这里是每个33ms就去执行一次kalman处理函数
    timer->start(33);
 //   timer->setSingleShot( true );

}

void kalman::kalman_process()
{
    Point2f center(img.cols*0.5f, img.rows*0.5f);
    float R = img.cols/3.f;
    //state中存放起始角,state为初始状态
    double stateAngle = state.at<float>(0);
    Point statePt = calcPoint(center, R, stateAngle);


    /*
      使用kalma步骤二
      调用kalman这个类的predict方法得到状态的预测值矩阵
    */
    //predicted state (x'(k)): x'(k)=A*x(k-1)+B*u(k)
    //其中x(k-1)为前面的校正状态,因此这里是用校正状态来预测的
    //而校正状态corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
    //又与本时刻的预测值和校正值有关系
    Mat prediction = KF.predict();
    //用kalman预测的是角度
    double predictAngle = prediction.at<float>(0);
    Point predictPt = calcPoint(center, R, predictAngle);


    randn( measurement, Scalar::all(0), Scalar::all(KF.measurementNoiseCov.at<float>(0)));


    // generate measurement
    //带噪声的测量
    measurement += KF.measurementMatrix*state;
   // measurement += KF.measurementMatrix*prediction;


    double measAngle = measurement.at<float>(0);
    Point measPt = calcPoint(center, R, measAngle);


    // plot points
    //这个define语句是画2条线段(线长很短),其实就是画一个“X”叉符号
    #define drawCross( center, color, d )                                 \
        line( img, Point( center.x - d, center.y - d ),                \
                     Point( center.x + d, center.y + d ), color, 1, CV_AA, 0); \
        line( img, Point( center.x + d, center.y - d ),                \
                     Point( center.x - d, center.y + d ), color, 1, CV_AA, 0 )


    img = Scalar::all(0);
    //状态坐标白色
    drawCross( statePt, Scalar(255,255,255), 3 );
    //测量坐标蓝色
    drawCross( measPt, Scalar(0,0,255), 3 );
    //预测坐标绿色
    drawCross( predictPt, Scalar(0,255,0), 3 );
    //真实值和测量值之间用红色线连接起来
    line( img, statePt, measPt, Scalar(0,0,255), 3, CV_AA, 0 );
    //真实值和估计值之间用黄色线连接起来
    line( img, statePt, predictPt, Scalar(0,255,255), 3, CV_AA, 0 );


    /*
      使用kalma步骤三
      调用kalman这个类的correct方法得到加入观察值校正后的状态变量值矩阵
    */
    //虽然该函数有返回值Mat,但是调用该函数校正后,其校正值已经保存在KF的statePost
    //中了,corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
    KF.correct(measurement);


    randn( processNoise, Scalar(0), Scalar::all(sqrt(KF.processNoiseCov.at<float>(0, 0))));
    //不加噪声的话就是匀速圆周运动,加了点噪声类似匀速圆周运动,因为噪声的原因,运动方向可能会改变
    state = KF.transitionMatrix*state + processNoise;


    imwrite("../kalman/img.jpg", img);
    ui->textBrowser->clear();
    //ui->textBrowser->setFixedSize(img.cols, img.rows);
    ui->textBrowser->append("<img src=../kalman/img.jpg>");
}


void kalman::on_closeButton_clicked()
{
    close();
}


  在用Qt编程中碰到了下面的疑问 

  疑问1:

  Qt界面编程中,由于构造函数中不能定义全局变量,我们需要把全局变量定义在内的内部(即在头文件中定义),那么在

使用类似于opencv中这些自带初始值赋值的方法是不是就不能那样使用了呢?比如说Mat img(500, 500, CV_8UC3);因为在

头文件中的定义不能同时初始化,在源文件中又不能定义。难道我们一定要将初始化和定义分开写?


     查了下资料,基本上只能分开写了。其实类似opencv等开源库中的函数都考虑到了这一点。如果是用c编程,其全局变

量可以直接这样赋值,Mat img(500, 500, CV_8UC3);如果是有关界面的c++编程,则Mat会提供另外一个函数来初始化的,

Mat这里提供的是create函数,img.create(500, 500, CV_8UC3);其它函数类似。
 
  疑问2:

  Qt界面编程中,当我们按下一个按钮时,需要在这个按钮的槽函数里面实现一个死循环的功能,比如说让界面中一直显

示一个运动的圆,由于该点在运动,所以需要代码不断的画圆,因此用了死循环。而当我们按下该界面中的另一个按钮,比

如退出程序按钮时,因为前一个按钮中一直在响应,所以无法响应我们的退出按钮请求,遇到这种情况该怎么解决呢?

  答案首先能肯定的是,一般情况下按钮响应槽函数中不宜使用死循环语句,否则外面函数无法响应。网上说一般解决这

种问题用多线程编程技术比较好(这里我没有去试过,因为不了解多线程技术,当然了,这个以后有时间一定要去学下的)

。我这里采用了另外一种方法即利用的定时器功能,触发定时器槽函数,每隔一段时间执行一下需要执行的函数。具体见代

码中。

     上面2个疑问是暂时的替代方案,

  总结:

  kalman应用比较广,虽然学习它的理论都是数学公式,不过我们可以先学会在程序中怎么使用它,在慢慢去看理论,2

者结合,这样效果会好很多。

========

OpenCV视频目标跟踪示例教程

http://blog.csdn.net/luopeiyuan1990/article/details/8796209
      使用Opencv中的Camshift进行视频中目标跟踪是一个不错的选择,这方面的示例很多,但是大多代码不全,或者代码
存在问题,不能正常使用,这里,对很多文章进行整理后,贴出了正确可以使用的代码。

另一个错误: 
错误::“cvSetMouseCallback”: 不能将参数 2 从“void (__cdecl *)(int,int,int,int)”转换为“CvMouseCallback

原因:函数命名不符合Opencv的命名规范如下更改即可。 
//void on_mouse( int event, int x, int y, int flags ) 
void on_mouse(int event, int x, int y, int flags, void* param) 

汇总例程的下载地址:
http://ishare.iask.sina.com.cn/f/36709094.html 
一个可以参考的教程下载地址如下:
http://ishare.iask.sina.com.cn/f/9105002.html 


下面贴出本例程中,使用的代码,实现了,简单的目标的跟踪:使用的是笔记本自带的摄像头,可以简单的跟踪你的脸哦,

呵呵。还不是太灵敏, 有待改进,本例程是结合,Opencv自带的例程以及网友的贡献代码更改,

#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#include <ctype.h>
IplImage *image = 0, *hsv = 0, *hue = 0, *mask = 0, *backproject = 0, *histimg = 0;
CvHistogram *hist = 0;
int backproject_mode = 0;
int select_object = 0;
int track_object = 0;
int show_hist = 1; 
CvPoint origin;
CvRect selection;
CvRect track_window;
CvBox2D track_box; // tracking 返回的区域 box,带角度
CvConnectedComp track_comp;
int hdims = 48;     // 划分HIST的个数,越高越精确
float hranges_arr[] = {0,180};
float* hranges = hranges_arr;
int vmin = 10, vmax = 256, smin = 30;
//void on_mouse( int event, int x, int y, int flags )
void on_mouse(int event, int x, int y, int flags, void* param)
{
    if( !image )
        return;
    if( image->origin )
        y = image->height - y;
    if( select_object )
    {
        selection.x = MIN(x,origin.x);
        selection.y = MIN(y,origin.y);
        selection.width = selection.x + CV_IABS(x - origin.x);
        selection.height = selection.y + CV_IABS(y - origin.y);
        
        selection.x = MAX( selection.x, 0 );
        selection.y = MAX( selection.y, 0 );
        selection.width = MIN( selection.width, image->width );
        selection.height = MIN( selection.height, image->height );
        selection.width -= selection.x;
        selection.height -= selection.y;
    }
    switch( event )
    {
    case CV_EVENT_LBUTTONDOWN:
        origin = cvPoint(x,y);
        selection = cvRect(x,y,0,0);
        select_object = 1;
        break;
    case CV_EVENT_LBUTTONUP:
        select_object = 0;
        if( selection.width > 0 && selection.height > 0 )
            track_object = -1;
#ifdef _DEBUG
    printf("\n # 鼠标的选择区域:"); 
    printf("\n   X = %d, Y = %d, Width = %d, Height = %d",
        selection.x, selection.y, selection.width, selection.height);
#endif
        break;
    }
}


CvScalar hsv2rgb( float hue )
{
    int rgb[3], p, sector;
    static const int sector_data[][3]=
        {{0,2,1}, {1,2,0}, {1,0,2}, {2,0,1}, {2,1,0}, {0,1,2}};
    hue *= 0.033333333333333333333333333333333f;
    sector = cvFloor(hue);
    p = cvRound(255*(hue - sector));
    p ^= sector & 1 ? 255 : 0;
    rgb[sector_data[sector][0]] = 255;
    rgb[sector_data[sector][1]] = 0;
    rgb[sector_data[sector][2]] = p;
#ifdef _DEBUG
    printf("\n # Convert HSV to RGB:"); 
    printf("\n   HUE = %f", hue);
    printf("\n   R = %d, G = %d, B = %d", rgb[0],rgb[1],rgb[2]);
#endif
    return cvScalar(rgb[2], rgb[1], rgb[0],0);
}
int main( int argc, char** argv )
{
    CvCapture* capture = 0;
    IplImage* frame = 0;
    
    if( argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
        capture = cvCaptureFromCAM( argc == 2 ? argv[1][0] - '0' : 0 );
    else if( argc == 2 )
        capture = cvCaptureFromAVI( argv[1] ); 
    if( !capture )
    {
        fprintf(stderr,"Could not initialize capturing...\n");
        return -1;
    }
    printf( "Hot keys: \n"
        "\tESC - quit the program\n"
        "\tc - stop the tracking\n"
        "\tb - switch to/from backprojection view\n"
        "\th - show/hide object histogram\n"
        "To initialize tracking, select the object with mouse\n" );
    //cvNamedWindow( "Histogram", 1 );
    cvNamedWindow( "CamShiftDemo", 1 );
    cvSetMouseCallback( "CamShiftDemo", on_mouse, NULL ); // on_mouse 自定义事件
    cvCreateTrackbar( "Vmin", "CamShiftDemo", &vmin, 256, 0 );
    cvCreateTrackbar( "Vmax", "CamShiftDemo", &vmax, 256, 0 );
    cvCreateTrackbar( "Smin", "CamShiftDemo", &smin, 256, 0 );
    for(;;)
    {
        int i, bin_w, c;
        frame = cvQueryFrame( capture );
        if( !frame )
            break;
        if( !image )
        {
            /* allocate all the buffers */
            image = cvCreateImage( cvGetSize(frame), 8, 3 );
            image->origin = frame->origin;
            hsv = cvCreateImage( cvGetSize(frame), 8, 3 );
            hue = cvCreateImage( cvGetSize(frame), 8, 1 );
            mask = cvCreateImage( cvGetSize(frame), 8, 1 );
            backproject = cvCreateImage( cvGetSize(frame), 8, 1 );
            hist = cvCreateHist( 1, &hdims, CV_HIST_ARRAY, &hranges, 1 ); // 计算直方图
            histimg = cvCreateImage( cvSize(320,200), 8, 3 );
            cvZero( histimg );
        }
        cvCopy( frame, image, 0 );
        cvCvtColor( image, hsv, CV_BGR2HSV ); // 彩色空间转换 BGR to HSV 
        if( track_object )
        {
            int _vmin = vmin, _vmax = vmax;
            cvInRangeS( hsv, cvScalar(0,smin,MIN(_vmin,_vmax),0),
                        cvScalar(180,256,MAX(_vmin,_vmax),0), mask ); // 得到二值的MASK
            cvSplit( hsv, hue, 0, 0, 0 ); // 只提取 HUE 分量
            if( track_object < 0 )
            {
                float max_val = 0.f;
                cvSetImageROI( hue, selection ); // 得到选择区域 for ROI
                cvSetImageROI( mask, selection ); // 得到选择区域 for mask
                cvCalcHist( &hue, hist, 0, mask ); // 计算直方图
                cvGetMinMaxHistValue( hist, 0, &max_val, 0, 0 ); // 只找最大值
                cvConvertScale( hist->bins, hist->bins, max_val ? 255. / max_val : 0., 0 ); // 缩放 bin 到区
间 [0,255] 
                cvResetImageROI( hue ); // remove ROI
                cvResetImageROI( mask );
                track_window = selection;
                track_object = 1;
                cvZero( histimg );
                bin_w = histimg->width / hdims; // hdims: 条的个数,则 bin_w 为条的宽度
                
                // 画直方图
                for( i = 0; i < hdims; i++ )
                {
                    int val = cvRound( cvGetReal1D(hist->bins,i)*histimg->height/255 );
                    CvScalar color = hsv2rgb(i*180.f/hdims);
                    cvRectangle( histimg, cvPoint(i*bin_w,histimg->height),
                                 cvPoint((i+1)*bin_w,histimg->height - val),
                                 color, -1, 8, 0 );
                }
            }
            cvCalcBackProject( &hue, backproject, hist ); // 使用 back project 方法
            cvAnd( backproject, mask, backproject, 0 );
            
            // calling CAMSHIFT 算法模块
            cvCamShift( backproject, track_window,
                        cvTermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ),
                        &track_comp, &track_box );
            track_window = track_comp.rect;
            
            if( backproject_mode )
                cvCvtColor( backproject, image, CV_GRAY2BGR ); // 使用backproject灰度图像
            if( image->origin )
                track_box.angle = -track_box.angle;
            cvEllipseBox( image, track_box, CV_RGB(255,0,0), 3, CV_AA, 0 );
        }
        
        if( select_object && selection.width > 0 && selection.height > 0 )
        {
            cvSetImageROI( image, selection );
            cvXorS( image, cvScalarAll(255), image, 0 );
            cvResetImageROI( image );
        }
        cvShowImage( "CamShiftDemo", image );
        cvShowImage( "Histogram", histimg );
        c = cvWaitKey(10);
        if( c == 27 )
            break; // exit from for-loop
        switch( c )
        {
        case 'b':
            backproject_mode ^= 1;
            break;
        case 'c':
            track_object = 0;
            cvZero( histimg );
            break;
        case 'h':
            show_hist ^= 1;
            if( !show_hist )
                cvDestroyWindow( "Histogram" );
            else
                cvNamedWindow( "Histogram", 1 );
            break;
        default:
            ;
        }
    }
    cvReleaseCapture( &capture );
    cvDestroyWindow("CamShiftDemo");
    return 0;
}
========

利用OpenCV实现-目标跟踪方法


目标跟踪一直是热门话题,对于实时帧跟踪首先要判断有无运动目标。
最简单的目标跟踪是首先处理视频(摄像头)帧,然后灰度化,二值化,包括滤波处理(Gaussian),模糊处理(blur),二值化

处理等等得到前景和背景分离的动态目标。
有一个简单示例见链接和链接,得到目标的相关信息。

本文介绍第一种目标跟踪方法!

可以处理视频文件 或者 摄像头实时采集 的背景分割图像。具体系列前景背景分离方法将在后文中详述。

本文方法核心是模板匹配

先上代码:

/*
 * Developer : Prakriti Chintalapoodi - c.prakriti@gmail.com 
*/


#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

#define MAX_FEATURES 400

using namespace cv;
using namespace std;

#define NUM_FRAMES_TO_PROCESS  600

// Global Variables
Mat frame1, img_object;
Point pt1, pt2;
bool roi_capture = false;
bool got_roi = false;

// Function Headers
void mouse_click(int event, int x, int y, int flags, void *param);
void findObject(Mat& frame, Mat& img_object);

int main(int argc, char* argv[])
{
    /*
if (argc != 2)
    {
        cout << "Usage: ./ObjectTracker <Input Video file>" << endl;
        return -1;
    }
*/
    VideoCapture inputVideo("./test_videos//football.avi");
    if (!inputVideo.isOpened())
    {
        cout << "Error opening Input Video " << argv[1] << endl;
        return -1;
    }

    double fps = inputVideo.get(CV_CAP_PROP_FPS);
    int delay = 1000/fps;    // time in ms between successive frames = 1000/fps
    cout << "FPS = " << fps << endl;
    cout << "Number of frames = " << static_cast<int>(inputVideo.get(CV_CAP_PROP_FRAME_COUNT)) << endl;

    inputVideo >> frame1;     // read first frame only
    if(!frame1.empty())
    {
        // Collect the ROI of the object, store it in img_object
        cout << "Click and drag to select the object to detect" << endl;
        namedWindow("Frame 1: Capture ROI", 1);
        moveWindow("Frame 1: Capture ROI", 600, 300);
        imshow("Frame 1: Capture ROI", frame1);
        setMouseCallback("Frame 1: Capture ROI", mouse_click, 0);

        waitKey(0);

        // close the window after ROI is saved
        destroyWindow("Frame 1: Capture ROI");
        destroyWindow("ROI");
    }

    Mat frame;
    bool stop(false);
    int frameNum = 0;


    // Set position in video back to beginning
    inputVideo.set(CV_CAP_PROP_POS_FRAMES, 0);


    // Process every frame of video only when object ROI has been selected
    if (!img_object.empty())
    {
        namedWindow("Input Video");
        moveWindow("Input Video", 600, 300);


        while(!stop)
        {
            inputVideo >> frame;            // read current frame
            if( frame.empty()) break;       // check if at end
            frameNum++;
            imshow("Input Video", frame);


            // Find the object in current frame, draw a rectangle around it
            findObject(frame, img_object);


            if (inputVideo.get(CV_CAP_PROP_POS_FRAMES) == NUM_FRAMES_TO_PROCESS)
            {
                cout << "\nDone" << endl;
                break;
            }
            // introduce delay or press key to stop
            if (waitKey(delay) >= 0)
                stop = true;
        }
    }


    waitKey(0);
    return 0;
}


// Records mouseclick events to get x-y coordinates of ROI
// Store coordinates in global variables pt1, pt2.
void mouse_click(int event, int x, int y, int flags, void *param)
{
    switch(event)
    {
    case CV_EVENT_LBUTTONDOWN:
    {
        cout << "Mouse Left Button Pressed" << endl;
        if (!roi_capture)
        {
            pt1.x = x;
            pt1.y = y;
        }
        else
        {
            cout << "ROI already acquired" << endl;
        }
        break;
    }
    case CV_EVENT_LBUTTONUP:
    {
        if (!got_roi)
        {
            cout << "Mouse Left Button released" << endl;
            pt2.x = x;
            pt2.y = y;


            Mat roi(frame1, Rect(pt1, pt2));
            roi.copyTo(img_object);
            cout << "ROI acquired" << endl;
            namedWindow("ROI", 1);
            moveWindow("ROI", 1000, 300);
            imshow("ROI", roi);
            got_roi = true;
            cout << "Press any key to close ROI windows and start detecting object in video" << endl;
        }
        else
        {
            cout << "ROI already acquired" << endl;
        }
        break;
    }
    }
}


// Function to perform template matching and track the object in the video
void findObject(Mat& frame, Mat& img_object)
{
    // Source image to display
    Mat img_display;
    frame.copyTo(img_display);


    // Create the result matrix
    Mat result;
    int result_cols =  frame.cols - img_object.cols + 1;
    int result_rows = frame.rows - img_object.rows + 1;
    result.create(result_cols, result_rows, CV_32FC1);


    // Do the Template Matching and Normalize
    // Matching method choices are:
    // CV_TM_SQDIFF, CV_TM_SQDIFF_NORMED, CV_TM_CCORR, CV_TM_CCORR_NORMED, CV_TM_CCOEFF, CV_TM_CCOEFF_NORMED
    int match_method = CV_TM_SQDIFF_NORMED;
    matchTemplate( frame, img_object, result, match_method );
    normalize( result, result, 0, 1, NORM_MINMAX, -1, Mat() );


    // Localizing the best match with minMaxLoc
    double minVal; double maxVal;
    Point minLoc; Point maxLoc; Point matchLoc;
    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());


    /// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the 


higher the better
    if( match_method  == TM_SQDIFF || match_method == TM_SQDIFF_NORMED )
    { matchLoc = minLoc; }
    else
    { matchLoc = maxLoc; }


    // Draw a rectangle around the found object
    rectangle(img_display, matchLoc, Point(matchLoc.x + img_object.cols , matchLoc.y + img_object.rows), 


Scalar(255,0,0), 2, 8, 0 );
    namedWindow("Object Tracking", 1);
    moveWindow("Object Tracking", 1000, 300);
    imshow("Object Tracking", img_display);
}
========

OpenCV Camshiftdemo 目标跟踪



1. 导言


这两天又有个机会又(为什么要说又呢?)要用到OpenCV中的camshift tracking。简单记录一下,算是这篇目标跟踪学习


笔记_1(opencv中meanshift和camshift例子的应用)文章的补充


2. 工作流程


2.1 使用方法
trackObject为0表示不跟踪,为1表示继续跟踪;鼠标选择完(新)跟踪区域后trackObject为-1, 根据新窗口准备跟踪的


数据(颜色空间转换,直方图等)---这些上面的代码注释里都有提到
刚读源码才发现可以按p暂停后再选跟踪目标---这个逻辑还挺有用


2.2 涉及的源码有:


$OPENCV_HOME/samples/cpp/camshiftdemo.cpp


$OPENCV_HOME//modules/video/src/camshift.cpp:总共4个函数


cv::CamShift
cv::meanShift
cvCamShift
cvMeanShift


2.3 呼叫流程:


cv::CamShift(...)-->cvCamShift(...)-->cvMeanShift(...)


i) cvCamShift会调整输入窗口大小。具体是四个矩形边各自向外扩张TOLERANCE像素。越界的情况会自动处理。TOLERANCE 


hard coded为10。
ii)对扩展后的矩形跟踪区域计算moments。如果零阶矩小于DBL_EPSILON,则直接返回-1。
iii)DBL_EPSILON: 前28位在本机上打印出来是这样:0.0000000000000002220446049250
注意之前cv::CamShift初始化了一个CvConnectedComp comp 为全0值,并传入cvCamShift。cvCamShift返回后再用这个comp


改变传入cv::CamShift的最最初始的跟踪窗口。


所以上面如果返回-1,则初始窗口(camshiftdemo中的trackWindow)会变成0。在下一帧时会抱如下错误:
camshift.cpp:81: error: (-5) Input window has non-positive sizes in function cvMeanShift




这也算是一个小bug吧。可以出现这种情况就提示不再跟踪任何窗口,或在返回-1前把当前跟踪窗口(其m00为零)赋给


_comp,这样跟踪窗口应该一直维持在那个小区域,直到选取新的区域---我这么试了,又出drawing的错误,应该是区域太


小了,没法fit elipse。 whatever啦。。。


每一帧的处理中,基本上有三个地方可能改变初始的跟踪窗口(注意这里初始窗口是针对每一帧而言,而不是鼠标选定的最


开始的初始窗口)


cvMeanshift本身最终收敛的结果
cvCamShift中,cvMeanShift之后窗口扩展,也就是上面的i)
属于CamShift本身的根据二阶矩调整跟踪窗口大小的算法


3. The End
========

OpenCV目标跟踪之质心跟踪(Centroid)



前言
     最近一直在看关于目标跟踪方面的算法实现,也是时候整理下思路看看怎么实现了。 这次我将带领大家看看基于
OpenCV的目标跟踪算法及其基本实现。由于目标跟踪方法众多,我将分为几次讲解逐个讲解。当然只是起个索引的
效果,要好的跟踪实现有待自己去深化。


 概述
      目标跟踪作为Machine Learning的一个重要分支,加之其广泛的应用(行人、军事中飞机导弹等),对其的研究
受到国内外学者的喜爱。
总的来说整个目标跟踪过程可分为目标特征提取和目标跟踪两部分。
    其中目标特征提取又可以细分为以下几种:
           1. 各种色彩空间直方图,利用色彩空间的直方图分布作为目标跟踪的特征的一个显著性特点是可以减少物体远
    近距离对跟踪的影响,因为其颜色分布大致相同。
           2.轮廓特征,提取目标的轮廓特征不但可以加快算法的速度还可以在目标有小部分影响的情况下同样有效果。
           3. 纹理特征,如果被跟踪目标是有纹理的,根据其纹理特征来跟踪,效果会有所改善。
    当前目标跟踪算法大体可分为以下七种
          1.质心跟踪算法(Centroid):这种跟踪方式用于跟踪有界目标如飞机,目标完全包含在摄像机的视场范围内,
    对于这种跟踪方式可选用一些预处理算法:如白热(正对比度)增强、黑热(负对比度)增强,和基于直方图的统计
 (双极性)增强。
          2.多目标跟踪算法(MTT):多目标跟踪用于有界目标如飞机、地面汽车等。它们完全在跟踪窗口内。在复杂环境



    的小目标跟踪MMT能给出一个较好的性能
          3.相关跟踪算法(Correlation):相关可用来跟踪多种类型的目标,当跟踪目标无边界且动态不是很强时这种方



    非常有效。典型应用于:目标在近距离的范围,且目标扩展到摄像机视场范围外,如一艘船。
          4.边缘跟踪算法(Edge):当跟踪目标有一个或多个确定的边缘而同时却又具有不确定的边缘,这时边缘跟踪是最



    效的算法。典型地火箭发射,它有确定好的前边缘,但尾边缘由于喷气而不定
          5.相位相关跟踪算法(Phase Correlation):相位相关算法是非常通用的算法,既可以用来跟踪无界目标也可以



    来跟踪有界目标。在复杂环境下(如地面的汽车)能给出一个好的效果。
          6.场景锁定算法(SceneLock):该算法专门用于复杂场景的跟踪。适合于空对地和地对地场景。这个算法跟踪场



   中的多个目标,然后依据每个点的运动,从而估计整个场景全局运动,场景中的目标和定位是自动选择的。当存在跟踪



   移动到摄像机视场外时,新的跟踪点能自动被标识。瞄准点初始化到场景中的某个点,跟踪启动,同时定位瞄准线。在



    种模式下,能连续跟踪和报告场景里的目标的位置
          7.组合(Combined)跟踪算法:顾名思义这种跟踪方式是两种具有互补特性的跟踪算法的组合:相关类算法 +质
          心类算法。它适合于目标尺寸、表面、特征改变很大的场景(如小船在波涛汹涌的大海里行驶)。 
   基于质心的跟踪算法大家可以参考《具有记忆跟踪功能的质心跟踪算法》刘士建,吴滢跃   这篇文章.从这篇文章中我们
   可以看出本算法实现的基本思路是通过对目标的位置、面积、灰度和形状等信息来判断目标是否被遮挡,如被遮挡则对目
   标的位置进行预测,直至目标重新出现。这种实现不仅使算法具有记忆跟踪的功,还使改进算法基本不受目标大小、旋转
   变化的影响,提高了跟踪算法的稳定性和可靠性下面是该算法的代码实现</span>




#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>


using namespace std;
using namespace cv;


/gloable variable/
bool isFirstFrame = true;
bool drawBox = false;
bool isChooseObj = false;
Rect box;//tracking object
Rect roi;//
float w = 0.4;//


vector<vector<Point> > contours;
vector<Vec4i> hierarchy;


/
 * input:
 * return :     NULL
 * Description: mouse callback function
 /
static void mouse_callback(int event, int x, int y, int, void *)
{
    switch( event )
    {
         case CV_EVENT_MOUSEMOVE:
               if (drawBox)
               {
                   box.width = x-box.x;
                   box.height = y-box.y;
               }
               break;
         case CV_EVENT_LBUTTONDOWN:
               drawBox = true;
               box = Rect(x, y, 0, 0);
               break;
         case CV_EVENT_LBUTTONUP:
               drawBox = false;
               if( box.width < 0 )
               {
                   box.x += box.width;
                   box.width *= -1;
               }
               if( box.height < 0 )
               {
                   box.y += box.height;
                   box.height *= -1;
               }
               isChooseObj = true;
               break;
     }
}


/
 * input:
 *       image---the image need to segment
 * return:
 *       threshold--segment value
 * Description: used to get the threshold for image binary
 /
int getThresholeValue(cv::Mat& image)
{
    assert(image.channels() == 1);
    int width = image.cols ;
    int height = image.rows ;
    int x = 0,y = 0;
    int pixelCount[256] = { 0 };
    float pixelPro[256] = { 0 };
    int i, j, pixelSum = width * height, threshold = 0;
    uchar* data = image.ptr<uchar>(0);
    //count every pixel number in whole image
    for(i = y; i < height; i++)
    {
        for(j = x;j <width;j++)
        {
            pixelCount[data[i * image.step + j]]++;
        }
    }
    //count every pixel's radio in whole image pixel
    for(i = 0; i < 256; i++)
    {
        pixelPro[i] = (float)(pixelCount[i]) / (float)(pixelSum);
    }
    // segmentation of the foreground and background
    // To traversal grayscale [0,255],and calculates the variance maximum
    //grayscale values for the best threshold value
    float w0, w1, u0tmp, u1tmp, u0, u1, u,deltaTmp, deltaMax = 0;
    for(i = 0; i < 256; i++)
    {
        w0 = w1 = u0tmp = u1tmp = u0 = u1 = u = deltaTmp = 0;
        for(j = 0; j < 256; j++)
        {
            if(j <= i)
            //background
            {
                w0 += pixelPro[j];
                u0tmp += j * pixelPro[j];
            }
            else
            {
                //foreground
                w1 += pixelPro[j];
                u1tmp += j * pixelPro[j];
            }
        }
        u0 = u0tmp / w0;
        u1 = u1tmp / w1;
        u = u0tmp + u1tmp;
        //Calculating the variance
        deltaTmp = w0 * (u0 - u)*(u0 - u) + w1 * (u1 - u)*(u1 - u);
        if(deltaTmp > deltaMax)
        {
            deltaMax = deltaTmp;
            threshold = i;
        }
    }


    //return the best threshold;
    return threshold;
}


/
 *
 * input:
 * curPoint-----current mass center point
 * prePoint-----previous mass center point
 * prePrePoint--prePre mass center point
 * return:next frame's obj mass center point
 * Description : use current mass center point,pre mess center
 *               point,prePre mass center point to preict next
 *               mass center point
 *
 /
Point objStatPredict(Point curPoint,Point prePoint,Point prePrePoint)
{
    Point nextMassCenter;
    nextMassCenter.x = (4*curPoint.x +prePrePoint.x - 2*prePoint.x)/3;
    nextMassCenter.y = (4*curPoint.y +prePrePoint.y - 2*prePoint.y)/3;


    return nextMassCenter;
}
/
 *
 * input:
 * argc-----current mass center point
 * argv-----previous mass center point
 *
 /
int main(int argc,char argv)
{
    Mat pFrame;//read frame from video
    VideoCapture pCapture;
    pCapture.open("./plane.mov");
    if(!pCapture.isOpened())
    {
        cout<<"open video error"<<endl;
        return -1;
    }


    namedWindow("centroid tracking");
    moveWindow("centroid tracking",100,100);
    pCapture.read(pFrame);//read a frame from video


    if(isFirstFrame)
    {


        //remind people to choose obj to tracking
        putText(pFrame,"choose obj to tracking",Point(20,20),\
                FONT_HERSHEY_SIMPLEX,1,Scalar(0,0,255));
        //Register mouse callback to draw the bounding box
        setMouseCallback("centroid tracking",mouse_callback,NULL);
        while(!isChooseObj)
        {
            imshow("centroid tracking", pFrame);
            if (cvWaitKey(33) == 'q')
            {
                return 1;
            }
        }
        cout<<"choosed tracking object"<<endl;
        roi = box;
        //Remove mouse_callback
        cvSetMouseCallback("centroid tracking", NULL, NULL );
    }


    Mat grayFrame;
    Mat thresholdFrame;
    Mat roiFrame;
    int segValue;//threshold value return by OSTU
    while(1)
    {
        pCapture.read(pFrame);


        pFrame(roi).copyTo(roiFrame);
        //absdiff(pFrame,bkground,roiFrame);
        cvtColor(roiFrame,grayFrame,CV_RGB2GRAY);
        blur(grayFrame,grayFrame, Size(7,7));
        segValue = getThresholeValue(grayFrame);
        threshold(grayFrame,thresholdFrame,segValue,255,CV_THRESH_BINARY);


        //Mat element = getStructuringElement(MORPH_RECT,Size(7,7),Point(-1,-1));
        //erode(thresholdFrame,thresholdFrame,element);
        /// Find outer contours
        findContours( thresholdFrame, contours, hierarchy, \
                      CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );


        double maxArea = 0.0;
        int maxAreaIdx = 0;
        //find the max area index
        for(int k = 0; k < contours.size(); ++k)
        {
            double tempArea = fabs(contourArea(contours[k]));
            if(tempArea > maxArea)
            {
                maxArea = tempArea;
                maxAreaIdx = k;
            }
        }


        Rect boundRect[1];
        boundRect[0] = boundingRect(contours[maxAreaIdx]);


        ///Get the moments
        vector<Moments> mu(1);
        mu[0] = moments(contours[maxAreaIdx],false);
        /// Get the mass centers:
        vector<Point2f> mc(1);
        mc[0] = Point2f(mu[0].m10/mu[0].m00+roi.x , mu[0].m01/mu[0].m00+roi.y);


        /// Draw contours
        //draw obj contour
        //drawContours(pFrame(roi),contours, maxAreaIdx,Scalar(0,0,255), \
           //          2, 8, hierarchy, 0, Point() );
        //draw mass center
        circle(pFrame,mc[maxAreaIdx], 4, Scalar(0,255,0), -1, 8, 0 );
        //calculates and returns the minimal up-right bounding
        //rectangle for the contours[maxAreaIdx] set.
        rectangle(pFrame,Rect(boundRect[0].tl().x+roi.x, \
                  boundRect[0].tl().y+roi.y,boundRect[0].width,\
                boundRect[0].height), Scalar(0,0,255), 2, 8, 0 );


        imshow("centroid tracking",pFrame);
        //imshow("centroid tracking",thresholdFrame);
        waitKey(33);
    }


    return 0;
}


实现效果如下:


OpenCV目标跟踪之质心跟踪(Centroid)


先简单实现质心跟踪算法,由于该视频只有一个目标,所以代码中直接提取的最大的那个轮廓。论文的实现我会做好后会重


新更新。
========

opencv学习系列 光流跟踪



光流跟踪法的框架图


#include "opencv2/opencv.hpp"
#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <opencv/cv.h>
#include <opencv/cvaux.h>
#include <opencv/cxcore.h>
#include <opencv/highgui.h>


const int MAX_CORNERS = 500;
int main(int argc, char argv) {
   // Initialize, load two images from the file system, and
   // allocate the images and other structures we will need for
   // results.
//
IplImage* imgA = cvLoadImage("OpticalFlow0.jpg",CV_LOAD_IMAGE_GRAYSCALE);
IplImage* imgB = cvLoadImage("OpticalFlow1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
CvSize      img_sz    = cvGetSize( imgA );
int         win_size = 10;
IplImage* imgC = cvLoadImage("OpticalFlow1.jpg",CV_LOAD_IMAGE_UNCHANGED);


// The first thing we need to do is get the features
// we want to track.
//
IplImage* eig_image = cvCreateImage( img_sz, IPL_DEPTH_32F, 1 );
IplImage* tmp_image = cvCreateImage( img_sz, IPL_DEPTH_32F, 1 );
int              corner_count = MAX_CORNERS;
CvPoint2D32f* cornersA        = new CvPoint2D32f[ MAX_CORNERS ];
/*
先计算二阶导数,在计算特征,返回马努跟踪的定义的一系列点
/
cvGoodFeaturesToTrack(
imgA,
eig_image,
tmp_image,
cornersA,
&corner_count,
0.01,
5.0,
0,
3,
0,
0.04
);
//发现亚像素精度的角点位置
cvFindCornerSubPix(
imgA,
cornersA,
corner_count,
cvSize(win_size,win_size),
cvSize(-1,-1),
cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,20,0.03)
);
// Call the Lucas Kanade algorithm
//
char features_found[ MAX_CORNERS ];
float feature_errors[ MAX_CORNERS ];
CvSize pyr_sz = cvSize( imgA->width+8, imgB->height/3 );
IplImage* pyrA = cvCreateImage( pyr_sz, IPL_DEPTH_32F, 1 );
  IplImage* pyrB = cvCreateImage( pyr_sz, IPL_DEPTH_32F, 1 );
  CvPoint2D32f* cornersB        = new CvPoint2D32f[ MAX_CORNERS ];
/
CalcOpticalFlowPyrLK( const CvArr*  prev, const CvArr*  curr,
                                     CvArr*  prev_pyr, CvArr*  curr_pyr,
                                     const CvPoint2D32f* prev_features,
                                     CvPoint2D32f* curr_features,
                                     int       count,
                                     CvSize    win_size,
                                     int       level,
                                     char*     status,
                                     float*    track_error,
                                     CvTermCriteria criteria,
                                     int       flags );
参数1:初始图像
参数2:最终图像
函数根据这两幅图像来进行光流跟踪
参数3,4:申请放入两幅图像金字塔的缓存 
参数5:存放用于寻找运动的点的数组
参数6:参数5中的点的新位置
参数7:寻找的运动的点的数目
参数8:定义了计算局部连续运动的窗口尺寸
参数9:status中的每个元素被置1(对应点在第二幅图像中被发现),否则置为0
参数10:表示被跟踪的点的原始图像小区域与此点在第二幅图像的小区域间的差的数组,可以删除那些局部外观小区


域随点的运动变化剧烈的点
参数11:迭代终止条件 ,是哟欧诺个cvTermCriteria()函数来生成该结构体
参数12:
/
  cvCalcOpticalFlowPyrLK(
     imgA,
     imgB,
     pyrA,
     pyrB,
     cornersA,
     cornersB,
     corner_count,
     cvSize( win_size,win_size ),
     5,
     features_found,
     feature_errors,
     cvTermCriteria( CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, .3 ),
     0
  );
  // Now make some image of what we are looking at:
  //
  for( int i=0; i<corner_count; i++ ) {
     if( features_found[i]==0|| feature_errors[i]>550 ) {
 //       printf("Error is %f/n",feature_errors[i]);
        continue;
     }
 //    printf("Got it/n");
     CvPoint p0 = cvPoint(
        cvRound( cornersA[i].x ),
        cvRound( cornersA[i].y )
     );
     CvPoint p1 = cvPoint(
        cvRound( cornersB[i].x ),
        cvRound( cornersB[i].y )
     );
     cvLine( imgC, p0, p1, CV_RGB(255,0,0),2 );
  }
  cvNamedWindow("ImageA",0);
  cvNamedWindow("ImageB",0);
  cvNamedWindow("LKpyr_OpticalFlow",0);
  cvShowImage("ImageA",imgA);
  cvShowImage("ImageB",imgB);
  cvShowImage("LKpyr_OpticalFlow",imgC);
  cvWaitKey(0);
  return 0;
}
========

Opencv学习笔记 光流法



本文目录:
      一.基于特征点的目标跟踪的一般方法
      二.光流法
      三.opencv中的光流法函数
      四.用类封装基于光流法的目标跟踪方法
      五.完整代码
      六.参考文献


一.基于特征点的目标跟踪的一般方法


      基于特征点的跟踪算法大致可以分为两个步骤:


      1)探测当前帧的特征点;


      2)通过当前帧和下一帧灰度比较,估计当前帧特征点在下一帧的位置;


      3)过滤位置不变的特征点,余下的点就是目标了。


      很显然,基于特征点的目标跟踪算法和1),2)两个步骤有关。特征点可以是Harris角点(见我的另外一篇博文),


也可以是边缘点等等,而估计下一帧位置的方法也有不少,比如这里要讲的光流法,也可以是卡尔曼滤波法(咱是控制系的


,上课经常遇到这个,所以看光流法看着看着就想到这个了)。


      本文中,用改进的Harris角点提取特征点(见另一篇博文:


http://blog.csdn.net/crzy_sparrow/article/details/7391511),用Lucas-Kanade光流法实现目标跟踪。


二.光流法


      这一部分《learing opencv》一书的第10章Lucas-Kanade光流部分写得非常详细,推荐大家看书。我这里也粘帖一些


选自书中的内容。


      另外我对这一部分附上一些个人的看法(谬误之处还望不吝指正):


      1.首先是假设条件:


       (1)亮度恒定,就是同一点随着时间的变化,其亮度不会发生改变。这是基本光流法的假定(所有光流法变种都必


须满足),用于得到光流法基本方程;


       (2)小运动,这个也必须满足,就是时间的变化不会引起位置的剧烈变化,这样灰度才能对位置求偏导(换句话说


,小运动情况下我们才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数),这也是光流法不可或缺


的假定;


       (3)空间一致,一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。这是Lucas-Kanade光流法特有


的假定,因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。我们假定特征点邻域内做相似运动


,就可以连立n多个方程求取x,y方向的速度(n为特征点邻域总点数,包括该特征点)。


      2.方程求解


      多个方程求两个未知变量,又是线性方程,很容易就想到用最小二乘法,事实上opencv也是这么做的。其中,最小误


差平方和为最优化指标。


      3.好吧,前面说到了小运动这个假定,聪明的你肯定很不爽了,目标速度很快那这货不是二掉了。幸运的是多尺度能


解决这个问题。首先,对每一帧建立一个高斯金字塔,最大尺度图片在最顶层,原始图片在底层。然后,从顶层开始估计下


一帧所在位置,作为下一层的初始位置,沿着金字塔向下搜索,重复估计动作,直到到达金字塔的底层。聪明的你肯定发现


了:这样搜索不仅可以解决大运动目标跟踪,也可以一定程度上解决孔径问题(相同大小的窗口能覆盖大尺度图片上尽量多


的角点,而这些角点无法在原始图片上被覆盖)。


三.opencv中的光流法函数


      opencv2.3.1中已经实现了基于光流法的特征点位置估计函数(当前帧位置已知,前后帧灰度已知),介绍如下(摘


自opencv2.3.1参考手册):


calcOpticalFlowPyrLK  
Calculates an optical flow for a sparse feature set using the iterative Lucas-Kanade method with pyramids.  
  
void calcOpticalFlowPyrLK(InputArray prevImg, InputArray nextImg, InputArray prevPts,  
InputOutputArray nextPts, OutputArray status, OutputArray err,  
Size winSize=Size(15,15), int maxLevel=3, TermCriteria crite-  
ria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),  
double derivLambda=0.5, int flags=0 )  
  
Parameters  
prevImg – First 8-bit single-channel or 3-channel input image.  
nextImg – Second input image of the same size and the same type as prevImg .  
prevPts – Vector of 2D points for which the flow needs to be found. The point coordinates  
must be single-precision floating-point numbers.  
nextPts – Output vector of 2D points (with single-precision floating-point coordinates)  
containing the calculated new positions of input features in the second image. When  
OPTFLOW_USE_INITIAL_FLOW flag is passed, the vector must have the same size as in the  
input.  
status – Output status vector. Each element of the vector is set to 1 if the flow for the  
corresponding features has been found. Otherwise, it is set to 0.  
err – Output vector that contains the difference between patches around the original and  
moved points.  
winSize – Size of the search window at each pyramid level.  
  
maxLevel – 0-based maximal pyramid level number. If set to 0, pyramids are not used  
(single level). If set to 1, two levels are used, and so on.  
criteria – Parameter specifying the termination criteria of the iterative search algorithm  
(after the specified maximum number of iterations criteria.maxCount or when the search  
window moves by less than criteria.epsilon .  
derivLambda – Not used.  
  
flags – Operation flags:  
– OPTFLOW_USE_INITIAL_FLOW Use initial estimations stored in nextPts . If the  
flag is not set, then prevPts is copied to nextPts and is considered as the initial estimate.  
calcOpticalFlowPyrLK
Calculates an optical flow for a sparse feature set using the iterative Lucas-Kanade method with pyramids.


void calcOpticalFlowPyrLK(InputArray prevImg, InputArray nextImg, InputArray prevPts,
InputOutputArray nextPts, OutputArray status, OutputArray err,
Size winSize=Size(15,15), int maxLevel=3, TermCriteria crite-
ria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
double derivLambda=0.5, int flags=0 )


Parameters
prevImg – First 8-bit single-channel or 3-channel input image.
nextImg – Second input image of the same size and the same type as prevImg .
prevPts – Vector of 2D points for which the flow needs to be found. The point coordinates
must be single-precision floating-point numbers.
nextPts – Output vector of 2D points (with single-precision floating-point coordinates)
containing the calculated new positions of input features in the second image. When
OPTFLOW_USE_INITIAL_FLOW flag is passed, the vector must have the same size as in the
input.
status – Output status vector. Each element of the vector is set to 1 if the flow for the
corresponding features has been found. Otherwise, it is set to 0.
err – Output vector that contains the difference between patches around the original and
moved points.
winSize – Size of the search window at each pyramid level.


maxLevel – 0-based maximal pyramid level number. If set to 0, pyramids are not used
(single level). If set to 1, two levels are used, and so on.
criteria – Parameter specifying the termination criteria of the iterative search algorithm
(after the specified maximum number of iterations criteria.maxCount or when the search
window moves by less than criteria.epsilon .
derivLambda – Not used.


flags – Operation flags:
– OPTFLOW_USE_INITIAL_FLOW Use initial estimations stored in nextPts . If the
flag is not set, then prevPts is copied to nextPts and is considered as the initial estimate.


四.用类封装基于光流法的目标跟踪方法


      废话少说,附上代码,包括特征点提取,跟踪特征点,标记特征点等。


//帧处理基类  
class FrameProcessor{  
    public:  
        virtual void process(Mat &input,Mat &ouput)=0;  
};  
  
//特征跟踪类,继承自帧处理基类   
class FeatureTracker :  public FrameProcessor{  
    Mat gray;  //当前灰度图   
    Mat gray_prev;  //之前的灰度图   
    vector<Point2f> points[2];//前后两帧的特征点   
    vector<Point2f> initial;//初始特征点   
    vector<Point2f> features;//检测到的特征   
    int max_count; //要跟踪特征的最大数目  
    double qlevel; //特征检测的指标  
    double minDist;//特征点之间最小容忍距离  
    vector<uchar> status; //特征点被成功跟踪的标志   
    vector<float> err; //跟踪时的特征点小区域误差和  
public:  
    FeatureTracker():max_count(500),qlevel(0.01),minDist(10.){}  
    void process(Mat &frame,Mat &output){  
        //得到灰度图   
        cvtColor (frame,gray,CV_BGR2GRAY);  
        frame.copyTo (output);  
        //特征点太少了,重新检测特征点   
        if(addNewPoint()){  
            detectFeaturePoint ();  
            //插入检测到的特征点   
            points[0].insert (points[0].end (),features.begin (),features.end ());  
            initial.insert (initial.end (),features.begin (),features.end ());  
        }  
        //第一帧   
        if(gray_prev.empty ()){  
                gray.copyTo (gray_prev);  
        }  
        //根据前后两帧灰度图估计前一帧特征点在当前帧的位置   
        //默认窗口是15*15   
        calcOpticalFlowPyrLK (  
                gray_prev,//前一帧灰度图   
                gray,//当前帧灰度图   
                points[0],//前一帧特征点位置   
                points[1],//当前帧特征点位置   
                status,//特征点被成功跟踪的标志   
                err);//前一帧特征点点小区域和当前特征点小区域间的差,根据差的大小可删除那些运动变化剧烈的点  
        int k = 0;  
        //去除那些未移动的特征点   
        for(int i=0;i<points[1].size ();i++){  
            if(acceptTrackedPoint (i)){  
                initial[k]=initial[i];  
                points[1][k++] = points[1][i];  
            }  
        }  
        points[1].resize (k);  
        initial.resize (k);  
        //标记被跟踪的特征点   
        handleTrackedPoint (frame,output);  
        //为下一帧跟踪初始化特征点集和灰度图像   
        std::swap(points[1],points[0]);  
        cv::swap(gray_prev,gray);  
    }  
  
    void detectFeaturePoint(){  
        goodFeaturesToTrack (gray,//输入图片   
                                 features,//输出特征点   
                                 max_count,//特征点最大数目  
                                 qlevel,//质量指标   
                                 minDist);//最小容忍距离  
    }  
    bool addNewPoint(){  
        //若特征点数目少于10,则决定添加特征点   
        return points[0].size ()<=10;  
    }  
  
    //若特征点在前后两帧移动了,则认为该点是目标点,且可被跟踪   
    bool acceptTrackedPoint(int i){  
        return status[i]&&  
                (abs(points[0][i].x-points[1][i].x)+  
                  abs(points[0][i].y-points[1][i].y) >2);  
    }  
  
    //画特征点   
    void  handleTrackedPoint(Mat &frame,Mat &output){  
            for(int i=0;i<points[i].size ();i++){  
                //当前特征点到初始位置用直线表示   
                line(output,initial[i],points[1][i],Scalar::all (0));  
                //当前位置用圈标出   
                circle(output,points[1][i],3,Scalar::all(0),(-1));  
            }  
        }  
};</SPAN>  
<span style="">//帧处理基类
class FrameProcessor{
    public:
        virtual void process(Mat &input,Mat &ouput)=0;
};


//特征跟踪类,继承自帧处理基类
class FeatureTracker :  public FrameProcessor{
    Mat gray;  //当前灰度图
    Mat gray_prev;  //之前的灰度图
    vector<Point2f> points[2];//前后两帧的特征点
    vector<Point2f> initial;//初始特征点
    vector<Point2f> features;//检测到的特征
    int max_count; //要跟踪特征的最大数目
    double qlevel; //特征检测的指标
    double minDist;//特征点之间最小容忍距离
    vector<uchar> status; //特征点被成功跟踪的标志
    vector<float> err; //跟踪时的特征点小区域误差和
public:
    FeatureTracker():max_count(500),qlevel(0.01),minDist(10.){}
    void process(Mat &frame,Mat &output){
        //得到灰度图
        cvtColor (frame,gray,CV_BGR2GRAY);
        frame.copyTo (output);
        //特征点太少了,重新检测特征点
        if(addNewPoint()){
            detectFeaturePoint ();
            //插入检测到的特征点
            points[0].insert (points[0].end (),features.begin (),features.end ());
            initial.insert (initial.end (),features.begin (),features.end ());
        }
        //第一帧
        if(gray_prev.empty ()){
                gray.copyTo (gray_prev);
        }
        //根据前后两帧灰度图估计前一帧特征点在当前帧的位置
        //默认窗口是15*15
        calcOpticalFlowPyrLK (
                gray_prev,//前一帧灰度图
                gray,//当前帧灰度图
                points[0],//前一帧特征点位置
                points[1],//当前帧特征点位置
                status,//特征点被成功跟踪的标志
                err);//前一帧特征点点小区域和当前特征点小区域间的差,根据差的大小可删除那些运动变化剧烈的点
        int k = 0;
        //去除那些未移动的特征点
        for(int i=0;i<points[1].size ();i++){
            if(acceptTrackedPoint (i)){
                initial[k]=initial[i];
                points[1][k++] = points[1][i];
            }
        }
        points[1].resize (k);
        initial.resize (k);
        //标记被跟踪的特征点
        handleTrackedPoint (frame,output);
        //为下一帧跟踪初始化特征点集和灰度图像
        std::swap(points[1],points[0]);
        cv::swap(gray_prev,gray);
    }


    void detectFeaturePoint(){
        goodFeaturesToTrack (gray,//输入图片
                                 features,//输出特征点
                                 max_count,//特征点最大数目
                                 qlevel,//质量指标
                                 minDist);//最小容忍距离
    }
    bool addNewPoint(){
        //若特征点数目少于10,则决定添加特征点
        return points[0].size ()<=10;
    }


    //若特征点在前后两帧移动了,则认为该点是目标点,且可被跟踪
    bool acceptTrackedPoint(int i){
        return status[i]&&
                (abs(points[0][i].x-points[1][i].x)+
                  abs(points[0][i].y-points[1][i].y) >2);
    }


    //画特征点
    void  handleTrackedPoint(Mat &frame,Mat &output){
            for(int i=0;i<points[i].size ();i++){
                //当前特征点到初始位置用直线表示
                line(output,initial[i],points[1][i],Scalar::all (0));
                //当前位置用圈标出
                circle(output,points[1][i],3,Scalar::all(0),(-1));
            }
        }
};</span>


五.完整代码
下载地址:http://download.csdn.net/detail/crzy_sparrow/4183674


  运行结果:


六.参考文献


The classic article by B. Lucas and T. Kanade, An iterative image registration technique with an application 


to stereo vision in Int. Joint Conference in Artificial Intelligence, pp. 674-679,1981, that describes the 


original feature point tracking algorithm.
The article by J. Shi and C. Tomasi, Good Features to Track in IEEE Conference on Computer Vision and 


Pattern Recognition, pp. 593-600, 1994, that describes an improved version of the original feature point 


tracking algorithm.


转载:http://blog.csdn.net/crzy_sparrow/article/details/7407604
========

目标跟踪理论方法小结



一、引言:在需要监控的环境里,如何能够判断出进入特定区域的目标,并且能够跟踪目标的轨迹。分为两种情况:一是静


态背景下的目标跟踪;二是动态背景下的目标跟踪。


二、静态背景下的目标跟踪方法
1、单目标:目标跟踪还可以分为单目标的跟踪和多目标的跟踪。单目标的静态背景下的目标跟踪指的是摄像头是固定在某


一方位,其所观察的视野也是静止的。通常采用背景差分法,即先对背景进行建模,然后从视频流中读取图像(我们称之为


前景图像),将前景图像与背景图像做差,就可以得到进入视野的目标物体。对于目标的描述,通常用目标连通区域的像素


数目的多少来表达目标的大小,或者用目标区域的高宽比等。目标的位置信息可采用投影的方式来定位。
2、多目标:静态环境下的多目标跟踪,需要确定每个目标的特征,位置,运动方向,速度等信息。
3、预处理:由于获得的图像总会有着噪声,需要对图像做一些预处理,如高斯平滑,均值滤波,或者进行一些灰度拉伸等


图像增强的操作。


三、动态背景下的目标跟踪
    摄像头在云台控制下的旋转,会使得他所采集的图像时可在变化,所以,对于整个目标跟踪过程来说,背景是变化,目


标也是在整个过程中运动的,所以跟踪起来较有难度。
    目前课题组提出的方案是:跟踪过程:在摄像头不同偏角情况下取得若干背景图片,建立背景图片库――>摄像头保持


固定时,取得当前帧图片,与图片库中的背景图像匹配,背景差分(灰度差分?),获得目标――>目标特征提取――>实时获


得当前帧图片,采用跟踪算法动态跟踪目标。
    提取特征是一个难点,课题组提出多颜色空间分析的方法。根据彩色图像在不同的颜色空间里表征同一物体呈现出的同


态性,可以把目标物体在不同的颜色空间里进行分解,并将这些关键特征信息进行融合,从而找出判别目标的本质特征。
跟踪过程中采用的各种方法说明:
1)在0-360度不同偏角时,获得背景图片,可进行混合高斯背景建模,建立图片库,以俯仰角和偏转角不同标志每张背景


图片,以备匹配使用;
2)背景差分获得目标后,对差分图像需要进行平滑、去噪等处理,去除干扰因素;
3)对目标采用多颜色空间(HSV、YUV)特征提取,对不同颜色空间的特征相与(AND),得到目标特征,以更好的在当前帧


图片中找到目标;
4)实时得到的当前帧图片,进行混合高斯建模,排除树叶摇动等引起的背景变化;
5)跟踪算法可采用多子块匹配方法、camshift方法等。


四、相关理论介绍
    近几年来,一种名为CamShift的跟踪算法凭借其在实时性和鲁棒性方面良好的表现,正受到越来越多的关注。现阶段


CamShift算法已经广泛应用到感知用户界面中的人脸跟踪中,以及一些半自动的运动目标跟踪。一方面,CamShift算法应该


属于基于区域的方法,它利用区域内的颜色信息对目标进行跟踪;另一方面,CamShift算法却是一种非参数技巧,它是通过


聚类的方式搜寻运动目标的。
    简单的说,CamShift算法利用目标的颜色特征在视频图像中找到运动目标所在的位置和大小,在下一帧视频图像中,用


运动目标当前的位置和大小初始化搜索窗口,重复这个过程就可以实现对目标的连续跟踪。在每次搜寻前将搜索窗口的初始


值设置为运动目标当前的位置和大小,由于搜索窗口就在运动目标可能出现的区域附近进行搜寻,这样就可以节省大量的搜


寻时间,使CamShift算法具有了良好的实时性。同时,CamShift算法是通过颜色匹配找到运动目标,在目标运动的过程中,


颜色信息变化不大,所以CamShift算法具有良好的鲁棒性。由于RGB颜色空间对光照亮度变化比较敏感,为了减少光照亮度


变化对跟踪效果的影响,CamShift 算法将图像由RGB 颜色空间转化到HSV 颜色空间进行后续处理。
    CamShift 的算法流程如图3.4 所示。首先选择初始搜索窗口,使窗口恰好包含整个跟踪目标,然后对窗口中每个像素


的H上的值采样,从而得到目标的色彩直方图,将该直方图保存下来作为目标的色彩直方图模型。在跟踪过程中,对视频图


像处理区域中的每一个像素,通过查询目标的色彩直方图模型,可以得到该像素为目标像素的概率,图像处理区域之外的其


他区域作为概率为0的区域。经上述处理,视频图像转换为目标色彩概率分布图,也称为目标颜色投影图。为便于显示,将


投影图转化为8位的灰度投影图,概率为1 的像素值设为255,概率为0的像素值为0,其他像素也转换为相应的灰度值。所以


,灰度投影图中越亮的像素表明该像素为目标像素的可能性越大。
    图中虚线标示的部分是CamShift算法的核心部分,主要目标是在视频图像中找到运动目标所在的位置,这一部分被称为


Mean Shift算法。由于Mean Shift是CamShift的核心,所以正确理解MeanShift就成了理解CamShift算法的关键,下面就重


点讨论Mean Shift算法。


2、混合高斯模型
    背景中当树叶在摇动时,它会反复地覆盖某像素点然后又离开,此像素点的值会发生剧烈变化,为有效地提取感兴趣的


运动目标,应该把摇动的树叶也看作背景。这时任何一个单峰分布都无法描述该像素点的背景,因为使用单峰分布就表示己


经假定像素点的背景在除了少量噪声以外是静止的,单模态模型无法描述复杂的背景。在现有效果较好的背景模型中有些为


像素点建立了多峰分布模型(如混合高斯模型),有些对期望的背景图像进行预测,这些算法的成功之处在于定义了合适的像


素级稳态(Stationarity )准则,满足此准则的像素值就认为是背景,在运动目标检测时予以忽略。对于特定的应用场景,


要想对特定算法的弱点与优势进行评价,必须明确这种像素级稳态准则。
    对于混乱的复杂背景,不能使用单高斯模型估计背景,考虑到背景像素值的分布是多峰的,可以根据单模态的思想方法


,用多个单模态的集合来描述复杂场景中像素点值的变化,混合高斯模型正是用多个单高斯函数来描述多模态的场景背景。


混合高斯模型的基本思想是:对每一个像素点,定义K个状态来表示其所呈现的颜色,K值一般取3-5之间(取决于计算机内存


及对算法的速度要求),K值越大,处理波动能力越强,相应所需的处理时间也就越长。K个状态中每个状态用一个高斯函数


表示,这些状态一部分表示背景的像素值其余部分则表示运动前景的像素值。 


计算机视觉目标检测跟踪 标准测试视频下载


    在视频中获取移动目标,这涉及到图像分割,背景消除和阴影去除,主要思想,是去除背景,获取前景。这里一般把近


似不动的物体默认为背景,而运动的物体为前景。除了采用分类器训练以外,还有一些很常见的算法。这里我参考一些论文


和朋友总结把这些算法归类,如果要相应论文的,可以联系我,希望对有用到的朋友一点帮助。


一,通过模板的图像分割:


1.       将边缘点聚类成线
2.       Hough变换
3.       RANSAC


二,背景消除:


1.    基于像素方法


a 帧差法(frame differencing)


b 均值-阈值方法(mean-threshold method)


c 混合高斯模型(Gaussian mixture model)


d 非参数模型(non-parametric)


2.    基于区域方法


a LBP纹理(LBP-texture)


b 共生矩阵(co-occurrence matrices)


c 协方差矩阵(covariance matrices)


d 直方图方法(accumulated histogram)


3.    其他方法


向量分解法(eigenspace decomposition)


西安电子科技大学张瑞娟的一篇硕士论文“图像配准理论及算法研究”,我收获很大,所以我也总结一些对我有用的算法,


将来便于查找应用。


我做的目标追踪这一块,虽然和图像配准不是一个方向,但是前期工作都是一样的,首先都需要物体检测,特征检测和匹配


。这里我总结一些对我有用的,也希望对和我一样研究方向的人有帮助。这里图像配准可以换成物体匹配的。


1,  图像配准要素结合:特征空间,搜索空间,搜索策略,近似性度量


2,  图像配准方法:


2.1基于灰度信息的方法,


交叉相关(互相关)方法,相关系数度量,序贯相似检测算法,信息理论的交换信息相似性准则


2.2基于变换域的方法


相位相关法,Walsh Transform变换


2.3基于特征的方法


常用的图像特征有:特征点(包括角点、高曲率点等)、直线段、边缘(Robert、高斯-拉普拉斯LoG、Canny、Gabor滤波等


边缘检测算子)或轮廓、闭合区域、特征结构以及统计特征如矩不变量等


注:像素灰度信息的互相关算法相比,特征提取包含了高层信号信息,所以该类算法对光照、噪声等的抗干扰能力强。


3,常用的空间变换模型


刚体变换(平移、旋转与缩放的组合)、仿射变换、透射变换、投影变换、非线性变换


4,  常用的相似性测度


4.1距离测度


均方根误差,差绝对值和误差,兰氏距离,Mahalanobis距离,绝对差,Hausdorff距离等


4.2角度度量法(概率测度)。


4.3 相关度量法


5,配准算法的评价标准


配准时间、配准率、算法复杂度、算法的可移植性、算法的适用性、图像数据对算法的影响等(这里虽然不是目标追踪的评


价标准,但是我们可以借鉴这些评价算法的标准)


以下转载自:http://blog.csdn.net/pp5576155/article/details/6962694。


图像跟踪是一个不断发展的研究方向,新的方法不断产生,再加上其它学科的方法的引入,因此对于图像跟踪算法的分类没


有确定的标准。对于所有的跟踪算法,需要解决两个关键问题:目标建模和目标定位[35]。以下根据目标建模所用的视觉特


征和目标定位所用的方法对跟踪算法分类。
      1.据视觉特征分类:欲实现目标的准确定位需要以描述目标的视觉特征建立其表观模型。具有良好可分性的视觉特


征,是实现对跟踪目标与视场背景精确分割与提取的关键,因此视觉特征的选择是实现鲁棒跟踪前提。若所选视觉特征具有


较强可分性,即使简单的跟踪算法也能实现可靠跟踪。反之不然。常用的视觉特征分类如下:
        颜色:由于颜色特征具有较好的抗击平面旋转、非刚性变形以及部分遮挡的
能力,变形目标跟踪中表现出较强的鲁棒性,因此广泛的应用于视频跟踪的目标特征选择上。文献[56]中的基于颜色直方图


跟踪算法(Color Histogram),采用Mean Shift算法实现对非刚性目标的鲁棒跟踪。此算法的不足表现在目标遮挡和相邻两


帧出现较大的目标位移时,由于Mean Shift算法搜索区域局限于局部状态空间,此时会出现跟踪发散。为解决此问题,文献


[57,58]中,由Perez等人和Nummiaro等人提出将颜色特征作为粒子滤波观测模型,实现了复杂环境下(目标遮挡)的可靠跟


踪。此算法不足在于,当背景出现与目标颜色分布相似干扰物时,易造成粒子发散,因此Birchfield等人[39]提出空间-颜


色直方图跟踪算法,充分利用像素点之间的空间关系而不局限于颜色分布,改善了跟踪性能。
       边缘:虽然颜色特征具有较好的抗目标形变的能力,但是缺乏对目标空间结构的描述,且对光照敏感。因此在光照


变化频繁的跟踪视场下,常采用目标边缘特征。文献[40-44]将边缘信息作为目标可分性特征,从而实现可靠跟踪。由于颜


色与边缘特征具有互补特性,因此将两种信息融合建立目标特征模型的算法,近年来引起研究者广泛关注[45-47]。上述基


于边缘特征的跟踪算法存在计算耗时较长以及形状模型单一的问题,制约了跟踪算法的实时性及可靠性。因此文献[48-50]


提出了基于边缘方位直方图特征的跟踪算法,此算法对光照变化不敏感且比单一轮廓边缘特征具有更丰富的信息。
      光流特征:光流特征通常是采用Lucak-Kande算法计算像素点光流的幅值和方向,文献[51]为利用光流实现人脸跟踪


实例。由于光流法运算量较大很难满足实时性要求,并且光流法本身对光照变化和噪声敏感,都限制了光流法的实际应用。
       小波:由于金字塔可实现在不同角度、尺度上对图像进行描述的功能,这也是实现差分运动估计的基础[52,53]。
       局部特征描述子:图像的局部区域特征具有对光照、尺度、旋转的不变性。局部区域特征从局部区域内提取特征点


,并以相应的描述子对其描述。文献[54,55]分别以局部二元模式和(SIFT)特征实现目标跟踪。
        空间与颜色融合:颜色特征描述目标全局颜色分布,属于全局信息,虽然具有一定的抗目标形变能力,但由于缺乏


对像素间空间结构的描述易受到背景中相似颜色分布区域的干扰,文献[56,57]将空间信息与颜色直方图融合,作为目标特


征取得了良好的跟踪效果。
       特征基(Eigen-Basis):将图像信息从高维空间映射到低维空间,图像在低维空间成为一个流形,通过一定的几何


、统计工具可以提取和分析。PCA、LDA是图像跟踪领域构建子空间广泛采用的方法。特征跟踪(Eigen-Tracking)方法


[18,58-61]以Karhunen-Loeve构建表征目标表观的特征基,再通过递增SVD实现对特征基的在线更新。文献[62]以局部线性


嵌入流形LLE将跟踪问题映射到非线性低维流型空间去解决。
       模式分类:利用分类器将跟踪目标从背景中分割出来是以模式分类的方法解决视频跟踪问题。文献[64,65]同时强调


目标与背景的重要性,通过特征评价算法建立对目标和背景具有良好可分性的的视觉特征实现跟踪。Avidan[65]以支持向量


机SVM离线学习得到目标与背景特征,称为支持向量机跟踪算法(SVM-Tracking)。文献[67]利用集成学习将弱分类器


(Adaboost方法训练得到弱分类器)组合成强分类器,由此强分类器实现对视频帧中目标与背景分类,即像素分类置信图(


Confidence Map),由分类置信图的模式得到当前帧中目标位置,将输出的目标位置反馈,训练出新的强分类器以实现后续


的分类。为克服上述文献中由于采用离线学习方法使得跟踪算法不满足实时性的问题,Grabner等人[68]提出了基于在线


Adaboost训练分类器的跟踪算法。
2.依据目标定位所使用的方法分类:目标定位根据历史信息推理当前帧中目标位置信息的过程。依据目标定位方法对跟踪


算法分类如下:
       概率跟踪方法:概率跟踪方法是采用Bayesian滤波理论解决状态估计问题在视频跟踪领域的应用,通过预测和修正


过程采用一种递推方式实现时变状态的估计。表征目标位置信息的状态量通常由位置坐标、速度、尺度以及旋转角度构成,


状态量通过状态转移模型向前推进即实现状态预测,通过最新观测值以及观测似然模型对状态预测置信度进行评价,从而对


预测值做出修正。在模型线性(状态转移模型和观测模型)、系统噪声和观测噪声服从高斯分布时,Kalman滤波能给出


Bayesian滤波最优解;对于非线性Bayesian滤波,扩展Kalman(EKF)以及无味Kalman(UKF) [69,70]给出了次优解。隐马尔科


夫模型(HMM)[44]用于实现状态空间有限、离散情况下的状态估计。对于状态模型和观测模型均为非线性且噪声为非高斯,


同时状态分布呈多模态,利用Monte Carlo(MC)方法通过采样估计目标状态后验分布,取得了良好的效果,其中以PF为代表


的MC采样方法成为了研究热点。
      确定性跟踪方法:该类算法的基本思想是由目标检测或者手动设置方式获取目标模板,度量目标模板与备选目标位置


的相识度称为评价函数。跟踪的过程,即将备选目标位置与目标模板匹配的过程。以最优化方法计算评价函数最大值,使得


评价函数取得最大值时的备选目标位置判断为是目标在当前视频帧中的估计位置[36,56,71]。通常选择颜色直方图距离作为


相识度评价函数。该类算法在一定场景下能实现快速可靠的跟踪,但是该类算法的可靠性是建立在目标模板在跟踪过程中不


发生变化的假设之上,因此当目标表观模型改变,跟踪结果与实际目标位置会产生较大偏离甚至失跟。针对此问题,文献


[72]提出实时更新目标表观直方图的方法,提高了确定性跟踪算法的鲁棒性。由于概率跟踪方法能够解决复杂背景下的目标


状态估计,特别是以PF为代表的MC积分法实现对Bayesian滤波的近似已成为跟踪算法的主流。由于PF以一组随机加权样本近


似Bayesian滤波,不受模型线性高斯假设的限制,PF已成为解决非线性非高斯模型下的状态估计问题的有力工具[38,41,42]





========

目标跟踪学习笔记_1(opencv中meanshift和camshift例子的应用)



     在这一节中,主要讲目标跟踪的一个重要的算法Camshift,因为它是连续自使用的meanShift,所以这2个函数opencv


中都有,且都很重要。为了让大家先达到一个感性认识。这节主要是看懂和运行opencv中给的sample并稍加修改。


     Camshift函数的原型为:RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)。


     其中probImage为输入图像直方图的反向投影图,window为要跟踪目标的初始位置矩形框,criteria为算法结束条件。


函数返回一个有方向角度的矩阵。该函数的实现首先是利用meanshift算法计算出要跟踪的中心,然后调整初始窗口的大小


位置和方向角度。在camshift内部调用了meanshift算法计算目标的重心。


     下面是一个opencv自带的CamShift算法使用工程实例。该实例的作用是跟踪摄像头中目标物体,目标物体初始位置用


鼠标指出,其跟踪窗口大小和方向随着目标物体的变化而变化。其代码及注释大概如下:


目标跟踪学习笔记_1(opencv中meanshift和camshift例子的应用)
  1 #include "StdAfx.h"
  2 
  3 #include "opencv2/video/tracking.hpp"
  4 #include "opencv2/imgproc/imgproc.hpp"
  5 #include "opencv2/highgui/highgui.hpp"
  6 
  7 
  8 #include <iostream>
  9 #include <ctype.h>
 10 
 11 using namespace cv;
 12 using namespace std;
 13 
 14 Mat image;
 15 
 16 bool backprojMode = false; //表示是否要进入反向投影模式,ture表示准备进入反向投影模式
 17 bool selectObject = false;//代表是否在选要跟踪的初始目标,true表示正在用鼠标选择
 18 int trackObject = 0; //代表跟踪目标数目
 19 bool showHist = true;//是否显示直方图
 20 Point origin;//用于保存鼠标选择第一次单击时点的位置
 21 Rect selection;//用于保存鼠标选择的矩形框
 22 int vmin = 10, vmax = 256, smin = 30;
 23 
 24 void onMouse( int event, int x, int y, int, void* )
 25 {
 26     if( selectObject )//只有当鼠标左键按下去时才有效,然后通过if里面代码就可以确定所选择的矩形区域


selection了
 27     {
 28         selection.x = MIN(x, origin.x);//矩形顶点坐标
 29         selection.y = MIN(y, origin.y);
 30         selection.width = std::abs(x - origin.x);//矩形宽
 31         selection.height = std::abs(y - origin.y);//矩形高
 32 
 33         selection &= Rect(0, 0, image.cols, image.rows);//用于确保所选的矩形区域在图片范围内
 34     }
 35 
 36     switch( event )
 37     {
 38     case CV_EVENT_LBUTTONDOWN:
 39         origin = Point(x,y);
 40         selection = Rect(x,y,0,0);//鼠标刚按下去时初始化了一个矩形区域
 41         selectObject = true;
 42         break;
 43     case CV_EVENT_LBUTTONUP:
 44         selectObject = false;
 45         if( selection.width > 0 && selection.height > 0 )
 46             trackObject = -1;
 47         break;
 48     }
 49 }
 50 
 51 void help()
 52 {
 53     cout << "\nThis is a demo that shows mean-shift based tracking\n"
 54             "You select a color objects such as your face and it tracks it.\n"
 55             "This reads from video camera (0 by default, or the camera number the user enters\n"
 56             "Usage: \n"
 57             "    ./camshiftdemo [camera number]\n";
 58 
 59     cout << "\n\nHot keys: \n"
 60             "\tESC - quit the program\n"
 61             "\tc - stop the tracking\n"
 62             "\tb - switch to/from backprojection view\n"
 63             "\th - show/hide object histogram\n"
 64             "\tp - pause video\n"
 65             "To initialize tracking, select the object with mouse\n";
 66 }
 67 
 68 const char* keys = 
 69 {
 70     "{1|  | 0 | camera number}"
 71 };
 72 
 73 int main( int argc, const char argv )
 74 {
 75     help();
 76 
 77     VideoCapture cap; //定义一个摄像头捕捉的类对象
 78     Rect trackWindow;
 79     RotatedRect trackBox;//定义一个旋转的矩阵类对象
 80     int hsize = 16;
 81     float hranges[] = {0,180};//hranges在后面的计算直方图函数中要用到
 82     const float* phranges = hranges;
 83     CommandLineParser parser(argc, argv, keys);//命令解析器函数
 84     int camNum = parser.get<int>("1");     
 85     
 86     cap.open(camNum);//直接调用成员函数打开摄像头
 87 
 88     if( !cap.isOpened() )
 89     {
 90         help();
 91         cout << "*Could not initialize capturing...*\n";
 92         cout << "Current parameter's value: \n";
 93         parser.printParams();
 94         return -1;
 95     }
 96 
 97     namedWindow( "Histogram", 0 );
 98     namedWindow( "CamShift Demo", 0 );
 99     setMouseCallback( "CamShift Demo", onMouse, 0 );//消息响应机制
100     createTrackbar( "Vmin", "CamShift Demo", &vmin, 256, 0 );//createTrackbar函数的功能是在对应的窗口创


建滑动条,滑动条Vmin,vmin表示滑动条的值,最大为256
101     createTrackbar( "Vmax", "CamShift Demo", &vmax, 256, 0 );//最后一个参数为0代表没有调用滑动拖动的响应


函数
102     createTrackbar( "Smin", "CamShift Demo", &smin, 256, 0 );//vmin,vmax,smin初始值分别为10,256,30
103 
104     Mat frame, hsv, hue, mask, hist, histimg = Mat::zeros(200, 320, CV_8UC3), backproj;
105     bool paused = false;
106     
107     for(;;)
108     {
109         if( !paused )//没有暂停
110         {
111             cap >> frame;//从摄像头抓取一帧图像并输出到frame中
112             if( frame.empty() )
113                 break;
114         }
115 
116         frame.copyTo(image);
117         
118         if( !paused )//没有按暂停键
119         {
120             cvtColor(image, hsv, CV_BGR2HSV);//将rgb摄像头帧转化成hsv空间的
121 
122             if( trackObject )//trackObject初始化为0,或者按完键盘的'c'键后也为0,当鼠标单击松开后为-1
123             {
124                 int _vmin = vmin, _vmax = vmax;
125 
126                 //inRange函数的功能是检查输入数组每个元素大小是否在2个给定数值之间,可以有多通道,mask保


存0通道的最小值,也就是h分量
127 //这里利用了hsv的3个通道,比较h,0~180,s,smin~256,v,min(vmin,vmax),max(vmin,vmax)。如果3个通道都在对应的


范围内,则
128 //mask对应的那个点的值全为1(0xff),否则为0(0x00).
129                 inRange(hsv, Scalar(0, smin, MIN(_vmin,_vmax)),
130                         Scalar(180, 256, MAX(_vmin, _vmax)), mask);
131                 int ch[] = {0, 0};
132                 hue.create(hsv.size(), hsv.depth());//hue初始化为与hsv大小深度一样的矩阵,色调的度量是用


角度表示的,红绿蓝之间相差120度,反色相差180度
133                 mixChannels(&hsv, 1, &hue, 1, ch, 1);//将hsv第一个通道(也就是色调)的数到hue中,0索引数组
134 
135                 if( trackObject < 0 )//鼠标选择区域松开后,该函数内部又将其赋值1
136                 {
137                     //此处的构造函数roi用的是Mat hue的矩阵头,且roi的数据指针指向hue,即共用相同的数据,


select为其感兴趣的区域
138                     Mat roi(hue, selection), maskroi(mask, selection);//mask保存的hsv的最小值
139 
140 //calcHist()函数第一个参数为输入矩阵序列,第2个参数表示输入的矩阵数目,第3个参数表示将被计算直方图维数通


道的列表,第4个参数表示可选的掩码函数
141 //第5个参数表示输出直方图,第6个参数表示直方图的维数,第7个参数为每一维直方图数组的大小,第8个参数为每一


维直方图bin的边界
142                     calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);//将roi的0通道计算直方图并


通过mask放入hist中,hsize为每一维直方图的大小
143                     normalize(hist, hist, 0, 255, CV_MINMAX);//将hist矩阵进行数组范围归一化,都归一化到


0~255
144                     
145                     trackWindow = selection;
146                     trackObject = 1;//只要鼠标选完区域松开后,且没有按键盘清0键'c',则trackObject一直保


持为1,因此该if函数只能执行一次,除非重新选择跟踪区域
147 
148                     histimg = Scalar::all(0);//与按下'c'键是一样的,这里的all(0)表示的是标量全部清0
149                     int binW = histimg.cols / hsize;  //histing是一个200*300的矩阵,hsize应该是每一个bin


的宽度,也就是histing矩阵能分出几个bin出来
150                     Mat buf(1, hsize, CV_8UC3);//定义一个缓冲单bin矩阵
151                     for( int i = 0; i < hsize; i++ )//saturate_case函数为从一个初始类型准确变换到另一个


初始类型
152                         buf.at<Vec3b>(i) = Vec3b(saturate_cast<uchar>(i*180./hsize), 255, 255);//Vec3b为


3个char值的向量
153                     cvtColor(buf, buf, CV_HSV2BGR);//将hsv又转换成bgr
154                         
155                     for( int i = 0; i < hsize; i++ )
156                     {
157                         int val = saturate_cast<int>(hist.at<float>(i)*histimg.rows/255);//at函数为返回


一个指定数组元素的参考值
158                         rectangle( histimg, Point(i*binW,histimg.rows),    //在一幅输入图像上画一个简单


抽的矩形,指定和,并定义颜色,大小,线型等
159                                    Point((i+1)*binW,histimg.rows - val),
160                                    Scalar(buf.at<Vec3b>(i)), -1, 8 );
161                     }
162                 }
163 
164                 calcBackProject(&hue, 1, 0, hist, backproj, &phranges);//计算直方图的反向投影,计算hue图


像0通道直方图hist的反向投影,并让入backproj中
165                 backproj &= mask;
166 
167                 //opencv2.0以后的版本函数命名前没有cv两字了,并且如果函数名是由2个意思的单词片段组成的话


,且前面那个片段不够成单词,则第一个字母要
168 //大写,比如Camshift,如果第一个字母是个单词,则小写,比如meanShift,但是第二个字母一定要大写
169                 RotatedRect trackBox = CamShift(backproj, trackWindow,               //trackWindow为鼠标


选择的区域,TermCriteria为确定迭代终止的准则
170                                     TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 


));//CV_TERMCRIT_EPS是通过forest_accuracy,CV_TERMCRIT_ITER
171                 if( trackWindow.area() <= 1 )                                                  //是通过


max_num_of_trees_in_the_forest  
172                 {
173                     int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
174                     trackWindow = Rect(trackWindow.x - r, trackWindow.y - r,
175                                        trackWindow.x + r, trackWindow.y + r) &
176                                   Rect(0, 0, cols, rows);//Rect函数为矩阵的偏移和大小,即第一二个参数为


矩阵的点坐标,第三四个参数为矩阵的宽和高
177                 }
178 
179                 if( backprojMode )
180                     cvtColor( backproj, image, CV_GRAY2BGR );//因此投影模式下显示的也是rgb图?
181                 ellipse( image, trackBox, Scalar(0,0,255), 3, CV_AA );//跟踪的时候以椭圆为代表目标
182             }
183         }
184 
185         //后面的代码是不管pause为真还是为假都要执行的
186         else if( trackObject < 0 )//同时也是在按了暂停字母以后
187             paused = false;
188 
189         if( selectObject && selection.width > 0 && selection.height > 0 )
190         {
191             Mat roi(image, selection);
192             bitwise_not(roi, roi);//bitwise_not为将每一个bit位取反
193         }
194 
195         imshow( "CamShift Demo", image );
196         imshow( "Histogram", histimg );
197 
198         char c = (char)waitKey(10);
199         if( c == 27 )              //退出键
200             break;
201         switch(c)
202         {
203         case 'b':             //反向投影模型交替
204             backprojMode = !backprojMode;
205             break;
206         case 'c':            //清零跟踪目标对象
207             trackObject = 0;
208             histimg = Scalar::all(0);
209             break;
210         case 'h':          //显示直方图交替
211             showHist = !showHist;
212             if( !showHist )
213                 destroyWindow( "Histogram" );
214             else
215                 namedWindow( "Histogram", 1 );
216             break;
217         case 'p':       //暂停跟踪交替
218             paused = !paused;
219             break;
220         default:
221             ;
222         }
223     }
224     return 0;
225 }
目标跟踪学习笔记_1(opencv中meanshift和camshift例子的应用) 


运行截图如下(由于摄像头中一般会拍到人,影响不好,所以含目标物体的截图就不贴上来了):


     另外,由于Camshift主要是利用到了meanShift算法,在目标跟踪领域应用比较广泛,而meanShift也可以用于目标跟


踪,只是自适用性没CamShift好,但也可以用。首先看看meanShift算法的声明:


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


      与CamShift函数不同的一点是,它返回的不是一个矩形框,而是一个int型变量。该int型变量应该是代表找到目标物


体的个数。特别需要注意的是参数window,它不仅是目标物体初始化的位置,还是实时跟踪目标后的位置,所以其实也是一


个返回值。由于meanShift好像主要不是用于目标跟踪上,很多应用是在图像分割上。但是这里还是将CamShift算法例子稍


微改一下,就成了meanShift算法了。主要是用window代替CamShift中的trackWindow.


      本文感性上认识了怎样使用meanShift()和CamShift()函数,跟进一步的实现原理需要看其相关的论文和代码才能理


解。但是从本例中调用的其它函数也可以学到很多opencv函数,效果还是很不错的。
========
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

opencv动态目标跟踪学习总结 的相关文章

  • 积跬步,聚小流------用smartpaginator来做分页

    网络上的实例 xff08 jquery smarypaginator 例图 xff09 如果说是从 百度 上搜索过 jquery分页插件 的朋友 xff0c 相信对上面的图片不会陌生 xff0c 几乎所有介绍 jquery分页插件 的文章中
  • 我的2017-搭建个人网站,搭建PHP环境(2)

    上周确定了 xff0c 想要应用的后台语言 xff0c 面临的最大问题就是 xff1a php我不会啊 xff0c 哈哈哈哈 xff0c 所以接下来首先要做的就是了解 学习php的相关知识 接下来的第一步 xff1a 环境搭建 1 下载安装
  • 我的2017-搭建个人网站,hello PHP(2)

    学习一门语言 xff0c 例行惯例 xff0c 先来个 hello world 搭建好了php环境 xff0c 然后就可以运行php了 xff0c 首先用一种最简单的方法 xff0c 在wamp安装位置 xff08 相应的文件夹 xff09
  • 我的2017-搭建个人网站,自拟定代码根目录

    wampserver集成安装环境安装的php的运行根目录在wamp文件夹中的www文件夹下 xff0c 而为了有效的将代码和服务器进行分离 xff0c 可以采用自拟定代码根目录进行修改 1 确定代码编辑位置 xff0c 修改服务器默认指向
  • 2013年终总结

    2013年即将过去 xff0c 回顾这一年 xff0c 有得有失 xff0c 有喜有悲 xff0c 些许记忆碎片留在脑海中 简单做个总结 xff0c 也算划上一个完美的句号 xff0c 再迎接充满挑战的2014 xff01 项目 一年过来
  • 编译原理:求First集与Follow集的方法

    明天就要考试了 xff0c 发现一直理解错了First集与Follow集的解法 xff0c 贴上比较好理解的 文法 xff1a S ABc A a B b First集合求法 能 由非终结符号推出的所有的开头符号或可能的 xff0c 但要求
  • 位运算n & (n-1)的妙用

    本文转自 xff1a http blog csdn net zheng0518 article details 8882394 按位与的知识 n amp n 1 作用 xff1a 将n的二进制表示中的最低位为1的改为0 xff0c 先看一个
  • 了解CesiumLab地理信息基础数据处理平台模型切片参数设置

    前文转换一个fbx模型为3dtiles没有成功 xff0c 先来看一下参数设置 xff1b 参数设置 空间参考 通用模型大部分没有自带空间参考 xff0c 使用一个默认值 ENU 39 90691 116 39123 xff1b 此位置在天
  • 二分查找算法(Java版)

    二分查找算法是非常经典且基本的算法 1 二分查找又称折半查找 xff0c 优点是比较次数少 xff0c 查找速度快 xff0c 平均性能好 xff1b 其缺点是要求待查表为有序表 xff0c 且插入删除困难 因此 xff0c 折半查找方法适
  • 电脑眼睛保护色——绿豆沙色

    眼科医生建议电脑屏幕不要用白色 xff0c 因为白色对眼睛的刺激是最大的 xff0c 最损伤视力 xff01 眼科医生推荐的颜色是 柔和的淡绿色 xff0c 也有人叫做绿豆沙色 按HSU 设置为 xff1a 色度 HUE xff1a 85
  • Java中两个数交换的细节问题

    在码代码过程中 xff0c 两个数交换是经常用到的 xff0c 但在Java 中 xff0c 如果忽略了值传递和引用传递就很容易出错 看一下两个整数交换代码 xff1a public void swap int a int b int t
  • 微信 Mars Android Sample 源码分析

    注 xff1a 原文首发地址 零 前言 Mars 是微信官方开源的跨平台跨业务的终端基础组件 xff0c 具有高质量网络连接模块 长短连接 智能心跳机制 高性能日志模块和网络监测组件等 而整个 Android Sample 是基于 Mars
  • Android NDK 开发:实战案例

    0 前言 如果只学理论 xff0c 不做实践 xff0c 不踩踩坑 xff0c 一般很难发现真正实践项目中的问题的 xff0c 也比较难以加深对技术的理解 所以延续上篇 JNI 的实战Android NDK开发 xff1a JNI实战篇 x
  • Android NDK 开发:CMake 使用

    1 前言 当在做 Android NDK 开发时 xff0c 如果不熟悉用 CMake 来构建 xff0c 读不懂 CMakeLists txt 的配置脚本 xff0c 很容易就会踩坑 xff0c 遇到编译失败 xff0c 一个很小的配置问
  • python中的“main()方法”

    估计很多人跟我一样初学python看代码的时候先找一下main 方法 xff0c 从main往下看 但事实上python中是没有你理解中的 main 方法的 言归正传 if name 61 61 34 main 34 可以看成是python
  • 【大陆ARS408毫米波雷达】一种利用串口解析雷达数据的方法

    硬件平台 xff1a ARS408毫米波雷达 can转485转换器 485转串口转换器 软件平台 xff1a Windows10 python3 本篇博客实现的功能 xff1a 一 通过两个转换器将毫米波雷达的原始数据传入电脑端的串口中 二
  • ubuntu14.04系统下对SD卡分区

    在ubuntu14 04系统下对SD卡进行分区分为3步 xff1a 注意 xff1a 进行SD卡分区时 xff0c 用户操作权限为root权限 xff01 1 umount SD卡 查看SD卡挂载目录 xff0c 一般在 media目录下
  • docker安装图形化管理界面

    首先看下这个界面的样子 还是比较好看 xff0c 而且在同一个局域网中都可以登录进行管理 说下安装教程吧 首先下载这个镜像 xff1a sudo docker pull portainer portainer 然后创建这个容器 sudo d
  • JSP小脚本学习

    小脚本 可以将任何数量的小脚本包含在页面中 xff0c 小脚本是有效的JAVA语言语句 xff0c 变量或方法声明或表达式 小脚本的语法 xff0c lt code fragment gt 入门示例 xff1b lt 64 page lan
  • 34. 在排序数组中查找元素的第一个和最后一个位置(C语言)

    笨办法 xff0c 先找第一个等于target的位置 xff0c 再找最后一个等于target的位置 Note The returned array must be malloced assume caller calls free int

随机推荐

  • 详解如何将TensorFlow训练的模型移植到Android手机

    前言 本文中出现的TF皆为TensorFlow的简称 先说两句题外话吧 xff0c TensorFlow 前两天热热闹闹的发布了正式版r1 0 xff0c 可感觉自己才刚刚上手 r0 12 xff0c 这个时代发展的太快 xff0c 脚步是
  • ROS实践手册(一)ROS安装教程

    笔者根据 古月居 ROS入门21讲 学习整理 xff0c 并参考 ROS机器人开发实践 一书 虚拟机安装 注 该部分可参考 古月居 ROS入门21讲 第2讲下载并安装 VMware Workstation Pro下载 Ubuntu18 04
  • Shell系统学习之如何执行Shell程序

    系列文章目录 Shell系统学习之什么是Shell Shell系统学习之创建一个Shell程序 Shell系统学习之向Shell脚本传递参数 Shell系统学习之如何执行Shell程序 Shell系统学习之Shell变量和引用 Shell系
  • target_link_libraries接口的使用

    target link libraries需要放在add executable之后 xff0c 用于指明连接进来的库 xff0c 官方推荐使用这个接口 xff0c 而不推荐使用link libraries xff0c link librar
  • TTL和RS232之间的详细对比

    背景 之前就听过TTL xff0c 一直没搞懂其和RS232的区别 最近 xff0c 打算去买个USB转RS232的芯片 xff0c 结果找到此产品 xff1a 六合一多功能USB转UART串口模块CP2102 usb TTL485 232
  • STL 解算法题目例子

    STL解算法题目例子
  • 双子天蝎,爱情是不老的传说

    双子天蝎 xff0c 爱情是不老的传说 自注 此文章乃双子座所写 定有主观上的个人倾向 转帖者 xff1a 就是我啦 xff0c 一个双子座的帅哥 xff08 自封 xff09 关于双子和天蝎 xff0c 我是很想很完整的写一些 xff0c
  • ASP2.0-130道ASP.NET面试题

    1 简述 private protected public internal 修饰符的访问权限 答 private 私有成员 在类的内部才可以访问 protected 保护成员 xff0c 该类内部和继承类中可以访问 public 公共成员
  • opencv 图像去噪学习总结

    OpenCV图像处理篇之图像平滑 图像平滑算法 程序分析及结果 图像平滑算法 图像平滑与图像模糊是同一概念 xff0c 主要用于图像的去噪 平滑要使用滤波器 xff0c 为不改变图像的相位信息 xff0c 一般使用线性滤波器 xff0c 其
  • Ubuntu18.04 装系统、cuda、cudnn,主要是Ubuntu的内核版本不能太高,亲测很成功

    一 装系统 简单的我就不说了 xff0c 之说要点 1 选择为图形或无线硬件 安装第三方软件 2 在安装类型中 xff0c 选择其他选项 3 分区 xff0c 我选择分区2 3个 EFI分区 xff0c 主空间 xff0c 空间起始位置 x
  • 被透明元素遮挡的元素还可以被点击到吗?

    遮挡 关于是否被遮挡的判断 xff0c 可以从对层叠级别的判断而确定 见 xff1a 说说标准 CSS核心可视化格式模型 visual formatting model 之十三 xff1a 分层的显示 Layered presentatio
  • 闲谈两句windows,linux

    今天无意在一个群里说了一句 34 我觉得ubuntu比vista还好用 34 马上引来一帮人的反击 xff0c 所用伎俩仍然没有新意 1偷换概念 xff0c 开始用winxp说事 2游戏 xff0c 网银 3windows的系统很稳定 xf
  • python类的基本操作

    本节给出类的基本操作函数 xff0c 方法查阅备用 0 定义类 span class token keyword class span span class token class name student span span class
  • python的异常类型

    1 内建的异常类 异常类含义Exception所有异常的基类AttributeError特性引用或赋值失败引发IOError试图打开不存在文件 包括其他情况 时引发IndexError使用序列中不存在的索引时引发KeyError在使用映射时
  • TFLearn代码示例

    span class token keyword import span tflearn span class token keyword from span tflearn span class token punctuation spa
  • 爬虫爬取大学排名示例

    span class token comment coding utf 8 span span class token triple quoted string string 34 34 34 Created on Wed Nov 7 19
  • TensorFlow2.0 搭建简单的ResNet和训练

    1 resnet py span class token keyword import span os span class token keyword import span tensorflow span class token key
  • Tensorflow2.0 保存和加载模型的几种方法

    零 综述 save load weights save load entire model saved model 一 Save the weights 1 一次性保存所有参数 model span class token punctuat
  • 深度学习/机器学习常用网址汇总(持续更新)

    这里汇总了入坑深度学习两年以来常用的网址 xff0c 以便查找 xff0c 持续更新 xff0c 欢迎评论区交流补充 一 深度学习 机器学习 网址名称简介Tensorflow python文档Tensorflow的python API的定义
  • opencv动态目标跟踪学习总结

    用opencv实现对视频中动态目标的追踪 第一步 xff0c 是要建立一个编程环境 xff0c 然后加载opencv的库路径等等 具体步骤在 http www opencv org cn 的 安装 中 有详细介绍 第二步 xff0c 建立一