金字塔Lucas-Kanade稀疏光流

光流分为稀疏光流和稠密光流,稠密光流的每个像素与速度或者可以说是与位移相关,使用稠密光流得以跟踪运动的方法有Horn-Shrunk方法,还有块匹配方法,但是现在已经很少使用到了,这里不做介绍,稠密光流需要使用某种插值方法在比较容易跟踪的像素之间进行插值以解决那些运动不明确的像素,因此可想而知,计算量是相当大的。

而对于稀疏光流来说,在他计算时需要在被跟踪之前指定一组点(角点),因此在使用LK方法之前我们需要配合使用cvGoodFeatureToTrack()来寻找角点,继而使用cvFindCornerSubPix()在之前基础之上精确角点的位置,即寻找亚像素角点。然后利用金字塔LK光流算法,对运动进行跟踪。

LK方法的数学解析部分比较难,所以我们跳过数学原理,直接谈论其算法原理,首先我们需要明白LK算法的三个假设:

1、亮度恒定,图像场景中的目标的像素在帧间运动时外观上保持不变。

2、时间连续或者运动是小运动,图像随时间的运动比较缓慢,实际中指的是时间变化相对图像中的运动的比例要足够小。

3、空间一致。一个场景中的同一表面上的邻近点具有相似的运动,在图像平面上的投影也在邻近区域。

下面给出使用金字塔LK光流算法的代码,笔者在算法相应处给出了一定的注释:

// TestLucasKanade.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
const int MAX_CORNERS=500;
int main(int argc,char** argv)
{
 IplImage* imgA=cvLoadImage(argv[1],CV_LOAD_IMAGE_GRAYSCALE);
 IplImage* imgB=cvLoadImage(argv[2],CV_LOAD_IMAGE_GRAYSCALE);
 CvSize img_sz=cvGetSize(imgA);
 int win_size=10;

 IplImage* imgC=cvLoadImage(argv[3],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,//返回角点之间的最短距离不应小于min_distance
  0,
  3,
  0,
  0.04
  );
 cvFindCornerSubPix(//根据上一步精确角点位置,确定亚像素角点
  imgA,
  cornersA,//整数值的像素位置
  corner_count,//角点数目
  cvSize(win_size,win_size),//等式产生窗口的尺寸
  cvSize(-1,-1),//禁区,不需要时设置cvSize(-1,-1)
  cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,20,0.03)
  );

 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];
 cvCalcOpticalFlowPyrLK(
  imgA,//初始图像
  imgB,//最终图像
  pyrA,//申请存放两幅输入图像金字塔的缓存,大小至少为(img.width-8)*img.height/3字节
  pyrB,
  cornersA,//用于寻找运动的点
  cornersB,//存放featureA中点的新的位置
  corner_count,//featureA中点的数目
  cvSize(win_size,win_size),
  5,//金字塔层数
  features_found,//对应点是否在第二副图像中发现
  feature_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||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;

}

 

根据上述代码,可以总结出金字塔LK光流应用的基本过程:首先输入图像,在featureA中列出需要跟踪的点,然后调用函数,函数返回后,检查status数组以确定哪些点被成功跟踪,再检查featureB得到这些点的新位置。

以下是程序的运行结果:

金字塔Lucas-Kanade稀疏光流_第1张图片
通过红线的运动方向可以明显的看出手的运动趋势。


转载自 http://zhuyuge0.blog.163.com/blog/static/13230361420117102449387/

你可能感兴趣的:(C++,算法,光流法)