运动估计之光流估计LK

目的:完成图像点的跟踪

概念:如下面两帧图像I和J,存在像素点的移动,即上一帧I中蓝色像素点d在下一帧J中,其位置会有些轻微的变动,则该变动即为位移向量,也就是像素点的光流。

运动估计之光流估计LK_第1张图片

而要计算光流,需满足以下三个前提条件:

1.相邻帧之间的亮度恒定

2.相邻视频帧的取帧时间连续,或者相邻帧之间物体的运动比较“微小”

3.保持空间一致性,也就是同一子像素的像素点具有相同的运动

推导略

其中函数:

void calcOpticalFlowPyrLK(InputArray prevImg, InputArray nextImg, InputArray prevPts, 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 )

1.prevImg:第一帧图像

2.nextImg:第二帧图像

3.prevPts:第一帧图像中所有特征点向量

4.nextPts: 第二帧图像中所有特征点向量

5.status:输出状态量,若相应点光流被发现,向量的每个元素被设置为1,否则,被设置为0

其他参数一般设为默认值,可参考:openCV

代码:

#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

void main()
{
	char *fn = "C:\\mywork\\workspace\\opencv\\sources\\samples\\data\\vtest.avi";
	VideoCapture cap;//该类对视频进行读取操作以及调用摄像头
	Mat source, result, gray, lastGray;  // gray, lastGray对应本帧和上一帧灰度图
	vector points[2], temp;  // 对应上一帧和本帧的特征点,上一帧时是定的,本帧是预测结果
	vector status; // 每一个特征点检测状态
	vector err; // 每一个特征点计算误差

	cap.open(fn);// 打开一个视频文件或打开一个摄像头
	if (!cap.isOpened())// 判断视频读取或者摄像头调用是否成功,成功则返回true
	{
		cout << "无法打开视频源或视频文件" << endl;
		return;
	}
	for (;;)
	{
		cap >> source;
		if (source.empty())
			break;

		cvtColor(source, gray, COLOR_BGR2GRAY);//转为灰度图,供后面使用

		
		if (points[0].size() < 10) // 若点数太少,则重新检测特征点
		{
			//1._image = gray:上面cvtColor转换来的单通道灰度图
			//2._corners = points[0]:保存检测出的角点
			//3.maxCorners = 200:角点数目最大值,若实际检测的角点超过此值,则只返回前
			//                  maxCorners个强角点
			//4.qualityLevel = 0.01: 角点的品质因子
			//5.minDistance = 20:对初选出的角点而言,若在其周围minDistance范围内存在其他
			//更强角点,则将此角点删除。
			//6.mask = Mat() 若指定,它的维度必须和输入图像一致,且在mask值为0处不进行
			// 角点检测
			//7. blockSize = 3:表示在计算角点时参与运算的区域大小,常用值为3,若图像分辨率
			// 高则使用较大一点的值
			//8. useHarrisDetector: 为True表示使用Harris角点检测,为False表示Shi Tomasi检测
			// 但Harris角点检测存在很多缺陷,其角点是像素级别的,速度较慢,
			// 而goodFeaturesToTrack不仅支持Harris角点检测,也支持Shi Tomasi算法角点检测,
			// 该函数也是像素级别的,在实际使用中可能并不满足要求,若要获取更精细的角点坐标,	
			// 则可使用cornerSubPix()进一步细化处理,也就是精度达到亚像素级别。
			goodFeaturesToTrack(gray, points[0], 200, 0.01, 20, Mat(), 3, false, 0.04);
		}

		if (lastGray.empty()) //若上一帧为空
		{
			gray.copyTo(lastGray);//将本帧灰度图复制到上一帧灰度图矩阵中
		}

		//1.prevImg = lastGray:第一帧图像,上一帧图像
		//2.nextImg = gray:第二帧图像,当前图像
		//3.prevPts = points[0]:第一帧图像中所有特征点向量,上一帧图像中所有特征点向量
		//4.nextPts = points[1]: 第二帧图像中所有特征点向量,本帧图像中所有特征点向量
		//5.status:输出状态量,若相应点光流被发现,向量的每个元素被设置为1,否则,被设置为0
		//其他参数一般设为默认值
		calcOpticalFlowPyrLK(lastGray, gray, points[0], points[1], status, err);

		int counter = 0;
		for (int i = 0; i < points[1].size(); i++)
		{
			double dist = norm(points[1][i] - points[0][i]); //求向量差的范数也就是长度
			if (status[i] && dist >= 2.0 && dist <= 20.0) // 将2.0到20.0范围内的特征点存储起来
			{
				points[0][counter] = points[0][i];
				points[1][counter++] = points[1][i];
				
			}
		}

		points[0].resize(counter); //根据给定的counter添加或删除元素
		points[1].resize(counter);

		
		source.copyTo(result); //将source图像矩阵拷贝到result中

		for (int i = 0; i < points[1].size(); i++)
		{
			//result 要绘制线段的图像
			// points[0][i] 线段的起点
			// points[1][i] 线段的终点
			// Scalar(0,0,0xff) 线段的颜色 分别对应BGR
			line(result, points[0][i], points[1][i], Scalar(0,0,0xff));
			//result 为源图像指针
			//points[1][i] 为画圆的圆心坐标
			// 3为圆的半径
			// Scalar(0, 0xff, 0) 为BGR 颜色
			circle(result, points[1][i], 3, Scalar(0, 0xff, 0));
		}

		swap(points[0], points[1]); //交换矩阵
		swap(lastGray, gray); //交换矩阵

		imshow("视频源", source);
		imshow("结果", result);

		char key = waitKey(100); 
		if (key == 27)
			break;
	}
	waitKey(0);
}

运行结果:

你可能感兴趣的:(机器学习,运动估计,光流估计,LK金字塔)