最近正好准备想试试利用EKF实现多传感器的融合。但没想到本身ROS里面就已经有EKF的功能包了。这个包用于评估机器人的3D位姿,使用了来自不同源的位姿测量信息,它使用带有6D(3D position and 3D orientation)模型信息的扩展卡尔曼滤波器来整合来自轮子里程计,IMU传感器和视觉里程计的数据信息。 基本思路就是用松耦合方式融合不同传感器信息实现位姿估计。下面来学习一下
目录
robot_pose_ekf节点
概要
配置
节点订阅的topic
imu_data(sensor_msgs/Imu.msg)
发布的topics
robot_pose_ekf/odom_combined(geometry_msgs/PoseWithCovarianceStamped.msg)
tf变换
robot_pose_ekf节点的工作原理
姿势解释Pose interpretation
协方差解释Covariance interpretation
Timing
扩展卡尔曼滤波的原理介绍
线性化卡尔曼滤波
泰勒级数展开
EKF
代码解读
将想使用的传感器加入到EKF的输入源中
参考资料
robot_pose_ekf 是 ROS Navigation stack 中的一个包,通过扩展卡尔曼滤波器对 imu、里程计 odom、视觉里程计 vo 的数据进行融合,来估计平面移动机器人的真实位置姿态,输出 odom_combined 消息。
Robot Pose EKF 包用于评估机器人的3D位姿,基于来自不同来源的位姿测量信息。
它使用带有6D(3D position and 3D orientation)模型信息的扩展卡尔曼滤波器来整合来自轮式里程计,IMU传感器和视觉里程计的数据信息。换言之,如果要改造这个包,则需要其他传感器输出的位姿也按照它的结构。
基本思路就是用松耦合方式融合不同传感器信息实现位姿估计。
该包的源码: https://github.com/ros-planning/robot_pose_ekf
先将这个包下载下来,然后放置于工作空间。EKF节点的默认启动文件位于robot_pose_ekf包目录中
先打开启动文件robot_pose_ekf.launch,里面有些参数是可以被修改的
可以使用 remap 将其映射到新名称的 topic上。重映射是基于替换的思想,每个重映射包含一个原始名称和一个新名称。每当节点使用重映射中的原始名称时,ROS 客户端库就会将它默默地替换成其对应的新名称。
cm一下即可编辑该功能包,但是报错了
原来是需要先安装
rosdep install robot_pose_ekf
然后在cm则成功了哈~
原始消息定义如下所示
# This represents an estimate of a position and velocity (速度) in free space.
# The pose in this message should be specified in the coordinate frame given by header.frame_id.
# The twist in this message should be specified in the coordinate frame given by the child_frame_id
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
缩消息定义
std_msgs/Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
关于欧拉角的求解,可以参考博客《ROS学习笔记之——机器人航向角的求解 》和《 ROS学习笔记之——机器人初始姿态角的确定 》
原始消息定义如下所示
# This is a message to hold data from an IMU (Inertial Measurement Unit)
#
# Accelerations should be in m/s^2 (not in g's), and rotational velocity should be in rad/sec
#
# If the covariance of the measurement is known, it should be filled in (if all you know is the
# variance of each measurement, e.g. from the datasheet, just put those along the diagonal)
# A covariance matrix of all zeros will be interpreted as "covariance unknown", and to use the
# data a covariance will have to be assumed or gotten from some other source
#
# If you have no estimate for one of the data elements (e.g. your IMU doesn't produce an orientation
# estimate), please set element 0 of the associated covariance matrix to -1
# If you are interpreting this message, please check for a value of -1 in the first element of each
# covariance matrix, and disregard the associated estimate.
Header header
geometry_msgs/Quaternion orientation
float64[9] orientation_covariance # Row major about x, y, z axes
geometry_msgs/Vector3 angular_velocity
float64[9] angular_velocity_covariance # Row major about x, y, z axes
geometry_msgs/Vector3 linear_acceleration
float64[9] linear_acceleration_covariance # Row major x, y z
缩消息定义
std_msgs/Header header
geometry_msgs/Quaternion orientation
float64[9] orientation_covariance
geometry_msgs/Vector3 angular_velocity
float64[9] angular_velocity_covariance
geometry_msgs/Vector3 linear_acceleration
float64[9] linear_acceleration_covariance
原始消息定义如下所示
# This represents an estimate of a position and velocity in free space.
# The pose in this message should be specified in the coordinate frame given by header.frame_id.
# The twist in this message should be specified in the coordinate frame given by the child_frame_id
Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
缩消息定义
std_msgs/Header header
string child_frame_id
geometry_msgs/PoseWithCovariance pose
geometry_msgs/TwistWithCovariance twist
robot_pose_ekf node并不需要所有3个传感器源一直同时可用。 每个源都能提供位姿和协方差,且这些源以不同速率和延时工作。 随着时间推移某个源可能出现和消失(比如某个传感器突然不工作了),该node可以自动探测当前可用的源。 如果要把自己想使用的传感器加入到输入源中,请参考指南(http://wiki.ros.org/robot_pose_ekf/Tutorials/AddingGpsSensor)
滤波器输出 (估计的3D机器人位姿)
原始消息定义如下所示
# This expresses an estimated pose with a reference coordinate frame and timestamp
Header header
PoseWithCovariance pose
缩消息定义
std_msgs/Header header
geometry_msgs/PoseWithCovariance pose
odom_combined → base_footprint
给滤波器node提供信息的所有传感器源都有自己的参考坐标系,并且随着时间推移都可能出现漂移现象。因此,每个传感器发出来的绝对位姿不能直接对比。 因此该node使用每个传感器的相对位姿差异来更新扩展卡尔曼滤波器。
当机器人在四周移动时候,随着时间推移位姿不确定性会变得越来越大,协方差将无限增长。这样一来发布位姿自身协方差没有意义,因此传感器源公布协方差如何随时间变化(例如,速度的协方差)。请注意,使用世界观测(例如测量到已知墙的距离)将减少机器人姿势的不确定性; 然而,这是定位,而不是里程计
假定机器人上次更新位姿滤波器在t_0时刻, 该node只有在收到每个传感器测量值(时间戳>t_0)之后才会进行下一次的滤波器更新。 例如,在odom topic收到一条消息时间戳(t_1 > t_0), 且在imu_data topic上也收到一条消息( 其时间戳t_2 > t_1 > t_0), 滤波器将被更新到所有传感器信息可用的最新时刻,这个时刻是t_1。 在t_1时刻odom位姿直接给出了,但是imu位姿需要通过在t_0和t_2两时刻之间进行线性插值求得。 在t_0到 t_1时间段,机器人位姿滤波器使用odom和IMU相对位姿进行更新。
下图是PR2机器人机器人的实验结果显示,从绿色初始位置开始移动最后回到出发位置。 完美的odometry x-y曲线图应该是一个精确的闭环曲线图。 上图蓝色线是轮式里程计的输入,蓝色点是其估计的结束位置。红色线是robot_pose_ekf的输出, robot_pose_ekf整合了轮式里程计和IMU的信息,给出了红色的结束位置点。
这部分是数学部分,其实之前博客《学习笔记之——基于粒子滤波器的目标跟踪算法(内含卡尔曼滤波的学习笔记) 》就学习过卡尔曼滤波了,来看看二者的区别吧~
对于kalman滤波,从一开始就是为线性系统设计的算法,不能用于非线性系统。在kalman滤波中,对于小车当前的状态(x,v),预测其下一秒的位置为这个就是一个线性系统的预测。而实际中往往都是非线性系统。
(一个不错的视频https://www.bilibili.com/video/av4356232?from=search&seid=7045130318637455155)
再付上几个不错的参考资料
http://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
https://blog.csdn.net/u010720661/article/details/63253509
https://en.wikipedia.org/wiki/Extended_Kalman_filter
线性化卡尔曼滤波和线性卡尔曼滤波器在滤波器的算法方面有同样的算法结构。不一样的地方在于这两者的系统模型不同。线性卡尔曼滤波器的系统本身就是线性系统,而线性化卡尔曼滤波器的系统本身是非线性系统,但是机智的大神们将非线性的系统进行了线性化,于是卡尔曼滤波就可以用在非线性系统中了。
但是我们在对非线性系统进行线性化的过程中,只有被线性化的那个点附近的线性化模型和真实的模型相近,远的误差就大了,那么这个时候卡尔曼滤波器的效果就不好。估计线性化卡尔曼滤波器会发散。而线性化卡尔曼的表现取决于线性化做得好不好
泰勒级数展开是将一个在处具有
阶导数的函数
,利用关于
的
次多项式逼近函数值的方法。
若函数在包含
的某个闭区间
上具有
阶导数,且在开区间
上具有
阶导数,则对闭区间
上的任意一点
,都有:
其中表示函数
在
处的
阶导数,等式右边成为泰勒展开式,剩余的
是泰勒展开式的余项,是
的高阶无穷小。
(著名的欧拉公式就是利用
,
和
的泰勒展开式得来的!)
当变量是多维向量时,一维的泰勒展开就需要做拓展,具体形式如下:
其中,表示雅克比矩阵,
表示海塞矩阵,
表示高阶无穷小。
这里,为
维,
状态向量为
维,
一般来说,EKF在对非线性函数做泰勒展开时,只取到一阶导和二阶导,而由于二阶导的计算复杂性,更多的实际应用只取到一阶导,同样也能有较好的结果。取一阶导时,状态转移方程和观测方程就近似为线性方程,高斯分布的变量经过线性变换之后仍然是高斯分布,这样就能够延用标准卡尔曼滤波的框架。
扩展卡尔曼算法是一种近似的状态估计方法,它是将非线性系统模型在状态估计值附近进行泰勒级数展幵,并在一阶截断,然后用得到的一阶泰勒展开式的近似项作为原状态方程和测量方程的线性逼近方程。泰勒展开式线性化过程是通过计算状态方程和测
量方程偏导的雅克比矩阵实现非线性系统的线性化。传统的卡尔曼滤波器只适合线性系统的状态估计,因此卡尔曼滤波完成非线性系统的线性化后则可依据传统卡尔曼滤波算法实现状态估计。
标准卡尔曼滤波KF的状态转移方程和观测方程为
真实的状态值,预测的状态值
,估计的状态值
,观测值
,观测值的预测
,估计值与真实值之间的误差协方差矩阵
,求期望的符号
。.扩展卡尔曼滤波EKF的状态转移方程和观测方程为:
(1)(状态值)状态转移方程
(2)(测量值)观测方程
其中,以及
为加性高斯噪声
利用泰勒展开式对(1)式在上一次的估计值处展开得
(3)
再利用泰勒展开式对(2)式在本轮的状态预测值处展开得
(4)
其中,和
分别表示函数
和
在
和
处的雅克比矩阵。这里对泰勒展开式只保留到一阶导,二阶导数以上的都舍去,噪声假设均为加性高斯噪声
由公式3、4可以得到下面两个等式
(7) 注意:真实的状态值
,预测的状态值
基于以上的公式,给出EKF的预测(Predict)和更新(Update)两个步骤:
Propagation:
上一个状态的估计值,通过f函数来预测当前的状态值
下面计算一下估计值与真实值
之间的误差协方差矩阵
观测方程在本轮的状态预测值处展开得
有:
估计值与真实值
之间的误差协方差矩阵
其中表示真实的状态值
与预测的状态值
之间的误差协方差矩阵。于是得到式
(8)
因为的对角元即为真实值与估计值的误差的平方,矩阵的迹(用
表示)即为总误差的平方和,即
利用以下矩阵迹的求导公式(其中和
表示矩阵,
表示列向量):
要让估计值更接近于真实值,就要使上面的迹尽可能的小,因此要取得合适的卡尔曼增益,使得迹得到最小,言外之意就是使得迹对
的偏导为0,即
这样就能算出合适的卡尔曼增益了,即
代回式(8)得到
接下来就差真实值与预测值之间的协方差矩阵的求值公式了
将以下两个等式代入到上式,
,
可以得到
有、
与观测噪声
是独立的,求期望等于零;;
表示观测噪声的协方差矩阵,用
表示。于是得到
其中的协方差矩阵的转置矩阵就是它本身。
从而得到Update:
引入反馈,估计的状态值为:
(5)
其中的雅克比矩阵和
分别为
,
代码解读这块我就直接把注释解读放上来了哈~
(http://wiki.ros.org/robot_pose_ekf/Tutorials/AddingGpsSensor)
http://wiki.ros.org/robot_pose_ekf (官方文档)
https://github.com/ros-planning/navigation(ROS导航)
https://blog.csdn.net/zhczz1994/article/details/89790570
https://blog.csdn.net/eaibot/article/details/51405152
https://www.ncnynl.com/archives/201708/1909.html
https://blog.csdn.net/Qm13416479599/article/details/90056741?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase
https://blog.csdn.net/shenghuaijing3314/article/details/78220151
https://answers.ros.org/question/11682/robot_pose_ekf-with-an-external-sensor/?answer=17402#post-id-17402
https://zhuanlan.zhihu.com/p/67138271
https://blog.csdn.net/weixin_42647783/article/details/89054641
https://www.cnblogs.com/ymxiansen/p/5368547.html
https://zhuanlan.zhihu.com/p/70605004