一、API介绍
稀疏光流跟踪是在图像上的当前帧与前一帧的特征点比较,有变换的就标记出来。
calcOpticalFlowPyrLK() 函数
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 第一幅8位输入图像 ,你的标定图像的灰度图,前一帧。
- nextImg 第二幅与preImg大小和类型相同的输入图像,也就是你想搜寻的图像的灰度图 。当前帧。
- prevPts 输入的标定图像的特征点(可以是其他特征点检测方法找到的点) 。前一帧的特征点。
- nextPts 输出场景的特征点 。当使用OPTFLOW_USE_INITIAL_FLOW 标志时,nextPts的vector必须与input的大小相同。当前帧的特征点。
- status 输出状态向量(无符号char),如果在当前图像中能够光流得到标定的特征点位置改变,则设置status的对应位置为1,否则设置为0
- err 输出错误向量;向量的每个元素被设为相应特征的一个错误,误差测量的类型可以在flags参数中设置;如果流不被发现然后错误未被定义(使用status(状态)参数找到此情形)。
- winSize 每级金字塔的搜索窗口大小。
- maxLevel 基于最大金字塔层次数。如果设置为0,则不使用金字塔(单级);如果设置为1,则使用两个级别,等等。如果金字塔被传递到input,那么算法使用的级别与金字塔同级别但不大于MaxLevel。
- criteria 指定迭代搜索算法的终止准则(在指定的最大迭代次数标准值(criteria.maxCount)之后,或者当搜索窗口移动小于criteria.epsilon。)
- flags 操作标志,可选参数:OPTFLOW_USE_INITIAL_FLOW:使用初始估计,存储在nextPts中;如果未设置标志,则将prevPts复制到- - nextPts并被视为初始估计。OPTFLOW_LK_GET_MIN_EIGENVALS:使用最小本征值作为误差度量(见minEigThreshold描述);如果未设置标志,则将原始周围的一小部分和移动的点之间的 L1 距离除以窗口中的像素数,作为误差度量。
- minEigThreshold 算法所计算的光流方程的2x2标准矩阵的最小本征值(该矩阵称为[Bouguet00]中的空间梯度矩阵)÷ 窗口中的像素数。如果该值小于MinEigThreshold,则过滤掉相应的特征,相应的流也不进行处理。因此可以移除不好的点并提升性能。
当使用calcOpticalFlowPyrLK作为光流金字塔的算法时候,我们只需要知道以下的几点:
-
calcOpticalFlowPyrLK必须和其他的角点识别算法进行搭配使用,比如我这里使用的goodFeaturesToTrack,将其他的角点识别算法中获得的角点作为光流算法的prevPts。
-
status 的大小和当前需要识别的光流移动的特征点大小一样,所以我们可以判定当前的图像是否还能与标定图像进行光流的依据。
二、代码演示
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat frame, gray, prev_frame, prev_gray;
vector<Point2f> corners; //shi-Tomasi 交点检测 存储特征数据
vector<Point2f> iniPoint; // 初始化特征数据
vector<Point2f> fpts[2]; // 保存当前帧和前一帧的特征点位置
vector<uchar> status; // 特征点跟踪成功标志位
vector<float> errors; // 跟踪是区域误差和
void detectFeature(Mat& inFrame, Mat& inGray)
{
double maxCorners = 5000;
double qualityLevel = 0.01;
double minDistance = 10; // 小于这个就属于同一个特征点
double blockSize = 3;
goodFeaturesToTrack(inGray, corners, maxCorners, qualityLevel, minDistance, Mat(), blockSize, false, 0.04);
printf("detect features = %d\n", corners.size());
}
void drawFeature(Mat& inFrame)
{
for (int i = 0; i < fpts[0].size(); i++)
{
circle(inFrame, fpts[0][i], 2, Scalar(0, 0, 255), 2, 16);
}
}
void drawTrackLines()
{
for (int i = 0; i < fpts[1].size(); i++)
{
line(frame, iniPoint[i], fpts[1][i], Scalar(0, 255, 0), 2, 8);
circle(frame, fpts[1][i], 2, Scalar(0, 0, 255), 2, 8);
}
}
void klTrackFeature()
{
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);
if (dist > 2 && status[i]) // 两个点有位移了,并且判断成功了
{
iniPoint[k] = iniPoint[i];
fpts[1][k++] = fpts[1][i]; // 当前帧中有位移的特征点
}
}
// 保存特征点并绘制跟踪轨迹
iniPoint.resize(k);
fpts[1].resize(k);
drawTrackLines();
swap(fpts[1], fpts[0]);
}
int main()
{
VideoCapture capture(0);
if (!capture.isOpened())
{
puts("dont open video.");
system("pause");
return -1;
}
while (capture.read(frame))
{
flip(frame, frame, 1);
cvtColor(frame, gray, COLOR_BGR2GRAY);
if (prev_gray.empty()) // 第一帧
{
gray.copyTo(prev_gray);
}
if (fpts[0].size() < 40) // 特征点损失
{
detectFeature(frame, gray);
fpts[0].insert(fpts[0].end(), corners.begin(), corners.end());
iniPoint.insert(iniPoint.end(), corners.begin(), corners.end());
}
else
{
printf("跟踪\n");
}
klTrackFeature(); // 光流跟踪
// 当前帧拷贝的前一帧,更新前一帧数据,用于光流跟踪
gray.copyTo(prev_gray);
frame.copyTo(prev_frame);
imshow("input video", frame);
if (27 == waitKey(60)) break;
}
capture.release();
waitKey(0);
return 0;
}
三、结果展示
移动摄像头之后: