网上有很多opencv cv::calcOpticalFlowPyrLK的使用方法介绍,但是除了直接的翻译,还有官网的链接,具体的参数使用分析还是不够全面,真正使用过程中发现还有问题。我在这里做了一些简单总结,希望对大家有帮助。
CV_EXPORTS_W 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 );
/** @example samples/cpp/lkdemo.cpp
An example using the Lucas-Kanade optical flow algorithm
*/
/** @brief Calculates an optical flow for a sparse feature set using the iterative Lucas-Kanade method with
pyramids.
@param prevImg first 8-bit input image or pyramid constructed by buildOpticalFlowPyramid.
@param nextImg second input image or pyramid of the same size and the same type as prevImg.
@param prevPts vector of 2D points for which the flow needs to be found; point coordinates must be
single-precision floating-point numbers.
@param nextPts output vector of 2D points (with single-precision floating-point coordinates)
containing the calculated new positions of input features in the second image; when
OPTFLOW_USE_INITIAL_FLOW flag is passed, the vector must have the same size as in the input.
@param status output status vector (of unsigned chars); each element of the vector is set to 1 if
the flow for the corresponding features has been found, otherwise, it is set to 0.
@param err output vector of errors; each element of the vector is set to an error for the
corresponding feature, type of the error measure can be set in flags parameter; if the flow wasn't
found then the error is not defined (use the status parameter to find such cases).
@param winSize size of the search window at each pyramid level.
@param maxLevel 0-based maximal pyramid level number; if set to 0, pyramids are not used (single
level), if set to 1, two levels are used, and so on; if pyramids are passed to input then
algorithm will use as many levels as pyramids have but no more than maxLevel.
@param criteria parameter, specifying the termination criteria of the iterative search algorithm
(after the specified maximum number of iterations criteria.maxCount or when the search window
moves by less than criteria.epsilon.
@param flags operation flags:
- **OPTFLOW_USE_INITIAL_FLOW** uses initial estimations, stored in nextPts; if the flag is
not set, then prevPts is copied to nextPts and is considered the initial estimate.
- **OPTFLOW_LK_GET_MIN_EIGENVALS** use minimum eigen values as an error measure (see
minEigThreshold description); if the flag is not set, then L1 distance between patches
around the original and a moved point, divided by number of pixels in a window, is used as a
error measure.
@param minEigThreshold the algorithm calculates the minimum eigen value of a 2x2 normal matrix of
optical flow equations (this matrix is called a spatial gradient matrix in @cite Bouguet00), divided
by number of pixels in a window; if this value is less than minEigThreshold, then a corresponding
feature is filtered out and its flow is not processed, so it allows to remove bad points and get a
performance boost.
The function implements a sparse iterative version of the Lucas-Kanade optical flow in pyramids. See
@cite Bouguet00 . The function is parallelized with the TBB library.
@note
- An example using the Lucas-Kanade optical flow algorithm can be found at
opencv_source_code/samples/cpp/lkdemo.cpp
- (Python) An example using the Lucas-Kanade optical flow algorithm can be found at
opencv_source_code/samples/python/lk_track.py
- (Python) An example using the Lucas-Kanade tracker for homography matching can be found at
opencv_source_code/samples/python/lk_homography.py
*/
/** @example samples/cpp/lkdemo.cpp
使用 Lucas-Kanade 光流算法的示例
*/
/** @brief 使用迭代 Lucas-Kanade 方法计算稀疏特征集的光流
金字塔。
@param prevImg 由 buildOpticalFlowPyramid 构建的第一个 8 位输入图像或金字塔。
@param nextImg 第二个输入图像或与 prevImg 相同大小和类型的金字塔。
@param prevPts 需要找到流的二维点向量;点坐标必须是
单精度浮点数。
@param nextPts 二维点的输出向量(单精度浮点坐标)
包含计算出的第二张图像中输入特征的新位置;什么时候
传递了 OPTFLOW_USE_INITIAL_FLOW 标志,向量必须与输入中的大小相同。
@param status 输出状态向量(无符号字符);向量的每个元素都设置为 1 如果
已找到对应特征的流,否则设置为 0。
@param err 输出错误向量;向量的每个元素都设置为错误
相应的特征,误差度量的类型可以在flags参数中设置;如果流量不是
找到则错误未定义(使用状态参数查找此类情况)。
@param winSize 每个金字塔级别的搜索窗口大小。
@param maxLevel 基于 0 的最大金字塔等级数;如果设置为 0,则不使用金字塔(单个
level),如果设置为 1,则使用两个级别,依此类推;如果金字塔被传递给输入,那么
算法将使用与金字塔一样多的级别,但不超过 maxLevel。
@param criteria 参数,指定迭代搜索算法的终止条件
(指定最大迭代次数后criteria.maxCount 或搜索窗口时
移动小于标准.epsilon。
@param flags 操作标志:
- **OPTFLOW_USE_INITIAL_FLOW** 使用初始估计,存储在 nextPts 中;如果标志是
未设置,则 prevPts 被复制到 nextPts 并被视为初始估计。
- **OPTFLOW_LK_GET_MIN_EIGENVALS** 使用最小特征值作为误差度量(参见
minEigThreshold 描述);如果未设置标志,则补丁之间的 L1 距离
围绕原始点和移动点除以窗口中的像素数,用作
误差测量。
@param minEigThreshold 算法计算 2x2 法线矩阵的最小特征值
光流方程(这个矩阵在@cite Bouguet00 中称为空间梯度矩阵),划分
按窗口中的像素数;如果该值小于 minEigThreshold,则对应
特征被过滤掉并且它的流没有被处理,所以它允许删除坏点并得到一个
性能提升。
该函数在金字塔中实现了 Lucas-Kanade 光流的稀疏迭代版本。看
@cite Bouguet00 。该函数与 TBB 库并行化。
@笔记
- 使用 Lucas-Kanade 光流算法的示例可在以下位置找到
opencv_source_code/samples/cpp/lkdemo.cpp
- (Python) 使用 Lucas-Kanade 光流算法的示例可在以下位置找到
opencv_source_code/samples/python/lk_track.py
- (Python) 使用 Lucas-Kanade 跟踪器进行单应匹配的示例可在以下位置找到
opencv_source_code/samples/python/lk_homography.py
*/
说实话我用的时候和别人不太一样,他们用的时候,特征点的选取用的一般是cv::goodFeaturesToTrack,我没用这个,因为用过了试了一下,发现效果不咋样。因为这个特征可控性太差了,靠几个参数在图像上随即找几个点,和开玩笑一样。
我用的方法是因地制宜的,在下面的示例里,我用的是cv::SimpleBlobDetector,这玩意是为了找一些斑点,黑块,比较有特征的圆形色块的。在我的这里用起来正好。因为我做的项目里,是要在纯白背景上找黑色斑块的运动速度。
用这个cv::SimpleBlobDetector可以很好的通过一些很明确的参数拿到斑块的中心位置,也就是能够拿到这个特征点。而且一个斑块一个特征点,不会出现错位,乱抢位置的问题。
接下来就到了用cv::calcOpticalFlowPyrLK 的时候。需要注意的就是。网上其他人的使用里,前面第一张图的数据获取都是给了point[0],而官网上给的是point[1],所以我用的就是point[1],来表示当前图上的找到的特征点位,然后用point[0]来表示上一张图的特征点位。
我的程序是取的每一帧图进来以后对图的处理,灰度图转换什么的在另外一个地方已经做了,得到的是gray。
cv::SimpleBlobDetector::Params params;
params.minDistBetweenBlobs = 0.0f;
params.filterByInertia = false;
params.filterByConvexity = false;
params.filterByColor = false;
params.filterByCircularity = false;
params.filterByArea = false;
// 声明根据面积过滤,设置最大与最小面积 颗粒面积大概是2700 直径60个像素
params.filterByArea = true;
params.minArea = 1000.0f;
params.maxArea = 10000.0f;
// 声明根据圆度过滤,设置最大与最小圆度
params.filterByCircularity = true;
params.minCircularity = 0.3;
params.maxCircularity = 1.0;
// 凸包形状分析 - 过滤凹包
//params.filterByConvexity = true;
//params.minConvexity = 0.5;
//params.minConvexity = 1.0;
// 参数初始化BLOB检测器,
detector = cv::SimpleBlobDetector::create(params);
//blob+光流法
void blob_tracking(cv::Mat& src, cv::Mat& dst)
{
src.copyTo(dst);
//清理一下现在找到的特征点
points[1].clear();
// 添加特征点
if (points[1].size() <= 10)//尽量保证当前图上的特征点没那么多
{
keypoints.clear();
detector->detect(gray, keypoints);
cv::drawKeypoints(gray, keypoints, dst, cv::Scalar(0, 0, 255), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
for (int i(0); i < keypoints.size(); i++)
{
points[1].push_back(keypoints.at(i).pt);
}
}
//判断是否是第一张图,如果是第一张图,那么复制一下灰度图之后,灰度图换位,特征点换位就结束了
if (gray_prev.empty())
{
gray.copyTo(gray_prev);
}
else if (points[0].size() > 0)
{
//不是第一张图,且point[0]也就是上一张图是有特征点的,那么就可以通过光流法找当前图的特征点
std::vector<cv::Point2f> prev_pt = {}; // 初始化跟踪点的位置 这里就和别人不一样了,我新增了一个点向量,专门用来存预测出来的点的位置。保留了point[1]的数据,防止出现一部分数据被后面的k清除掉
cv::calcOpticalFlowPyrLK(gray_prev, gray, points[0], prev_pt, status, err, cv::Size(49, 49));
std::vector<cv::Point2f> good_prev = {}; //优秀的上一个点 用来存放有效的上一个点
std::vector<cv::Point2f> good_next = {}; //优秀的下一个点 用来存放有效的下一个点
int k = 0;
for (size_t i = 0; i < prev_pt.size(); i++)
{
if (!status.at(i))
continue;
prev_pt[k] = prev_pt[i];
k++;
good_prev.push_back(points[0][i]);
good_next.push_back(prev_pt[i]);
}
prev_pt.resize(k);
if (k > 0)
{
calculateDist(good_prev, good_next, dist);
// 显示特征点和运动轨迹
for (size_t i = 0; i < k; i++)
{
cv::line(dst, good_prev[i], good_next[i], cv::Scalar(0, 0, 255));
cv::circle(dst, good_next[i], 3, cv::Scalar(0, 255, 0), -1);
}
double sum = std::accumulate(std::begin(dist), std::end(dist), 0.0);
double mean = sum / dist.size();
pic_speed = mean;
cv::putText(dst, std::to_string(mean), cv::Point(50, 50), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 255));
}
else
{
pic_speed = 0;
cv::putText(dst, std::to_string(0), cv::Point(50, 50), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 255));
}
}
// 把当前跟踪结果作为下一此参考
swap(points[1], points[0]);
swap(gray_prev, gray);
}
基本上就这些了,大家理性参考,欢迎讨论。
代码没做优化,就是提供一个思路。