视觉slam十四讲学习笔记(七)视觉里程计 2

介绍直接法的原理,并利用 g2o 实现直接法中的一些核心算法。

前言

1. 理解光流法跟踪特征点的原理。

2. 理解直接法是如何估计相机位姿的。

3. 使用 g2o 进行直接法的计算。

哔哩哔哩课程连接:视觉SLAM十四讲ch7_2_哔哩哔哩_bilibili


一、直接法的引出

视觉slam十四讲学习笔记(六)视觉里程计 1-CSDN博客介绍了使用特征点估计相机运动的方法。尽管特征点法在视觉里程计中占据主流地位,但有以下几个缺点:

  1. 关键点的提取与描述子的计算非常耗时。实践当中,SIFT 目前在 CPU 上是无法实时计算的,而 ORB 也需要近 20 毫秒的计算。如果整个 SLAM 30 毫秒/帧的速度运行,那么一大半时间都花在计算特征点上。
  2. 使用特征点时,忽略了除特征点以外的所有信息。一张图像有几十万个像素,而特征点只有几百个。只使用特征点丢弃了大部分可能有用的图像信息。
  3. 相机有时会运动到特征缺失的地方,往往这些地方没有明显的纹理信息。例如,有时会面对一堵白墙,或者一个空荡荡的走廓。这些场景下特征点数量会明显减少,可能找不到足够的匹配点来计算相机运动。

几种思路:

  • 保留特征点,但只计算关键点,不计算描述子。同时,使用光流法(Optical Flow)来跟踪特征点的运动。这样可以回避计算和匹配描述子带来的时间,但光流本身的计算需要一定时间;
  • 只计算关键点,不计算描述子。同时,使用直接法(Direct Method)来计算特征点在下一时刻图像的位置。这同样可以跳过描述子的计算过程,而且直接法的计算更加简单。
  • 既不计算关键点、也不计算描述子,而是根据像素灰度的差异,直接计算相机运动。

第一种方法仍然使用特征点,只是把匹配描述子替换成了光流跟踪,估计相机运动时仍使用对极几何、PnP ICP 算法。而在后两个方法中,会根据图像的像素灰度信息来计算相机运动,它们都称为直接法

使用特征点法估计相机运动时,把特征点看作固定在三维空间的不动点。根据它们在相机中的投影位置,通过最小化重投影误差Reprojection error)来优化相机运动。

在直接法中,并不需要知道点与点之间之间的对应关系,而是通过最小化光度误差Photometric error)来求得它们。

