视觉SLAM笔记(42) 光流法跟踪特征点
视觉SLAM笔记(42) 光流法跟踪特征点
本文介绍了使用TUM公开数据集进行视觉SLAM的相关内容,并详细讲解了光流法(LK算法)在跟踪特征点中的应用。具体包括:
TUM数据集:
使用自慕尼黑工业大学(TUM)提供的公开RGB-D数据集进行实验。该数据集包含多组图像和深度图,并提供了精确的标准轨迹以校准SLAM系统。
LK光流算法:
通过提取FAST角点并使用LK光流算法对其进行跟踪,在OpenCV中实现了对目标特征点的动态更新与绘制。
程序运行:
程序需要指定数据集目录路径(如$ ./LKFlow ../data),每次循环处理图像并跟踪特征点。结果显示大部分特征点能够稳定跟踪,但部分因运动或遮挡丢失。
特征点稳定性分析:
角点处的特征更稳定,边缘处的特征沿边缘滑动较为明显。减少特征点数量可降低计算时间。
优缺点总结:
LK光流方法避免了描述子匹配过程且速度快于基于描述子的方法;但鲁棒性较弱,在较大相机运动时易失败。
整体而言,该方法适合基于特征点的视觉里程计算,但在较大运动情况下需结合其他方法(如ICP或双视图几何)。
视觉SLAM笔记(42) 光流法跟踪特征点
- 1. 使用 TUM 公开数据集
- 2. 使用 LK 光流
1. 使用 TUM 公开数据集
生成了多幅数据集图像,并在程序目录 VSLAM_note/042/data/ 中进行存储。这些图像源自慕尼黑工业大学(TUM)提供的公开 RGB-D 数据集。以后将此数据集命名为 TUM 数据集。
该系统提供了大量 RGB-D 视频数据集, 适合作为 RGB-D 或单目 SLAM 方法的有效实验材料。
该系统能够生成精确的轨迹坐标数据, 可作为基准轨迹用于校准和优化 SLAM 系统性能。
鉴于该数据集规模较大, 在此基础上我们选取了其中一部分 'freiburg1_desk' 数据集中的图像进行分析。

该下载链接可从TUM数据集主页获取。亦或直接调用GitHub上的相关资源(共包含十张图片)。
数据存储于 data/ 文件夹内,并以压缩包的形式提供(具体文件名为 data.tar.gz)。
因为 TUM 数据集是从实际环境中采集的,因此有必要解释其具体的数据格式(通常这些集合会包含自定义的格式)。

在解压后,将看到以下这些文件:
rgb.txt 和 depth.txt 存储了各文件的采集时间及其对应名称。
rgb/ 和 depth/ 目录存放着获取到的 PNG 格式图像文件。
彩色图像采用 8 位三通道编码格式,深度图采用 16 位单通道编码格式更清晰。此外,所有文件名直接标示了其采集时间。
groundtruth.txt为外部运动捕捉系统采集到的相机位姿

格式为:(time, tx, ty, tz, qx, qy, qz, qw)
可以把它看成标准轨迹
请注意彩色图、深度图以及标准轨迹的采集均是独立进行的;同时,在时间上与图像采集相比频率更高。为了确保数据的一致性与准确性,在处理数据前需对采样时间进行精确对齐。按照一定的时间阈值划分后得到的数据集中的每一组样本均可被视为一组图像配对;而将相近时间段内的位姿信息作为对应图像的真实捕捉位置提供。TUM团队已经提供了一个名为‘associate.py’的Python脚本工具用于实现这一功能;可以在GitHub存储库GitHub中找到相关资源。

请把此文件放到数据集目录下,运行:
$ python associate.py rgb.txt depth.txt > associate.txt
AI助手

该脚本将根据输入的两个文件中的采集时间进行配对操作;最终将输出结果至指定的 associate.txt 文件中;输出结果中包含配对成功的两张图像的时间戳和文件名信息;此外,在TUM数据集中还提供了用于对比估计轨迹与标准轨迹工具包。
2. 使用 LK 光流
旨在利用 LK 跟踪特征点。首先,在第一张图像中提取FAST角点作为初始特征点,并利用LK光流算法实现这些角点的精确追踪,并将追踪结果标注在图中标记出来。
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <chrono>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/video/tracking.hpp>
using namespace std;
int main(int argc, char** argv)
{
// 加载 associate.txt
if (argc != 2)
{
cout << "usage: LKFlow path_to_dataset" << endl;
return 1;
}
string path_to_dataset = argv[1];
string associate_file = path_to_dataset + "/associate.txt";
ifstream fin(associate_file);
if (!fin)
{
cerr << "Cann't find associate.txt!" << endl;
return 1;
}
string rgb_file, depth_file, time_rgb, time_depth;
cv::Mat color, depth, last_color;
list< cv::Point2f > keypoints; // 因为要删除跟踪失败的点,使用list
for (int index = 0; index < 100; index++)
{
fin >> time_rgb >> rgb_file >> time_depth >> depth_file;
color = cv::imread(path_to_dataset + "/" + rgb_file);
depth = cv::imread(path_to_dataset + "/" + depth_file, -1);
if (index == 0)
{
// 对第一帧提取FAST特征点
vector<cv::KeyPoint> kps;
cv::Ptr<cv::FastFeatureDetector> detector = cv::FastFeatureDetector::create();
detector->detect(color, kps);
for (auto kp : kps)
keypoints.push_back(kp.pt);
last_color = color;
continue;
}
if (color.data == nullptr || depth.data == nullptr)
continue;
// 对其他帧用LK跟踪特征点
vector<cv::Point2f> next_keypoints;
vector<cv::Point2f> prev_keypoints;
for (auto kp : keypoints)
prev_keypoints.push_back(kp);
vector<unsigned char> status;
vector<float> error;
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
cv::calcOpticalFlowPyrLK(last_color, color, prev_keypoints, next_keypoints, status, error);
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
cout << "LK Flow use time:" << time_used.count() << " seconds." << endl;
// 把跟丢的点删掉
int i = 0;
for (auto iter = keypoints.begin(); iter != keypoints.end(); i++)
{
if (status[i] == 0)
{
iter = keypoints.erase(iter);
continue;
}
*iter = next_keypoints[i];
iter++;
}
cout << "tracked keypoints: " << keypoints.size() << endl;
if (keypoints.size() == 0)
{
cout << "all keypoints are lost." << endl;
break;
}
// 画出 keypoints
cv::Mat img_show = color.clone();
for (auto kp : keypoints)
cv::circle(img_show, kp, 10, cv::Scalar(0, 240, 0), 1);
cv::imshow("corners", img_show);
cv::waitKey(0);
last_color = color;
}
return 0;
}
AI助手
该程序的运行参数里需要指定数据集所在的目录,例如:
$ ./LKFlow ../data
AI助手
会在每次循环后暂停程序,按任意键可以继续运行

当使用全部数据进行处理时

深入分析了特征点的跟踪过程后发现,在物体角部位置上分布着更加稳定的特征标记。在边缘区域上分布着特殊的特征点,在沿着边缘移动时这些特征块的内容基本保持不变。这些区域容易被误判为同一位置。在既非角部也非边缘的位置上分布着一些较为敏感的特征点,在这些区域中这些特征点的位置会发生频繁的变化。这个现象与围棋中的"金角银边草肚皮"类似,在围棋中"金角"指的是占据棋盘四个角落的位置,在这些区域上布局最为稳固;其次是在边边上分布较为密集;而在中间部分则较少有布局空间可用。
另一方面,在追踪大约1749个特征点时可见光流法执行所需的时间约为48毫秒左右。
减少追踪的数量将显著降低运算所需的时间。
值得注意的是,在计算资源上LKF光流方法表现出较高的效率。
同时该算法无需进行描述子计算步骤即可进行目标跟踪。
然而其自身运算消耗也存在一定的限制。
在基于云的计算平台上应用LKF方法可显著提升运算效率。
但在实际SLAM系统中选择使用光流还是匹配描述子最好通过实际测试来确定。
此外,LK 光流跟踪即可直接得到特征点之间的对应关系。*这个对应关系类似于* 描述子之间的配对关系。*实际上我们** 大多数时候会遇到特征点丢失的情况,并不常见到误配对的情形。** 这从某种角度来看,在相机运动较小的情况下使用 LK 光流方法更为合适。** 然而,在相机运动较大的情况下,匹配* 描述子* 的方法仍然有效可靠地完成配对任务。** 基于此分析可知,在相机运动较大的情况下,匹配* 描述子* 的方法更具鲁棒性与稳定性优势
最后, 利用光流跟踪技术中的特征点, 并采用 PnP 算法、ICP 方法或基于对极几何的理论, 可以实现相机运动的估计。总体而言, 光流法能够显著提升基于关键点的视觉里程计算效率, 同时跳过了计算特征描述子和进行配准的过程。然而, 这种技术仅适用于相机运动缓慢的情况(即较高的数据采集频率)。
参考:
相关推荐:
该光流算法在视觉SLAM领域中被广泛采用
该文深入探讨了特征点检测技术中存在的局限性
该研究主要针对迭代最近点算法进行了深入分析
本文专注于三维空间点云配准问题的研究
该文系统性地探讨了PnP问题的求解方法
