关于光流法cv::calcOpticalFlowPyrLK的学习使用记录-参数的分析以及方法使用的思考


前言

网上有很多opencv cv::calcOpticalFlowPyrLK的使用方法介绍,但是除了直接的翻译,还有官网的链接,具体的参数使用分析还是不够全面,真正使用过程中发现还有问题。我在这里做了一些简单总结,希望对大家有帮助。


一、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 );

二、calcOpticalFlowPyrLK opencv自带参数说明

1.英文原话

/** @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
 */

2.Google翻译

/** @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
 */

3.人话翻译

  • 你可以在opencv安装目录下的 samples/cpp/lkdemo.cpp 中,找到这个函数的使用示例
  • prevImg 参数 放上一张图
  • nextImg 参数 放新的图,也就是下一张图。简单说一下,这个东西是通过前后两张图的光流变化来预测位置的,所以需要两张图。
  • prevPts 参数 把你在上一张图找到的关键特征点或者什么重要的坐标放进去,需要时单精度浮点数,也就是float格式的cv::Point2f
  • nextPts 参数 这个说到底不是你输入的,而是你要得到的,是个计算结果。最终得到的是和你输入的prevPts一样多的点。也就是通过光流法,以及上一个图的你给的坐标的位置,来根据下一个图,得到新的坐标的位置。这个数值不是你输入的,不管你放进去啥,拿到的都是新的东西。所以别想着往里放数据。重要
  • status 参数 这个参数也是个输出量,也就是你放一个向量state在这里,它会根据他去图里找,去预测得到的预测点。如果预测点没找到,那么不好意思,这个就是0,如果找到了就是1。
  • err 参数 就是个错误输出向量,大概率你用不上。如果你不幸用得上,那么看看上面的google翻译,祝愿你不会用得上。
  • winSize 参数 我理解就是他去找下一个新的坐标点的范围,这个值越大,拿到的新的点的位置就能够偏离的越大。这个是我猜的,如果不对可以在评论区告诉我。。。
  • maxLevel 参数 这个是光流法原理的一部分,可以去介绍光流法的大神那里去看看学习学习。在这里基本上就设置成3就行,或者默认就是3。
  • criteria 参数 没用过。
  • flags 参数 标志位,可以用OPTFLOW_USE_INITIAL_FLOW 或者OPTFLOW_LK_GET_MIN_EIGENVALS ,说实话我还没用上,如果有用上的欢迎分享。
  • minEigThreshold 没用过,不好意思,一般也不用。
  • 使用 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

三、calcOpticalFlowPyrLK 使用示例

说实话我用的时候和别人不太一样,他们用的时候,特征点的选取用的一般是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);
}

总结

基本上就这些了,大家理性参考,欢迎讨论。
代码没做优化,就是提供一个思路。

  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
读取文件夹下所有图片,可以使用OpenCV中的`cv::glob`函数,该函数可以根据通配符模式匹配指定目录下的所有文件。具体步骤如下: 1. 使用`cv::glob`函数获取指定目录下的所有图像文件路径。 2. 按顺序读取每一张图像,计算对应点的运动,绘制轨迹。 下面是一个代码示例: ```c++ #include <iostream> #include <string> #include <vector> #include <opencv2/opencv.hpp> int main() { // 指定图像文件夹路径 std::string image_folder = "path/to/image/folder"; // 获取图像文件路径 std::vector<cv::String> image_paths; cv::glob(image_folder, image_paths); // 定义关键点和颜色 std::vector<cv::Point2f> prev_keypoints, curr_keypoints; cv::Scalar prev_color = cv::Scalar(0, 0, 255), curr_color = cv::Scalar(0, 255, 0); // 读取第一张图像 cv::Mat prev_image = cv::imread(image_paths[0]); cv::cvtColor(prev_image, prev_image, cv::COLOR_BGR2GRAY); cv::goodFeaturesToTrack(prev_image, prev_keypoints, 500, 0.01, 10); // 遍历每一张图像 for (int i = 1; i < image_paths.size(); i++) { // 读取当前帧图像 cv::Mat curr_image = cv::imread(image_paths[i]); cv::cvtColor(curr_image, curr_image, cv::COLOR_BGR2GRAY); // 计算光流 std::vector<uchar> status; std::vector<float> err; cv::calcOpticalFlowPyrLK(prev_image, curr_image, prev_keypoints, curr_keypoints, status, err); // 绘制轨迹 for (int j = 0; j < prev_keypoints.size(); j++) { if (status[j]) { cv::line(curr_image, prev_keypoints[j], curr_keypoints[j], curr_color, 2); } } // 更新关键点和颜色 prev_keypoints = curr_keypoints; prev_color = curr_color; // 重新检测关键点 cv::goodFeaturesToTrack(curr_image, curr_keypoints, 500, 0.01, 10); // 更新上一帧图像 prev_image = curr_image.clone(); } return 0; } ``` 在上面的代码中,我们使用了`cv::goodFeaturesToTrack`函数来检测关键点,使用`cv::calcOpticalFlowPyrLK`函数计算对应点的运动,使用`cv::line`函数绘制轨迹。注意,为了方便绘制轨迹,我们将图像从灰度图转换为了彩色图。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷失的walker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值