二、光流(Optical Flow

直接法是从光流演变而来的。它们非常相似,具有相同的假设条件。光流描述了像素在图像中的运动,而直接法则附带着一个相机运动模型。

LK 光流法示意图

光流是一种描述像素随着时间,在图像之间运动的方法。随着时间的经过,同一个像素会在图像中运动,而希望追踪它的运动过程。计算部分像素运动的称为稀疏光流,计算所有像素的称为稠密光流。稀疏光流以 Lucas-Kanade 光流为代表,并可以在 SLAM 中用于跟踪特征点位置。

Lucas-Kanade 光流

LK 光流中,认为来自相机的图像是随时间变化的。图像可以看作时间的函数: I(t)。那么,一个在 t 时刻,位于 (x, y) 处的像素,它的灰度可以写成:

这种方式把图像看成了关于位置与时间的函数,它的值域就是图像中像素的灰度。

灰度不变假设:同一个空间点的像素灰度值,在各个图像中是固定不变的。

LK 光流中,假设某一个窗口内的像素具有相同的运动

光流是描述图像中像素随时间变化的位移的一种技术。Lucas-Kanade(LK)光流是一种基于局部区域的光流方法,用于估计图像中每个像素的运动矢量。该方法假设在一个小的局部区域内,图像灰度不随时间变化,从而简化了运动场的估计。

以下是Lucas-Kanade光流方法的基本思想和步骤:

  1. 基本假设: LK光流假设在一个小的局部窗口内,图像灰度是恒定不变的。这个窗口可以是一个小的矩形区域。

  2. 运动模型: 使用一个简单的运动模型,通常是二维的平移模型,来描述窗口内的像素运动。这个模型有两个参数,表示水平和垂直方向上的位移。

  3. 光流方程: 利用灰度恒定不变的假设,可以得到光流方程。对于一个窗口内的像素点,其灰度不随时间变化,可以表示为:

    Ix​u+Iy​v=−It​

    其中,Ix​ 和 Iy​ 是图像在x和y方向上的梯度,It​ 是图像随时间的变化率。u 和 v 分别是像素点在水平和垂直方向上的位移。

  4. 方程求解: 对于每个窗口内的像素点,可以形成一个方程,构成一个线性方程组。通过求解这个方程组,可以得到每个像素点的运动矢量 u 和 v。

  5. 稀疏或密集估计: LK光流可以应用于稀疏点集或整个图像。在稀疏点集中,选择一些具有代表性的点进行光流估计;在密集估计中,对整个图像中的每个像素都进行估计。

Lucas-Kanade光流方法是一种常用的光流估计技术,特别适用于描述相对较小的位移。然而,它对于像素变化剧烈的区域可能表现不佳,这时候可能需要其他更复杂的光流方法。

t 取离散的时刻而不是连续时间时,可以估计某块像素在若干个图像中出现的位置。由于像素梯度仅在局部有效,所以如果一次迭代不够好的话,会多迭代几次这个方程。在 SLAM 中,LK 光流常被用来跟踪角点的运动。

三、实践:LK 光流

1 使用 TUM 公开数据集

准备了若干张数据集图像,存放在程序目录中的 data/文件夹下。来自于慕尼黑工业大学(TUM)提供的公开 RGB-D 数据集,称之为 TUM 数据集。

http://vision.in.tum.de/data/datasets/rgbd-dataset/download

它含有许多个 RGB-D 视频,可以作为 RGB-D 或单目 SLAM 的实验数据。它还提供了用运动捕捉系统测量的精确轨迹,可以作为标准轨迹以校准 SLAM 系统。

数据位于本章目录的 data/下,以压缩包形式提供(data.tar.gz)。由于 TUM 数据集是从实际环境中采集的,需要解释一下它的数据格式(数据集一般都有自己定义的格式)。在解压后,你将看到以下这些文件:

  1. rgb.txt depth.txt 记录了各文件的采集时间和对应的文件名。
  2. rgb/ depth/目录存放着采集到的 png 格式图像文件。彩色图像为八位三通道,深度图为 16 位单通道图像。文件名即采集时间。
  3. groundtruth.txt 为外部运动捕捉系统采集到的相机位姿,格式为,可以把它看成标准轨迹。

请注意彩色图、深度图和标准轨迹的采集都是独立的,轨迹的采集频率比图像高很多。在使用数据之前,需要根据采集时间,对数据进行一次时间上的对齐,以便对彩色图和深度图进行配对。原则上,可以把采集时间相近于一个阈值的数据,看成是一对图像。并把相近时间的位姿,看作是该图像的真实采集位置。TUM 提供了一个 python 脚本“associate.py”(或使用 slambook/tools/associate.py)帮我们完成这件事。请把此文件放到数据集目录下,运行:

python associate.py rgb.txt depth.txt > associate.txt

这段脚本会根据输入两个文件中的采集时间进行配对,最后输出到一个文件 associate.txt。输出文件含有被配对的两个图像的时间、文件名信息,可以作为后续处理的来源。

2 使用 LK 光流

使用 LK 的目的是跟踪特征点。对第一张图像提取 FAST 角点,然后用 LK 光流跟踪它们,并画在图中。slambook/ch8/useLK/useLK.cpp

根据《视觉slam十四讲》实践,结果如下:

也可以自己写一个程序实现:

import cv2
import numpy as np# 读取第一张图像
img1 = cv2.imread('image1.jpg', 0)# 初始化FAST角点检测器
fast = cv2.FastFeatureDetector_create()# 在第一张图像中检测FAST角点
keypoints = fast.detect(img1, None)# 转换成NumPy数组
pts1 = np.array([kp.pt for kp in keypoints], dtype=np.float32).reshape(-1, 1, 2)# 创建一个空图像,用于绘制角点
img_with_keypoints = np.copy(img1)
img_with_keypoints = cv2.cvtColor(img_with_keypoints, cv2.COLOR_GRAY2BGR)# 用红色标记角点
cv2.drawKeypoints(img_with_keypoints, keypoints, img_with_keypoints, color=(0, 0, 255))# 读取第二张图像
img2 = cv2.imread('image2.jpg', 0)# 使用Lucas-Kanade光流跟踪角点
pts2, status, error = cv2.calcOpticalFlowPyrLK(img1, img2, pts1, None)# 筛选跟踪失败的点
good_pts1 = pts1[status == 1]
good_pts2 = pts2[status == 1]# 在第二张图像上绘制跟踪结果
for i, (new, old) in enumerate(zip(good_pts2, good_pts1)):a, b = old.ravel()c, d = new.ravel()img_with_keypoints = cv2.line(img_with_keypoints, (int(a), int(b)), (int(c), int(d)), (0, 255, 0), 2)img_with_keypoints = cv2.circle(img_with_keypoints, (int(c), int(d)), 5, (0, 255, 0), -1)# 显示结果图像
cv2.imshow('Tracking with LK Optical Flow', img_with_keypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、直接法(Direct Methods

P 是一个已知位置的空间点,根据 P 的来源,可以把直接法进行分类:

1. P 来自于稀疏关键点,称之为稀疏直接法。通常使用数百个至上千个关键点,并且像 L-K 光流那样,假设它周围像素也是不变的。这种稀疏直接法不必计算描述子,并且只使用数百个像素,因此速度最快,但只能计算稀疏的重构。

2. P 来自部分像素。如果像素梯度为零,整一项雅可比就为零,不会对计算运动增量有任何贡献。因此,可以考虑只使用带有梯度的像素点,舍弃像素梯度不明显的地方。这称之为半稠密(Semi-Dense)的直接法,可以重构一个半稠密结构。

3. P 为所有像素,称为稠密直接法。稠密重构需要计算所有像素(一般几十万至几百万个),因此多数不能在现有的 CPU 上实时计算,需要 GPU 的加速。但是,梯度不明显的点,在运动估计中不会有太大贡献,在重构时也会难以估计位置。

从稀疏到稠密重构,都可以用直接法来计算。它们的计算量是逐渐增长的。稀疏方法可以快速地求解相机位姿,而稠密方法可以建立完整地图。具体使用哪种方法,需要视机器人的应用环境而定。特别地,在低端的计算平台上,稀疏直接法可以做到非常快速的效果,适用于实时性较高且计算资源有限的场合。

五、实践:RGB-D 的直接法

1 稀疏直接法

1. 优化变量为一个相机位姿,因此需要一个位姿顶点。由于在推导中使用了李代数,故程序中使用李代数表达的 SE(3) 位姿顶点。将使用“VertexSE3Expmap”作为相机位姿。

2. 误差项为单个像素的光度误差。由于整个优化过程中 I1(p1) 保持不变,可以把它当成一个固定的预设值,然后调整相机位姿,使 I2(p2) 接近这个值。于是,这种边只连接一个顶点,为一元边。由于 g2o 中本身没有计算光度误差的边,需要自己定义一种新的边。

先来定义一种用于直接法位姿估计的边,然后,使用该边构建图优化问题并求解之。实验工程位于“slambook/ch8/directMethod”中。

2 定义直接法的边

首先来定义计算光度误差的边。按照前面的推导,还需要给出它的雅可比矩阵: slambook/ch8/directMethod/direct_sparse.cpp

在程序中,相机位姿是用浮点数表示的,投影到像素坐标也是浮点形式。为了更精细地计算像素亮度,要对图像进行插值。这里采用了简单的双线性插值,也可以使用更复杂的插值方式,但计算代价可能会变高一些。

使用直接法估计相机运动

定义了 g2o 边后,将节点和边组合成图,就可以调用 g2o 进行优化了。实现代码位于 slambook/ch8/directMethod/direct_sparse.cpp 中。

在这个实验中,读取数据集的 RGB-D 图像序列。以第一个图像为参考帧,然后用直接法求解后续图像的位姿。在参考帧中,对第一张图像提取 FAST 关键点(不需要描述子),并使用直接法估计这些关键点在第二个图像中的位置,以及第二个图像的相机位姿。这就构成了一种简单的稀疏直接法。最后,画出这些关键点在第二个图像中的投影。

build/direct_sparse ~/dataset/rgbd_dataset_freiburg1_desk

稀疏直接法的实验。左:误差随着迭代下降。右:参考帧与后 1 至 9 帧对比(选取部分关键点)。

在两个图像相差不多的时候,直接法会调整相机的位姿,使得大部分像素都能够正确跟踪。但是,在稍长一点的时间内,比如说 0-9 帧之间的对比,发现由于相机位姿估计不准确,特征点出现了明显的偏移现象。

4 半稠密直接法

很容易就能把程序拓展成半稠密的直接法形式。对参考帧中,先提取梯度较明显的像素,然后用直接法,以这些像素为图优化边,来估计相机运动。slambook/ch8/direct_semidense.cpp

// select the pixels with high gradiantsfor ( int x=10; x<gray.cols-10; x++ )for ( int y=10; y<gray.rows-10; y++ ){Eigen::Vector2d delta (gray.ptr<uchar>(y)[x+1] - gray.ptr<uchar>(y)[x-1],gray.ptr<uchar>(y+1)[x] - gray.ptr<uchar>(y-1)[x]);if ( delta.norm() < 50 )continue;ushort d = depth.ptr<ushort> (y)[x];if ( d==0 )continue;Eigen::Vector3d p3d = project2Dto3D ( x, y, d, fx, fy, cx, cy, depth_scale );float grayscale = float ( gray.ptr<uchar> (y) [x] );measurements.push_back ( Measurement ( p3d, grayscale ) );}

这只是一个很简单的改动。把先前的稀疏特征点改成了带有明显梯度的像素。于是在图优化中会增加许多的边。这些边都会参与估计相机位姿的优化问题,利用大量的像素而不单单是稀疏的特征点。由于并没有使用所有的像素,所以这种方式又称为半稠密方法(Semi-dense。把参与估计的像素取出来并把它们在图像中显示出来。

5 直接法的讨论

相比于特征点法,直接法完全依靠优化来求解相机位姿。像素梯度引导着优化的方向。如果想要得到正确的优化结果,就必须保证大部分像素梯 度能够把优化引导到正确的方向

半稠密直接法的实验。参考帧与 2,5,8 帧的对比,绿色为参与优化的像素

一次迭代的图形化显示

6 直接法优缺点总结

优点如下:

  • 可以省去计算特征点、描述子的时间。
  • 只要求有像素梯度即可,无须特征点。因此,直接法可以在特征缺失的场合下使用。比较极端的例子是只有渐变的一张图像。它可能无法提取角点类特征,但可以用直接法估计它的运动。
  • 可以构建半稠密乃至稠密的地图,这是特征点法无法做到的。

另一方面,它的缺点也很明显:

  • 非凸性——直接法完全依靠梯度搜索,降低目标函数来计算相机位姿。其目标函数中需要取像素点的灰度值,而图像是强烈非凸的函数。这使得优化算法容易进入极小,只在运动很小时直接法才能成功。
  • 单个像素没有区分度。找一个和他像的实在太多了!——于是我们要么计算图像块,要么计算复杂的相关性。由于每个像素对改变相机运动的“意见”不一致。只能少数服从多数,以数量代替质量。
  • 灰度值不变是很强的假设。如果相机是自动曝光的,当它调整曝光参数时,会使得图像整体变亮或变暗。光照变化时亦会出现这种情况。特征点法对光照具有一定的容忍性,而直接法由于计算灰度间的差异,整体灰度变化会破坏灰度不变假设,使算法失败。针对这一点,目前的直接法开始使用更细致的光度模型标定相机,以便在曝光时间变化时也能让直接法工作。

总结

直接法是介绍的重点。它是为了克服特征点法的上述缺点而存在的。直接法根据像素的亮度信息,估计相机的运动,可以完全不用计算关键点和描述子,于是,既避免了特征的计算时间,也避免了特征缺失的情况。只要场景中存在明暗变化(可以是渐变,不形成局部的图像梯度),直接法就能工作。根据使用像素的数量,直接法分为稀疏、稠密和半稠密三种。相比于特征点法只能重构稀疏特征点(稀疏地图),直接法还具有恢复稠密或半稠密结构的能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/728193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

django中URL配置和视图渲染

前提&#xff1a; 使用django-admin startproject XXX创建了一个django项目【项目目录为project】 django-admin startproject project 一&#xff1a;控制器配置 在项目的根目录创建一个Controller目录&#xff0c;后续所有的控制器方法都放在此目录下 这里我们在Control…

20240307-2-前端开发校招面试问题整理HTML

前端开发校招面试问题整理【2】——HTML 1、HTML 元素&#xff08;element&#xff09; Q&#xff1a;简单介绍下常用的 HTML 元素&#xff1f; 块状标签&#xff1a;元素独占一行&#xff0c;可指定宽、高。 常用的块状元素有&#xff1a; <div>、<p>、<h1&…

unity学习(50)——服务器三次注册限制以及数据库化角色信息5--角色信息数据库化收尾

上一节内容结束后确实可以写入文件了&#xff0c;但还有两个问题&#xff1a; 1.一个是players.txt中&#xff0c;每次重启服务器&#xff0c;当注册新账号创建角色时&#xff0c;players.txt之前内容都会清空。 2.players.txt之前已经注册3次的账号&#xff0c;新注册的角色…

首发:鸿蒙面试真题分享【独此一份】

最早在23年华为秋季发布会中&#xff0c;就已经宣布了“纯血鸿蒙”。而目前鸿蒙处于星河版中&#xff0c;加速了各大互联网厂商的合作。目前已经有200参与鸿蒙的原生应用开发当中。对此各大招聘网站上的鸿蒙开发需求&#xff0c;每日都在增长中。 2024大厂面试真题 目前的鸿蒙…

使用Java生成JWT(JSON Web Token)的详细指南

介绍 在现代应用程序中&#xff0c;身份验证和授权是至关重要的。JSON Web Token&#xff08;JWT&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种紧凑且自包含的方式用于在各方之间安全地传输信息。在本文中&#xff0c;我们将学习如何使…

驱动调试第013期-G120XA驱动同步电机应用案例

概述 SINAMICS G120XA是西门子SINAMICS系列变频器的新成员&#xff0c; 功率范围覆盖0.75 kW~560 kW&#xff0c;内置风机和水泵行业应用功能&#xff0c;汇集了优异的高性能矢量控制算法&#xff0c;可以轻松的驱动风机、水泵及压缩机等负载。胜任各种应用场合&#xff0c;专…

Postman 接口自动化测试教程:入门介绍和从 0 到 1 搭建 Postman 接口自动化测试项目

关于Postman接口自动化测试的导引&#xff0c;全面介绍入门基础和从零开始搭建项目的步骤。学习如何有效地使用Postman进行API测试&#xff0c;了解项目搭建的基础结构、环境设置和测试用例的编写。无论您是新手还是经验丰富的测试人员&#xff0c;这篇教程都将为您提供清晰的指…

pycuda安装失败问题

pycuda安装失败问题 遇到一个pycuda安装失败的问题&#xff0c; 这里有一个合理的解释可以去尝试一下&#xff0c;看起来很有道理&#xff1a;

MySQL为什么要用B+树?

二叉树&#xff08;二叉查找树&#xff09; 平衡二叉树&#xff08;B树就是B-树&#xff09;(解决了二叉查找树的极端情况&#xff09; Q&#xff1a;具体是怎么解决的呢&#xff1f; A&#xff1a; 树左右两边层数相差不大于1一旦符合条件1的时候&#xff0c;就进行左旋/右…

无法更新下载安装升级谷歌浏览器Chrome无法更新至最新版本怎么解决下载更新谷歌浏览器?

谷歌Chrome浏览器有新版本chrome可用&#xff0c;点击后无法更新chrome至最新版本&#xff0c;造成每次在电脑上打开谷歌chrome浏览器都会提示更新&#xff0c;重新安装chrome又无法打开谷歌浏览器官方网站。 谷歌浏览器Chrome无法更新至最新版本怎么办&#xff1f; 1、百度搜…

System Verilog学习笔记(十八)——线程控制

线程控制 发生器把激励传给代理时&#xff0c;环境类需要知道发生器什么时候完成任务&#xff0c;以便及时终止测试平台中还在运行的线程&#xff0c;这个过程就需要借助线程间的通信来完成。常用的线程间通信有事件控制、wait语句、SV信箱和旗语等。 Verilog对语句有两种分组…

Numpy 数组转换为 Pandas DataFrame

参考&#xff1a;Convert Numpy Array to Pandas DataFrame Numpy 介绍 Numpy是Python中一个非常强大的科学计算库&#xff0c;它提供了许多高效的数组操作方法。Pandas是另一个重要的数据处理库&#xff0c;它基于Numpy&#xff0c;并提供了更高级别的数据分析和处理工具。在…

SMT贴片加工——品质检验要求

一、元器件贴装工艺品质要求 1.元器件贴装需整齐、正中&#xff0c;无偏移、歪斜 2.贴装的元器件型号规格应正确&#xff1b;元器件应无漏贴、错贴 3.贴片元器件不允许有反贴 4.有极性要求的贴片器件安装需按正确的极性标示安装 二、元器件焊锡工艺要求 1.FPC板面应无影响…

Mybatis-Plus——05,乐观锁(新注解)

乐观锁&#xff08;新注解&#xff09; 一、数据库添加一个字段二、实体类添加version注解三、注册乐观锁插件四、测试一下4.1成功的乐观锁4.2失败的乐观锁————————创作不易&#xff0c;笔记不易&#xff0c;如觉不错&#xff0c;请三连&#xff0c;谢谢~~ 乐观锁实现方…

[LeetCode][8]【学习日记】实现字符串转换整数 (atoi)函数

题目 8. 字符串转换整数 (atoi) 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一个字…

nginx代理minio客户端

错误方式 在点击桶名查看文件时, 会一直处于loading加载中 worker_processes 1; #设置 Nginx 启动的工作进程数为 1。events {worker_connections 1024; ##设置每个工作进程的最大并发连接数为 1024。 }http {include mime.types; #该文件定义了文件扩展名和 MIME 类型…

Linux文件描述符剖析

文章目录 文件描述符文件描述符分配规则重定向软硬链接软链接&#xff08;Symbolic Link&#xff09;&#xff1a;硬链接&#xff08;Hard Link&#xff09;&#xff1a; 文件描述符 文件描述符&#xff08;File Descriptor&#xff09;是一个非负整数&#xff0c;用于标识打开…

Java开发从入门到精通(一):Java的基础语法高阶

Java大数据开发和安全开发 &#xff08;一)Java的流程控制1.1 分支语句1.1.1 IF分支语句第一种IF语句第二种IF-ELSE语句第三种IF-ELSE IF-ELSE语句if语句使用的几个常见问题 1.1.2 switch分支语句switch分支的执行流程switch分支的导学案例:电子备忘录if、switch的比较&#xf…

从一个问题开始聊聊clickhouse的物化视图

【问题】 今天有A问我一个问题&#xff0c;我明明创建了一个物化视图&#xff0c;源表是有数据的&#xff0c;为什么查询物化视图就没有数据&#xff1f; 创建物化视图的SQL示意如下&#xff1a; CREATE MATERIALIZED VIEW schema1.test_mvon cluster clusterNameTO schema1…

【物联网应用案例】从0到N,智慧农业的数据价值

智慧农业全方位渗透到农业的每一个环节&#xff0c;云端解决方案更推动了研究人员、农艺师及农民间的密切协作&#xff0c;为研发企业提供了既经济又具扩展性的完美方案。 据IDC预计&#xff0c;到2036年&#xff0c;农场收集的数据量将增加800%以上&#xff0c;这凸显了农业数…