目的:完成图像点的跟踪
概念:如下面两帧图像I和J,存在像素点的移动,即上一帧I中蓝色像素点d在下一帧J中,其位置会有些轻微的变动,则该变动即为位移向量,也就是像素点的光流。
而要计算光流,需满足以下三个前提条件:
1.相邻帧之间的亮度恒定
2.相邻视频帧的取帧时间连续,或者相邻帧之间物体的运动比较“微小”
3.保持空间一致性,也就是同一子像素的像素点具有相同的运动
推导略
其中函数:
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.prevImg:第一帧图像
2.nextImg:第二帧图像
3.prevPts:第一帧图像中所有特征点向量
4.nextPts: 第二帧图像中所有特征点向量
5.status:输出状态量,若相应点光流被发现,向量的每个元素被设置为1,否则,被设置为0
其他参数一般设为默认值,可参考:openCV
代码:
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
void main()
{
char *fn = "C:\\mywork\\workspace\\opencv\\sources\\samples\\data\\vtest.avi";
VideoCapture cap;//该类对视频进行读取操作以及调用摄像头
Mat source, result, gray, lastGray; // gray, lastGray对应本帧和上一帧灰度图
vector<Point2f> points[2], temp; // 对应上一帧和本帧的特征点,上一帧时是定的,本帧是预测结果
vector<uchar> status; // 每一个特征点检测状态
vector<float> err; // 每一个特征点计算误差
cap.open(fn);// 打开一个视频文件或打开一个摄像头
if (!cap.isOpened())// 判断视频读取或者摄像头调用是否成功,成功则返回true
{
cout << "无法打开视频源或视频文件" << endl;
return;
}
for (;;)
{
cap >> source;
if (source.empty())
break;
cvtColor(source, gray, COLOR_BGR2GRAY);//转为灰度图,供后面使用
if (points[0].size() < 10) // 若点数太少,则重新检测特征点
{
//1._image = gray:上面cvtColor转换来的单通道灰度图
//2._corners = points[0]:保存检测出的角点
//3.maxCorners = 200:角点数目最大值,若实际检测的角点超过此值,则只返回前
// maxCorners个强角点
//4.qualityLevel = 0.01: 角点的品质因子
//5.minDistance = 20:对初选出的角点而言,若在其周围minDistance范围内存在其他
//更强角点,则将此角点删除。
//6.mask = Mat() 若指定,它的维度必须和输入图像一致,且在mask值为0处不进行
// 角点检测
//7. blockSize = 3:表示在计算角点时参与运算的区域大小,常用值为3,若图像分辨率
// 高则使用较大一点的值
//8. useHarrisDetector: 为True表示使用Harris角点检测,为False表示Shi Tomasi检测
// 但Harris角点检测存在很多缺陷,其角点是像素级别的,速度较慢,
// 而goodFeaturesToTrack不仅支持Harris角点检测,也支持Shi Tomasi算法角点检测,
// 该函数也是像素级别的,在实际使用中可能并不满足要求,若要获取更精细的角点坐标,
// 则可使用cornerSubPix()进一步细化处理,也就是精度达到亚像素级别。
goodFeaturesToTrack(gray, points[0], 200, 0.01, 20, Mat(), 3, false, 0.04);
}
if (lastGray.empty()) //若上一帧为空
{
gray.copyTo(lastGray);//将本帧灰度图复制到上一帧灰度图矩阵中
}
//1.prevImg = lastGray:第一帧图像,上一帧图像
//2.nextImg = gray:第二帧图像,当前图像
//3.prevPts = points[0]:第一帧图像中所有特征点向量,上一帧图像中所有特征点向量
//4.nextPts = points[1]: 第二帧图像中所有特征点向量,本帧图像中所有特征点向量
//5.status:输出状态量,若相应点光流被发现,向量的每个元素被设置为1,否则,被设置为0
//其他参数一般设为默认值
calcOpticalFlowPyrLK(lastGray, gray, points[0], points[1], status, err);
int counter = 0;
for (int i = 0; i < points[1].size(); i++)
{
double dist = norm(points[1][i] - points[0][i]); //求向量差的范数也就是长度
if (status[i] && dist >= 2.0 && dist <= 20.0) // 将2.0到20.0范围内的特征点存储起来
{
points[0][counter] = points[0][i];
points[1][counter++] = points[1][i];
}
}
points[0].resize(counter); //根据给定的counter添加或删除元素
points[1].resize(counter);
source.copyTo(result); //将source图像矩阵拷贝到result中
for (int i = 0; i < points[1].size(); i++)
{
//result 要绘制线段的图像
// points[0][i] 线段的起点
// points[1][i] 线段的终点
// Scalar(0,0,0xff) 线段的颜色 分别对应BGR
line(result, points[0][i], points[1][i], Scalar(0,0,0xff));
//result 为源图像指针
//points[1][i] 为画圆的圆心坐标
// 3为圆的半径
// Scalar(0, 0xff, 0) 为BGR 颜色
circle(result, points[1][i], 3, Scalar(0, 0xff, 0));
}
swap(points[0], points[1]); //交换矩阵
swap(lastGray, gray); //交换矩阵
imshow("视频源", source);
imshow("结果", result);
char key = waitKey(100);
if (key == 27)
break;
}
waitKey(0);
}
运行结果: