1.1 问题需求及实现流程:
要求:使用光流法跟踪给定视频或摄像头中的运动特点。
检测流程:1)视频采集(取到视频中当前帧图像);2)图像预处理;3)提取特征点;4)使用光流法估计特征运动;5)相邻帧及特征点交换。
1.2 OpenCV实现:
OpenCV相关函数:
Ø 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)
prevImg,第一帧图像。
nextImg,第二帧图像。
prevPts,第一帧图像中的所有特征向量。
nextPts,第二帧图像中的所有特征向量。
status,输出状态向量;如果相应点光流被发现,向量的每个单元被设置为1,否则,被置为0。
Ø void goodFeatureToTrack(InputArray _image, OutputArray _corners,int maxCorners, double qualityLevel,double minDistance, InputArray _mask,int blockSize, bool useHarrisDeterctor,double harrisK)
_image,8位或32位浮点型输入图像,单通道。
_corners,保存检测出的角点。
maxCorners,角点数目最大值,如果实际检测的角点超过此值,则只返回前maxCorners个强角点。
qualityLevel,角点的品质因子,决定角点可信度。
minDistance,此领域范围内如果存在更强角点,则删除此角点。
#include
using namespace cv;
using namespace std;
const int MAX_POINT_COUNT = 500; // 全局变量限制单词循环中检测的特征点数量
bool needTolnit();
void main()
{
char *fn = "D:\\CommonSoftware\\OpenCV\\Workplace\\Example3_OpticalFlow\\vtest.avi";
VideoCapture cap;
Mat source, result, gray, lastGray; // 原始图像、、上一帧灰度图像、本帧灰度图像
vector points[2];
// 定义一维向量数组points[2],分别存放上一帧和当前帧的特征点
// vector temp;
vector status;
vector err;
cap.open(fn); // 打开视频
if (!cap.isOpened()) // 检测是否成功打开视频
{
cout << "Cannot open the source, check camera or file." << endl;
return;
}
for ( ; ; ) // 无条件循环
{
cap >> source; // 将cap抓取的当前帧图像传递给原始图像
if (source.empty()) // 通过判断提取到的当前帧是否为空来判断是否到了视频结尾
{
break;
}
cvtColor(source, gray, COLOR_BGR2GRAY); // 将当前帧原始图像转换为灰度图像
if (points[0].size() < 10) // 如果上一帧中的特征点数量小于10个认为不可信,则重新检测
{
goodFeaturesToTrack(gray, points[0], MAX_POINT_COUNT, 0.01, 20);
} // 从灰度图像gray中提取特征点放入points[0]中,角点品质因子为0.01,20的范围内仅保留最强角点
if (lastGray.empty())
{ // 如果上一帧灰度图像为空,即视频开头,将当前帧灰度图像复制到上一帧灰度图像中
gray.copyTo(lastGray);
}
calcOpticalFlowPyrLK(lastGray, gray, points[0], points[1], status, err);
// 从上一帧和当前帧灰度图像中的特征点计算光流,第i个特征点如光流发现则status[i]=1,否则为0
int counter = 0;
for (int i = 0; i < points[1].size(); i++) // 当i小于当前帧特征点总数时,进行循环
{
double dist = norm(points[1][i] - points[0][i]);
// 计算当前帧第i个特征点与上一帧第i个特征点的距离
if (status[i] && dist >= 2.0 && dist <= 20.0) // 对所有得到的特征点光流进行一个筛选
{ // 筛选依据是帧间光流被发现且帧间光流长度介于2到20之间
points[0][counter] = points[0][i];
points[1][counter++] = points[1][i];
} // 将points[0]和points[1]分别的前counter个元素用过滤合格的元素代替
}
points[0].resize(counter);
points[1].resize(counter); // 将前counter以后的元素去除
source.copyTo(result); // 将原始图像复制到result当中
for (int i = 0; i < points[1].size(); i++) // 当i小于滤除后当前帧中特征点总数
{
line(result, points[0][i], points[1][i], Scalar(0, 0, 0xff));
// 在result图像中用红色线将当前帧和上一帧中分别的第i个特征点连起来
circle(result, points[1][i], 3, Scalar (0, 0xff, 0));
// 在result图像中将所有筛选出的特征点用半径为3的绿色圈标识出来
}
swap(points[0], points[1]);
swap(lastGray, gray);
// 将当前帧和上一帧的特征点和图像数据交换,从而将当前帧放入上一帧中以进行后续循环
imshow("Source Image", source);
imshow("Result Image", result);
char key = waitKey(100); // 等待100好眠
if (key == 27) // Esc键退出
{
break;
}
}
}
bool needTolnit()
{
return true;
}