OpenCV目标跟踪(二)-LK光流法

这几天主要看了光流的有关内容,下面就关于光流的有关内容进行个简单的总结。
所谓的光流是一种运动模式,这种运动模式即是指一个物体、表面、边缘在一个视角下由一个观察者和背景之间形成的明显移动。在如下的图中给出了光流的直观解释。
这里写图片描述

这里的每个像素都与速度相关联,这样得到的即是稠密光流,在光流中主要分为稠密光流和稀疏光流,相对于稠密光流,稀疏光流的计算则需要在跟踪之前指定一组点,下面我主要介绍下比较流行的基于金字塔的Lucas-Kanade光流算法。

(1)LK算法
LK算法其实是基于三个假设进行的:

a.亮度恒定。图像场景中目标的像素在帧间运动时外观上保持不变,对于灰度图像,需要假设像素被逐帧跟踪时其亮度不发生变化。这样的假设,我们可以用下面的数学表达式来表达:
这里写图片描述
也即是亮度I对时间t的偏导数为0,即:
  这里写图片描述

b.时间连续或者运动是“小运动”。图像的运动随时间的变化比较缓慢。实际应用中指的是时间变化相对于图像中运动的比例要足够小,这样目标在帧间的运动就比较小。
这条假设,就只能针对小运动,但实际上的运动往往是比较大的运动,这时我们就会将现在的LK算法加以改进,采用基于图像金字塔的LK算法,这在后面将进一步进行介绍。
针对这条假设,换句话说,可以将运动的变化看成是亮度对时间的导数,此时将f(x,t)用I(x(t),t)替换,应用偏微分的链式法则即可得到:
这里写图片描述

我们接着将从一维的情况过渡到二维的情况下去分析即得到:

这里写图片描述
这里写图片描述

c.空间一致性假设。一个场景中同一表面上邻近的点具有相似的运动,在图像平面上的投影也在邻近区域。
下面的这幅图,直观的给出了上述的三条假设。

这里写图片描述

但垂直光流由孔径问题产生,即用小孔或者小窗口去测量运动。这种情况下,我们通常只能观测到边缘而观测不到角点,而只靠边缘是不足以判断整个物体是如何运动的,如下图解释的那样:

这里写图片描述

单像素是不能解决整个运动的,要想解决这样的一个问题,需用到光流的最后一个假设。若一个局部区域的像素运动是一致的,则可以建立邻域像素的系统方程来求解中心像素的移动。这个约束方程有点复杂,如果感兴趣可以去查阅相关文献。

以上的LK算法只需要每个感兴趣点周围小窗口的局部信息,但是使用小窗口的LK算法存在着不足之处就是,较大的运动点将点移出这个小窗口,从而造成算法无法找到这些点。而金字塔的LK算法可以解决这样的问题,即从图像金字塔的最高层(细节最少)开始向金字塔的低层(丰富的细节)进行跟踪。

(2)图像金字塔的LK算法
在图像金字塔的最高层计算光流,用得到的运动估计结果作为下一层金字塔的起始点,重复这个过程直到达到金字塔的最底层。这样就将不满足运动假设的可能性降到最小从而实现对更快和更长的运动的跟踪,这个算法就叫做基于金字塔的LK算法。如图所示:
这里写图片描述

在OpenCV中主要有个基于图像金字塔的LK算法的函数cvCalcOpticalFlowPyrLK(),下面就简要介绍下这个函数的具体的参数的意义,并尝试给出完整的示例程序。

void cvCalcOpticalFlowPyrLK(
    const CvArr* imgA,//初始图像
    const CvArr* imgB,//最终图像
    CvArr* pyrA,
    CvArr* pyrB,//pyrA,B申请存放两幅图像金字塔的缓存
    CvPoint2D32f* featuresA,//存放的是用于寻找运动的点
    CvPoint2D32f* featuresB,//存放featuresA中点的新位置
    int count,//featuresA中点的数目
    CvSize winsize,//定义了计算局部连续运动的窗口尺寸
    int level,//用于设置构建的图像金字塔的栈的层数,若设置为0,则不使用金字塔
    char* status,//函数调用结束时,status中的每个元素被置1(对应点在第二幅图中发现)或者0(未发现)
    float* track_error,//为可选参数,表示被跟踪点的原始图像小区域与此点在第二幅图像的小区域间的差的数组
    CvTermCriteria criteria,//迭代终止条件
    int flags//标志位
);

应用上述函数的过程非常简单:
输入图像,在featuresA中列出需要跟踪的点,然后调用函数。函数返回后,检查status数组以确定哪些点被成功跟踪,再检查featuresB得到这些点新的位置。
下面给出基于图像金字塔的LK算法完整的示例程序:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
const int MAX_CORNERS = 500;

int main()
{
    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);

    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)
    );

    char features_found[MAX_CORNERS];
    float features_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];

    cvCalcOpticalFlowPyrLK(
        imgA,
        imgB,
        pyrA,
        pyrB,
        cornersA,
        cornersB,
        corner_count,
        cvSize(win_size, win_size),
        5,
        features_found,
        features_errors,
        cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.3),
        0
        );

    for (int i = 0; i < corner_count; ++i)
    {
        if (features_found[i] == 0 || features_errors[i] > 550)
        {
            cout << "Error is " << features_errors[i];
            continue;
        }
        cout << "Got it!" << endl;
        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中的视频捕捉功能用起来,用摄像头捕捉两帧图像进行基于图像金字塔的LK算法。

最后如果想继续研究下LK算法的数学原理,可以参考以下这篇博客
http://www.cnblogs.com/gnuhpc/archive/2012/12/04/2802124.html

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页