OpenCV2编程手册笔记之 10.5应用光流法跟踪视频中的特征点

    这次,我们用举例的方法来了解一下视频处理,我们采用上一篇博客中用到的代码,使用类继承来减少代码量

    这次的目标是跟踪移动物体,我们先创建一个FeatureTrack类,它继承自VideoProcess类

    在看这个类之前,我们先介绍几个函数

    首先是calcOpticalFlowPyrLK,这个函数的作用原理是光流法,它输入的点(第三个参数)是图像2中的角点坐标,它将会根据输入的图像1和图像2,对这两张图中的亮度进行比较,这些比较都是在输入的角点坐标附近进行比较,得出最终的运动角点坐标(第四个参数),也就是可能的运动角点

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

    第六个参数表示跟踪失败,给出提示,但我们一般只是用status


    然后是goodFeaturesToTrack,这个函数用来确定所有角点

void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask = noArray(), int blockSize = 3,
                                     bool useHarrisDetector = false, double k = 0.04 );
    第一个参数是输入图像

    第二个参数是返回的角点坐标

    第三个参数是最大角点数

    第四个参数是质量等级,用来确定是否选取检测到的角点

    第五个参数是最小距离,用来筛选距离较小的角点

   

    现在,就可以得到我们的类Featuretrack

class FeatureTrack:public VideoProcess
{
public:
	//当前读取图像
	cv::Mat gray;
	//之前读取图像
	cv::Mat gray_prev;
	//当前和上一幅图像的跟踪点
	std::vectorpoints[2];
	//跟踪点的相对初始位置
	std::vectorinitial;
	//检测到的特征
	std::vectorfeatures;
	//最大特征数目
	int max_count;
	//质量等级
	double qlevel;
	//两点间最小距离
	double minDist;
	//检测到的特征状态
	std::vectorstatus;
	//跟踪中的错误
	std::vectorerr;
public:
	FeatureTrack()
	{
		max_count = 500;
		qlevel = 0.01;
		minDist = 10.;
	}
	//视频处理方法
	void process();
	//运行函数
	void run();
	bool addNewPoints();
	void detectFeaturePoints();
	bool acceptTrackedPoint(int i);
	void handleTrackedPoints();
};
    它是继承自类VideoProcess

class VideoProcess
{
public:
	double delay;
	cv::Mat Frame;
	cv::Mat Result;
	cv::VideoCapture capture;
public:
	void get_capture(std::string name);
	void run();
	cv::Mat canny(cv::Mat &img, cv::Mat &out);
	void display(cv::Mat &Frame);
};
    定义好类之后,我们首先要读取视频和视频帧,并对每一帧应用处理函数process

    首先应用父类中的视频读取函数

void VideoProcess::get_capture(std::string name)
{
	capture.open(name);
	if (!capture.isOpened())
	{
		std::cout << "打开失败" << std::endl;
	}
	int rate = capture.get(CV_CAP_PROP_FPS);
	delay = 1000 / rate;
}
    再使用子类中的读取视频帧函数

void FeatureTrack::run()
{
	while (capture.read(Frame))
	{
		process();
		display(Frame);
	}
}
    接下来就是重头戏process


    想了解这个方法,就需要掌握这个方法的实现内涵:

    我们假设有一个视频,其中有一个人在运动。因为这个人在移动,所有这个人的角点时刻都在变换位置,但是帧与帧之间间隔极短,位置变化不会很大。因此,我们只需要知道这个人在上一时间所处的位置(上一帧),结合现在这个人处在的位置(当前帧)和当前帧中所有的角点,我们就能得到他的运动角点坐标,这就是光流法的原理。


    首先,我们把每一帧都显示为灰度图,这是因为很多处理都需要单通道图像

cv::cvtColor(Frame, gray, CV_RGB2GRAY);
    之后,我们判定是否需要对移动的物体添加特征点(因为一个移动的人上将会有很多的特征点)。在这里我们定义如果特征点小于10,我们就添加特征点

if (addNewPoints())
	{
		detectFeaturePoints();
		points[0].insert(points[0].end(), features.begin(), features.end());
		initial.insert(initial.end(), features.begin(), features.end());
	}
    这里的addNewPoints是一个bool类型函数,用来判断特征点数目
bool FeatureTrack::addNewPoints()
{
	return points[0].size() <= 10;
}
    如果判定小于10个特征点,我们就进行检测并将所有特征点加入到已经确定的上一帧运动角点的位置向量后面

    之后,我们再用光流法计算、判定是否接收角点就可以画出整个轨迹


--------------------分割线-----------------------


    网上大多数博主的光流法中没有说明其具体的实现步骤,这里我就做一个人工调试器,写一下光流法的逻辑原理


    第一次读取视频帧:

    1.给出第一张图片的所有角点存在p[0]和ini中

    2.将第一张图片深拷贝至prev中(第一次读取,必须要有拷贝这一环节)

    3.calc函数比较,由于两张图一样的,不存在光流变化,这时候p[0]与p[1]完全一样

    4.将ini和p[1]缩放为k长度(这里k为0)(第一次)

    5.p[0]与p[1]互换, p[1]为第一张图所有角点,prev是第一张图片(第一帧)


    第二次读取视频帧:

    1.将第二张图片所有角点存在p[0]和ini中,此时的gray为第二帧

    2.根据p[0],得到在p[0]周围可能存在的运动角点p[1](注意此时p[1]已经更新)(使用第一张和第二张图片进行比较得到)

    3.交换使得pre为第二帧,p[1]变为第二张中全部的角点

。。。。。。

    第十九次读取:(我这个视频从第十九帧开始有运动目标)

    此时p[1]代表最后一帧没有自行车的图片,p[0]=0

    1.获得角点,p[0]和ini代表第一张有自行车图片(帧)中所含的角点

    2.计算光流得到运动角点向量p[1]

    3.根据遍历发现有一个点符合光流特性,p[1]和ini都为这个点

    4.交换后,p[1]代表第一张有自行车图像的所有角点,p[0]代表第一个符合光流标准的点


    第二十次读取视频帧:

    1.由获得角点(goodFeaturesToTrack函数)p[0],ini为第一个确定的运动角点和第二十张图像中的所有角点叠加在一起

    2.经过计算,p[1]为可能存在运动角点的点

    3.p[0]与p[1]比较,得到运动角点

    4.变换,p[0]变为运动角点

。。。。。

    重复性工作,不再叙述

    得到的结果:

OpenCV2编程手册笔记之 10.5应用光流法跟踪视频中的特征点_第1张图片

源代码:

//类
#pragma once
#include "stdafx.h"
#include 
class VideoProcess
{
public:
	double delay;
	cv::Mat Frame;
	cv::Mat Result;
	cv::VideoCapture capture;
public:
	void get_capture(std::string name);
	void run();
	cv::Mat canny(cv::Mat &img, cv::Mat &out);
	void display(cv::Mat &Frame);
};

class FeatureTrack:public VideoProcess
{
public:
	//当前读取图像
	cv::Mat gray;
	//之前读取图像
	cv::Mat gray_prev;
	//当前和上一幅图像的跟踪点
	std::vectorpoints[2];
	//跟踪点的相对初始位置
	std::vectorinitial;
	//检测到的特征
	std::vectorfeatures;
	//最大特征数目
	int max_count;
	//质量等级
	double qlevel;
	//两点间最小距离
	double minDist;
	//检测到的特征状态
	std::vectorstatus;
	//跟踪中的错误
	std::vectorerr;
	int cc = 0;
public:
	FeatureTrack()
	{
		max_count = 500;
		qlevel = 0.01;
		minDist = 10.;
	}
	//视频处理方法
	void process();
	//运行函数
	void run();
	bool addNewPoints();
	void detectFeaturePoints();
	bool acceptTrackedPoint(int i);
	void handleTrackedPoints();
};

//方法
#include "stdafx.h"
#include "VideoProcess.h"

//打开视频图像
void VideoProcess::get_capture(std::string name)
{
	capture.open(name);
	if (!capture.isOpened())
	{
		std::cout << "打开失败" << std::endl;
	}
	int rate = capture.get(CV_CAP_PROP_FPS);
	delay = 1000 / rate;
}

//在VideoProcess类中的读取视频帧方法
void VideoProcess::run()
{
	while (capture.read(Frame))
	{
		Result = canny(Frame, Result);
		display(Frame);
	}
}

//VideoProcess类中的测试方法
cv::Mat VideoProcess::canny(cv::Mat &img, cv::Mat &out)
{
	cv::cvtColor(img, out, cv::COLOR_RGB2GRAY);
	cv::Canny(out, out, 100, 200);
	return out;
}

//父类中的图像显示方法
void VideoProcess::display(cv::Mat &Frame)
{
	cv::imshow("Before", Frame);
	cv::waitKey(delay);
}

//子类中的读取视频帧
void FeatureTrack::run()
{
	while (capture.read(Frame))
	{
		process();
		display(Frame);
	}
}

//子类中的视频处理方法
void FeatureTrack::process()
{
	cv::cvtColor(Frame, gray, CV_RGB2GRAY);
	if (addNewPoints())
	{
		detectFeaturePoints();
		points[0].insert(points[0].end(), features.begin(), features.end());
		initial.insert(initial.end(), features.begin(), features.end());
	}
	if (gray_prev.empty())
	{
		gray.copyTo(gray_prev);
	}
	cv::calcOpticalFlowPyrLK(gray_prev, gray, points[0], points[1], status, err);
	int k = 0;
	cc++;
	if (cc == 19)
	{
		std::cout << "";
	}
	for (int i = 0; i < points[1].size(); i++)
	{
		if (acceptTrackedPoint(i))
		{
			initial[k] = initial[i];
			points[1][k++] = points[1][i];
		}
	}
	initial.resize(k);
	points[1].resize(k);
	handleTrackedPoints();
	std::swap(points[1], points[0]);
	cv::swap(gray_prev, gray);
}

//是否需要检测新的特征点
bool FeatureTrack::addNewPoints()
{
	return points[0].size() <= 10;
}

//检测特征点
void FeatureTrack::detectFeaturePoints()
{
	cv::goodFeaturesToTrack(gray, features, max_count, qlevel, minDist);
}

//判断是否是跟踪点
bool FeatureTrack::acceptTrackedPoint(int i)
{
	return status[i] && ((abs(points[0][i].x - points[1][i].x) + abs(points[0][i].y - points[1][i].y))) > 1;
}

//画点
void FeatureTrack::handleTrackedPoints()
{
	for (int i = 0; i < points[1].size(); i++)
	{
		cv::line(Frame, initial[i], points[1][i], cv::Scalar(255, 255, 255));
		cv::circle(Frame, points[1][i], 3, cv::Scalar(255, 255, 255), -1);
	}
	display(Frame);
}

//主函数
#include "stdafx.h"
#include 
#include "VideoProcess.h"


int main()
{
	FeatureTrack track;
	std::string name;
	std::cin >> name;
	track.get_capture(name);
	track.run();
	return 0;
}



你可能感兴趣的:(OpenCV2,计算机视觉编程手册,学习笔记)