Lucas-Kanada光流假设:
- 场景中物体被跟踪的部分的亮度不变;
- 相邻帧之间的运动较小;
- 相邻的点保持相邻。
LK算法只需要每个感兴趣点周围小窗口的局部信息,但是较大的运动会将点移除这个小窗口,从而造成算法无法再找到这些点。金字塔的LK算法可以解决这个问题,即从金字塔的最高层(细节最少)开始向金字塔的最低层(丰富的细节)进行跟踪。跟踪图像金字塔允许小窗口部或较大的运动。
在开始跟踪前,首先要在初始帧中检测特征点,之后在下一帧中尝试跟踪这些点。你必须找到新的图像帧中这些点的位置,因此,你必须在特征点的先前位置附近进行搜索,以找到下一帧中它的新位置。这正是cv::calcOpticalFlowPyrLK函数所实现的工作。你输入两个连续的图像帧以及第一幅图像中检测到的特征点数组,该函数将返回一组新的特征点为位置。为了跟踪完整的序列,你需要在帧与帧之间重复这个过程,不可避免地你也会丢失其中一些点,于是被跟踪的特征点数目会减少。为了解决这个问题,我们可以不时地检测新的特征值。
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: 第二帧/当前帧
prev_Pts:第一帧特征点集
next_Pts:计算输出的第二帧光流特征点集
status :状态标志位,如果对应特征的光流被发现,数组中的每一个元素都被设置为 1, 否则设置为 0。
err:双精度数组,包含原始图像碎片与移动点之间的误差。
#include<opencv2\opencv.hpp>
using namespace cv;
using namespace std;
void detectFeatures(Mat &gray);
void drawFeatures(Mat &frame);
void KLTtracker();
void drawTrackLine();
vector<Point2f>features;//shi-Tomasi角点检测特征数据
vector<Point2f>iniPoints;//初始化特征数据
vector<Point2f>fpts[2];//保存当前帧和前一帧的特征点位置
vector<uchar>status;//特征点跟踪成功标志位
vector<float>errors;//跟踪误差
Mat frame,gray, pregray;
int main(int arc, char** argv) {
VideoCapture capture(0);
namedWindow("output", CV_WINDOW_AUTOSIZE);
capture.read(frame);//因为摄像头打开会有延迟,所以提前打开一帧
while (capture.read(frame)) {
imshow("frame", frame);
cvtColor(frame, gray, CV_BGR2GRAY);
if (fpts[0].size() < 10) {
detectFeatures(gray);
fpts[0] = features;
iniPoints = features;
}
if (pregray.empty()) {
gray.copyTo(pregray);
}
KLTtracker();
drawFeatures(frame);
//光流跟踪后把当前帧当作前一帧,再与下一帧进行匹配跟踪
gray.copyTo(pregray);
imshow("output", frame);
char c = waitKey(50);
if (c == 27) {
break;
}
}
capture.release();
waitKey(0);
return 0;
}
void detectFeatures(Mat &gray) {
goodFeaturesToTrack(gray, features, 5000, 0.01, 10, Mat(), 3, false);
}
void drawFeatures(Mat &frame) {
for (int i = 0; i < fpts[0].size(); i++) {
circle(frame, fpts[0][i], 2, Scalar(0, 0, 255), 2);
}
}
void KLTtracker() {
calcOpticalFlowPyrLK(pregray, 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]) {
iniPoints[k] = iniPoints[i];
fpts[1][k++] = fpts[1][i];
}
}
//保存特征点并绘制跟踪轨迹
iniPoints.resize(k);
fpts[1].resize(k);
drawTrackLine();
//fpts[0] = fpts[1];
swap(fpts[1], fpts[0]);
}
void drawTrackLine() {
for (int i = 0; i < fpts[1].size(); i++) {
line(frame, iniPoints[i], fpts[1][i], Scalar(0, 255, 0), 2);
circle(frame, features[i], 2, Scalar(0, 0, 255), 2);
}
}