OpenCV:金字塔LK光流法

金字塔LK光流法的三个假设

 

  • 亮度恒定,即图像场景中目标的像素在帧间运动时外观上保持不变;
  • 时间连续或者运动是”小运动“,即图像的运动随时间的变化比较缓慢;
  • 空间一致,即一个场景中同一表面上邻近的点具有相似的运动。

光流法的原理

 

        光流的概念是Gibson在1950年首先提出来的。它是空间运动物体在观察成像平面上的像素运动的瞬时速度,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的对应关系,从而计算出相邻帧之间物体的运动信息的一种方法。

        一般而言,光流是由于场景中前景目标本身的移动、相机的运动,或者两者的共同运动所产生的。当人的眼睛观察运动物体时,物体的景象在人眼的视网膜上形成一系列连续变化的图像,这一系列连续变化的信息不断“流过”视网膜(即图像平面),好像一种光的“流”,故称之为光流(optical flow)。光流表达了图像的变化,由于它包含了目标运动的信息,因此可被观察者用来确定目标的运动情况。 研究光流场的目的就是为了从图片序列中近似得到不能直接得到的运动场。运动场,其实就是物体在三维真实世界中的运动;光流场,是运动场在二维图像平面上(人的眼睛或者摄像头)的投影。

        那通俗的讲就是通过一个图片序列,把每张图像中每个像素的运动速度和运动方向找出来就是光流场。第t帧的时候A点的位置是(x1, y1),那么我们在第t+1帧的时候再找到A点,假如它的位置是(x2,y2),那么我们就可以确定A点的运动了:(ux, vy) = (x2, y2) - (x1,y1)。

 

流程图

如何获取起始的points[0]

光流法预测当前帧的点

 

源代码

/************************************************************************
* @ Creator:OYXL
* @ Project Creation time:2018/5/31
* @ Function:LK光流法跟踪点
* @	Attention:NO
************************************************************************/
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

#include <iostream>
#include <ctype.h>

using namespace cv;
using namespace std;

static void help()
{
	// print a welcome message, and the OpenCV version
	cout << "\nThis is a demo of Lukas-Kanade optical flow lkdemo(),\n"
		"Using OpenCV version " << CV_VERSION << endl;
	cout << "\nIt uses camera by default, but you can provide a path to video as an argument.\n";
	cout << "\nHot keys: \n"
		"\tESC - quit the program\n"
		"\tr - auto-initialize tracking\n"
		"\tc - delete all the points\n"
		"\tn - switch the \"night\" mode on/off\n"
		"To add/remove a feature point click it\n" << endl;
}

Point2f point;
bool addRemovePt = false;

static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
{
	if( event == CV_EVENT_LBUTTONDOWN )
	{
		point = Point2f((float)x, (float)y);
		addRemovePt = true;
	}
}

int main()
{
	help();

	VideoCapture cap(0);
	TermCriteria termcrit(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 20, 0.03);
	Size subPixWinSize(10,10), winSize(31,31);

	const int MAX_COUNT = 500;
	bool needToInit = false;
	bool nightMode = false;


	if( !cap.isOpened() )
	{
		cout << "Could not initialize capturing...\n";
		return 0;
	}

	namedWindow( "LK Demo", 1 );
	setMouseCallback( "LK Demo", onMouse, 0 );

	Mat gray, prevGray, image, frame;
	vector<Point2f> points[2];

	while (true)
	{
		cap >> frame;
		if( frame.empty() )
			break;

		frame.copyTo(image);
		cvtColor(image, gray, COLOR_BGR2GRAY);

		if( nightMode )
			image = Scalar::all(0);
		//<按下"r"按键,自动初始化
		if(needToInit)
		{
			// automatic initialization
			goodFeaturesToTrack(gray, points[1], MAX_COUNT, 0.01, 10, Mat(), 3, 0, 0.04);
			cornerSubPix(gray, points[1], subPixWinSize, Size(-1,-1), termcrit);
			addRemovePt = false;
		}
		else if(!points[0].empty())//<前一帧的点不能为空
		{
			vector<uchar> status;
			vector<float> err;
			if(prevGray.empty())
			{
				gray.copyTo(prevGray);
			}
			calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err, winSize,3, termcrit, 0, 0.001);
			int i, k;
			for( i = k = 0; i < points[1].size(); i++ )
			{
				if( addRemovePt )//<鼠标添加点
				{
					if( norm(point - points[1][i]) <= 5)//<point到points[1][i]的距离<5之间不能描点
					{
						addRemovePt = false;
						continue;
					}
				}
				//<如果相应特征的流发现则status置为1,否则,为0。
				if( !status[i] )
					continue;

				points[1][k++] = points[1][i];
				circle( image, points[1][i], 3, Scalar(0,255,0), -1, 8);
			}
			points[1].resize(k);
		}
		//<添加点
		if( addRemovePt && points[1].size() < (int)MAX_COUNT )
		{
			vector<Point2f> tmp;//<初始化
			tmp.push_back(point);
			cornerSubPix( gray, tmp, winSize, cvSize(-1,-1), termcrit);
			points[1].push_back(tmp[0]);
			addRemovePt = false;
		}

		needToInit = false;
		imshow("LK Demo", image);

		char c = (char)waitKey(10);
		if( c == 27 )
			break;
		switch( c )
		{
		case 'r':
			needToInit = true;
			break;
		case 'c':
			points[0].clear();
			points[1].clear();
			break;
		case 'n':
			nightMode = !nightMode;
			break;
		}

		std::swap(points[1], points[0]);
		cv::swap(prevGray, gray);
	}

	return 0;
}

 

分析

按下“r”键,自动检测角点,将检测到角点存入points[1]中

// automatic initialization
goodFeaturesToTrack(gray, points[1], MAX_COUNT, 0.01, 10, Mat(), 3, 0, 0.04);
cornerSubPix(gray, points[1], subPixWinSize, Size(-1,-1), termcrit);

鼠标描点,存入points[1]中

static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
{
	if( event == CV_EVENT_LBUTTONDOWN )
	{
		point = Point2f((float)x, (float)y);
		addRemovePt = true;
	}
}

LK光流法预测当前帧的点

calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err, winSize,3, termcrit, 0, 0.001);

函数原型:

void calcOpticalFlowPyrLK( InputArray prevImg, InputArray nextImg,
                           InputArray prevPts, CV_OUT InputOutputArray nextPts,
                           OutputArray status, OutputArray err,
                           Size winSize=Size(21,21), int maxLevel=3,
                           TermCriteria criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01),
                           int flags=0, double minEigThreshold=1e-4);

prevImg:深度为8位的前一帧图像或金字塔图像。

nextImg:和prevImg有相同的大小和类型,后一帧图像或金字塔。

prevPts:计算光流所需要的输入2D点矢量,点坐标必须是单精度浮点数(前一帧的点集)。

nextPts:输出2D点矢量(也是单精度浮点数坐标),点矢量中包含的是在后一帧图像上计算得到的输入特征新位置(预测当前帧的点)。

status:输出状态矢量(元素是无符号char类型,uchar),如果相应特征的流发现则矢量元素置为1,否则,为0。

err:输出误差矢量。

winSize:每个金字塔层搜索窗大小。

maxLevel:金字塔层的最大数目;如果置0,金字塔不使用(单层);如果置1,金字塔2层,等等以此类推。

criteria:指定搜索算法收敛迭代的类型,默认criteria=TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01)。

flags:默认为0

minEigThreshold:算法计算的光流等式的2x2常规矩阵的最小特征值。

注意

if( norm(point - points[1][i]) <= 5)//<point到points[1][i]的距离<5之间不能描点

这句话表达的意思是,鼠标新画的点与点points[1][i]之间的距离不能小于5个像素,否则画的点将不会显示在当前帧上,有兴趣的话可以将这个值改大一点,再试试。

norm(point - points[1][i])这句话可以翻译成sqrt((point.x-points[1][i].x)^2+(point.y-points[1][i].y)^2),sqrt是开根函数。

 

效果图

如果将前一帧的也画出来,然后将前后两帧的点用线段连接起来,看起来效果会更明显一点。

如果想要了解算法公式,推荐博客:

https://blog.csdn.net/zouxy09/article/details/8683859

https://blog.csdn.net/u014568921/article/details/46638557

https://blog.csdn.net/ap1005834/article/details/51226660

  • 6
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
金字塔光流法是一种计算光流的方法,用于估计图像中像素的运动方向和速度。它基于图像金字塔的概念,通过对图像进行多次降采样,构建不同分辨率的图像金字塔,以便处理不同尺度的运动。 在OpenCV中,可以使用`cv::calcOpticalFlowPyrLK`函数来实现金字塔光流法。这个函数接受两幅输入图像和一组初始关键点位置,并返回估计的下一帧图像中的关键点位置。 以下是一个使用金字塔光流法进行光流估计的简单示例代码: ```cpp #include <opencv2/opencv.hpp> int main() { cv::VideoCapture cap(0); cv::Mat prevFrame, currFrame; std::vector<cv::Point2f> prevPoints, currPoints; std::vector<uchar> status; std::vector<float> err; if (!cap.isOpened()) { return -1; } cap >> prevFrame; cv::cvtColor(prevFrame, prevFrame, cv::COLOR_BGR2GRAY); cv::goodFeaturesToTrack(prevFrame, prevPoints, 100, 0.3, 7); while (true) { cap >> currFrame; cv::cvtColor(currFrame, currFrame, cv::COLOR_BGR2GRAY); cv::calcOpticalFlowPyrLK(prevFrame, currFrame, prevPoints, currPoints, status, err); // 绘制光流轨迹 for (int i = 0; i < prevPoints.size(); i++) { if (status[i]) { cv::line(currFrame, prevPoints[i], currPoints[i], cv::Scalar(0, 0, 255), 2); cv::circle(currFrame, currPoints[i], 3, cv::Scalar(0, 255, 0), -1); } } cv::imshow("Optical Flow", currFrame); if (cv::waitKey(1) == 27) { break; } prevFrame = currFrame.clone(); prevPoints = currPoints; } return 0; } ``` 这个代码片段使用摄像头捕获视频,并实时计算相邻帧之间的光流,并在当前帧上绘制光流轨迹。你可以根据自己的需求进行修改和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值