Opencv -- 基于光流的对象跟踪

本文介绍了光流的基本原理,包括稀疏光流的KLT算法和稠密光流的HF方法。在稀疏光流中,通过Lucas-Kanade迭代法跟踪特征点,而在稠密光流中,所有像素点都被比较以检测运动。OpenCV库提供了goodFeaturesToTrack和calcOpticalFlowPyrLK等API用于角点检测和光流计算。代码示例展示了如何使用这些API进行稀疏和稠密光流跟踪。
摘要由CSDN通过智能技术生成

1. 基本原理

2. 稀疏光流 - KLT

稀疏光流 -- 流程:

3. 稠密光流 - HF

稠密光流跟踪是将当前帧的所有像素点与前一帧比较,有变化的标记出来。对比的点比较多,不是对比变化的那几个特征点。所以速度较慢。没有稀疏光流的速度快。但有的时候效果比稀疏光流要好。

4. 相关API

(1)opencv中的goodFeaturesToTrack函数可以计算Harris角点和shi-tomasi角点,但默认情况下计算的是shi-tomasi角点,函数原型如下:

void cv::goodFeaturesToTrack( InputArray _image, OutputArray _corners,
                              int maxCorners, double qualityLevel, double minDistance,
                              InputArray _mask, int blockSize,
                              bool useHarrisDetector, double harrisK )

参数解释:

image:8位或32位浮点型输入图像,单通道

corners:保存检测出的角点

maxCorners:角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点

qualityLevel:角点的品质因子

minDistance:对于初选出的角点而言,如果在其周围minDistance范围内存在其他更强角点,则将此角点删除

mask:指定感兴趣区,如不需在整幅图上寻找角点,则用此参数指定ROI

blockSize:计算协方差矩阵时的窗口大小

useHarrisDetector:指示是否使用Harris角点检测,如不指定,则计算shi-tomasi角点

harrisK:Harris角点检测需要的k值

(2)计算一个稀疏特征光流使用Lucas Kanade迭代法与金字塔。opencv中的函数为calcOpticalFlowPyrLK

函数原型及参数解释如下:

void cv::calcOpticalFlowPyrLK(
InputArray     prevImg, // 前一帧图像
InputArray     nextImg, // 后一帧图像
InputArray     prevPts, // 前一帧的稀疏光流点
InputOutputArray     nextPts, // 后一帧光流点
OutputArray     status, // 输出状态(无符号char),如果在当前图像中能够光流得到标定的特征点位置改 
                      //变,则设置status的对应位置为1,否则设置为0
OutputArray     err, // 表示错误
Size winSize = Size(21, 21), // 光流法对象窗口大小
int maxLevel = 3, // 金字塔层数,0表示只检测当前图像,不构建金字塔图像
TermCriteria     criteria = TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 30, 0.01), // 窗口搜索时候停止条件
int flags = 0, // 操作标志
double     minEigThreshold = 1e-4 // 最小特征值响应,低于最小值不做处理
)

(3)稠密光流函数 calcOpticalFlowFarneback

void cv::calcOpticalFlowFarneback	( 
        InputArray	prev,  //输入前一帧图像(8位单通道);
        InputArray	next, //输入后一帧图像(与prev大小和类型相同);
        InputOutputArray	flow,  //计算的流量图像具有与prev相同的大小并为CV_32FC2类型;
        double 	pyr_scale, //指定图像比例(\ <1)为每个图像构建金字塔; 
                       // pyr_scale = 0.5 ,意味着一个古典金字塔,其中每个下一层比前一层小两倍。                                          
        int 	levels, //金字塔层数包括初始图像; 
                                //levels = 1意味着不会创建额外的图层,只会使用原始图像。
        int 	winsize, //平均窗口大小;较大的值会增加算法对图像噪声的鲁棒性,并可以检测更快                        
                             // 速的运动,但会产生更模糊的运动场。
        int 	iterations,  // 每个金字塔等级上执行迭代算法的迭代次数。用于在每个像素中查找        
                           // 多项式展开的像素邻域;
        int 	poly_n, //较大的值意味着图像将近似于更光滑的表面,产生更稳健的算法和更模糊的 
                        // 运动场,一般取poly_n = 5或7。
        double 	poly_sigma,  //用于平滑导数的高斯的标准偏差,用作多项式展开的基础;对于    
          //poly_n = 5,可以设置poly_sigma = 1.1,对于poly_n = 7,可以设置poly_sigma = 1.5;
        int 	flags //操作标志,可取计算方法有:
                   // OPTFLOW_USE_INITIAL_FLOW 使用输入流作为初始流近似。
                   // OPTFLOW_FARNEBACK_GAUSSIAN 使用Gaussian winsize×winsiz过滤器代替光流估计的相同大小的盒子过滤器;通常情况下,这个选项可以比使用箱式过滤器提供更精确的流量,代价是速度更低;通常,应将高斯窗口的胜利设置为更大的值以实现相同的稳健性水平。

      )

5. 代码演示

(1)稀疏光流跟踪

/*
    稀疏光流跟踪
*/
#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

Mat frame, gray;
Mat prev_frame, prev_gray;

vector<Point2f>features; //shi-tomasi角点检测 -- 特征数据

vector<Point2f>iniPoints; //存储需要跟踪的特征点(静止点会被删除)
vector<Point2f>fpts[2]; //定义两个vector数组,一个为fpts[0],一个为fpts[1],保存当前帧和前一帧的特征点

vector<uchar>status; //特征点跟踪成功标志位
vector<float>errors; //跟踪时候区域误差和

void detectFeatures(Mat &inFrame,Mat &inGray);
void drawFeature(Mat &inFrame);
void kltTrackFeature();
void drawTrackLines(); 

int main(int argc, char** argv)
{
	VideoCapture capture;
	capture.open("E:/技能学习/Opencv视频分析与对象跟踪/video_006.mp4");
	if (!capture.isOpened())
	{
		cout << "could not find the video file..." << endl;
		return -1;
	}

	namedWindow("input video", WINDOW_AUTOSIZE);
	while (capture.read(frame))
	{
		//转为灰度图
		cvtColor(frame, gray, COLOR_BGR2GRAY); 

		if (fpts[0].size() < 40)
		{
			//特征点检测
			detectFeatures(frame, gray);
			fpts[0].insert(fpts[0].end(), features.begin(), features.end());
			iniPoints.insert(iniPoints.end(), features.begin(), features.end());
		}
		else
		{
			cout << "**************" << endl;
		}
		

		if (prev_gray.empty())
		{
			gray.copyTo(prev_gray);
		}

		//稀疏光流跟踪
		kltTrackFeature();

		//绘制出特征点
		drawFeature(frame);
		

		//更新前一帧数据
		gray.copyTo(prev_gray);
		frame.copyTo(prev_frame);
		imshow("input video", frame);

		char c = waitKey(500);
		if (c == 27)
		{
			break;
		}
	}

	capture.release();
	waitKey(0);
	destroyAllWindows();
	return 0;
}

//角点检测
void detectFeatures(Mat &inFrame, Mat &inGray)
{
	double maxCorners = 5000;
	double qualitylevel = 0.01;
	double minDistance = 10;
	double blockSize = 3;
	double k = 0.04;
	
	goodFeaturesToTrack(inGray, features, maxCorners, qualitylevel, minDistance, Mat(), blockSize, false, k);
	cout << "detect features:" << features.size() << endl;
}

//稀疏光流跟踪
void kltTrackFeature()
{
	//光流法跟踪
	calcOpticalFlowPyrLK(prev_gray, gray, fpts[0], fpts[1], status, errors);

	int k = 0;
	//特征点过滤
	for (int i = 0; i < fpts[1].size(); i++)
	{
		// 距离与状态测量(上一帧的坐标 - 下一帧的坐标)
		double dist = abs(fpts[0][i].x - fpts[1][i].x) + abs(fpts[0][i].y - fpts[1][i].y);
		
		//如果状态量为1且每次移动到下一帧的距离大于2,就会画跟踪线
		if (dist > 2 && status[i])
		{
			iniPoints[k] = iniPoints[i];
			fpts[1][k++] = fpts[1][i];
		}
	}

	//保存有用的特征点 并 绘制跟踪轨迹
	iniPoints.resize(k);
	fpts[1].resize(k);

	drawTrackLines();

	//更新数据
	std::swap(fpts[1], fpts[0]); //将新一帧图像数据作为旧一帧图像数据,继续进入循环
}

void drawTrackLines()
{
	for (size_t t = 0; t < fpts[1].size(); t++)
	{
		line(frame, iniPoints[t], fpts[1][t], Scalar(0, 255, 0), 2, 8, 0);

		circle(frame, fpts[1][t], 2, Scalar(0, 0, 255), 2, 8, 0);
	}
}

//绘制出特征点
void drawFeature(Mat &inFrame)
{
	for (size_t t = 0; t < fpts[0].size(); t++)
	{
		circle(inFrame, features[t], 2, Scalar(0, 0, 255), 2, 8, 0);
	}
}

(2)稠密光流跟踪

/*
	稠密光流跟踪
*/
#include<iostream>
#include<opencv2/opencv.hpp>

using namespace std;
using namespace cv;

void drawOpticalFlowHF(Mat &flowdata, Mat &image, int step);

int main(int argc, char** argv)
{
	VideoCapture capture;
	capture.open("E:/技能学习/Opencv视频分析与对象跟踪/video_006.mp4");
	if (!capture.isOpened())
	{
		cout << "could not find the video file..." << endl;
		return -1;
	}

	Mat frame, gray;
	Mat prev_frame, prev_gray;
	Mat flowResult, flowdata;

	capture.read(frame);
	cvtColor(frame, prev_gray, COLOR_BGR2GRAY);

	namedWindow("input", WINDOW_AUTOSIZE);
	namedWindow("flow", WINDOW_AUTOSIZE);

	//从第二帧数据开始
	while (capture.read(frame))
	{
		cvtColor(frame, gray, COLOR_BGR2GRAY);

		if (!prev_gray.empty())
		{
			//稠密光流
			calcOpticalFlowFarneback(prev_gray, gray, flowdata, 0.5, 3, 15, 3, 5, 1.2, 0);
			cvtColor(prev_gray, flowResult, COLOR_GRAY2BGR);
			drawOpticalFlowHF(flowdata, flowResult, 1);
			imshow("input", frame);
			imshow("flow", flowResult);
		}
		

		char c = waitKey(500);
		if (c == 27)
		{
			break;
		}
	}

	capture.release();
	waitKey(0);
	destroyAllWindows();
	return 0;
}

void drawOpticalFlowHF(Mat &flowdata, Mat &image, int step)
{
	for (int row = 0; row < image.rows; row++)
	{
		for (int col = 0; col < image.cols; col++)
		{
			const Point2f fxy = flowdata.at<Point2f>(row, col);
			if (fxy.x > 2 || fxy.y > 2)
			{
				line(image, Point(col, row), Point(cvRound(col + fxy.x), cvRound(row + fxy.y)), Scalar(0, 255, 0), 2, 8, 0);
				circle(image, Point(col, row), 2, Scalar(0, 0, 255), -1);
			}
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值