视觉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.hqwc.cn/news/512031.html

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

相关文章

小程序分销系统怎么开发_开启您的智能营销新时代

小程序分销系统&#xff1a;开启您的智能营销新时代 随着移动互联网的迅猛发展&#xff0c;微信小程序以其轻便、快捷的特性赢得了广大用户的喜爱。而在激烈的市场竞争中&#xff0c;如何借助小程序这一工具&#xff0c;实现产品的快速传播和销售&#xff0c;成为了众多商家关…

面试问答总结之Java进阶

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;注解Annotaion &#xff08;java标注&#xff09;&#x1f415;内置注解&#x1f415;元注解 &#x1f380;对象克隆&#x1f415;如何实现克隆&#x1f415;如何实现深克…

Vue+SpringBoot打造医院门诊预约挂号系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2 科室医生档案模块2.1.3 预约挂号模块2.1.4 医院时政模块 2.2 可行性分析2.2.1 可靠性2.2.2 易用性2.2.3 维护性 三、数据库设计3.1 用户表3.2 科室档案表3.3 医生档案表3.4 医生放号…

STM32CubeIDE基础学习-新建STM32CubeIDE基础工程

STM32CubeIDE基础学习-新建STM32CubeIDE基础工程 前言 有开发过程序的朋友都清楚&#xff0c;后面开发是不需要再新建工程的&#xff0c;一般都是在初学时或者有特殊需要的时候才需要新建项目工程的。 后面开发都是可以在这种已有的工程上添加相关功能就行&#xff0c;只要前…

研发芯片行业ERP系统的优点

随着信息技术的快速发展&#xff0c;企业资源计划(ERP)系统已成为企业管理的核心工具。在芯片行业&#xff0c;研发活动尤为复杂&#xff0c;涉及资源、流程、技术等多方面的管理。因此&#xff0c;专为芯片行业研发的ERP系统应运而生&#xff0c;为企业带来了诸多优点。 一、资…

深度学习系列61:在CPU上运行大模型

1. 快速版 1.1 llamafile https://github.com/Mozilla-Ocho/llamafile 直接下载就可以用&#xff0c;链接为&#xff1a;https://huggingface.co/jartine/llava-v1.5-7B-GGUF/resolve/main/llava-v1.5-7b-q4.llamafile?downloadtrue 启动&#xff1a;./llava-v1.5-7b-q4.lla…

docker-compose Install Dockge

Dockge Dockge 是一个精美的、易于使用的、反应式的自托管 docker compose.yaml 面向堆栈的管理器。 主要特性: 通过Web页面管理compose.yaml文件。 创建/编辑/启动/停止/重新启动/删除容器。更新Docker镜像。交互式Web终端。响应式设计,实时更新进度(Pull/Up/Down)和Web…

数据结构(一)综述

一、常见的数据结构 数据结构优点缺点数组查找快增删慢链表增删快查找慢哈希表增删、查找都快数据散列&#xff0c;对存储空间有浪费栈顶部元素插入和取出快除顶部元素外&#xff0c;存取其他元素都很慢队列顶部元素取出和尾部元素插入快存取其他元素都很慢二叉树增删、查找都快…

C++学习第七天(string类)

1、学习string的原因&#xff1f; C语言中的字符串 C语言中&#xff0c;字符串是以‘\0’结尾的一些字符的集合&#xff0c;为了操作方便&#xff0c;C标准库中提供了一些str系列的库函数&#xff0c;但是这些库函数与字符串是分离开的&#xff0c;而且底层空间需要用户自己管…

【会议征稿通知】第二届数字化经济与管理科学国际学术会议(CDEMS 2024)

第二届数字化经济与管理科学国际学术会议&#xff08;CDEMS 2024&#xff09; 2024 2nd International Conference on Digital Economy and Management Science&#xff08;CDEMS 2024&#xff09; 2024年第二届数字经济与管理科学国际会议(CDEMS 2024) 定于2023年4月26-28日…

Processing一些基础程序

一、学会绘制窗口 (1)首先我们需要学会绘制一个窗口&#xff0c;size()函数有两个参数&#xff1a;第一个设定窗口的宽度&#xff0c;第二个设定窗口的高度&#xff0c;如果想要设置一个窗口为宽800像素&#xff0c;高为600像素的窗口&#xff0c;输入以下代码&#xff1a;size…

C语言操作符详解(一)

一、操作符的分类 • 算术操作符&#xff1a; 、- 、* 、/ 、% • 移位操作符:<< >> • 位操作符: & | ^ • 赋值操作符: 、 、 - 、 * 、 / 、% 、<< 、>> 、& 、| 、^ • 单⽬操作符&#xff1a; &#xff01;、、--、&、*、、…