Graph Based SLAM 基本原理
作者 | Alex
01 引言
SLAM 基本框架大致分为两大类:基于概率的方法如 EKF, UKF, particle filters 和基于图的方法 。基于图的方法本质上是种优化方法,一个以最小化对环境的观测误差为目标的优化问题。至今仍是主流的框架的核心,karto,cartographer,hector 等都是基于优化的。这种框架 20 年前就已经兴起,比如著名的 Atlas,今天依然是主流。
Atlas 初衷是设计一个通用框架,以便在其中实验各种建图算法。目的就是通过建立小块的局部地图,再用这些小地图拼成一整块大地图。用“鬼话”说就是:在小尺度区域和有限计算下局部建图算法基础上,建立全局一致的地图。
Atlas 的名字来源于古希腊神话,众所周知的神王宙斯只是第三代,第一代是他的爷爷乌拉诺斯 ,宙斯和他爸爸克罗诺斯连续上演了儿子灭了老子、篡位夺权的的狗血剧,到宙斯时代奥林匹斯山上的坐次才基本排定。想当年爸爸克罗诺斯干掉爷爷乌拉诺斯赢得胜利后,巨人族首领泰坦由于跟着爷爷混,站错了队,败者为寇嘛,秋后算账的时候他和他的一众喽啰统统被打入地狱--黑暗深渊塔尔塔洛斯。其中一个叫 Atlas 的巨人,大概是因为个儿特别大、特别有劲儿,被挑出来去西方站在地母盖娅(Gaia)身上,擎住天父乌拉诺斯,天父就是天空啦,把他托在那儿(相当于囚禁)不能下来捣乱。简而言之,Atlas 是那个站在地球上托举天空的巨人,就象中国古代神话里的盘古。
好了回来说 Atlas 的 SLAM 框架,框架的思想一般是将局部坐标系表示为图上的节点,这个局部坐标系可以是某些 landmark(路标),相邻坐标系之间的变换表示为边。目的要使所有的边加起来最短。
本文介绍一下最简版的基于图的 SLAM 框架,然而麻雀虽小五脏俱全。理解了以后,可以在此基础上搭建自己的 SLAM 系统的。
02 先看看演示结果
蓝线:ground truth.打算走出这样的轨迹
黑线:航位推算.
航位推算法是在知道当前时刻位置的条件下,通过测量移动的距离和方位,推算下一时刻位置的方法,并实施。
红线:算法估计的运动轨迹
反正每个人的路都要自己走,还只能学习那么有限的时间,学习结束后,走出的结果怎么样不太好说,红色就是能走出的最好结果了。
同一个程序运行两次的结果不一样,这是由于每次都加入随机噪声,会导致估计结果不同,实际应用中也是这样。
03 流程图及代码注释
1.程序入口: main()函数:
• 给定路标点:5 个星星
• 开着小车观赏这 5 个点。xTrue:groundtruth 从(0,0)点开始,真实轨迹 xDR 也从(0,0)开始。
◦ 函数 u = calc_input()计算需要的控制向量:速度=1m/s,角速度=0.1 rad/s 。
◦ 并做一次观测 xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)
• 共仿真 SIM_TIME = 100 秒,每隔 DT = 2 秒,对环境观察一次,对小车进行一次操作。
◦ 首先需要计算一次控制向量。
◦每隔show_graph_d_time=20秒调用一次x_opt = graph_based_slam(hxDR, hz),共 5 次,根据当前位置和历史轨迹推断的最佳路径,如图中的红线所示。
def main():…...# RFID positions [x, y, yaw]路标点坐标#这里路标是固定的#通常路标需要满足 4 个条件:# 1.易于被重复观测# 2.不同路标点之间有明显的区别# 3.环境中应该有足够数量的路标点# 4.路标点应该是稳定的,或者说是静态的,如醒目的建筑、交通标识等RFID = np.array([[10.0, -2.0, 0.0],[15.0, 10.0, 0.0],[3.0, 15.0, 0.0],[-5.0, 20.0, 0.0],[-5.0, 5.0, 0.0]])# State Vector [x y yaw v]'xTrue = np.zeros((STATE_SIZE, 1))#ground truthxDR = np.zeros((STATE_SIZE, 1)) # 航位推算.也就是程序中小车真实的轨迹while SIM_TIME >= time:#仿真 100 秒...time += DT#计时,每两秒做给小车一个控制信号,并观察一次环境d_time += DTu = calc_input()#产生控制信号xTrue, z, xDR, ud = observation(xTrue, xDR, u, RFID)hz.append(z)if d_time >= show_graph_d_time:#每隔 20 秒,调用一次 graph_based_slam ,估计一次最佳轨迹x_opt = graph_based_slam(hxDR, hz)#通过真实轨迹和到路标的距离产生最佳轨迹……
2.observation()观测函数
在 main()函数中每 2 秒调用一次,有两个作用:
①调用 xTrue = motion_model(xTrue, u)产生 ground truth 数据(轨迹和控制都没有噪声)
②调用 xd = motion_model(xd, ud) ,在观测数据基础上添加噪声(轨迹和控制都有噪声),用上一时刻的轨迹、真实轨迹和控制量和路标会产生下一个观测。实际应用中,观测比较复杂。观测数据通常来自各种传感器,如相机(单目、或双目)、imu、雷达、声纳、GPS 等,需要对传感器做标定,从不同传感器取回的数据还需要融合。传感器融合也是当前的研究热点。
def observation(xTrue, xd, u, RFID):xTrue = motion_model(xTrue, u)#无噪声轨迹和控制、调用运动模型、为下一时刻生成 groundtruth 轨迹# add noise to gps x-yz = np.zeros((0, 4))for i in range(len(RFID[:, 0])):#这个循环是对小车到路标点之间的距离、角度添加噪声dx = RFID[i, 0] - xTrue[0, 0]#RFID 是 main()中定义的路标点坐标,是固定的dy = RFID[i, 1] - xTrue[1, 0]#xTrue 是 ground truth 轨迹d = math.hypot(dx, dy)#sqrt(dx^2+dy^2)#求距离,勾股定理angle = pi_2_pi(math.atan2(dy, dx)) - xTrue[2, 0]#反余切求角度phi = pi_2_pi(math.atan2(dy, dx))#角度转化为弧度if d <= MAX_RANGE:#MAX_RANGE = 30dn = d + np.random.randn() * Q_sim[0, 0] # add noiseangle_noise = np.random.randn() * Q_sim[1, 1]angle += angle_noisephi += angle_noisezi = np.array([dn, angle, phi, i])#拼成数组z = np.vstack((z, zi))# add noise to input 对控制信号添加噪声ud1 = u[0, 0] + np.random.randn() * R_sim[0, 0]ud2 = u[1, 0] + np.random.randn() * R_sim[1, 1]ud = np.array([[ud1, ud2]]).Txd = motion_model(xd, ud)#用真实轨迹和噪声控制信号,调用运动模型生成下一时刻产生有噪声轨迹return xTrue, z, xd, ud#
3.motion_model 运动模型
飞行器、车辆、机器人都有自己的运动模型,用于描述自己独特的运动方式,如何将各种控制量转换为各种运动:如何产生旋翼、车轮,行走的腿的位移或旋转、直行、转弯、翻转等操作。
4.graph_based_slam(hxDR, hz)
这个函数是框架的核心。
函数的输入是历史数据包括:①真实轨迹②从真实轨迹各点对路标的观测输出最优轨迹。
def graph_based_slam(x_init, hz):print("start graph based slam")z_list = copy.deepcopy(hz)x_opt = copy.deepcopy(x_init)nt = x_opt.shape[1]n = nt * STATE_SIZE for itr in range(MAX_ITR):edges = calc_edges(x_opt, z_list)#计算总的代价函数H = np.zeros((n, n))b = np.zeros((n, 1))for edge in edges:H, b = fill_H_and_b(H, b, edge)#抽取信息矩阵# to fix originH[0:STATE_SIZE, 0:STATE_SIZE] += np.identity(STATE_SIZE)dx = - np.linalg.inv(H) @ b#将总代价与每个点的参数关联起来,计算需要修正的量for i in range(nt):x_opt[0:3, i] += dx[i * 3:i * 3 + 3, 0]#修正真实轨迹,得到最优轨迹diff = dx.T @ dxprint("iteration: %d, diff: %f" % (itr + 1, diff))if diff < 1.0e-5:breakreturn x_opt
5.calc_edge()
建立图—graph.轨迹上每个点以及每个路标都是图上的节点,路标到轨迹点观测误差构成边--代价函数。
def calc_edge(x1, y1, yaw1, x2, y2, yaw2, d1,angle1, d2, angle2, t1, t2):edge = Edge()tangle1 = pi_2_pi(yaw1 + angle1)tangle2 = pi_2_pi(yaw2 + angle2)tmp1 = d1 * math.cos(tangle1)tmp2 = d2 * math.cos(tangle2)tmp3 = d1 * math.sin(tangle1)tmp4 = d2 * math.sin(tangle2)edge.e[0, 0] = x2 - x1 - tmp1 + tmp2edge.e[1, 0] = y2 - y1 - tmp3 + tmp4edge.e[2, 0] = 0Rt1 = calc_rotational_matrix(tangle1)Rt2 = calc_rotational_matrix(tangle2)sig1 = cal_observation_sigma()sig2 = cal_observation_sigma()edge.omega = np.linalg.inv(Rt1 @ sig1 @ Rt1.T + Rt2 @ sig2 @ Rt2.T)#权矩阵edge.d1, edge.d2 = d1, d2edge.yaw1, edge.yaw2 = yaw1, yaw2edge.angle1, edge.angle2 = angle1, angle2edge.id1, edge.id2 = t1, t2return edge
6.calc_edges(hxDR, hz)
计算轨迹上每个点对代价函数的贡献,计算轨迹上所有点产生的总的代价函数。
def calc_edges(x_list, z_list):edges = [] cost = 0.0z_ids = list(itertools.combinations(range(len(z_list)), 2))for (t1, t2) in z_ids:x1, y1, yaw1 = x_list[0, t1], x_list[1, t1], x_list[2, t1]x2, y2, yaw2 = x_list[0, t2], x_list[1, t2], x_list[2, t2]if z_list[t1] is None or z_list[t2] is None:continue # No observationfor iz1 in range(len(z_list[t1][:, 0])):for iz2 in range(len(z_list[t2][:, 0])):if z_list[t1][iz1, 3] == z_list[t2][iz2, 3]:d1 = z_list[t1][iz1, 0]angle1, phi1 = z_list[t1][iz1, 1], z_list[t1][iz1, 2]d2 = z_list[t2][iz2, 0]angle2, phi2 = z_list[t2][iz2, 1], z_list[t2][iz2, 2]edge = calc_edge(x1, y1, yaw1, x2, y2, yaw2, d1, angle1, d2, angle2, t1, t2)edges.append(edge)cost += (edge.e.T @ edge.omega @ edge.e)[0, 0]print("cost:", cost, ",n_edge:", len(edges))return edges
7.信息矩阵与代价函数
信息矩阵是用来表达不确定性的,将总的代价函数与每个变量的梯度关联起来:
需要通过信息矩阵来完成:最优轨迹的原则:
为了最小化总的代价函数,需要如何更新变量:
信息矩阵就是协方差矩阵的逆。
def fill_H_and_b(H, b, edge):#计算信息矩阵和更新的量A, B = calc_jacobian(edge)id1 = edge.id1 * STATE_SIZEid2 = edge.id2 * STATE_SIZEH[id1:id1 + STATE_SIZE, id1:id1 + STATE_SIZE] += A.T @ edge.omega @ AH[id1:id1 + STATE_SIZE, id2:id2 + STATE_SIZE] += A.T @ edge.omega @ BH[id2:id2 + STATE_SIZE, id1:id1 + STATE_SIZE] += B.T @ edge.omega @ AH[id2:id2 + STATE_SIZE, id2:id2 + STATE_SIZE] += B.T @ edge.omega @ B b[id1:id1 + STATE_SIZE] += (A.T @ edge.omega @ edge.e)b[id2:id2 + STATE_SIZE] += (B.T @ edge.omega @ edge.e)return H, b在 graph_based_slam()函数中:for i in range(nt):x_opt[0:3, i] += dx[i * 3:i * 3 + 3, 0]#更新实际轨迹的每一点,得到最优轨迹												
											Graph Based SLAM 基本原理的更多相关文章
- 论文阅读-Temporal Phenotyping from Longitudinal Electronic Health Records: A Graph Based Framework
 - [SLAM]Karto SLAM算法学习(草稿)
		
Karto_slam算法是一个Graph based SLAM算法.包括前端和后端.关于代码要分成两块内容来看. 一类是OpenKarto项目,是最初的开源代码,包括算法的核心内容: https:// ...
 - (转) 实时SLAM的未来及与深度学习的比较
		
首页 视界智尚 算法技术 每日技术 来打我呀 注册 实时SLAM的未来及与深度学习的比较 The Future of Real-Time SLAM and “Deep Learni ...
 - Visualizing MNIST with t-SNE, MDS, Sammon’s Mapping and Nearest neighbor graph
		
MNIST 可视化 Visualizing MNIST: An Exploration of Dimensionality Reduction At some fundamental level, n ...
 - Survey of single-target visual tracking methods based on online learning 翻译
		
基于在线学习的单目标跟踪算法调研 摘要 视觉跟踪在计算机视觉和机器人学领域是一个流行和有挑战的话题.由于多种场景下出现的目标外貌和复杂环境变量的改变,先进的跟踪框架就有必要采用在线学习的原理.本论文简 ...
 - g2o:一种图优化的C++框架
		
转载自 Taylor Guo g2o: A general framework for graph optimization 原文发表于IEEE InternationalConference on ...
 - 【Cocos2d-x 3.x】 事件处理机制源码分析
		
在游戏中,触摸是最基本的,必不可少的.Cocos2d-x 3.x中定义了一系列事件,同时也定义了负责监听这些事件的监听器,另外,cocos定义了事件分发类,用来将事件派发出去以便可以实现相应的事件. ...
 - {ICIP2014}{收录论文列表}
		
This article come from HEREARS-L1: Learning Tuesday 10:30–12:30; Oral Session; Room: Leonard de Vinc ...
 - arcmap Command
		
The information in this document is useful if you are trying to programmatically find a built-in com ...
 
随机推荐
- .NET Core 利用委托进行动态流程组装
			
引言 在看.NET Core 源码的管道模型中间件(Middleware)部分,觉得这个流程组装,思路挺好的,于是就分享给大家.本次代码实现就直接我之前写的动态代理实现AOP的基础上直接改了,就不另起 ...
 - JuiceFS 即将发布 1.0 并调整开源许可
			
开源一周年 JuiceFS 开始于 2017 年,是一款云原生分布式文件系统,旨在帮助企业解决多云.跨云.混合云环境下所面临的诸多挑战:数据安全和保护.大数据架构升级.海量小文件访问.Kubernet ...
 - promise到底怎么理解
			
Promise的含义promise是异步编程的一种解决方法.所谓promise,简单说是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上说,promise是一个对象,从 ...
 - access偏移注入原理
			
前言:近段时间在学习access偏移注入时,在网上查询了大量的资料,感觉很多资料讲解的十分模糊并且我个人认为有很多不够严谨的地方,于是我便在线下经过大量测试,写出以下文章,如有错误,望指出. 如要转载 ...
 - 毫米转像素dpi
			
public static double MillimeterToPixel_X(double length) //length是毫米,1厘米=10毫米 { System.Windows.Forms. ...
 - 【机器学习】svm
			
机器学习算法--SVM 目录 机器学习算法--SVM 1. 背景 2. SVM推导 2.1 几何间隔和函数间隔 2.2 SVM原问题 2.3 SVM对偶问题 2.4 SMO算法 2.4.1 更新公式 ...
 - 搭服务器之centos-ipv6源--配置各虚拟机系统的ipv6网络安装源。
			
在2g内存的台式机里安装了三台虚拟机,跑起来好可以,就是swap用的比较多,图见上一篇随笔.现在平台基本有了,自己笔记本算总控,实验室台式机跑着4台机器(一实三虚),加上一台服务器,可以做很多事情了, ...
 - GitHub镜像
			
GitHub 官网镜像(可以用来clone push等,但是不能登录) https://github.com.cnpmjs.org https://git.sdut.me https://hub.fa ...
 - 你可能不知道的Animation动画技巧与细节
			
引言 在web应用中,前端同学在实现动画效果时往往常用的几种方案: css3 transition / animation - 实现过渡动画 setInterval / setTimeout - 通过 ...
 - thingsboard源码编译启动
			
开发环境 不同的版本对应的开发环境不同(这里以3.3.3版本说明) jdk11+:参考jdk11+安装(win) Maven3.6+:Maven安装配置 Git:参考Git安装 IDEA: 参考IDE ...