目的:完成图像点的跟踪
概念:如下面两帧图像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); }运行结果: