前言:
在学习点追踪技术前需要先了解下光流发追踪目标,可以看上一章内容:光流法检测运动目标
如果以光流的方式追踪目标,基本上我们可以通过goodFeaturesToTrack函数计算一系列特征点,然后通过Lucas-Kanade算法进行一系列特征点的预测,并画出这些预测点的运动轨迹。点的追踪技术就是在这种方式基础上把goodFeaturesToTrack计算的一系列特征点换成一个我们选中的点进行追踪。
点追踪基本流程:
(1)采集一帧图像,用鼠标在图像上选择一个特征点,并使用cornerSubPix对特征点进行精细化调整
(2)循环采集下一帧图像,利用Lucas-Kanade光流法估计上一帧的特征点在下一帧中的位置
(3)保存估计的特征点在图像上进行追踪显示
需要用到的几个重要的函数:
cornerSubPix 是 OpenCV 中用于提高角点检测精度的函数之一。它用于在角点检测后对检测到的角点位置进行亚像素级别的精细化调整。
在角点检测过程中,通常使用诸如 goodFeaturesToTrack 等函数检测图像中的角点。这些函数可以找到可能是角点的位置,但它们的精度可能会受到像素级别的限制。cornerSubPix 的作用就是对这些检测到的角点位置进行进一步的精细化调整,使得角点的位置更加精确。
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria);
image:输入的灰度图像,用于角点精细化的操作。
corners:输入/输出参数,是一个 vector 或 Mat,包含需要精细化的角点的坐标。经过 cornerSubPix 处理后,这个数组中的角点坐标会被更新为更准确的位置。
winSize:搜索窗口的大小。这个参数定义了算法用于寻找角点的搜索范围。
zeroZone:定义了搜索区域的一些特殊的约束条件。
criteria:定义了终止条件,通常包括迭代的最大次数、精度等。
cornerSubPix 的工作原理是基于像素灰度值的梯度,它通过迭代优化来细化角点位置。这种亚像素级别的精细化可以提高角点检测的准确性,特别是在计算光流等计算中,有时候需要非常精确的角点位置信息。
对于cornerSubPix和calcOpticalFlowPyrLK,如果使用搜索窗口winSize怎么设置比较好?
winSize 参数的合适值取决于你正在处理的图像以及需要进行光流估计或特征跟踪的具体应用场景。这个值影响着光流算法或特征跟踪的准确性、计算效率和对运动变化的敏感度。
一般来说,winSize 的大小应该与你希望跟踪的特征尺度相匹配。以下是一些参考建议:
特征尺度大小:
对于小尺度的特征(如角点),选择一个相对较小的窗口大小可能更合适。这样可以更好地捕捉和跟踪这些特征的运动。
对于大尺度的特征(如纹理区域或物体),可能需要一个更大的窗口来更好地描述它们的运动。
运动速度和变化范围:
如果你处理的场景中物体的运动速度较快或者变化范围较大,可能需要一个更大的窗口来保证算法能够正确地捕捉到这些运动。
计算资源和实时性:
较大的窗口可能需要更多的计算资源,因此在资源受限或需要实时处理的情况下,可能需要权衡窗口大小和计算效率之间的关系。
一般情况下,使用较小的窗口大小可能对光流估计和特征跟踪有利,因为它们更灵敏,但也更容易受到图像噪声和局部变化的影响。同时,过小的窗口也可能限制了算法对大尺度运动的捕捉能力。
如果没有特定的规则来选择 winSize,可以通过实验和调整来找到最适合你的场景的值。通常,初始选择一个合理的默认值,然后通过观察跟踪结果和实际效果来进行调整。这个调整过程可能需要根据应用的具体情况进行多次迭代
calcOpticalFlowPyrLK函数已在上一章介绍过,这里不再赘述。
代码示例:
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
Point2f point;
bool addRemovePt = false;
//--------------------------------【onMouse( )回调函数】------------------------------------
// 描述:鼠标操作回调
//-------------------------------------------------------------------------------------------------
static void onMouse(int event, int x, int y, int /*flags*/, void* /*param*/)
{
if (event == EVENT_LBUTTONDOWN)
{
point = Point2f((float)x, (float)y);
addRemovePt = true;
}
}
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
VideoCapture cap;
TermCriteria termcrit(TermCriteria::MAX_ITER | TermCriteria::EPS, 20, 0.03);
Size winSize(31, 31);
const int MAX_COUNT = 500;
cap.open(0);
if (!cap.isOpened())
{
cout << "Could not initialize capturing...\n";
return 0;
}
namedWindow("LK Demo", 1);
setMouseCallback("LK Demo", onMouse, 0);
Mat image;
Mat grayNow, grayPre;
vector<Point2f> pointsPre;
vector<Point2f> pointsNow;
for (;;)
{
//采集下一帧图像
Mat frame;
cap >> frame;
if (frame.empty())
break;
frame.copyTo(image);
cvtColor(image, grayNow, COLOR_BGR2GRAY);
if (!pointsPre.empty())
{
vector<uchar> status;
vector<float> err;
//Lucas-Kanade光流法运动估计(根据上一帧中选择的点进行估计,pointsNow存放的是估计下一帧grayNow图像中的特征点)
//搜索窗口大小为 winSize(31, 31)
//termcrit为迭代终止条件
calcOpticalFlowPyrLK(grayPre, grayNow, pointsPre, pointsNow, status, err, winSize,
3, termcrit, 0, 0.001);
size_t i, k;
for (i = k = 0; i < pointsNow.size(); i++)
{
//去除不好的预测点
if (!status[i])
continue;
pointsNow[k++] = pointsNow[i]; //把好的特征点重新保存到pointsNow容器中
circle(image, pointsNow[i], 3, Scalar(0, 255, 0), -1, 8); //在图像上显示鼠标选的特征点
}
pointsNow.resize(k); //修改容器长度为最新数据的长度
}
//每次鼠标在下一帧图像上选择一个点,先对选择点进行精细化调整在保存
if (addRemovePt && pointsNow.size() < (size_t)MAX_COUNT)
{
//获取鼠标点击坐标
vector<Point2f> tmp;
tmp.push_back(point);
//对下一帧图像中选择的点进行精细化调整
cornerSubPix(grayNow, tmp, winSize, Size(-1, -1), termcrit);
//保存精细化调整后的选择点
pointsNow.push_back(tmp[0]);
addRemovePt = false;
}
imshow("LK Demo", image);
char c = (char)waitKey(10);
//采集下一帧图像之前,把当前帧数据保存到上一帧容器中
pointsPre = pointsNow;
grayPre = grayNow.clone();
}
return 0;
}
效果显示: